diff options
Diffstat (limited to 'libfreerdp/codec')
70 files changed, 41688 insertions, 0 deletions
diff --git a/libfreerdp/codec/audio.c b/libfreerdp/codec/audio.c new file mode 100644 index 0000000..ff2786d --- /dev/null +++ b/libfreerdp/codec/audio.c @@ -0,0 +1,298 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Audio Formats + * + * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <winpr/crt.h> + +#include <freerdp/log.h> +#include <freerdp/codec/audio.h> + +#define TAG FREERDP_TAG("codec") + +UINT32 audio_format_compute_time_length(const AUDIO_FORMAT* format, size_t size) +{ + UINT32 mstime = 0; + UINT32 wSamples = 0; + + /** + * [MSDN-AUDIOFORMAT]: + * http://msdn.microsoft.com/en-us/library/ms713497.aspx + */ + + if (format->wBitsPerSample) + { + const size_t samples = (size * 8) / format->wBitsPerSample; + WINPR_ASSERT(samples <= UINT32_MAX); + wSamples = (UINT32)samples; + mstime = (((wSamples * 1000) / format->nSamplesPerSec) / format->nChannels); + } + else + { + mstime = 0; + + if (format->wFormatTag == WAVE_FORMAT_GSM610) + { + UINT16 nSamplesPerBlock = 0; + + if ((format->cbSize == 2) && (format->data)) + { + nSamplesPerBlock = *((UINT16*)format->data); + const size_t samples = (size / format->nBlockAlign) * nSamplesPerBlock; + WINPR_ASSERT(samples <= UINT32_MAX); + wSamples = (UINT32)samples; + mstime = (((wSamples * 1000) / format->nSamplesPerSec) / format->nChannels); + } + else + { + WLog_ERR(TAG, + "audio_format_compute_time_length: invalid WAVE_FORMAT_GSM610 format"); + } + } + else + { + WLog_ERR(TAG, "audio_format_compute_time_length: unknown format %" PRIu16 "", + format->wFormatTag); + } + } + + return mstime; +} + +char* audio_format_get_tag_string(UINT16 wFormatTag) +{ + switch (wFormatTag) + { + case WAVE_FORMAT_PCM: + return "WAVE_FORMAT_PCM"; + + case WAVE_FORMAT_ADPCM: + return "WAVE_FORMAT_ADPCM"; + + case WAVE_FORMAT_ALAW: + return "WAVE_FORMAT_ALAW"; + + case WAVE_FORMAT_MULAW: + return "WAVE_FORMAT_MULAW"; + + case WAVE_FORMAT_DVI_ADPCM: + return "WAVE_FORMAT_DVI_ADPCM"; + + case WAVE_FORMAT_GSM610: + return "WAVE_FORMAT_GSM610"; + + case WAVE_FORMAT_MSG723: + return "WAVE_FORMAT_MSG723"; + + case WAVE_FORMAT_DSPGROUP_TRUESPEECH: + return "WAVE_FORMAT_DSPGROUP_TRUESPEECH "; + + case WAVE_FORMAT_MPEGLAYER3: + return "WAVE_FORMAT_MPEGLAYER3"; + + case WAVE_FORMAT_WMAUDIO2: + return "WAVE_FORMAT_WMAUDIO2"; + + case WAVE_FORMAT_AAC_MS: + return "WAVE_FORMAT_AAC_MS"; + } + + return "WAVE_FORMAT_UNKNOWN"; +} + +void audio_format_print(wLog* log, DWORD level, const AUDIO_FORMAT* format) +{ + WLog_Print(log, level, + "%s:\t wFormatTag: 0x%04" PRIX16 " nChannels: %" PRIu16 " nSamplesPerSec: %" PRIu32 + " " + "nAvgBytesPerSec: %" PRIu32 " nBlockAlign: %" PRIu16 " wBitsPerSample: %" PRIu16 + " cbSize: %" PRIu16 "", + audio_format_get_tag_string(format->wFormatTag), format->wFormatTag, + format->nChannels, format->nSamplesPerSec, format->nAvgBytesPerSec, + format->nBlockAlign, format->wBitsPerSample, format->cbSize); +} + +void audio_formats_print(wLog* log, DWORD level, const AUDIO_FORMAT* formats, UINT16 count) +{ + if (formats) + { + WLog_Print(log, level, "AUDIO_FORMATS (%" PRIu16 ") ={", count); + + for (UINT32 index = 0; index < count; index++) + { + const AUDIO_FORMAT* format = &formats[index]; + WLog_Print(log, level, "\t"); + audio_format_print(log, level, format); + } + + WLog_Print(log, level, "}"); + } +} + +BOOL audio_format_read(wStream* s, AUDIO_FORMAT* format) +{ + if (!s || !format) + return FALSE; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 18)) + return FALSE; + + Stream_Read_UINT16(s, format->wFormatTag); + Stream_Read_UINT16(s, format->nChannels); + Stream_Read_UINT32(s, format->nSamplesPerSec); + Stream_Read_UINT32(s, format->nAvgBytesPerSec); + Stream_Read_UINT16(s, format->nBlockAlign); + Stream_Read_UINT16(s, format->wBitsPerSample); + Stream_Read_UINT16(s, format->cbSize); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, format->cbSize)) + return FALSE; + + format->data = NULL; + + if (format->cbSize > 0) + { + format->data = malloc(format->cbSize); + + if (!format->data) + return FALSE; + + Stream_Read(s, format->data, format->cbSize); + } + + return TRUE; +} + +BOOL audio_format_write(wStream* s, const AUDIO_FORMAT* format) +{ + if (!s || !format) + return FALSE; + + if (!Stream_EnsureRemainingCapacity(s, 18 + format->cbSize)) + return FALSE; + + Stream_Write_UINT16(s, format->wFormatTag); /* wFormatTag (WAVE_FORMAT_PCM) */ + Stream_Write_UINT16(s, format->nChannels); /* nChannels */ + Stream_Write_UINT32(s, format->nSamplesPerSec); /* nSamplesPerSec */ + Stream_Write_UINT32(s, format->nAvgBytesPerSec); /* nAvgBytesPerSec */ + Stream_Write_UINT16(s, format->nBlockAlign); /* nBlockAlign */ + Stream_Write_UINT16(s, format->wBitsPerSample); /* wBitsPerSample */ + Stream_Write_UINT16(s, format->cbSize); /* cbSize */ + + if (format->cbSize > 0) + Stream_Write(s, format->data, format->cbSize); + + return TRUE; +} + +BOOL audio_format_copy(const AUDIO_FORMAT* srcFormat, AUDIO_FORMAT* dstFormat) +{ + if (!srcFormat || !dstFormat) + return FALSE; + + *dstFormat = *srcFormat; + + if (srcFormat->cbSize > 0) + { + dstFormat->data = malloc(srcFormat->cbSize); + + if (!dstFormat->data) + return FALSE; + + memcpy(dstFormat->data, srcFormat->data, dstFormat->cbSize); + } + + return TRUE; +} + +BOOL audio_format_compatible(const AUDIO_FORMAT* with, const AUDIO_FORMAT* what) +{ + if (!with || !what) + return FALSE; + + if (with->wFormatTag != WAVE_FORMAT_UNKNOWN) + { + if (with->wFormatTag != what->wFormatTag) + return FALSE; + } + + if (with->nChannels != 0) + { + if (with->nChannels != what->nChannels) + return FALSE; + } + + if (with->nSamplesPerSec != 0) + { + if (with->nSamplesPerSec != what->nSamplesPerSec) + return FALSE; + } + + if (with->wBitsPerSample != 0) + { + if (with->wBitsPerSample != what->wBitsPerSample) + return FALSE; + } + + return TRUE; +} + +static BOOL audio_format_valid(const AUDIO_FORMAT* format) +{ + if (!format) + return FALSE; + + if (format->nChannels == 0) + return FALSE; + + if (format->nSamplesPerSec == 0) + return FALSE; + + return TRUE; +} + +AUDIO_FORMAT* audio_format_new(void) +{ + return audio_formats_new(1); +} + +AUDIO_FORMAT* audio_formats_new(size_t count) +{ + return calloc(count, sizeof(AUDIO_FORMAT)); +} + +void audio_format_free(AUDIO_FORMAT* format) +{ + if (format) + free(format->data); +} + +void audio_formats_free(AUDIO_FORMAT* formats, size_t count) +{ + if (formats) + { + for (size_t index = 0; index < count; index++) + { + AUDIO_FORMAT* format = &formats[index]; + audio_format_free(format); + } + + free(formats); + } +} diff --git a/libfreerdp/codec/bitmap.c b/libfreerdp/codec/bitmap.c new file mode 100644 index 0000000..d51f2f7 --- /dev/null +++ b/libfreerdp/codec/bitmap.c @@ -0,0 +1,1088 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Bitmap Compression + * + * Copyright 2004-2012 Jay Sorg <jay.sorg@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <freerdp/codec/bitmap.h> +#include <freerdp/codec/planar.h> + +static INLINE UINT16 GETPIXEL16(const void* d, UINT32 x, UINT32 y, UINT32 w) +{ + const BYTE* src = (const BYTE*)d + ((y * w + x) * sizeof(UINT16)); + return (UINT16)(((UINT16)src[1] << 8) | (UINT16)src[0]); +} + +static INLINE UINT32 GETPIXEL32(const void* d, UINT32 x, UINT32 y, UINT32 w) +{ + const BYTE* src = (const BYTE*)d + ((y * w + x) * sizeof(UINT32)); + return (((UINT32)src[3]) << 24) | (((UINT32)src[2]) << 16) | (((UINT32)src[1]) << 8) | + (src[0] & 0xFF); +} + +/*****************************************************************************/ +static INLINE UINT16 IN_PIXEL16(const void* in_ptr, UINT32 in_x, UINT32 in_y, UINT32 in_w, + UINT16 in_last_pixel) +{ + if (in_ptr == 0) + return 0; + else if (in_x < in_w) + return GETPIXEL16(in_ptr, in_x, in_y, in_w); + else + return in_last_pixel; +} + +/*****************************************************************************/ +static INLINE UINT32 IN_PIXEL32(const void* in_ptr, UINT32 in_x, UINT32 in_y, UINT32 in_w, + UINT32 in_last_pixel) +{ + if (in_ptr == 0) + return 0; + else if (in_x < in_w) + return GETPIXEL32(in_ptr, in_x, in_y, in_w); + else + return in_last_pixel; +} + +/*****************************************************************************/ +/* color */ +static UINT16 out_color_count_2(UINT16 in_count, wStream* in_s, UINT16 in_data) +{ + if (in_count > 0) + { + if (in_count < 32) + { + const BYTE temp = ((0x3 << 5) | in_count) & 0xFF; + Stream_Write_UINT8(in_s, temp); + } + else if (in_count < 256 + 32) + { + const BYTE temp = (in_count - 32) & 0xFF; + Stream_Write_UINT8(in_s, 0x60); + Stream_Write_UINT8(in_s, temp); + } + else + { + Stream_Write_UINT8(in_s, 0xf3); + Stream_Write_UINT16(in_s, in_count); + } + + Stream_Write_UINT16(in_s, in_data); + } + + return 0; +} +#define OUT_COLOR_COUNT2(in_count, in_s, in_data) \ + in_count = out_color_count_2(in_count, in_s, in_data) + +/*****************************************************************************/ +/* color */ +static UINT16 out_color_count_3(UINT16 in_count, wStream* in_s, UINT32 in_data) +{ + if (in_count > 0) + { + if (in_count < 32) + { + const BYTE temp = ((0x3 << 5) | in_count) & 0xFF; + Stream_Write_UINT8(in_s, temp); + } + else if (in_count < 256 + 32) + { + const BYTE temp = (in_count - 32) & 0xFF; + Stream_Write_UINT8(in_s, 0x60); + Stream_Write_UINT8(in_s, temp); + } + else + { + Stream_Write_UINT8(in_s, 0xf3); + Stream_Write_UINT16(in_s, in_count); + } + + Stream_Write_UINT8(in_s, in_data & 0xFF); + + Stream_Write_UINT8(in_s, (in_data >> 8) & 0xFF); + Stream_Write_UINT8(in_s, (in_data >> 16) & 0xFF); + } + + return 0; +} + +#define OUT_COLOR_COUNT3(in_count, in_s, in_data) \ + in_count = out_color_count_3(in_count, in_s, in_data) + +/*****************************************************************************/ +/* copy */ +static INLINE UINT16 out_copy_count_2(UINT16 in_count, wStream* in_s, wStream* in_data) + +{ + if (in_count > 0) + { + if (in_count < 32) + { + const BYTE temp = ((0x4 << 5) | in_count) & 0xFF; + Stream_Write_UINT8(in_s, temp); + } + else if (in_count < 256 + 32) + { + const BYTE temp = (in_count - 32) & 0xFF; + Stream_Write_UINT8(in_s, 0x80); + Stream_Write_UINT8(in_s, temp); + } + else + { + Stream_Write_UINT8(in_s, 0xf4); + Stream_Write_UINT16(in_s, in_count); + } + + Stream_Write(in_s, Stream_Buffer(in_data), in_count * 2); + } + + Stream_SetPosition(in_data, 0); + return 0; +} +#define OUT_COPY_COUNT2(in_count, in_s, in_data) \ + in_count = out_copy_count_2(in_count, in_s, in_data) +/*****************************************************************************/ +/* copy */ +static INLINE UINT16 out_copy_count_3(UINT16 in_count, wStream* in_s, wStream* in_data) +{ + if (in_count > 0) + { + if (in_count < 32) + { + const BYTE temp = ((0x4 << 5) | in_count) & 0xFF; + Stream_Write_UINT8(in_s, temp); + } + else if (in_count < 256 + 32) + { + const BYTE temp = (in_count - 32) & 0xFF; + Stream_Write_UINT8(in_s, 0x80); + Stream_Write_UINT8(in_s, temp); + } + else + { + Stream_Write_UINT8(in_s, 0xf4); + Stream_Write_UINT16(in_s, in_count); + } + + Stream_Write(in_s, Stream_Pointer(in_data), in_count * 3); + } + + Stream_SetPosition(in_data, 0); + return 0; +} +#define OUT_COPY_COUNT3(in_count, in_s, in_data) \ + in_count = out_copy_count_3(in_count, in_s, in_data) + +/*****************************************************************************/ +/* bicolor */ +static INLINE UINT16 out_bicolor_count_2(UINT16 in_count, wStream* in_s, UINT16 in_color1, + UINT16 in_color2) +{ + if (in_count > 0) + { + if (in_count / 2 < 16) + { + const BYTE temp = ((0xe << 4) | (in_count / 2)) & 0xFF; + Stream_Write_UINT8(in_s, temp); + } + else if (in_count / 2 < 256 + 16) + { + const BYTE temp = (in_count / 2 - 16) & 0xFF; + Stream_Write_UINT8(in_s, 0xe0); + Stream_Write_UINT8(in_s, temp); + } + else + { + Stream_Write_UINT8(in_s, 0xf8); + Stream_Write_UINT16(in_s, in_count / 2); + } + + Stream_Write_UINT16(in_s, in_color1); + Stream_Write_UINT16(in_s, in_color2); + } + + return 0; +} + +#define OUT_BICOLOR_COUNT2(in_count, in_s, in_color1, in_color2) \ + in_count = out_bicolor_count_2(in_count, in_s, in_color1, in_color2) + +/*****************************************************************************/ +/* bicolor */ +static INLINE UINT16 out_bicolor_count_3(UINT16 in_count, wStream* in_s, UINT32 in_color1, + UINT32 in_color2) +{ + if (in_count > 0) + { + if (in_count / 2 < 16) + { + const BYTE temp = ((0xe << 4) | (in_count / 2)) & 0xFF; + Stream_Write_UINT8(in_s, temp); + } + else if (in_count / 2 < 256 + 16) + { + const BYTE temp = (in_count / 2 - 16) & 0xFF; + Stream_Write_UINT8(in_s, 0xe0); + Stream_Write_UINT8(in_s, temp); + } + else + { + Stream_Write_UINT8(in_s, 0xf8); + Stream_Write_UINT16(in_s, in_count / 2); + } + + Stream_Write_UINT8(in_s, in_color1 & 0xFF); + Stream_Write_UINT8(in_s, (in_color1 >> 8) & 0xFF); + Stream_Write_UINT8(in_s, (in_color1 >> 16) & 0xFF); + Stream_Write_UINT8(in_s, in_color2 & 0xFF); + Stream_Write_UINT8(in_s, (in_color2 >> 8) & 0xFF); + Stream_Write_UINT8(in_s, (in_color2 >> 16) & 0xFF); + } + + return 0; +} + +#define OUT_BICOLOR_COUNT3(in_count, in_s, in_color1, in_color2) \ + in_count = out_bicolor_count_3(in_count, in_s, in_color1, in_color2) + +/*****************************************************************************/ +/* fill */ +static INLINE UINT16 out_fill_count_2(UINT16 in_count, wStream* in_s) +{ + if (in_count > 0) + { + if (in_count < 32) + { + Stream_Write_UINT8(in_s, in_count & 0xFF); + } + else if (in_count < 256 + 32) + { + const BYTE temp = (in_count - 32) & 0xFF; + Stream_Write_UINT8(in_s, 0x0); + Stream_Write_UINT8(in_s, temp); + } + else + { + Stream_Write_UINT8(in_s, 0xf0); + Stream_Write_UINT16(in_s, in_count); + } + } + + return 0; +} + +#define OUT_FILL_COUNT2(in_count, in_s) in_count = out_fill_count_2(in_count, in_s) + +/*****************************************************************************/ +/* fill */ +static INLINE UINT16 out_fill_count_3(UINT16 in_count, wStream* in_s) +{ + if (in_count > 0) + { + if (in_count < 32) + { + Stream_Write_UINT8(in_s, in_count & 0xFF); + } + else if (in_count < 256 + 32) + { + const BYTE temp = (in_count - 32) & 0xFF; + Stream_Write_UINT8(in_s, 0x0); + Stream_Write_UINT8(in_s, temp); + } + else + { + Stream_Write_UINT8(in_s, 0xf0); + Stream_Write_UINT16(in_s, in_count); + } + } + + return 0; +} +#define OUT_FILL_COUNT3(in_count, in_s) in_count = out_fill_count_3(in_count, in_s) + +/*****************************************************************************/ +/* mix */ +static INLINE UINT16 out_mix_count_2(UINT16 in_count, wStream* in_s) +{ + if (in_count > 0) + { + if (in_count < 32) + { + const BYTE temp = ((0x1 << 5) | in_count) & 0xFF; + Stream_Write_UINT8(in_s, temp); + } + else if (in_count < 256 + 32) + { + const BYTE temp = (in_count - 32) & 0xFF; + Stream_Write_UINT8(in_s, 0x20); + Stream_Write_UINT8(in_s, temp); + } + else + { + Stream_Write_UINT8(in_s, 0xf1); + Stream_Write_UINT16(in_s, in_count); + } + } + + return 0; +} +#define OUT_MIX_COUNT2(in_count, in_s) in_count = out_mix_count_2(in_count, in_s) + +/*****************************************************************************/ +/* mix */ +static INLINE UINT16 out_mix_count_3(UINT16 in_count, wStream* in_s) +{ + if (in_count > 0) + { + if (in_count < 32) + { + const BYTE temp = ((0x1 << 5) | in_count) & 0xFF; + Stream_Write_UINT8(in_s, temp); + } + else if (in_count < 256 + 32) + { + const BYTE temp = (in_count - 32) & 0xFF; + Stream_Write_UINT8(in_s, 0x20); + Stream_Write_UINT8(in_s, temp); + } + else + { + Stream_Write_UINT8(in_s, 0xf1); + Stream_Write_UINT16(in_s, in_count); + } + } + + return 0; +} + +#define OUT_MIX_COUNT3(in_count, in_s) in_count = out_mix_count_3(in_count, in_s) + +/*****************************************************************************/ +/* fom */ +static INLINE UINT16 out_from_count_2(UINT16 in_count, wStream* in_s, const char* in_mask, + size_t in_mask_len) +{ + if (in_count > 0) + { + if ((in_count % 8) == 0 && in_count < 249) + { + const BYTE temp = ((0x2 << 5) | (in_count / 8)) & 0xFF; + Stream_Write_UINT8(in_s, temp); + } + else if (in_count < 256) + { + const BYTE temp = (in_count - 1) & 0xFF; + Stream_Write_UINT8(in_s, 0x40); + Stream_Write_UINT8(in_s, temp); + } + else + { + Stream_Write_UINT8(in_s, 0xf2); + Stream_Write_UINT16(in_s, in_count); + } + + Stream_Write(in_s, in_mask, in_mask_len); + } + + return 0; +} +#define OUT_FOM_COUNT2(in_count, in_s, in_mask, in_mask_len) \ + in_count = out_from_count_2(in_count, in_s, in_mask, in_mask_len) + +/*****************************************************************************/ +/* fill or mix (fom) */ +static INLINE UINT16 out_from_count_3(UINT16 in_count, wStream* in_s, const char* in_mask, + size_t in_mask_len) +{ + if (in_count > 0) + { + if ((in_count % 8) == 0 && in_count < 249) + { + const BYTE temp = ((0x2 << 5) | (in_count / 8)) & 0xFF; + Stream_Write_UINT8(in_s, temp); + } + else if (in_count < 256) + { + const BYTE temp = (in_count - 1) & 0xFF; + Stream_Write_UINT8(in_s, 0x40); + Stream_Write_UINT8(in_s, temp); + } + else + { + Stream_Write_UINT8(in_s, 0xf2); + Stream_Write_UINT16(in_s, in_count); + } + + Stream_Write(in_s, in_mask, in_mask_len); + } + + return 0; +} +#define OUT_FOM_COUNT3(in_count, in_s, in_mask, in_mask_len) \ + in_count = out_from_count_3(in_count, in_s, in_mask, in_mask_len) + +#define TEST_FILL ((last_line == 0 && pixel == 0) || (last_line != 0 && pixel == ypixel)) +#define TEST_MIX ((last_line == 0 && pixel == mix) || (last_line != 0 && pixel == (ypixel ^ mix))) +#define TEST_FOM TEST_FILL || TEST_MIX +#define TEST_COLOR pixel == last_pixel +#define TEST_BICOLOR \ + ((pixel != last_pixel) && \ + ((!bicolor_spin && (pixel == bicolor1) && (last_pixel == bicolor2)) || \ + (bicolor_spin && (pixel == bicolor2) && (last_pixel == bicolor1)))) +#define RESET_COUNTS \ + do \ + { \ + bicolor_count = 0; \ + fill_count = 0; \ + color_count = 0; \ + mix_count = 0; \ + fom_count = 0; \ + fom_mask_len = 0; \ + bicolor_spin = FALSE; \ + } while (0) + +static SSIZE_T freerdp_bitmap_compress_24(const void* srcData, UINT32 width, UINT32 height, + wStream* s, UINT32 byte_limit, UINT32 start_line, + wStream* temp_s, UINT32 e) +{ + char fom_mask[8192]; /* good for up to 64K bitmap */ + SSIZE_T lines_sent = 0; + UINT16 count = 0; + UINT16 color_count = 0; + UINT32 last_pixel = 0; + UINT32 last_ypixel = 0; + UINT16 bicolor_count = 0; + UINT32 bicolor1 = 0; + UINT32 bicolor2 = 0; + BOOL bicolor_spin = FALSE; + UINT32 end = width + e; + UINT32 out_count = end * 3; + UINT16 fill_count = 0; + UINT16 mix_count = 0; + const UINT32 mix = 0xFFFFFF; + UINT16 fom_count = 0; + size_t fom_mask_len = 0; + const char* start = (const char*)srcData; + const char* line = start + width * start_line * 4; + const char* last_line = NULL; + + while ((line >= start) && (out_count < 32768)) + { + size_t i = Stream_GetPosition(s) + count * 3U; + + if ((i - (color_count * 3) >= byte_limit) && (i - (bicolor_count * 3) >= byte_limit) && + (i - (fill_count * 3) >= byte_limit) && (i - (mix_count * 3) >= byte_limit) && + (i - (fom_count * 3) >= byte_limit)) + { + break; + } + + out_count += end * 3; + + for (UINT32 j = 0; j < end; j++) + { + /* read next pixel */ + const UINT32 pixel = IN_PIXEL32(line, j, 0, width, last_pixel); + const UINT32 ypixel = IN_PIXEL32(last_line, j, 0, width, last_ypixel); + + if (!TEST_FILL) + { + if (fill_count > 3 && fill_count >= color_count && fill_count >= bicolor_count && + fill_count >= mix_count && fill_count >= fom_count) + { + if (fill_count > count) + return -1; + + count -= fill_count; + OUT_COPY_COUNT3(count, s, temp_s); + OUT_FILL_COUNT3(fill_count, s); + RESET_COUNTS; + } + + fill_count = 0; + } + + if (!TEST_MIX) + { + if (mix_count > 3 && mix_count >= fill_count && mix_count >= bicolor_count && + mix_count >= color_count && mix_count >= fom_count) + { + if (mix_count > count) + return -1; + + count -= mix_count; + OUT_COPY_COUNT3(count, s, temp_s); + OUT_MIX_COUNT3(mix_count, s); + RESET_COUNTS; + } + + mix_count = 0; + } + + if (!(TEST_COLOR)) + { + if (color_count > 3 && color_count >= fill_count && color_count >= bicolor_count && + color_count >= mix_count && color_count >= fom_count) + { + if (color_count > count) + return -1; + + count -= color_count; + OUT_COPY_COUNT3(count, s, temp_s); + OUT_COLOR_COUNT3(color_count, s, last_pixel); + RESET_COUNTS; + } + + color_count = 0; + } + + if (!TEST_BICOLOR) + { + if (bicolor_count > 3 && bicolor_count >= fill_count && + bicolor_count >= color_count && bicolor_count >= mix_count && + bicolor_count >= fom_count) + { + if ((bicolor_count % 2) != 0) + bicolor_count--; + + if (bicolor_count > count) + return -1; + + count -= bicolor_count; + OUT_COPY_COUNT3(count, s, temp_s); + OUT_BICOLOR_COUNT3(bicolor_count, s, bicolor2, bicolor1); + RESET_COUNTS; + } + + bicolor_count = 0; + bicolor1 = last_pixel; + bicolor2 = pixel; + bicolor_spin = FALSE; + } + + if (!(TEST_FOM)) + { + if (fom_count > 3 && fom_count >= fill_count && fom_count >= color_count && + fom_count >= mix_count && fom_count >= bicolor_count) + { + if (fom_count > count) + return -1; + + count -= fom_count; + OUT_COPY_COUNT3(count, s, temp_s); + OUT_FOM_COUNT3(fom_count, s, fom_mask, fom_mask_len); + RESET_COUNTS; + } + + fom_count = 0; + fom_mask_len = 0; + } + + if (TEST_FILL) + { + fill_count++; + } + + if (TEST_MIX) + { + mix_count++; + } + + if (TEST_COLOR) + { + color_count++; + } + + if (TEST_BICOLOR) + { + bicolor_spin = !bicolor_spin; + bicolor_count++; + } + + if (TEST_FOM) + { + if ((fom_count % 8) == 0) + { + fom_mask[fom_mask_len] = 0; + fom_mask_len++; + } + + if (pixel == (ypixel ^ mix)) + { + fom_mask[fom_mask_len - 1] |= (1 << (fom_count % 8)); + } + + fom_count++; + } + + Stream_Write_UINT8(temp_s, pixel & 0xff); + Stream_Write_UINT8(temp_s, (pixel >> 8) & 0xff); + Stream_Write_UINT8(temp_s, (pixel >> 16) & 0xff); + count++; + last_pixel = pixel; + last_ypixel = ypixel; + } + + /* can't take fix, mix, or fom past first line */ + if (last_line == 0) + { + if (fill_count > 3 && fill_count >= color_count && fill_count >= bicolor_count && + fill_count >= mix_count && fill_count >= fom_count) + { + if (fill_count > count) + return -1; + + count -= fill_count; + OUT_COPY_COUNT3(count, s, temp_s); + OUT_FILL_COUNT3(fill_count, s); + RESET_COUNTS; + } + + fill_count = 0; + + if (mix_count > 3 && mix_count >= fill_count && mix_count >= bicolor_count && + mix_count >= color_count && mix_count >= fom_count) + { + if (mix_count > count) + return -1; + + count -= mix_count; + OUT_COPY_COUNT3(count, s, temp_s); + OUT_MIX_COUNT3(mix_count, s); + RESET_COUNTS; + } + + mix_count = 0; + + if (fom_count > 3 && fom_count >= fill_count && fom_count >= color_count && + fom_count >= mix_count && fom_count >= bicolor_count) + { + if (fom_count > count) + return -1; + + count -= fom_count; + OUT_COPY_COUNT3(count, s, temp_s); + OUT_FOM_COUNT3(fom_count, s, fom_mask, fom_mask_len); + RESET_COUNTS; + } + + fom_count = 0; + fom_mask_len = 0; + } + + last_line = line; + line = line - width * 4; + start_line--; + lines_sent++; + } + + Stream_SetPosition(temp_s, 0); + + if (fill_count > 3 && fill_count >= color_count && fill_count >= bicolor_count && + fill_count >= mix_count && fill_count >= fom_count) + { + if (fill_count > count) + return -1; + + count -= fill_count; + OUT_COPY_COUNT3(count, s, temp_s); + OUT_FILL_COUNT3(fill_count, s); + } + else if (mix_count > 3 && mix_count >= color_count && mix_count >= bicolor_count && + mix_count >= fill_count && mix_count >= fom_count) + { + if (mix_count > count) + return -1; + + count -= mix_count; + OUT_COPY_COUNT3(count, s, temp_s); + OUT_MIX_COUNT3(mix_count, s); + } + else if (color_count > 3 && color_count >= mix_count && color_count >= bicolor_count && + color_count >= fill_count && color_count >= fom_count) + { + if (color_count > count) + return -1; + + count -= color_count; + OUT_COPY_COUNT3(count, s, temp_s); + OUT_COLOR_COUNT3(color_count, s, last_pixel); + } + else if (bicolor_count > 3 && bicolor_count >= mix_count && bicolor_count >= color_count && + bicolor_count >= fill_count && bicolor_count >= fom_count) + { + if ((bicolor_count % 2) != 0) + bicolor_count--; + + if (bicolor_count > count) + return -1; + + count -= bicolor_count; + OUT_COPY_COUNT3(count, s, temp_s); + OUT_BICOLOR_COUNT3(bicolor_count, s, bicolor2, bicolor1); + + if (bicolor_count > count) + return -1; + + count -= bicolor_count; + OUT_COPY_COUNT3(count, s, temp_s); + OUT_BICOLOR_COUNT3(bicolor_count, s, bicolor1, bicolor2); + } + else if (fom_count > 3 && fom_count >= mix_count && fom_count >= color_count && + fom_count >= fill_count && fom_count >= bicolor_count) + { + if (fom_count > count) + return -1; + + count -= fom_count; + OUT_COPY_COUNT3(count, s, temp_s); + OUT_FOM_COUNT3(fom_count, s, fom_mask, fom_mask_len); + } + else + { + OUT_COPY_COUNT3(count, s, temp_s); + } + + return lines_sent; +} + +static SSIZE_T freerdp_bitmap_compress_16(const void* srcData, UINT32 width, UINT32 height, + wStream* s, UINT32 bpp, UINT32 byte_limit, + UINT32 start_line, wStream* temp_s, UINT32 e) +{ + char fom_mask[8192]; /* good for up to 64K bitmap */ + SSIZE_T lines_sent = 0; + UINT16 count = 0; + UINT16 color_count = 0; + UINT16 last_pixel = 0; + UINT16 last_ypixel = 0; + UINT16 bicolor_count = 0; + UINT16 bicolor1 = 0; + UINT16 bicolor2 = 0; + BOOL bicolor_spin = FALSE; + UINT32 end = width + e; + UINT32 out_count = end * 2; + UINT16 fill_count = 0; + UINT16 mix_count = 0; + const UINT32 mix = (bpp == 15) ? 0xBA1F : 0xFFFF; + UINT16 fom_count = 0; + size_t fom_mask_len = 0; + const char* start = (const char*)srcData; + const char* line = start + width * start_line * 2; + const char* last_line = NULL; + + while ((line >= start) && (out_count < 32768)) + { + size_t i = Stream_GetPosition(s) + count * 2; + + if ((i - (color_count * 2) >= byte_limit) && (i - (bicolor_count * 2) >= byte_limit) && + (i - (fill_count * 2) >= byte_limit) && (i - (mix_count * 2) >= byte_limit) && + (i - (fom_count * 2) >= byte_limit)) + { + break; + } + + out_count += end * 2; + + for (UINT32 j = 0; j < end; j++) + { + /* read next pixel */ + const UINT16 pixel = IN_PIXEL16(line, j, 0, width, last_pixel); + const UINT16 ypixel = IN_PIXEL16(last_line, j, 0, width, last_ypixel); + + if (!TEST_FILL) + { + if (fill_count > 3 && fill_count >= color_count && fill_count >= bicolor_count && + fill_count >= mix_count && fill_count >= fom_count) + { + if (fill_count > count) + return -1; + + count -= fill_count; + OUT_COPY_COUNT2(count, s, temp_s); + OUT_FILL_COUNT2(fill_count, s); + RESET_COUNTS; + } + + fill_count = 0; + } + + if (!TEST_MIX) + { + if (mix_count > 3 && mix_count >= fill_count && mix_count >= bicolor_count && + mix_count >= color_count && mix_count >= fom_count) + { + if (mix_count > count) + return -1; + + count -= mix_count; + OUT_COPY_COUNT2(count, s, temp_s); + OUT_MIX_COUNT2(mix_count, s); + RESET_COUNTS; + } + + mix_count = 0; + } + + if (!(TEST_COLOR)) + { + if (color_count > 3 && color_count >= fill_count && color_count >= bicolor_count && + color_count >= mix_count && color_count >= fom_count) + { + if (color_count > count) + return -1; + + count -= color_count; + OUT_COPY_COUNT2(count, s, temp_s); + OUT_COLOR_COUNT2(color_count, s, last_pixel); + RESET_COUNTS; + } + + color_count = 0; + } + + if (!TEST_BICOLOR) + { + if ((bicolor_count > 3) && (bicolor_count >= fill_count) && + (bicolor_count >= color_count) && (bicolor_count >= mix_count) && + (bicolor_count >= fom_count)) + { + if ((bicolor_count % 2) != 0) + bicolor_count--; + + if (bicolor_count > count) + return -1; + + count -= bicolor_count; + OUT_COPY_COUNT2(count, s, temp_s); + OUT_BICOLOR_COUNT2(bicolor_count, s, bicolor2, bicolor1); + RESET_COUNTS; + } + + bicolor_count = 0; + bicolor1 = last_pixel; + bicolor2 = pixel; + bicolor_spin = FALSE; + } + + if (!(TEST_FOM)) + { + if (fom_count > 3 && fom_count >= fill_count && fom_count >= color_count && + fom_count >= mix_count && fom_count >= bicolor_count) + { + if (fom_count > count) + return -1; + + count -= fom_count; + OUT_COPY_COUNT2(count, s, temp_s); + OUT_FOM_COUNT2(fom_count, s, fom_mask, fom_mask_len); + RESET_COUNTS; + } + + fom_count = 0; + fom_mask_len = 0; + } + + if (TEST_FILL) + { + fill_count++; + } + + if (TEST_MIX) + { + mix_count++; + } + + if (TEST_COLOR) + { + color_count++; + } + + if (TEST_BICOLOR) + { + bicolor_spin = !bicolor_spin; + bicolor_count++; + } + + if (TEST_FOM) + { + if ((fom_count % 8) == 0) + { + fom_mask[fom_mask_len] = 0; + fom_mask_len++; + } + + if (pixel == (ypixel ^ mix)) + { + fom_mask[fom_mask_len - 1] |= (1 << (fom_count % 8)); + } + + fom_count++; + } + + Stream_Write_UINT16(temp_s, pixel); + count++; + last_pixel = pixel; + last_ypixel = ypixel; + } + + /* can't take fix, mix, or fom past first line */ + if (last_line == 0) + { + if (fill_count > 3 && fill_count >= color_count && fill_count >= bicolor_count && + fill_count >= mix_count && fill_count >= fom_count) + { + if (fill_count > count) + return -1; + + count -= fill_count; + OUT_COPY_COUNT2(count, s, temp_s); + OUT_FILL_COUNT2(fill_count, s); + RESET_COUNTS; + } + + fill_count = 0; + + if (mix_count > 3 && mix_count >= fill_count && mix_count >= bicolor_count && + mix_count >= color_count && mix_count >= fom_count) + { + if (mix_count > count) + return -1; + + count -= mix_count; + OUT_COPY_COUNT2(count, s, temp_s); + OUT_MIX_COUNT2(mix_count, s); + RESET_COUNTS; + } + + mix_count = 0; + + if (fom_count > 3 && fom_count >= fill_count && fom_count >= color_count && + fom_count >= mix_count && fom_count >= bicolor_count) + { + if (fom_count > count) + return -1; + + count -= fom_count; + OUT_COPY_COUNT2(count, s, temp_s); + OUT_FOM_COUNT2(fom_count, s, fom_mask, fom_mask_len); + RESET_COUNTS; + } + + fom_count = 0; + fom_mask_len = 0; + } + + last_line = line; + line = line - width * 2; + start_line--; + lines_sent++; + } + + Stream_SetPosition(temp_s, 0); + + if (fill_count > 3 && fill_count >= color_count && fill_count >= bicolor_count && + fill_count >= mix_count && fill_count >= fom_count) + { + if (fill_count > count) + return -1; + + count -= fill_count; + OUT_COPY_COUNT2(count, s, temp_s); + OUT_FILL_COUNT2(fill_count, s); + } + else if (mix_count > 3 && mix_count >= color_count && mix_count >= bicolor_count && + mix_count >= fill_count && mix_count >= fom_count) + { + if (mix_count > count) + return -1; + + count -= mix_count; + OUT_COPY_COUNT2(count, s, temp_s); + OUT_MIX_COUNT2(mix_count, s); + } + else if (color_count > 3 && color_count >= mix_count && color_count >= bicolor_count && + color_count >= fill_count && color_count >= fom_count) + { + if (color_count > count) + return -1; + + count -= color_count; + OUT_COPY_COUNT2(count, s, temp_s); + OUT_COLOR_COUNT2(color_count, s, last_pixel); + } + else if (bicolor_count > 3 && bicolor_count >= mix_count && bicolor_count >= color_count && + bicolor_count >= fill_count && bicolor_count >= fom_count) + { + if ((bicolor_count % 2) != 0) + bicolor_count--; + + if (bicolor_count > count) + return -1; + + count -= bicolor_count; + OUT_COPY_COUNT2(count, s, temp_s); + OUT_BICOLOR_COUNT2(bicolor_count, s, bicolor2, bicolor1); + + if (bicolor_count > count) + return -1; + + count -= bicolor_count; + OUT_COPY_COUNT2(count, s, temp_s); + OUT_BICOLOR_COUNT2(bicolor_count, s, bicolor1, bicolor2); + } + else if (fom_count > 3 && fom_count >= mix_count && fom_count >= color_count && + fom_count >= fill_count && fom_count >= bicolor_count) + { + if (fom_count > count) + return -1; + + count -= fom_count; + OUT_COPY_COUNT2(count, s, temp_s); + OUT_FOM_COUNT2(fom_count, s, fom_mask, fom_mask_len); + } + else + { + OUT_COPY_COUNT2(count, s, temp_s); + } + + return lines_sent; +} + +SSIZE_T freerdp_bitmap_compress(const void* srcData, UINT32 width, UINT32 height, wStream* s, + UINT32 bpp, UINT32 byte_limit, UINT32 start_line, wStream* temp_s, + UINT32 e) +{ + Stream_SetPosition(temp_s, 0); + + switch (bpp) + { + case 15: + case 16: + return freerdp_bitmap_compress_16(srcData, width, height, s, bpp, byte_limit, + start_line, temp_s, e); + + case 24: + return freerdp_bitmap_compress_24(srcData, width, height, s, byte_limit, start_line, + temp_s, e); + + default: + return -1; + } +} diff --git a/libfreerdp/codec/bulk.c b/libfreerdp/codec/bulk.c new file mode 100644 index 0000000..1f5beb3 --- /dev/null +++ b/libfreerdp/codec/bulk.c @@ -0,0 +1,391 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Bulk Compression + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <math.h> +#include <winpr/assert.h> + +#include <freerdp/config.h> + +#include "../core/settings.h" +#include "bulk.h" +#include "../codec/mppc.h" +#include "../codec/ncrush.h" +#include "../codec/xcrush.h" + +#include <freerdp/log.h> +#define TAG FREERDP_TAG("core") + +//#define WITH_BULK_DEBUG 1 + +struct rdp_bulk +{ + ALIGN64 rdpContext* context; + ALIGN64 UINT32 CompressionLevel; + ALIGN64 UINT32 CompressionMaxSize; + ALIGN64 MPPC_CONTEXT* mppcSend; + ALIGN64 MPPC_CONTEXT* mppcRecv; + ALIGN64 NCRUSH_CONTEXT* ncrushRecv; + ALIGN64 NCRUSH_CONTEXT* ncrushSend; + ALIGN64 XCRUSH_CONTEXT* xcrushRecv; + ALIGN64 XCRUSH_CONTEXT* xcrushSend; + ALIGN64 BYTE OutputBuffer[65536]; +}; + +#if defined(WITH_BULK_DEBUG) +static INLINE const char* bulk_get_compression_flags_string(UINT32 flags) +{ + flags &= BULK_COMPRESSION_FLAGS_MASK; + + if (flags == 0) + return "PACKET_UNCOMPRESSED"; + else if (flags == PACKET_COMPRESSED) + return "PACKET_COMPRESSED"; + else if (flags == PACKET_AT_FRONT) + return "PACKET_AT_FRONT"; + else if (flags == PACKET_FLUSHED) + return "PACKET_FLUSHED"; + else if (flags == (PACKET_COMPRESSED | PACKET_AT_FRONT)) + return "PACKET_COMPRESSED | PACKET_AT_FRONT"; + else if (flags == (PACKET_COMPRESSED | PACKET_FLUSHED)) + return "PACKET_COMPRESSED | PACKET_FLUSHED"; + else if (flags == (PACKET_AT_FRONT | PACKET_FLUSHED)) + return "PACKET_AT_FRONT | PACKET_FLUSHED"; + else if (flags == (PACKET_COMPRESSED | PACKET_AT_FRONT | PACKET_FLUSHED)) + return "PACKET_COMPRESSED | PACKET_AT_FRONT | PACKET_FLUSHED"; + + return "PACKET_UNKNOWN"; +} +#endif + +static UINT32 bulk_compression_level(rdpBulk* bulk) +{ + rdpSettings* settings = NULL; + WINPR_ASSERT(bulk); + WINPR_ASSERT(bulk->context); + settings = bulk->context->settings; + WINPR_ASSERT(settings); + bulk->CompressionLevel = (settings->CompressionLevel >= PACKET_COMPR_TYPE_RDP61) + ? PACKET_COMPR_TYPE_RDP61 + : settings->CompressionLevel; + return bulk->CompressionLevel; +} + +UINT32 bulk_compression_max_size(rdpBulk* bulk) +{ + WINPR_ASSERT(bulk); + bulk_compression_level(bulk); + bulk->CompressionMaxSize = (bulk->CompressionLevel < PACKET_COMPR_TYPE_64K) ? 8192 : 65536; + return bulk->CompressionMaxSize; +} + +#if defined(WITH_BULK_DEBUG) +static INLINE int bulk_compress_validate(rdpBulk* bulk, const BYTE* pSrcData, UINT32 SrcSize, + const BYTE* pDstData, UINT32 DstSize, UINT32 Flags) +{ + int status; + const BYTE* v_pSrcData = NULL; + const BYTE* v_pDstData = NULL; + UINT32 v_SrcSize = 0; + UINT32 v_DstSize = 0; + UINT32 v_Flags = 0; + + WINPR_ASSERT(bulk); + WINPR_ASSERT(pSrcData); + WINPR_ASSERT(pDstData); + + v_pSrcData = pDstData; + v_SrcSize = DstSize; + v_Flags = Flags | bulk->CompressionLevel; + status = bulk_decompress(bulk, v_pSrcData, v_SrcSize, &v_pDstData, &v_DstSize, v_Flags); + + if (status < 0) + { + WLog_DBG(TAG, "compression/decompression failure"); + return status; + } + + if (v_DstSize != SrcSize) + { + WLog_DBG(TAG, + "compression/decompression size mismatch: Actual: %" PRIu32 ", Expected: %" PRIu32 + "", + v_DstSize, SrcSize); + return -1; + } + + if (memcmp(v_pDstData, pSrcData, SrcSize) != 0) + { + WLog_DBG(TAG, "compression/decompression input/output mismatch! flags: 0x%08" PRIX32 "", + v_Flags); +#if 1 + WLog_DBG(TAG, "Actual:"); + winpr_HexDump(TAG, WLOG_DEBUG, v_pDstData, SrcSize); + WLog_DBG(TAG, "Expected:"); + winpr_HexDump(TAG, WLOG_DEBUG, pSrcData, SrcSize); +#endif + return -1; + } + + return status; +} +#endif + +int bulk_decompress(rdpBulk* bulk, const BYTE* pSrcData, UINT32 SrcSize, const BYTE** ppDstData, + UINT32* pDstSize, UINT32 flags) +{ + UINT32 type = 0; + int status = -1; + rdpMetrics* metrics = NULL; + UINT32 CompressedBytes = 0; + UINT32 UncompressedBytes = 0; + double CompressionRatio = NAN; + + WINPR_ASSERT(bulk); + WINPR_ASSERT(bulk->context); + WINPR_ASSERT(pSrcData); + WINPR_ASSERT(ppDstData); + WINPR_ASSERT(pDstSize); + + metrics = bulk->context->metrics; + WINPR_ASSERT(metrics); + + bulk_compression_max_size(bulk); + type = flags & BULK_COMPRESSION_TYPE_MASK; + + if (flags & BULK_COMPRESSION_FLAGS_MASK) + { + switch (type) + { + case PACKET_COMPR_TYPE_8K: + mppc_set_compression_level(bulk->mppcRecv, 0); + status = + mppc_decompress(bulk->mppcRecv, pSrcData, SrcSize, ppDstData, pDstSize, flags); + break; + + case PACKET_COMPR_TYPE_64K: + mppc_set_compression_level(bulk->mppcRecv, 1); + status = + mppc_decompress(bulk->mppcRecv, pSrcData, SrcSize, ppDstData, pDstSize, flags); + break; + + case PACKET_COMPR_TYPE_RDP6: + status = ncrush_decompress(bulk->ncrushRecv, pSrcData, SrcSize, ppDstData, pDstSize, + flags); + break; + + case PACKET_COMPR_TYPE_RDP61: + status = xcrush_decompress(bulk->xcrushRecv, pSrcData, SrcSize, ppDstData, pDstSize, + flags); + break; + + case PACKET_COMPR_TYPE_RDP8: + WLog_ERR(TAG, "Unsupported bulk compression type %08" PRIx32, + bulk->CompressionLevel); + status = -1; + break; + default: + WLog_ERR(TAG, "Unknown bulk compression type %08" PRIx32, bulk->CompressionLevel); + status = -1; + break; + } + } + else + { + *ppDstData = pSrcData; + *pDstSize = SrcSize; + status = 0; + } + + if (status >= 0) + { + CompressedBytes = SrcSize; + UncompressedBytes = *pDstSize; + CompressionRatio = metrics_write_bytes(metrics, UncompressedBytes, CompressedBytes); +#ifdef WITH_BULK_DEBUG + { + WLog_DBG(TAG, + "Decompress Type: %" PRIu32 " Flags: %s (0x%08" PRIX32 + ") Compression Ratio: %f (%" PRIu32 " / %" PRIu32 "), Total: %f (%" PRIu64 + " / %" PRIu64 ")", + type, bulk_get_compression_flags_string(flags), flags, CompressionRatio, + CompressedBytes, UncompressedBytes, metrics->TotalCompressionRatio, + metrics->TotalCompressedBytes, metrics->TotalUncompressedBytes); + } +#else + WINPR_UNUSED(CompressionRatio); +#endif + } + else + { + WLog_ERR(TAG, "Decompression failure!"); + } + + return status; +} + +int bulk_compress(rdpBulk* bulk, const BYTE* pSrcData, UINT32 SrcSize, const BYTE** ppDstData, + UINT32* pDstSize, UINT32* pFlags) +{ + int status = -1; + rdpMetrics* metrics = NULL; + UINT32 CompressedBytes = 0; + UINT32 UncompressedBytes = 0; + double CompressionRatio = NAN; + + WINPR_ASSERT(bulk); + WINPR_ASSERT(bulk->context); + WINPR_ASSERT(pSrcData); + WINPR_ASSERT(ppDstData); + WINPR_ASSERT(pDstSize); + + metrics = bulk->context->metrics; + WINPR_ASSERT(metrics); + + if ((SrcSize <= 50) || (SrcSize >= 16384)) + { + *ppDstData = pSrcData; + *pDstSize = SrcSize; + return 0; + } + + *pDstSize = sizeof(bulk->OutputBuffer); + bulk_compression_level(bulk); + bulk_compression_max_size(bulk); + + switch (bulk->CompressionLevel) + { + case PACKET_COMPR_TYPE_8K: + case PACKET_COMPR_TYPE_64K: + mppc_set_compression_level(bulk->mppcSend, bulk->CompressionLevel); + status = mppc_compress(bulk->mppcSend, pSrcData, SrcSize, bulk->OutputBuffer, ppDstData, + pDstSize, pFlags); + break; + case PACKET_COMPR_TYPE_RDP6: + status = ncrush_compress(bulk->ncrushSend, pSrcData, SrcSize, bulk->OutputBuffer, + ppDstData, pDstSize, pFlags); + break; + case PACKET_COMPR_TYPE_RDP61: + status = xcrush_compress(bulk->xcrushSend, pSrcData, SrcSize, bulk->OutputBuffer, + ppDstData, pDstSize, pFlags); + break; + case PACKET_COMPR_TYPE_RDP8: + WLog_ERR(TAG, "Unsupported bulk compression type %08" PRIx32, bulk->CompressionLevel); + status = -1; + break; + default: + WLog_ERR(TAG, "Unknown bulk compression type %08" PRIx32, bulk->CompressionLevel); + status = -1; + break; + } + + if (status >= 0) + { + CompressedBytes = *pDstSize; + UncompressedBytes = SrcSize; + CompressionRatio = metrics_write_bytes(metrics, UncompressedBytes, CompressedBytes); +#ifdef WITH_BULK_DEBUG + { + WLog_DBG(TAG, + "Compress Type: %" PRIu32 " Flags: %s (0x%08" PRIX32 + ") Compression Ratio: %f (%" PRIu32 " / %" PRIu32 "), Total: %f (%" PRIu64 + " / %" PRIu64 ")", + bulk->CompressionLevel, bulk_get_compression_flags_string(*pFlags), *pFlags, + CompressionRatio, CompressedBytes, UncompressedBytes, + metrics->TotalCompressionRatio, metrics->TotalCompressedBytes, + metrics->TotalUncompressedBytes); + } +#else + WINPR_UNUSED(CompressionRatio); +#endif + } + +#if defined(WITH_BULK_DEBUG) + + if (bulk_compress_validate(bulk, pSrcData, SrcSize, *ppDstData, *pDstSize, *pFlags) < 0) + status = -1; + +#endif + return status; +} + +void bulk_reset(rdpBulk* bulk) +{ + WINPR_ASSERT(bulk); + + mppc_context_reset(bulk->mppcSend, FALSE); + mppc_context_reset(bulk->mppcRecv, FALSE); + ncrush_context_reset(bulk->ncrushRecv, FALSE); + ncrush_context_reset(bulk->ncrushSend, FALSE); + xcrush_context_reset(bulk->xcrushRecv, FALSE); + xcrush_context_reset(bulk->xcrushSend, FALSE); +} + +rdpBulk* bulk_new(rdpContext* context) +{ + rdpBulk* bulk = NULL; + WINPR_ASSERT(context); + + bulk = (rdpBulk*)calloc(1, sizeof(rdpBulk)); + + if (!bulk) + goto fail; + + bulk->context = context; + bulk->mppcSend = mppc_context_new(1, TRUE); + if (!bulk->mppcSend) + goto fail; + bulk->mppcRecv = mppc_context_new(1, FALSE); + if (!bulk->mppcRecv) + goto fail; + bulk->ncrushRecv = ncrush_context_new(FALSE); + if (!bulk->ncrushRecv) + goto fail; + bulk->ncrushSend = ncrush_context_new(TRUE); + if (!bulk->ncrushSend) + goto fail; + bulk->xcrushRecv = xcrush_context_new(FALSE); + if (!bulk->xcrushRecv) + goto fail; + bulk->xcrushSend = xcrush_context_new(TRUE); + if (!bulk->xcrushSend) + goto fail; + bulk->CompressionLevel = context->settings->CompressionLevel; + + return bulk; +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + bulk_free(bulk); + WINPR_PRAGMA_DIAG_POP + return NULL; +} + +void bulk_free(rdpBulk* bulk) +{ + if (!bulk) + return; + + mppc_context_free(bulk->mppcSend); + mppc_context_free(bulk->mppcRecv); + ncrush_context_free(bulk->ncrushRecv); + ncrush_context_free(bulk->ncrushSend); + xcrush_context_free(bulk->xcrushRecv); + xcrush_context_free(bulk->xcrushSend); + free(bulk); +} diff --git a/libfreerdp/codec/bulk.h b/libfreerdp/codec/bulk.h new file mode 100644 index 0000000..4c85406 --- /dev/null +++ b/libfreerdp/codec/bulk.h @@ -0,0 +1,45 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Bulk Compression + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_LIB_CORE_BULK_H +#define FREERDP_LIB_CORE_BULK_H + +typedef struct rdp_bulk rdpBulk; + +#include <freerdp/api.h> +#include <freerdp/freerdp.h> + +#define BULK_COMPRESSION_FLAGS_MASK 0xE0 +#define BULK_COMPRESSION_TYPE_MASK 0x0F + +FREERDP_LOCAL UINT32 bulk_compression_max_size(rdpBulk* bulk); + +FREERDP_LOCAL int bulk_decompress(rdpBulk* bulk, const BYTE* pSrcData, UINT32 SrcSize, + const BYTE** ppDstData, UINT32* pDstSize, UINT32 flags); +FREERDP_LOCAL int bulk_compress(rdpBulk* bulk, const BYTE* pSrcData, UINT32 SrcSize, + const BYTE** ppDstData, UINT32* pDstSize, UINT32* pFlags); + +FREERDP_LOCAL void bulk_reset(rdpBulk* bulk); + +FREERDP_LOCAL void bulk_free(rdpBulk* bulk); + +WINPR_ATTR_MALLOC(bulk_free, 1) +FREERDP_LOCAL rdpBulk* bulk_new(rdpContext* context); + +#endif /* FREERDP_LIB_CORE_BULK_H */ diff --git a/libfreerdp/codec/clear.c b/libfreerdp/codec/clear.c new file mode 100644 index 0000000..5c009d8 --- /dev/null +++ b/libfreerdp/codec/clear.c @@ -0,0 +1,1188 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * ClearCodec Bitmap Compression + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2016 Armin Novak <armin.novak@thincast.com> + * Copyright 2016 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <winpr/crt.h> +#include <winpr/print.h> +#include <winpr/bitstream.h> + +#include <freerdp/codec/color.h> +#include <freerdp/codec/clear.h> +#include <freerdp/log.h> + +#define TAG FREERDP_TAG("codec.clear") + +#define CLEARCODEC_FLAG_GLYPH_INDEX 0x01 +#define CLEARCODEC_FLAG_GLYPH_HIT 0x02 +#define CLEARCODEC_FLAG_CACHE_RESET 0x04 + +#define CLEARCODEC_VBAR_SIZE 32768 +#define CLEARCODEC_VBAR_SHORT_SIZE 16384 + +typedef struct +{ + UINT32 size; + UINT32 count; + UINT32* pixels; +} CLEAR_GLYPH_ENTRY; + +typedef struct +{ + UINT32 size; + UINT32 count; + BYTE* pixels; +} CLEAR_VBAR_ENTRY; + +struct S_CLEAR_CONTEXT +{ + BOOL Compressor; + NSC_CONTEXT* nsc; + UINT32 seqNumber; + BYTE* TempBuffer; + UINT32 TempSize; + UINT32 nTempStep; + UINT32 TempFormat; + UINT32 format; + CLEAR_GLYPH_ENTRY GlyphCache[4000]; + UINT32 VBarStorageCursor; + CLEAR_VBAR_ENTRY VBarStorage[CLEARCODEC_VBAR_SIZE]; + UINT32 ShortVBarStorageCursor; + CLEAR_VBAR_ENTRY ShortVBarStorage[CLEARCODEC_VBAR_SHORT_SIZE]; +}; + +static const UINT32 CLEAR_LOG2_FLOOR[256] = { + 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7 +}; + +static const BYTE CLEAR_8BIT_MASKS[9] = { 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF }; + +static void clear_reset_vbar_storage(CLEAR_CONTEXT* clear, BOOL zero) +{ + if (zero) + { + for (size_t i = 0; i < ARRAYSIZE(clear->VBarStorage); i++) + winpr_aligned_free(clear->VBarStorage[i].pixels); + + ZeroMemory(clear->VBarStorage, sizeof(clear->VBarStorage)); + } + + clear->VBarStorageCursor = 0; + + if (zero) + { + for (size_t i = 0; i < ARRAYSIZE(clear->ShortVBarStorage); i++) + winpr_aligned_free(clear->ShortVBarStorage[i].pixels); + + ZeroMemory(clear->ShortVBarStorage, sizeof(clear->ShortVBarStorage)); + } + + clear->ShortVBarStorageCursor = 0; +} + +static void clear_reset_glyph_cache(CLEAR_CONTEXT* clear) +{ + for (size_t i = 0; i < ARRAYSIZE(clear->GlyphCache); i++) + winpr_aligned_free(clear->GlyphCache[i].pixels); + + ZeroMemory(clear->GlyphCache, sizeof(clear->GlyphCache)); +} + +static BOOL convert_color(BYTE* dst, UINT32 nDstStep, UINT32 DstFormat, UINT32 nXDst, UINT32 nYDst, + UINT32 nWidth, UINT32 nHeight, const BYTE* src, UINT32 nSrcStep, + UINT32 SrcFormat, UINT32 nDstWidth, UINT32 nDstHeight, + const gdiPalette* palette) +{ + if (nWidth + nXDst > nDstWidth) + nWidth = nDstWidth - nXDst; + + if (nHeight + nYDst > nDstHeight) + nHeight = nDstHeight - nYDst; + + return freerdp_image_copy(dst, DstFormat, nDstStep, nXDst, nYDst, nWidth, nHeight, src, + SrcFormat, nSrcStep, 0, 0, palette, FREERDP_KEEP_DST_ALPHA); +} + +static BOOL clear_decompress_nscodec(NSC_CONTEXT* nsc, UINT32 width, UINT32 height, wStream* s, + UINT32 bitmapDataByteCount, BYTE* pDstData, UINT32 DstFormat, + UINT32 nDstStep, UINT32 nXDstRel, UINT32 nYDstRel) +{ + BOOL rc = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, bitmapDataByteCount)) + return FALSE; + + rc = nsc_process_message(nsc, 32, width, height, Stream_Pointer(s), bitmapDataByteCount, + pDstData, DstFormat, nDstStep, nXDstRel, nYDstRel, width, height, + FREERDP_FLIP_NONE); + Stream_Seek(s, bitmapDataByteCount); + return rc; +} + +static BOOL clear_decompress_subcode_rlex(wStream* s, UINT32 bitmapDataByteCount, UINT32 width, + UINT32 height, BYTE* pDstData, UINT32 DstFormat, + UINT32 nDstStep, UINT32 nXDstRel, UINT32 nYDstRel, + UINT32 nDstWidth, UINT32 nDstHeight) +{ + UINT32 x = 0; + UINT32 y = 0; + UINT32 pixelCount = 0; + UINT32 bitmapDataOffset = 0; + size_t pixelIndex = 0; + UINT32 numBits = 0; + BYTE startIndex = 0; + BYTE stopIndex = 0; + BYTE suiteIndex = 0; + BYTE suiteDepth = 0; + BYTE paletteCount = 0; + UINT32 palette[128] = { 0 }; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, bitmapDataByteCount)) + return FALSE; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return FALSE; + Stream_Read_UINT8(s, paletteCount); + bitmapDataOffset = 1 + (paletteCount * 3); + + if ((paletteCount > 127) || (paletteCount < 1)) + { + WLog_ERR(TAG, "paletteCount %" PRIu8 "", paletteCount); + return FALSE; + } + + if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, paletteCount, 3ull)) + return FALSE; + + for (UINT32 i = 0; i < paletteCount; i++) + { + BYTE r = 0; + BYTE g = 0; + BYTE b = 0; + Stream_Read_UINT8(s, b); + Stream_Read_UINT8(s, g); + Stream_Read_UINT8(s, r); + palette[i] = FreeRDPGetColor(DstFormat, r, g, b, 0xFF); + } + + pixelIndex = 0; + pixelCount = width * height; + numBits = CLEAR_LOG2_FLOOR[paletteCount - 1] + 1; + + while (bitmapDataOffset < bitmapDataByteCount) + { + UINT32 tmp = 0; + UINT32 color = 0; + UINT32 runLengthFactor = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return FALSE; + + Stream_Read_UINT8(s, tmp); + Stream_Read_UINT8(s, runLengthFactor); + bitmapDataOffset += 2; + suiteDepth = (tmp >> numBits) & CLEAR_8BIT_MASKS[(8 - numBits)]; + stopIndex = tmp & CLEAR_8BIT_MASKS[numBits]; + startIndex = stopIndex - suiteDepth; + + if (runLengthFactor >= 0xFF) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return FALSE; + + Stream_Read_UINT16(s, runLengthFactor); + bitmapDataOffset += 2; + + if (runLengthFactor >= 0xFFFF) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return FALSE; + + Stream_Read_UINT32(s, runLengthFactor); + bitmapDataOffset += 4; + } + } + + if (startIndex >= paletteCount) + { + WLog_ERR(TAG, "startIndex %" PRIu8 " > paletteCount %" PRIu8 "]", startIndex, + paletteCount); + return FALSE; + } + + if (stopIndex >= paletteCount) + { + WLog_ERR(TAG, "stopIndex %" PRIu8 " > paletteCount %" PRIu8 "]", stopIndex, + paletteCount); + return FALSE; + } + + suiteIndex = startIndex; + + if (suiteIndex > 127) + { + WLog_ERR(TAG, "suiteIndex %" PRIu8 " > 127]", suiteIndex); + return FALSE; + } + + color = palette[suiteIndex]; + + if ((pixelIndex + runLengthFactor) > pixelCount) + { + WLog_ERR(TAG, + "pixelIndex %" PRIu32 " + runLengthFactor %" PRIu32 " > pixelCount %" PRIu32 + "", + pixelIndex, runLengthFactor, pixelCount); + return FALSE; + } + + for (UINT32 i = 0; i < runLengthFactor; i++) + { + BYTE* pTmpData = &pDstData[(nXDstRel + x) * FreeRDPGetBytesPerPixel(DstFormat) + + (nYDstRel + y) * nDstStep]; + + if ((nXDstRel + x < nDstWidth) && (nYDstRel + y < nDstHeight)) + FreeRDPWriteColor(pTmpData, DstFormat, color); + + if (++x >= width) + { + y++; + x = 0; + } + } + + pixelIndex += runLengthFactor; + + if ((pixelIndex + (suiteDepth + 1)) > pixelCount) + { + WLog_ERR(TAG, + "pixelIndex %" PRIu32 " + suiteDepth %" PRIu8 " + 1 > pixelCount %" PRIu32 "", + pixelIndex, suiteDepth, pixelCount); + return FALSE; + } + + for (UINT32 i = 0; i <= suiteDepth; i++) + { + BYTE* pTmpData = &pDstData[(nXDstRel + x) * FreeRDPGetBytesPerPixel(DstFormat) + + (nYDstRel + y) * nDstStep]; + UINT32 ccolor = palette[suiteIndex]; + + if (suiteIndex > 127) + { + WLog_ERR(TAG, "suiteIndex %" PRIu8 " > 127", suiteIndex); + return FALSE; + } + + suiteIndex++; + + if ((nXDstRel + x < nDstWidth) && (nYDstRel + y < nDstHeight)) + FreeRDPWriteColor(pTmpData, DstFormat, ccolor); + + if (++x >= width) + { + y++; + x = 0; + } + } + + pixelIndex += (suiteDepth + 1); + } + + if (pixelIndex != pixelCount) + { + WLog_ERR(TAG, "pixelIndex %" PRIdz " != pixelCount %" PRIu32 "", pixelIndex, pixelCount); + return FALSE; + } + + return TRUE; +} + +static BOOL clear_resize_buffer(CLEAR_CONTEXT* clear, UINT32 width, UINT32 height) +{ + UINT32 size = 0; + + if (!clear) + return FALSE; + + size = ((width + 16) * (height + 16) * FreeRDPGetBytesPerPixel(clear->format)); + + if (size > clear->TempSize) + { + BYTE* tmp = (BYTE*)winpr_aligned_recalloc(clear->TempBuffer, size, sizeof(BYTE), 32); + + if (!tmp) + { + WLog_ERR(TAG, "clear->TempBuffer winpr_aligned_recalloc failed for %" PRIu32 " bytes", + size); + return FALSE; + } + + clear->TempSize = size; + clear->TempBuffer = tmp; + } + + return TRUE; +} + +static BOOL clear_decompress_residual_data(CLEAR_CONTEXT* clear, wStream* s, + UINT32 residualByteCount, UINT32 nWidth, UINT32 nHeight, + BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep, + UINT32 nXDst, UINT32 nYDst, UINT32 nDstWidth, + UINT32 nDstHeight, const gdiPalette* palette) +{ + UINT32 nSrcStep = 0; + UINT32 suboffset = 0; + BYTE* dstBuffer = NULL; + UINT32 pixelIndex = 0; + UINT32 pixelCount = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, residualByteCount)) + return FALSE; + + suboffset = 0; + pixelIndex = 0; + pixelCount = nWidth * nHeight; + + if (!clear_resize_buffer(clear, nWidth, nHeight)) + return FALSE; + + dstBuffer = clear->TempBuffer; + + while (suboffset < residualByteCount) + { + BYTE r = 0; + BYTE g = 0; + BYTE b = 0; + UINT32 runLengthFactor = 0; + UINT32 color = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return FALSE; + + Stream_Read_UINT8(s, b); + Stream_Read_UINT8(s, g); + Stream_Read_UINT8(s, r); + Stream_Read_UINT8(s, runLengthFactor); + suboffset += 4; + color = FreeRDPGetColor(clear->format, r, g, b, 0xFF); + + if (runLengthFactor >= 0xFF) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return FALSE; + + Stream_Read_UINT16(s, runLengthFactor); + suboffset += 2; + + if (runLengthFactor >= 0xFFFF) + { + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return FALSE; + + Stream_Read_UINT32(s, runLengthFactor); + suboffset += 4; + } + } + + if ((pixelIndex + runLengthFactor) > pixelCount) + { + WLog_ERR(TAG, + "pixelIndex %" PRIu32 " + runLengthFactor %" PRIu32 " > pixelCount %" PRIu32 + "", + pixelIndex, runLengthFactor, pixelCount); + return FALSE; + } + + for (UINT32 i = 0; i < runLengthFactor; i++) + { + FreeRDPWriteColor(dstBuffer, clear->format, color); + dstBuffer += FreeRDPGetBytesPerPixel(clear->format); + } + + pixelIndex += runLengthFactor; + } + + nSrcStep = nWidth * FreeRDPGetBytesPerPixel(clear->format); + + if (pixelIndex != pixelCount) + { + WLog_ERR(TAG, "pixelIndex %" PRIu32 " != pixelCount %" PRIu32 "", pixelIndex, pixelCount); + return FALSE; + } + + return convert_color(pDstData, nDstStep, DstFormat, nXDst, nYDst, nWidth, nHeight, + clear->TempBuffer, nSrcStep, clear->format, nDstWidth, nDstHeight, + palette); +} + +static BOOL clear_decompress_subcodecs_data(CLEAR_CONTEXT* clear, wStream* s, + UINT32 subcodecByteCount, UINT32 nWidth, UINT32 nHeight, + BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep, + UINT32 nXDst, UINT32 nYDst, UINT32 nDstWidth, + UINT32 nDstHeight, const gdiPalette* palette) +{ + UINT16 xStart = 0; + UINT16 yStart = 0; + UINT16 width = 0; + UINT16 height = 0; + UINT32 bitmapDataByteCount = 0; + BYTE subcodecId = 0; + UINT32 suboffset = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, subcodecByteCount)) + return FALSE; + + suboffset = 0; + + while (suboffset < subcodecByteCount) + { + UINT32 nXDstRel = 0; + UINT32 nYDstRel = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 13)) + return FALSE; + + Stream_Read_UINT16(s, xStart); + Stream_Read_UINT16(s, yStart); + Stream_Read_UINT16(s, width); + Stream_Read_UINT16(s, height); + Stream_Read_UINT32(s, bitmapDataByteCount); + Stream_Read_UINT8(s, subcodecId); + suboffset += 13; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, bitmapDataByteCount)) + return FALSE; + + nXDstRel = nXDst + xStart; + nYDstRel = nYDst + yStart; + + if (width > nWidth) + { + WLog_ERR(TAG, "width %" PRIu16 " > nWidth %" PRIu32 "", width, nWidth); + return FALSE; + } + + if (height > nHeight) + { + WLog_ERR(TAG, "height %" PRIu16 " > nHeight %" PRIu32 "", height, nHeight); + return FALSE; + } + + if (!clear_resize_buffer(clear, width, height)) + return FALSE; + + switch (subcodecId) + { + case 0: /* Uncompressed */ + { + UINT32 nSrcStep = width * FreeRDPGetBytesPerPixel(PIXEL_FORMAT_BGR24); + UINT32 nSrcSize = nSrcStep * height; + + if (bitmapDataByteCount != nSrcSize) + { + WLog_ERR(TAG, "bitmapDataByteCount %" PRIu32 " != nSrcSize %" PRIu32 "", + bitmapDataByteCount, nSrcSize); + return FALSE; + } + + if (!convert_color(pDstData, nDstStep, DstFormat, nXDstRel, nYDstRel, width, height, + Stream_Pointer(s), nSrcStep, PIXEL_FORMAT_BGR24, nDstWidth, + nDstHeight, palette)) + return FALSE; + + Stream_Seek(s, bitmapDataByteCount); + } + break; + + case 1: /* NSCodec */ + if (!clear_decompress_nscodec(clear->nsc, width, height, s, bitmapDataByteCount, + pDstData, DstFormat, nDstStep, nXDstRel, nYDstRel)) + return FALSE; + + break; + + case 2: /* CLEARCODEC_SUBCODEC_RLEX */ + if (!clear_decompress_subcode_rlex(s, bitmapDataByteCount, width, height, pDstData, + DstFormat, nDstStep, nXDstRel, nYDstRel, + nDstWidth, nDstHeight)) + return FALSE; + + break; + + default: + WLog_ERR(TAG, "Unknown subcodec ID %" PRIu8 "", subcodecId); + return FALSE; + } + + suboffset += bitmapDataByteCount; + } + + return TRUE; +} + +static BOOL resize_vbar_entry(CLEAR_CONTEXT* clear, CLEAR_VBAR_ENTRY* vBarEntry) +{ + if (vBarEntry->count > vBarEntry->size) + { + const UINT32 bpp = FreeRDPGetBytesPerPixel(clear->format); + const UINT32 oldPos = vBarEntry->size * bpp; + const UINT32 diffSize = (vBarEntry->count - vBarEntry->size) * bpp; + + vBarEntry->size = vBarEntry->count; + BYTE* tmp = + (BYTE*)winpr_aligned_recalloc(vBarEntry->pixels, vBarEntry->count, 1ull * bpp, 32); + + if (!tmp) + { + WLog_ERR(TAG, "vBarEntry->pixels winpr_aligned_recalloc %" PRIu32 " failed", + vBarEntry->count * bpp); + return FALSE; + } + + memset(&tmp[oldPos], 0, diffSize); + vBarEntry->pixels = tmp; + } + + if (!vBarEntry->pixels && vBarEntry->size) + { + WLog_ERR(TAG, "vBarEntry->pixels is NULL but vBarEntry->size is %" PRIu32 "", + vBarEntry->size); + return FALSE; + } + + return TRUE; +} + +static BOOL clear_decompress_bands_data(CLEAR_CONTEXT* clear, wStream* s, UINT32 bandsByteCount, + UINT32 nWidth, UINT32 nHeight, BYTE* pDstData, + UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst, + UINT32 nYDst, UINT32 nDstWidth, UINT32 nDstHeight) +{ + UINT32 suboffset = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, bandsByteCount)) + return FALSE; + + while (suboffset < bandsByteCount) + { + BYTE cr = 0; + BYTE cg = 0; + BYTE cb = 0; + UINT16 xStart = 0; + UINT16 xEnd = 0; + UINT16 yStart = 0; + UINT16 yEnd = 0; + UINT32 colorBkg = 0; + UINT16 vBarHeader = 0; + UINT16 vBarYOn = 0; + UINT16 vBarYOff = 0; + UINT32 vBarCount = 0; + UINT32 vBarPixelCount = 0; + UINT32 vBarShortPixelCount = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 11)) + return FALSE; + + Stream_Read_UINT16(s, xStart); + Stream_Read_UINT16(s, xEnd); + Stream_Read_UINT16(s, yStart); + Stream_Read_UINT16(s, yEnd); + Stream_Read_UINT8(s, cb); + Stream_Read_UINT8(s, cg); + Stream_Read_UINT8(s, cr); + suboffset += 11; + colorBkg = FreeRDPGetColor(clear->format, cr, cg, cb, 0xFF); + + if (xEnd < xStart) + { + WLog_ERR(TAG, "xEnd %" PRIu16 " < xStart %" PRIu16 "", xEnd, xStart); + return FALSE; + } + + if (yEnd < yStart) + { + WLog_ERR(TAG, "yEnd %" PRIu16 " < yStart %" PRIu16 "", yEnd, yStart); + return FALSE; + } + + vBarCount = (xEnd - xStart) + 1; + + for (UINT32 i = 0; i < vBarCount; i++) + { + UINT32 vBarHeight = 0; + CLEAR_VBAR_ENTRY* vBarEntry = NULL; + CLEAR_VBAR_ENTRY* vBarShortEntry = NULL; + BOOL vBarUpdate = FALSE; + const BYTE* cpSrcPixel = NULL; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return FALSE; + + Stream_Read_UINT16(s, vBarHeader); + suboffset += 2; + vBarHeight = (yEnd - yStart + 1); + + if (vBarHeight > 52) + { + WLog_ERR(TAG, "vBarHeight (%" PRIu32 ") > 52", vBarHeight); + return FALSE; + } + + if ((vBarHeader & 0xC000) == 0x4000) /* SHORT_VBAR_CACHE_HIT */ + { + const UINT16 vBarIndex = (vBarHeader & 0x3FFF); + vBarShortEntry = &(clear->ShortVBarStorage[vBarIndex]); + + if (!vBarShortEntry) + { + WLog_ERR(TAG, "missing vBarShortEntry %" PRIu16 "", vBarIndex); + return FALSE; + } + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 1)) + return FALSE; + + Stream_Read_UINT8(s, vBarYOn); + suboffset += 1; + vBarShortPixelCount = vBarShortEntry->count; + vBarUpdate = TRUE; + } + else if ((vBarHeader & 0xC000) == 0x0000) /* SHORT_VBAR_CACHE_MISS */ + { + vBarYOn = (vBarHeader & 0xFF); + vBarYOff = ((vBarHeader >> 8) & 0x3F); + + if (vBarYOff < vBarYOn) + { + WLog_ERR(TAG, "vBarYOff %" PRIu16 " < vBarYOn %" PRIu16 "", vBarYOff, vBarYOn); + return FALSE; + } + + vBarShortPixelCount = (vBarYOff - vBarYOn); + + if (vBarShortPixelCount > 52) + { + WLog_ERR(TAG, "vBarShortPixelCount %" PRIu32 " > 52", vBarShortPixelCount); + return FALSE; + } + + if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, vBarShortPixelCount, 3ull)) + return FALSE; + + if (clear->ShortVBarStorageCursor >= CLEARCODEC_VBAR_SHORT_SIZE) + { + WLog_ERR(TAG, + "clear->ShortVBarStorageCursor %" PRIu32 + " >= CLEARCODEC_VBAR_SHORT_SIZE (%" PRIu32 ")", + clear->ShortVBarStorageCursor, CLEARCODEC_VBAR_SHORT_SIZE); + return FALSE; + } + + vBarShortEntry = &(clear->ShortVBarStorage[clear->ShortVBarStorageCursor]); + vBarShortEntry->count = vBarShortPixelCount; + + if (!resize_vbar_entry(clear, vBarShortEntry)) + return FALSE; + + for (UINT32 y = 0; y < vBarShortPixelCount; y++) + { + BYTE r = 0; + BYTE g = 0; + BYTE b = 0; + BYTE* dstBuffer = + &vBarShortEntry->pixels[y * FreeRDPGetBytesPerPixel(clear->format)]; + UINT32 color = 0; + Stream_Read_UINT8(s, b); + Stream_Read_UINT8(s, g); + Stream_Read_UINT8(s, r); + color = FreeRDPGetColor(clear->format, r, g, b, 0xFF); + + if (!FreeRDPWriteColor(dstBuffer, clear->format, color)) + return FALSE; + } + + suboffset += (vBarShortPixelCount * 3); + clear->ShortVBarStorageCursor = + (clear->ShortVBarStorageCursor + 1) % CLEARCODEC_VBAR_SHORT_SIZE; + vBarUpdate = TRUE; + } + else if ((vBarHeader & 0x8000) == 0x8000) /* VBAR_CACHE_HIT */ + { + const UINT16 vBarIndex = (vBarHeader & 0x7FFF); + vBarEntry = &(clear->VBarStorage[vBarIndex]); + + /* If the cache was reset we need to fill in some dummy data. */ + if (vBarEntry->size == 0) + { + WLog_WARN(TAG, "Empty cache index %" PRIu16 ", filling dummy data", vBarIndex); + vBarEntry->count = vBarHeight; + + if (!resize_vbar_entry(clear, vBarEntry)) + return FALSE; + } + } + else + { + WLog_ERR(TAG, "invalid vBarHeader 0x%04" PRIX16 "", vBarHeader); + return FALSE; /* invalid vBarHeader */ + } + + if (vBarUpdate) + { + BYTE* pSrcPixel = NULL; + BYTE* dstBuffer = NULL; + + if (clear->VBarStorageCursor >= CLEARCODEC_VBAR_SIZE) + { + WLog_ERR(TAG, + "clear->VBarStorageCursor %" PRIu32 " >= CLEARCODEC_VBAR_SIZE %" PRIu32 + "", + clear->VBarStorageCursor, CLEARCODEC_VBAR_SIZE); + return FALSE; + } + + vBarEntry = &(clear->VBarStorage[clear->VBarStorageCursor]); + vBarPixelCount = vBarHeight; + vBarEntry->count = vBarPixelCount; + + if (!resize_vbar_entry(clear, vBarEntry)) + return FALSE; + + dstBuffer = vBarEntry->pixels; + /* if (y < vBarYOn), use colorBkg */ + UINT32 y = 0; + UINT32 count = vBarYOn; + + if ((y + count) > vBarPixelCount) + count = (vBarPixelCount > y) ? (vBarPixelCount - y) : 0; + + while (count--) + { + FreeRDPWriteColor(dstBuffer, clear->format, colorBkg); + dstBuffer += FreeRDPGetBytesPerPixel(clear->format); + } + + /* + * if ((y >= vBarYOn) && (y < (vBarYOn + vBarShortPixelCount))), + * use vBarShortPixels at index (y - shortVBarYOn) + */ + y = vBarYOn; + count = vBarShortPixelCount; + + if ((y + count) > vBarPixelCount) + count = (vBarPixelCount > y) ? (vBarPixelCount - y) : 0; + + if (count > 0) + pSrcPixel = + &vBarShortEntry + ->pixels[(y - vBarYOn) * FreeRDPGetBytesPerPixel(clear->format)]; + + for (UINT32 x = 0; x < count; x++) + { + UINT32 color = 0; + color = FreeRDPReadColor(&pSrcPixel[x * FreeRDPGetBytesPerPixel(clear->format)], + clear->format); + + if (!FreeRDPWriteColor(dstBuffer, clear->format, color)) + return FALSE; + + dstBuffer += FreeRDPGetBytesPerPixel(clear->format); + } + + /* if (y >= (vBarYOn + vBarShortPixelCount)), use colorBkg */ + y = vBarYOn + vBarShortPixelCount; + count = (vBarPixelCount > y) ? (vBarPixelCount - y) : 0; + + while (count--) + { + if (!FreeRDPWriteColor(dstBuffer, clear->format, colorBkg)) + return FALSE; + + dstBuffer += FreeRDPGetBytesPerPixel(clear->format); + } + + vBarEntry->count = vBarPixelCount; + clear->VBarStorageCursor = (clear->VBarStorageCursor + 1) % CLEARCODEC_VBAR_SIZE; + } + + if (vBarEntry->count != vBarHeight) + { + WLog_ERR(TAG, "vBarEntry->count %" PRIu32 " != vBarHeight %" PRIu32 "", + vBarEntry->count, vBarHeight); + vBarEntry->count = vBarHeight; + + if (!resize_vbar_entry(clear, vBarEntry)) + return FALSE; + } + + const UINT32 nXDstRel = nXDst + xStart; + const UINT32 nYDstRel = nYDst + yStart; + cpSrcPixel = vBarEntry->pixels; + + if (i < nWidth) + { + UINT32 count = vBarEntry->count; + + if (count > nHeight) + count = nHeight; + + if (nXDstRel + i > nDstWidth) + return FALSE; + + for (UINT32 y = 0; y < count; y++) + { + if (nYDstRel + y > nDstHeight) + return FALSE; + + BYTE* pDstPixel8 = + &pDstData[((nYDstRel + y) * nDstStep) + + ((nXDstRel + i) * FreeRDPGetBytesPerPixel(DstFormat))]; + UINT32 color = FreeRDPReadColor(cpSrcPixel, clear->format); + color = FreeRDPConvertColor(color, clear->format, DstFormat, NULL); + + if (!FreeRDPWriteColor(pDstPixel8, DstFormat, color)) + return FALSE; + + cpSrcPixel += FreeRDPGetBytesPerPixel(clear->format); + } + } + } + } + + return TRUE; +} + +static BOOL clear_decompress_glyph_data(CLEAR_CONTEXT* clear, wStream* s, UINT32 glyphFlags, + UINT32 nWidth, UINT32 nHeight, BYTE* pDstData, + UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst, + UINT32 nYDst, UINT32 nDstWidth, UINT32 nDstHeight, + const gdiPalette* palette, BYTE** ppGlyphData) +{ + UINT16 glyphIndex = 0; + + if (ppGlyphData) + *ppGlyphData = NULL; + + if ((glyphFlags & CLEARCODEC_FLAG_GLYPH_HIT) && !(glyphFlags & CLEARCODEC_FLAG_GLYPH_INDEX)) + { + WLog_ERR(TAG, "Invalid glyph flags %08" PRIX32 "", glyphFlags); + return FALSE; + } + + if ((glyphFlags & CLEARCODEC_FLAG_GLYPH_INDEX) == 0) + return TRUE; + + if ((nWidth * nHeight) > (1024 * 1024)) + { + WLog_ERR(TAG, "glyph too large: %" PRIu32 "x%" PRIu32 "", nWidth, nHeight); + return FALSE; + } + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + return FALSE; + + Stream_Read_UINT16(s, glyphIndex); + + if (glyphIndex >= 4000) + { + WLog_ERR(TAG, "Invalid glyphIndex %" PRIu16 "", glyphIndex); + return FALSE; + } + + if (glyphFlags & CLEARCODEC_FLAG_GLYPH_HIT) + { + UINT32 nSrcStep = 0; + CLEAR_GLYPH_ENTRY* glyphEntry = &(clear->GlyphCache[glyphIndex]); + BYTE* glyphData = NULL; + + if (!glyphEntry) + { + WLog_ERR(TAG, "clear->GlyphCache[%" PRIu16 "]=NULL", glyphIndex); + return FALSE; + } + + glyphData = (BYTE*)glyphEntry->pixels; + + if (!glyphData) + { + WLog_ERR(TAG, "clear->GlyphCache[%" PRIu16 "]->pixels=NULL", glyphIndex); + return FALSE; + } + + if ((nWidth * nHeight) > glyphEntry->count) + { + WLog_ERR(TAG, + "(nWidth %" PRIu32 " * nHeight %" PRIu32 ") > glyphEntry->count %" PRIu32 "", + nWidth, nHeight, glyphEntry->count); + return FALSE; + } + + nSrcStep = nWidth * FreeRDPGetBytesPerPixel(clear->format); + return convert_color(pDstData, nDstStep, DstFormat, nXDst, nYDst, nWidth, nHeight, + glyphData, nSrcStep, clear->format, nDstWidth, nDstHeight, palette); + } + + if (glyphFlags & CLEARCODEC_FLAG_GLYPH_INDEX) + { + const UINT32 bpp = FreeRDPGetBytesPerPixel(clear->format); + CLEAR_GLYPH_ENTRY* glyphEntry = &(clear->GlyphCache[glyphIndex]); + glyphEntry->count = nWidth * nHeight; + + if (glyphEntry->count > glyphEntry->size) + { + BYTE* tmp = + winpr_aligned_recalloc(glyphEntry->pixels, glyphEntry->count, 1ull * bpp, 32); + + if (!tmp) + { + WLog_ERR(TAG, "glyphEntry->pixels winpr_aligned_recalloc %" PRIu32 " failed!", + glyphEntry->count * bpp); + return FALSE; + } + + glyphEntry->size = glyphEntry->count; + glyphEntry->pixels = (UINT32*)tmp; + } + + if (!glyphEntry->pixels) + { + WLog_ERR(TAG, "glyphEntry->pixels=NULL"); + return FALSE; + } + + if (ppGlyphData) + *ppGlyphData = (BYTE*)glyphEntry->pixels; + + return TRUE; + } + + return TRUE; +} + +static INLINE BOOL updateContextFormat(CLEAR_CONTEXT* clear, UINT32 DstFormat) +{ + if (!clear || !clear->nsc) + return FALSE; + + clear->format = DstFormat; + return nsc_context_set_parameters(clear->nsc, NSC_COLOR_FORMAT, DstFormat); +} + +INT32 clear_decompress(CLEAR_CONTEXT* clear, const BYTE* pSrcData, UINT32 SrcSize, UINT32 nWidth, + UINT32 nHeight, BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep, + UINT32 nXDst, UINT32 nYDst, UINT32 nDstWidth, UINT32 nDstHeight, + const gdiPalette* palette) +{ + INT32 rc = -1; + BYTE seqNumber = 0; + BYTE glyphFlags = 0; + UINT32 residualByteCount = 0; + UINT32 bandsByteCount = 0; + UINT32 subcodecByteCount = 0; + wStream sbuffer = { 0 }; + wStream* s = NULL; + BYTE* glyphData = NULL; + + if (!pDstData) + return -1002; + + if ((nDstWidth == 0) || (nDstHeight == 0)) + return -1022; + + if ((nWidth > 0xFFFF) || (nHeight > 0xFFFF)) + return -1004; + + s = Stream_StaticConstInit(&sbuffer, pSrcData, SrcSize); + + if (!s) + return -2005; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 2)) + goto fail; + + if (!updateContextFormat(clear, DstFormat)) + goto fail; + + Stream_Read_UINT8(s, glyphFlags); + Stream_Read_UINT8(s, seqNumber); + + if (!clear->seqNumber && seqNumber) + clear->seqNumber = seqNumber; + + if (seqNumber != clear->seqNumber) + { + WLog_ERR(TAG, "Sequence number unexpected %" PRIu8 " - %" PRIu32 "", seqNumber, + clear->seqNumber); + WLog_ERR(TAG, "seqNumber %" PRIu8 " != clear->seqNumber %" PRIu32 "", seqNumber, + clear->seqNumber); + goto fail; + } + + clear->seqNumber = (seqNumber + 1) % 256; + + if (glyphFlags & CLEARCODEC_FLAG_CACHE_RESET) + { + clear_reset_vbar_storage(clear, FALSE); + } + + if (!clear_decompress_glyph_data(clear, s, glyphFlags, nWidth, nHeight, pDstData, DstFormat, + nDstStep, nXDst, nYDst, nDstWidth, nDstHeight, palette, + &glyphData)) + { + WLog_ERR(TAG, "clear_decompress_glyph_data failed!"); + goto fail; + } + + /* Read composition payload header parameters */ + if (Stream_GetRemainingLength(s) < 12) + { + const UINT32 mask = (CLEARCODEC_FLAG_GLYPH_HIT | CLEARCODEC_FLAG_GLYPH_INDEX); + + if ((glyphFlags & mask) == mask) + goto finish; + + WLog_ERR(TAG, + "invalid glyphFlags, missing flags: 0x%02" PRIx8 " & 0x%02" PRIx32 + " == 0x%02" PRIx32, + glyphFlags, mask, glyphFlags & mask); + goto fail; + } + + Stream_Read_UINT32(s, residualByteCount); + Stream_Read_UINT32(s, bandsByteCount); + Stream_Read_UINT32(s, subcodecByteCount); + + if (residualByteCount > 0) + { + if (!clear_decompress_residual_data(clear, s, residualByteCount, nWidth, nHeight, pDstData, + DstFormat, nDstStep, nXDst, nYDst, nDstWidth, + nDstHeight, palette)) + { + WLog_ERR(TAG, "clear_decompress_residual_data failed!"); + goto fail; + } + } + + if (bandsByteCount > 0) + { + if (!clear_decompress_bands_data(clear, s, bandsByteCount, nWidth, nHeight, pDstData, + DstFormat, nDstStep, nXDst, nYDst, nDstWidth, nDstHeight)) + { + WLog_ERR(TAG, "clear_decompress_bands_data failed!"); + goto fail; + } + } + + if (subcodecByteCount > 0) + { + if (!clear_decompress_subcodecs_data(clear, s, subcodecByteCount, nWidth, nHeight, pDstData, + DstFormat, nDstStep, nXDst, nYDst, nDstWidth, + nDstHeight, palette)) + { + WLog_ERR(TAG, "clear_decompress_subcodecs_data failed!"); + goto fail; + } + } + + if (glyphData) + { + if (!freerdp_image_copy(glyphData, clear->format, 0, 0, 0, nWidth, nHeight, pDstData, + DstFormat, nDstStep, nXDst, nYDst, palette, FREERDP_KEEP_DST_ALPHA)) + goto fail; + } + +finish: + rc = 0; +fail: + return rc; +} + +int clear_compress(CLEAR_CONTEXT* clear, const BYTE* pSrcData, UINT32 SrcSize, BYTE** ppDstData, + UINT32* pDstSize) +{ + WLog_ERR(TAG, "TODO: not implemented!"); + return 1; +} + +BOOL clear_context_reset(CLEAR_CONTEXT* clear) +{ + if (!clear) + return FALSE; + + /** + * The ClearCodec context is not bound to a particular surface, + * and its internal caches must NOT be reset on the ResetGraphics PDU. + */ + clear->seqNumber = 0; + return TRUE; +} + +CLEAR_CONTEXT* clear_context_new(BOOL Compressor) +{ + CLEAR_CONTEXT* clear = (CLEAR_CONTEXT*)winpr_aligned_calloc(1, sizeof(CLEAR_CONTEXT), 32); + + if (!clear) + return NULL; + + clear->Compressor = Compressor; + clear->nsc = nsc_context_new(); + + if (!clear->nsc) + goto error_nsc; + + if (!updateContextFormat(clear, PIXEL_FORMAT_BGRX32)) + goto error_nsc; + + if (!clear_resize_buffer(clear, 512, 512)) + goto error_nsc; + + if (!clear->TempBuffer) + goto error_nsc; + + if (!clear_context_reset(clear)) + goto error_nsc; + + return clear; +error_nsc: + clear_context_free(clear); + return NULL; +} + +void clear_context_free(CLEAR_CONTEXT* clear) +{ + if (!clear) + return; + + nsc_context_free(clear->nsc); + winpr_aligned_free(clear->TempBuffer); + + clear_reset_vbar_storage(clear, TRUE); + clear_reset_glyph_cache(clear); + + winpr_aligned_free(clear); +} diff --git a/libfreerdp/codec/color.c b/libfreerdp/codec/color.c new file mode 100644 index 0000000..186d477 --- /dev/null +++ b/libfreerdp/codec/color.c @@ -0,0 +1,1681 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Color Conversion Routines + * + * Copyright 2010 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2016 Armin Novak <armin.novak@thincast.com> + * Copyright 2016 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <winpr/crt.h> + +#include <freerdp/log.h> +#include <freerdp/freerdp.h> +#include <freerdp/primitives.h> + +#if defined(WITH_CAIRO) +#include <cairo.h> +#endif + +#if defined(WITH_SWSCALE) +#include <libswscale/swscale.h> +#endif + +#define TAG FREERDP_TAG("color") + +BYTE* freerdp_glyph_convert(UINT32 width, UINT32 height, const BYTE* data) +{ + /* + * converts a 1-bit-per-pixel glyph to a one-byte-per-pixel glyph: + * this approach uses a little more memory, but provides faster + * means of accessing individual pixels in blitting operations + */ + const UINT32 scanline = (width + 7) / 8; + BYTE* dstData = (BYTE*)winpr_aligned_malloc(1ull * width * height, 16); + + if (!dstData) + return NULL; + + ZeroMemory(dstData, width * height); + BYTE* dstp = dstData; + + for (UINT32 y = 0; y < height; y++) + { + const BYTE* srcp = &data[1ull * y * scanline]; + + for (UINT32 x = 0; x < width; x++) + { + if ((*srcp & (0x80 >> (x % 8))) != 0) + *dstp = 0xFF; + + dstp++; + + if (((x + 1) % 8 == 0) && x != 0) + srcp++; + } + } + + return dstData; +} + +BOOL freerdp_image_copy_from_monochrome(BYTE* WINPR_RESTRICT pDstData, UINT32 DstFormat, + UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst, UINT32 nWidth, + UINT32 nHeight, const BYTE* WINPR_RESTRICT pSrcData, + UINT32 backColor, UINT32 foreColor, + const gdiPalette* WINPR_RESTRICT palette) +{ + BOOL vFlip = 0; + UINT32 monoStep = 0; + const UINT32 dstBytesPerPixel = FreeRDPGetBytesPerPixel(DstFormat); + + if (!pDstData || !pSrcData || !palette) + return FALSE; + + if (nDstStep == 0) + nDstStep = dstBytesPerPixel * nWidth; + + vFlip = FALSE; + monoStep = (nWidth + 7) / 8; + + for (UINT32 y = 0; y < nHeight; y++) + { + const BYTE* monoBits = NULL; + BYTE* pDstLine = &pDstData[((nYDst + y) * nDstStep)]; + UINT32 monoBit = 0x80; + + if (!vFlip) + monoBits = &pSrcData[monoStep * y]; + else + monoBits = &pSrcData[monoStep * (nHeight - y - 1)]; + + for (UINT32 x = 0; x < nWidth; x++) + { + BYTE* pDstPixel = &pDstLine[((nXDst + x) * FreeRDPGetBytesPerPixel(DstFormat))]; + BOOL monoPixel = (*monoBits & monoBit) ? TRUE : FALSE; + + if (!(monoBit >>= 1)) + { + monoBits++; + monoBit = 0x80; + } + + if (monoPixel) + FreeRDPWriteColor(pDstPixel, DstFormat, backColor); + else + FreeRDPWriteColor(pDstPixel, DstFormat, foreColor); + } + } + + return TRUE; +} + +static INLINE UINT32 freerdp_image_inverted_pointer_color(UINT32 x, UINT32 y, UINT32 format) +{ +#if 1 + /** + * Inverted pointer colors (where individual pixels can change their + * color to accommodate the background behind them) only seem to be + * supported on Windows. + * Using a static replacement color for these pixels (e.g. black) + * might result in invisible pointers depending on the background. + * This function returns either black or white, depending on the + * pixel's position. + */ + BYTE fill = (x + y) & 1 ? 0x00 : 0xFF; +#else + BYTE fill = 0x00; +#endif + return FreeRDPGetColor(format, fill, fill, fill, 0xFF); +} + +/* + * DIB color palettes are arrays of RGBQUAD structs with colors in BGRX format. + * They are used only by 1, 2, 4, and 8-bit bitmaps. + */ +static void fill_gdi_palette_for_icon(const BYTE* colorTable, UINT16 cbColorTable, + gdiPalette* palette) +{ + WINPR_ASSERT(palette); + + palette->format = PIXEL_FORMAT_BGRX32; + ZeroMemory(palette->palette, sizeof(palette->palette)); + + if (!cbColorTable) + return; + + if ((cbColorTable % 4 != 0) || (cbColorTable / 4 > 256)) + { + WLog_WARN(TAG, "weird palette size: %u", cbColorTable); + return; + } + + for (UINT16 i = 0; i < cbColorTable / 4; i++) + { + palette->palette[i] = FreeRDPReadColor(&colorTable[4 * i], palette->format); + } +} + +static INLINE UINT32 div_ceil(UINT32 a, UINT32 b) +{ + return (a + (b - 1)) / b; +} + +static INLINE UINT32 round_up(UINT32 a, UINT32 b) +{ + return b * div_ceil(a, b); +} + +BOOL freerdp_image_copy_from_icon_data(BYTE* WINPR_RESTRICT pDstData, UINT32 DstFormat, + UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst, UINT16 nWidth, + UINT16 nHeight, const BYTE* WINPR_RESTRICT bitsColor, + UINT16 cbBitsColor, const BYTE* WINPR_RESTRICT bitsMask, + UINT16 cbBitsMask, const BYTE* WINPR_RESTRICT colorTable, + UINT16 cbColorTable, UINT32 bpp) +{ + DWORD format = 0; + gdiPalette palette; + + if (!pDstData || !bitsColor) + return FALSE; + + /* + * Color formats used by icons are DIB bitmap formats (2-bit format + * is not used by MS-RDPERP). Note that 16-bit is RGB555, not RGB565, + * and that 32-bit format uses BGRA order. + */ + switch (bpp) + { + case 1: + case 4: + /* + * These formats are not supported by freerdp_image_copy(). + * PIXEL_FORMAT_MONO and PIXEL_FORMAT_A4 are *not* correct + * color formats for this. Please fix freerdp_image_copy() + * if you came here to fix a broken icon of some weird app + * that still uses 1 or 4bpp format in the 21st century. + */ + WLog_WARN(TAG, "1bpp and 4bpp icons are not supported"); + return FALSE; + + case 8: + format = PIXEL_FORMAT_RGB8; + break; + + case 16: + format = PIXEL_FORMAT_RGB15; + break; + + case 24: + format = PIXEL_FORMAT_RGB24; + break; + + case 32: + format = PIXEL_FORMAT_BGRA32; + break; + + default: + WLog_WARN(TAG, "invalid icon bpp: %" PRIu32, bpp); + return FALSE; + } + + /* Ensure we have enough source data bytes for image copy. */ + if (cbBitsColor < nWidth * nHeight * FreeRDPGetBytesPerPixel(format)) + return FALSE; + + fill_gdi_palette_for_icon(colorTable, cbColorTable, &palette); + if (!freerdp_image_copy(pDstData, DstFormat, nDstStep, nXDst, nYDst, nWidth, nHeight, bitsColor, + format, 0, 0, 0, &palette, FREERDP_FLIP_VERTICAL)) + return FALSE; + + /* apply alpha mask */ + if (FreeRDPColorHasAlpha(DstFormat) && cbBitsMask) + { + BYTE nextBit = 0; + const BYTE* maskByte = NULL; + UINT32 stride = 0; + BYTE r = 0; + BYTE g = 0; + BYTE b = 0; + BYTE* dstBuf = pDstData; + UINT32 dstBpp = FreeRDPGetBytesPerPixel(DstFormat); + + /* + * Each byte encodes 8 adjacent pixels (with LSB padding as needed). + * And due to hysterical raisins, stride of DIB bitmaps must be + * a multiple of 4 bytes. + */ + stride = round_up(div_ceil(nWidth, 8), 4); + + for (UINT32 y = 0; y < nHeight; y++) + { + maskByte = &bitsMask[stride * (nHeight - 1 - y)]; + nextBit = 0x80; + + for (UINT32 x = 0; x < nWidth; x++) + { + UINT32 color = 0; + BYTE alpha = (*maskByte & nextBit) ? 0x00 : 0xFF; + + /* read color back, add alpha and write it back */ + color = FreeRDPReadColor(dstBuf, DstFormat); + FreeRDPSplitColor(color, DstFormat, &r, &g, &b, NULL, &palette); + color = FreeRDPGetColor(DstFormat, r, g, b, alpha); + FreeRDPWriteColor(dstBuf, DstFormat, color); + + nextBit >>= 1; + dstBuf += dstBpp; + if (!nextBit) + { + nextBit = 0x80; + maskByte++; + } + } + } + } + + return TRUE; +} + +static BOOL freerdp_image_copy_from_pointer_data_1bpp( + BYTE* WINPR_RESTRICT pDstData, UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst, + UINT32 nWidth, UINT32 nHeight, const BYTE* WINPR_RESTRICT xorMask, UINT32 xorMaskLength, + const BYTE* WINPR_RESTRICT andMask, UINT32 andMaskLength, UINT32 xorBpp) +{ + BOOL vFlip = 0; + UINT32 xorStep = 0; + UINT32 andStep = 0; + UINT32 xorBit = 0; + UINT32 andBit = 0; + UINT32 xorPixel = 0; + UINT32 andPixel = 0; + + vFlip = (xorBpp == 1) ? FALSE : TRUE; + andStep = (nWidth + 7) / 8; + andStep += (andStep % 2); + + if (!xorMask || (xorMaskLength == 0)) + return FALSE; + if (!andMask || (andMaskLength == 0)) + return FALSE; + + xorStep = (nWidth + 7) / 8; + xorStep += (xorStep % 2); + + if (xorStep * nHeight > xorMaskLength) + return FALSE; + + if (andStep * nHeight > andMaskLength) + return FALSE; + + for (UINT32 y = 0; y < nHeight; y++) + { + const BYTE* andBits = NULL; + const BYTE* xorBits = NULL; + BYTE* pDstPixel = + &pDstData[((nYDst + y) * nDstStep) + (nXDst * FreeRDPGetBytesPerPixel(DstFormat))]; + xorBit = andBit = 0x80; + + if (!vFlip) + { + xorBits = &xorMask[xorStep * y]; + andBits = &andMask[andStep * y]; + } + else + { + xorBits = &xorMask[xorStep * (nHeight - y - 1)]; + andBits = &andMask[andStep * (nHeight - y - 1)]; + } + + for (UINT32 x = 0; x < nWidth; x++) + { + UINT32 color = 0; + xorPixel = (*xorBits & xorBit) ? 1 : 0; + + if (!(xorBit >>= 1)) + { + xorBits++; + xorBit = 0x80; + } + + andPixel = (*andBits & andBit) ? 1 : 0; + + if (!(andBit >>= 1)) + { + andBits++; + andBit = 0x80; + } + + if (!andPixel && !xorPixel) + color = FreeRDPGetColor(DstFormat, 0, 0, 0, 0xFF); /* black */ + else if (!andPixel && xorPixel) + color = FreeRDPGetColor(DstFormat, 0xFF, 0xFF, 0xFF, 0xFF); /* white */ + else if (andPixel && !xorPixel) + color = FreeRDPGetColor(DstFormat, 0, 0, 0, 0); /* transparent */ + else if (andPixel && xorPixel) + color = freerdp_image_inverted_pointer_color(x, y, DstFormat); /* inverted */ + + FreeRDPWriteColor(pDstPixel, DstFormat, color); + pDstPixel += FreeRDPGetBytesPerPixel(DstFormat); + } + } + + return TRUE; +} + +static BOOL freerdp_image_copy_from_pointer_data_xbpp( + BYTE* WINPR_RESTRICT pDstData, UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst, + UINT32 nWidth, UINT32 nHeight, const BYTE* WINPR_RESTRICT xorMask, UINT32 xorMaskLength, + const BYTE* WINPR_RESTRICT andMask, UINT32 andMaskLength, UINT32 xorBpp, + const gdiPalette* palette) +{ + BOOL vFlip = 0; + UINT32 xorStep = 0; + UINT32 andStep = 0; + UINT32 andBit = 0; + UINT32 xorPixel = 0; + UINT32 andPixel = 0; + UINT32 dstBitsPerPixel = 0; + UINT32 xorBytesPerPixel = 0; + dstBitsPerPixel = FreeRDPGetBitsPerPixel(DstFormat); + + vFlip = (xorBpp == 1) ? FALSE : TRUE; + andStep = (nWidth + 7) / 8; + andStep += (andStep % 2); + + if (!xorMask || (xorMaskLength == 0)) + return FALSE; + + xorBytesPerPixel = xorBpp >> 3; + xorStep = nWidth * xorBytesPerPixel; + xorStep += (xorStep % 2); + + if (xorBpp == 8 && !palette) + { + WLog_ERR(TAG, "null palette in conversion from %" PRIu32 " bpp to %" PRIu32 " bpp", xorBpp, + dstBitsPerPixel); + return FALSE; + } + + if (xorStep * nHeight > xorMaskLength) + return FALSE; + + if (andMask) + { + if (andStep * nHeight > andMaskLength) + return FALSE; + } + + for (UINT32 y = 0; y < nHeight; y++) + { + const BYTE* xorBits = NULL; + const BYTE* andBits = NULL; + BYTE* pDstPixel = + &pDstData[((nYDst + y) * nDstStep) + (nXDst * FreeRDPGetBytesPerPixel(DstFormat))]; + andBit = 0x80; + + if (!vFlip) + { + if (andMask) + andBits = &andMask[andStep * y]; + + xorBits = &xorMask[xorStep * y]; + } + else + { + if (andMask) + andBits = &andMask[andStep * (nHeight - y - 1)]; + + xorBits = &xorMask[xorStep * (nHeight - y - 1)]; + } + + for (UINT32 x = 0; x < nWidth; x++) + { + UINT32 pixelFormat = 0; + UINT32 color = 0; + + if (xorBpp == 32) + { + pixelFormat = PIXEL_FORMAT_BGRA32; + xorPixel = FreeRDPReadColor(xorBits, pixelFormat); + } + else if (xorBpp == 16) + { + pixelFormat = PIXEL_FORMAT_RGB15; + xorPixel = FreeRDPReadColor(xorBits, pixelFormat); + } + else if (xorBpp == 8) + { + pixelFormat = palette->format; + xorPixel = palette->palette[xorBits[0]]; + } + else + { + pixelFormat = PIXEL_FORMAT_BGR24; + xorPixel = FreeRDPReadColor(xorBits, pixelFormat); + } + + xorPixel = FreeRDPConvertColor(xorPixel, pixelFormat, PIXEL_FORMAT_ARGB32, palette); + xorBits += xorBytesPerPixel; + andPixel = 0; + + if (andMask) + { + andPixel = (*andBits & andBit) ? 1 : 0; + + if (!(andBit >>= 1)) + { + andBits++; + andBit = 0x80; + } + } + + if (andPixel) + { + if (xorPixel == 0xFF000000) /* black -> transparent */ + xorPixel = 0x00000000; + else if (xorPixel == 0xFFFFFFFF) /* white -> inverted */ + xorPixel = freerdp_image_inverted_pointer_color(x, y, PIXEL_FORMAT_ARGB32); + } + + color = FreeRDPConvertColor(xorPixel, PIXEL_FORMAT_ARGB32, DstFormat, palette); + FreeRDPWriteColor(pDstPixel, DstFormat, color); + pDstPixel += FreeRDPGetBytesPerPixel(DstFormat); + } + } + + return TRUE; +} + +/** + * Drawing Monochrome Pointers: + * http://msdn.microsoft.com/en-us/library/windows/hardware/ff556143/ + * + * Drawing Color Pointers: + * http://msdn.microsoft.com/en-us/library/windows/hardware/ff556138/ + */ + +BOOL freerdp_image_copy_from_pointer_data(BYTE* WINPR_RESTRICT pDstData, UINT32 DstFormat, + UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst, + UINT32 nWidth, UINT32 nHeight, + const BYTE* WINPR_RESTRICT xorMask, UINT32 xorMaskLength, + const BYTE* WINPR_RESTRICT andMask, UINT32 andMaskLength, + UINT32 xorBpp, const gdiPalette* palette) +{ + UINT32 dstBitsPerPixel = 0; + UINT32 dstBytesPerPixel = 0; + dstBitsPerPixel = FreeRDPGetBitsPerPixel(DstFormat); + dstBytesPerPixel = FreeRDPGetBytesPerPixel(DstFormat); + + if (nDstStep <= 0) + nDstStep = dstBytesPerPixel * nWidth; + + for (UINT32 y = nYDst; y < nHeight; y++) + { + BYTE* WINPR_RESTRICT pDstLine = &pDstData[y * nDstStep + nXDst * dstBytesPerPixel]; + memset(pDstLine, 0, 1ull * dstBytesPerPixel * (nWidth - nXDst)); + } + + switch (xorBpp) + { + case 1: + return freerdp_image_copy_from_pointer_data_1bpp( + pDstData, DstFormat, nDstStep, nXDst, nYDst, nWidth, nHeight, xorMask, + xorMaskLength, andMask, andMaskLength, xorBpp); + + case 8: + case 16: + case 24: + case 32: + return freerdp_image_copy_from_pointer_data_xbpp( + pDstData, DstFormat, nDstStep, nXDst, nYDst, nWidth, nHeight, xorMask, + xorMaskLength, andMask, andMaskLength, xorBpp, palette); + + default: + WLog_ERR(TAG, "failed to convert from %" PRIu32 " bpp to %" PRIu32 " bpp", xorBpp, + dstBitsPerPixel); + return FALSE; + } +} + +static INLINE BOOL overlapping(const BYTE* pDstData, UINT32 nXDst, UINT32 nYDst, UINT32 nDstStep, + UINT32 dstBytesPerPixel, const BYTE* pSrcData, UINT32 nXSrc, + UINT32 nYSrc, UINT32 nSrcStep, UINT32 srcBytesPerPixel, + UINT32 nWidth, UINT32 nHeight) +{ + const BYTE* pDstStart = &pDstData[nXDst * dstBytesPerPixel + nYDst * nDstStep]; + const BYTE* pDstEnd = pDstStart + nHeight * nDstStep; + const BYTE* pSrcStart = &pSrcData[nXSrc * srcBytesPerPixel + nYSrc * nSrcStep]; + const BYTE* pSrcEnd = pSrcStart + nHeight * nSrcStep; + + WINPR_UNUSED(nWidth); + + if ((pDstStart >= pSrcStart) && (pDstStart <= pSrcEnd)) + return TRUE; + + if ((pDstEnd >= pSrcStart) && (pDstEnd <= pSrcEnd)) + return TRUE; + + return FALSE; +} + +static BOOL freerdp_image_copy_no_overlap(BYTE* WINPR_RESTRICT pDstData, DWORD DstFormat, + UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst, + UINT32 nWidth, UINT32 nHeight, + const BYTE* WINPR_RESTRICT pSrcData, DWORD SrcFormat, + UINT32 nSrcStep, UINT32 nXSrc, UINT32 nYSrc, + const gdiPalette* WINPR_RESTRICT palette, UINT32 flags) +{ + const UINT32 dstByte = FreeRDPGetBytesPerPixel(DstFormat); + const UINT32 srcByte = FreeRDPGetBytesPerPixel(SrcFormat); + const UINT32 copyDstWidth = nWidth * dstByte; + const UINT32 xSrcOffset = nXSrc * srcByte; + const UINT32 xDstOffset = nXDst * dstByte; + const BOOL vSrcVFlip = (flags & FREERDP_FLIP_VERTICAL) ? TRUE : FALSE; + UINT32 srcVOffset = 0; + INT32 srcVMultiplier = 1; + UINT32 dstVOffset = 0; + INT32 dstVMultiplier = 1; + + if ((nHeight > INT32_MAX) || (nWidth > INT32_MAX)) + return FALSE; + + if (!pDstData || !pSrcData) + return FALSE; + + if (nDstStep == 0) + nDstStep = nWidth * FreeRDPGetBytesPerPixel(DstFormat); + + if (nSrcStep == 0) + nSrcStep = nWidth * FreeRDPGetBytesPerPixel(SrcFormat); + + if (vSrcVFlip) + { + srcVOffset = (nHeight - 1) * nSrcStep; + srcVMultiplier = -1; + } + + if (((flags & FREERDP_KEEP_DST_ALPHA) != 0) && FreeRDPColorHasAlpha(DstFormat)) + { + for (UINT32 y = 0; y < nHeight; y++) + { + const BYTE* WINPR_RESTRICT srcLine = + &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset]; + BYTE* WINPR_RESTRICT dstLine = + &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset]; + + UINT32 color = FreeRDPReadColor(&srcLine[nXSrc * srcByte], SrcFormat); + UINT32 oldColor = color; + UINT32 dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette); + FreeRDPWriteColorIgnoreAlpha(&dstLine[nXDst * dstByte], DstFormat, dstColor); + for (UINT32 x = 1; x < nWidth; x++) + { + color = FreeRDPReadColor(&srcLine[(x + nXSrc) * srcByte], SrcFormat); + if (color == oldColor) + { + FreeRDPWriteColorIgnoreAlpha(&dstLine[(x + nXDst) * dstByte], DstFormat, + dstColor); + } + else + { + oldColor = color; + dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette); + FreeRDPWriteColorIgnoreAlpha(&dstLine[(x + nXDst) * dstByte], DstFormat, + dstColor); + } + } + } + } + else if (FreeRDPAreColorFormatsEqualNoAlpha(SrcFormat, DstFormat)) + { + for (UINT32 y = 0; y < nHeight; y++) + { + const BYTE* WINPR_RESTRICT srcLine = + &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset]; + BYTE* WINPR_RESTRICT dstLine = + &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset]; + memcpy(&dstLine[xDstOffset], &srcLine[xSrcOffset], copyDstWidth); + } + } + else + { + for (UINT32 y = 0; y < nHeight; y++) + { + const BYTE* WINPR_RESTRICT srcLine = + &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset]; + BYTE* WINPR_RESTRICT dstLine = + &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset]; + + UINT32 color = FreeRDPReadColor(&srcLine[nXSrc * srcByte], SrcFormat); + UINT32 oldColor = color; + UINT32 dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette); + FreeRDPWriteColor(&dstLine[nXDst * dstByte], DstFormat, dstColor); + for (UINT32 x = 1; x < nWidth; x++) + { + color = FreeRDPReadColor(&srcLine[(x + nXSrc) * srcByte], SrcFormat); + if (color == oldColor) + { + FreeRDPWriteColor(&dstLine[(x + nXDst) * dstByte], DstFormat, dstColor); + } + else + { + oldColor = color; + dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette); + FreeRDPWriteColor(&dstLine[(x + nXDst) * dstByte], DstFormat, dstColor); + } + } + } + } + + return TRUE; +} + +static BOOL freerdp_image_copy_overlap(BYTE* pDstData, DWORD DstFormat, UINT32 nDstStep, + UINT32 nXDst, UINT32 nYDst, UINT32 nWidth, UINT32 nHeight, + const BYTE* pSrcData, DWORD SrcFormat, UINT32 nSrcStep, + UINT32 nXSrc, UINT32 nYSrc, const gdiPalette* palette, + UINT32 flags) +{ + const UINT32 dstByte = FreeRDPGetBytesPerPixel(DstFormat); + const UINT32 srcByte = FreeRDPGetBytesPerPixel(SrcFormat); + const UINT32 copyDstWidth = nWidth * dstByte; + const UINT32 xSrcOffset = nXSrc * srcByte; + const UINT32 xDstOffset = nXDst * dstByte; + const BOOL vSrcVFlip = (flags & FREERDP_FLIP_VERTICAL) ? TRUE : FALSE; + UINT32 srcVOffset = 0; + INT32 srcVMultiplier = 1; + UINT32 dstVOffset = 0; + INT32 dstVMultiplier = 1; + + if ((nHeight > INT32_MAX) || (nWidth > INT32_MAX)) + return FALSE; + + if (!pDstData || !pSrcData) + return FALSE; + + if (nDstStep == 0) + nDstStep = nWidth * FreeRDPGetBytesPerPixel(DstFormat); + + if (nSrcStep == 0) + nSrcStep = nWidth * FreeRDPGetBytesPerPixel(SrcFormat); + + if (vSrcVFlip) + { + srcVOffset = (nHeight - 1) * nSrcStep; + srcVMultiplier = -1; + } + + if (((flags & FREERDP_KEEP_DST_ALPHA) != 0) && FreeRDPColorHasAlpha(DstFormat)) + { + for (UINT32 y = 0; y < nHeight; y++) + { + const BYTE* srcLine = &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset]; + BYTE* dstLine = &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset]; + + UINT32 color = FreeRDPReadColor(&srcLine[nXSrc * srcByte], SrcFormat); + UINT32 oldColor = color; + UINT32 dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette); + FreeRDPWriteColorIgnoreAlpha(&dstLine[nXDst * dstByte], DstFormat, dstColor); + for (UINT32 x = 1; x < nWidth; x++) + { + color = FreeRDPReadColor(&srcLine[(x + nXSrc) * srcByte], SrcFormat); + if (color == oldColor) + { + FreeRDPWriteColorIgnoreAlpha(&dstLine[(x + nXDst) * dstByte], DstFormat, + dstColor); + } + else + { + oldColor = color; + dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette); + FreeRDPWriteColorIgnoreAlpha(&dstLine[(x + nXDst) * dstByte], DstFormat, + dstColor); + } + } + } + } + else if (FreeRDPAreColorFormatsEqualNoAlpha(SrcFormat, DstFormat)) + { + /* Copy down */ + if (nYDst < nYSrc) + { + for (INT32 y = 0; y < (INT32)nHeight; y++) + { + const BYTE* srcLine = + &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset]; + BYTE* dstLine = &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset]; + memcpy(&dstLine[xDstOffset], &srcLine[xSrcOffset], copyDstWidth); + } + } + /* Copy up */ + else if (nYDst > nYSrc) + { + for (INT32 y = (INT32)nHeight - 1; y >= 0; y--) + { + const BYTE* srcLine = + &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset]; + BYTE* dstLine = &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset]; + memcpy(&dstLine[xDstOffset], &srcLine[xSrcOffset], copyDstWidth); + } + } + /* Copy left */ + else if (nXSrc > nXDst) + { + for (INT32 y = 0; y < (INT32)nHeight; y++) + { + const BYTE* srcLine = + &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset]; + BYTE* dstLine = &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset]; + memmove(&dstLine[xDstOffset], &srcLine[xSrcOffset], copyDstWidth); + } + } + /* Copy right */ + else if (nXSrc < nXDst) + { + for (INT32 y = (INT32)nHeight - 1; y >= 0; y--) + { + const BYTE* srcLine = + &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset]; + BYTE* dstLine = &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset]; + memmove(&dstLine[xDstOffset], &srcLine[xSrcOffset], copyDstWidth); + } + } + /* Source and destination are equal... */ + else + { + } + } + else + { + for (UINT32 y = 0; y < nHeight; y++) + { + const BYTE* srcLine = &pSrcData[(y + nYSrc) * nSrcStep * srcVMultiplier + srcVOffset]; + BYTE* dstLine = &pDstData[(y + nYDst) * nDstStep * dstVMultiplier + dstVOffset]; + + UINT32 color = FreeRDPReadColor(&srcLine[nXSrc * srcByte], SrcFormat); + UINT32 oldColor = color; + UINT32 dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette); + FreeRDPWriteColor(&dstLine[nXDst * dstByte], DstFormat, dstColor); + for (UINT32 x = 1; x < nWidth; x++) + { + color = FreeRDPReadColor(&srcLine[(x + nXSrc) * srcByte], SrcFormat); + if (color == oldColor) + { + FreeRDPWriteColor(&dstLine[(x + nXDst) * dstByte], DstFormat, dstColor); + } + else + { + oldColor = color; + dstColor = FreeRDPConvertColor(color, SrcFormat, DstFormat, palette); + FreeRDPWriteColor(&dstLine[(x + nXDst) * dstByte], DstFormat, dstColor); + } + } + } + } + + return TRUE; +} + +BOOL freerdp_image_copy(BYTE* pDstData, DWORD DstFormat, UINT32 nDstStep, UINT32 nXDst, + UINT32 nYDst, UINT32 nWidth, UINT32 nHeight, const BYTE* pSrcData, + DWORD SrcFormat, UINT32 nSrcStep, UINT32 nXSrc, UINT32 nYSrc, + const gdiPalette* palette, UINT32 flags) +{ + const UINT32 dstByte = FreeRDPGetBytesPerPixel(DstFormat); + const UINT32 srcByte = FreeRDPGetBytesPerPixel(SrcFormat); + + if ((nHeight > INT32_MAX) || (nWidth > INT32_MAX)) + return FALSE; + + if (!pDstData || !pSrcData) + return FALSE; + + if (nDstStep == 0) + nDstStep = nWidth * FreeRDPGetBytesPerPixel(DstFormat); + + if (nSrcStep == 0) + nSrcStep = nWidth * FreeRDPGetBytesPerPixel(SrcFormat); + + const BOOL ovl = overlapping(pDstData, nXDst, nYDst, nDstStep, dstByte, pSrcData, nXSrc, nYSrc, + nSrcStep, srcByte, nWidth, nHeight); + if (ovl) + return freerdp_image_copy_overlap(pDstData, DstFormat, nDstStep, nXDst, nYDst, nWidth, + nHeight, pSrcData, SrcFormat, nSrcStep, nXSrc, nYSrc, + palette, flags); + return freerdp_image_copy_no_overlap(pDstData, DstFormat, nDstStep, nXDst, nYDst, nWidth, + nHeight, pSrcData, SrcFormat, nSrcStep, nXSrc, nYSrc, + palette, flags); +} + +BOOL freerdp_image_fill(BYTE* WINPR_RESTRICT pDstData, DWORD DstFormat, UINT32 nDstStep, + UINT32 nXDst, UINT32 nYDst, UINT32 nWidth, UINT32 nHeight, UINT32 color) +{ + if ((nWidth == 0) || (nHeight == 0)) + return TRUE; + const UINT32 bpp = FreeRDPGetBytesPerPixel(DstFormat); + BYTE* WINPR_RESTRICT pFirstDstLine = NULL; + BYTE* WINPR_RESTRICT pFirstDstLineXOffset = NULL; + + if (nDstStep == 0) + nDstStep = (nXDst + nWidth) * FreeRDPGetBytesPerPixel(DstFormat); + + pFirstDstLine = &pDstData[nYDst * nDstStep]; + pFirstDstLineXOffset = &pFirstDstLine[nXDst * bpp]; + + for (UINT32 x = 0; x < nWidth; x++) + { + BYTE* pDst = &pFirstDstLine[(x + nXDst) * bpp]; + FreeRDPWriteColor(pDst, DstFormat, color); + } + + for (UINT32 y = 1; y < nHeight; y++) + { + BYTE* pDstLine = &pDstData[(y + nYDst) * nDstStep + nXDst * bpp]; + memcpy(pDstLine, pFirstDstLineXOffset, 1ull * nWidth * bpp); + } + + return TRUE; +} + +#if defined(WITH_SWSCALE) +static int av_format_for_buffer(UINT32 format) +{ + switch (format) + { + case PIXEL_FORMAT_ARGB32: + return AV_PIX_FMT_BGRA; + + case PIXEL_FORMAT_XRGB32: + return AV_PIX_FMT_BGR0; + + case PIXEL_FORMAT_BGRA32: + return AV_PIX_FMT_RGBA; + + case PIXEL_FORMAT_BGRX32: + return AV_PIX_FMT_RGB0; + + default: + return AV_PIX_FMT_NONE; + } +} +#endif + +BOOL freerdp_image_scale(BYTE* WINPR_RESTRICT pDstData, DWORD DstFormat, UINT32 nDstStep, + UINT32 nXDst, UINT32 nYDst, UINT32 nDstWidth, UINT32 nDstHeight, + const BYTE* WINPR_RESTRICT pSrcData, DWORD SrcFormat, UINT32 nSrcStep, + UINT32 nXSrc, UINT32 nYSrc, UINT32 nSrcWidth, UINT32 nSrcHeight) +{ + BOOL rc = FALSE; + + if (nDstStep == 0) + nDstStep = nDstWidth * FreeRDPGetBytesPerPixel(DstFormat); + + if (nSrcStep == 0) + nSrcStep = nSrcWidth * FreeRDPGetBytesPerPixel(SrcFormat); + +#if defined(WITH_SWSCALE) || defined(WITH_CAIRO) + const BYTE* src = &pSrcData[nXSrc * FreeRDPGetBytesPerPixel(SrcFormat) + nYSrc * nSrcStep]; + BYTE* dst = &pDstData[nXDst * FreeRDPGetBytesPerPixel(DstFormat) + nYDst * nDstStep]; +#endif + + /* direct copy is much faster than scaling, so check if we can simply copy... */ + if ((nDstWidth == nSrcWidth) && (nDstHeight == nSrcHeight)) + { + return freerdp_image_copy_no_overlap(pDstData, DstFormat, nDstStep, nXDst, nYDst, nDstWidth, + nDstHeight, pSrcData, SrcFormat, nSrcStep, nXSrc, + nYSrc, NULL, FREERDP_FLIP_NONE); + } + else +#if defined(WITH_SWSCALE) + { + int res = 0; + struct SwsContext* resize = NULL; + int srcFormat = av_format_for_buffer(SrcFormat); + int dstFormat = av_format_for_buffer(DstFormat); + const int srcStep[1] = { (int)nSrcStep }; + const int dstStep[1] = { (int)nDstStep }; + + if ((srcFormat == AV_PIX_FMT_NONE) || (dstFormat == AV_PIX_FMT_NONE)) + return FALSE; + + resize = sws_getContext((int)nSrcWidth, (int)nSrcHeight, srcFormat, (int)nDstWidth, + (int)nDstHeight, dstFormat, SWS_BILINEAR, NULL, NULL, NULL); + + if (!resize) + goto fail; + + res = sws_scale(resize, &src, srcStep, 0, (int)nSrcHeight, &dst, dstStep); + rc = (res == ((int)nDstHeight)); + fail: + sws_freeContext(resize); + } + +#elif defined(WITH_CAIRO) + { + const double sx = (double)nDstWidth / (double)nSrcWidth; + const double sy = (double)nDstHeight / (double)nSrcHeight; + cairo_t* cairo_context; + cairo_surface_t *csrc, *cdst; + + if ((nSrcWidth > INT_MAX) || (nSrcHeight > INT_MAX) || (nSrcStep > INT_MAX)) + return FALSE; + + if ((nDstWidth > INT_MAX) || (nDstHeight > INT_MAX) || (nDstStep > INT_MAX)) + return FALSE; + + csrc = cairo_image_surface_create_for_data((void*)src, CAIRO_FORMAT_ARGB32, (int)nSrcWidth, + (int)nSrcHeight, (int)nSrcStep); + cdst = cairo_image_surface_create_for_data(dst, CAIRO_FORMAT_ARGB32, (int)nDstWidth, + (int)nDstHeight, (int)nDstStep); + + if (!csrc || !cdst) + goto fail; + + cairo_context = cairo_create(cdst); + + if (!cairo_context) + goto fail2; + + cairo_scale(cairo_context, sx, sy); + cairo_set_operator(cairo_context, CAIRO_OPERATOR_SOURCE); + cairo_set_source_surface(cairo_context, csrc, 0, 0); + cairo_paint(cairo_context); + rc = TRUE; + fail2: + cairo_destroy(cairo_context); + fail: + cairo_surface_destroy(csrc); + cairo_surface_destroy(cdst); + } +#else + { + WLog_WARN(TAG, "SmartScaling requested but compiled without libcairo support!"); + } +#endif + return rc; +} + +DWORD FreeRDPAreColorFormatsEqualNoAlpha(DWORD first, DWORD second) +{ + const DWORD mask = (DWORD) ~(8UL << 12UL); + return (first & mask) == (second & mask); +} + +const char* FreeRDPGetColorFormatName(UINT32 format) +{ + switch (format) + { + /* 32bpp formats */ + case PIXEL_FORMAT_ARGB32: + return "PIXEL_FORMAT_ARGB32"; + + case PIXEL_FORMAT_XRGB32: + return "PIXEL_FORMAT_XRGB32"; + + case PIXEL_FORMAT_ABGR32: + return "PIXEL_FORMAT_ABGR32"; + + case PIXEL_FORMAT_XBGR32: + return "PIXEL_FORMAT_XBGR32"; + + case PIXEL_FORMAT_BGRA32: + return "PIXEL_FORMAT_BGRA32"; + + case PIXEL_FORMAT_BGRX32: + return "PIXEL_FORMAT_BGRX32"; + + case PIXEL_FORMAT_RGBA32: + return "PIXEL_FORMAT_RGBA32"; + + case PIXEL_FORMAT_RGBX32: + return "PIXEL_FORMAT_RGBX32"; + + case PIXEL_FORMAT_BGRX32_DEPTH30: + return "PIXEL_FORMAT_BGRX32_DEPTH30"; + + case PIXEL_FORMAT_RGBX32_DEPTH30: + return "PIXEL_FORMAT_RGBX32_DEPTH30"; + + /* 24bpp formats */ + case PIXEL_FORMAT_RGB24: + return "PIXEL_FORMAT_RGB24"; + + case PIXEL_FORMAT_BGR24: + return "PIXEL_FORMAT_BGR24"; + + /* 16bpp formats */ + case PIXEL_FORMAT_RGB16: + return "PIXEL_FORMAT_RGB16"; + + case PIXEL_FORMAT_BGR16: + return "PIXEL_FORMAT_BGR16"; + + case PIXEL_FORMAT_ARGB15: + return "PIXEL_FORMAT_ARGB15"; + + case PIXEL_FORMAT_RGB15: + return "PIXEL_FORMAT_RGB15"; + + case PIXEL_FORMAT_ABGR15: + return "PIXEL_FORMAT_ABGR15"; + + case PIXEL_FORMAT_BGR15: + return "PIXEL_FORMAT_BGR15"; + + /* 8bpp formats */ + case PIXEL_FORMAT_RGB8: + return "PIXEL_FORMAT_RGB8"; + + /* 4 bpp formats */ + case PIXEL_FORMAT_A4: + return "PIXEL_FORMAT_A4"; + + /* 1bpp formats */ + case PIXEL_FORMAT_MONO: + return "PIXEL_FORMAT_MONO"; + + default: + return "UNKNOWN"; + } +} + +void FreeRDPSplitColor(UINT32 color, UINT32 format, BYTE* _r, BYTE* _g, BYTE* _b, BYTE* _a, + const gdiPalette* palette) +{ + UINT32 tmp = 0; + + switch (format) + { + /* 32bpp formats */ + case PIXEL_FORMAT_ARGB32: + if (_a) + *_a = (BYTE)(color >> 24); + + if (_r) + *_r = (BYTE)(color >> 16); + + if (_g) + *_g = (BYTE)(color >> 8); + + if (_b) + *_b = (BYTE)color; + + break; + + case PIXEL_FORMAT_XRGB32: + if (_r) + *_r = (BYTE)(color >> 16); + + if (_g) + *_g = (BYTE)(color >> 8); + + if (_b) + *_b = (BYTE)color; + + if (_a) + *_a = 0xFF; + + break; + + case PIXEL_FORMAT_ABGR32: + if (_a) + *_a = (BYTE)(color >> 24); + + if (_b) + *_b = (BYTE)(color >> 16); + + if (_g) + *_g = (BYTE)(color >> 8); + + if (_r) + *_r = (BYTE)color; + + break; + + case PIXEL_FORMAT_XBGR32: + if (_b) + *_b = (BYTE)(color >> 16); + + if (_g) + *_g = (BYTE)(color >> 8); + + if (_r) + *_r = (BYTE)color; + + if (_a) + *_a = 0xFF; + + break; + + case PIXEL_FORMAT_RGBA32: + if (_r) + *_r = (BYTE)(color >> 24); + + if (_g) + *_g = (BYTE)(color >> 16); + + if (_b) + *_b = (BYTE)(color >> 8); + + if (_a) + *_a = (BYTE)color; + + break; + + case PIXEL_FORMAT_RGBX32: + if (_r) + *_r = (BYTE)(color >> 24); + + if (_g) + *_g = (BYTE)(color >> 16); + + if (_b) + *_b = (BYTE)(color >> 8); + + if (_a) + *_a = 0xFF; + + break; + + case PIXEL_FORMAT_BGRA32: + if (_b) + *_b = (BYTE)(color >> 24); + + if (_g) + *_g = (BYTE)(color >> 16); + + if (_r) + *_r = (BYTE)(color >> 8); + + if (_a) + *_a = (BYTE)color; + + break; + + case PIXEL_FORMAT_BGRX32: + if (_b) + *_b = (BYTE)(color >> 24); + + if (_g) + *_g = (BYTE)(color >> 16); + + if (_r) + *_r = (BYTE)(color >> 8); + + if (_a) + *_a = 0xFF; + + break; + + /* 24bpp formats */ + case PIXEL_FORMAT_RGB24: + if (_r) + *_r = (BYTE)(color >> 16); + + if (_g) + *_g = (BYTE)(color >> 8); + + if (_b) + *_b = (BYTE)color; + + if (_a) + *_a = 0xFF; + + break; + + case PIXEL_FORMAT_BGR24: + if (_b) + *_b = (BYTE)(color >> 16); + + if (_g) + *_g = (BYTE)(color >> 8); + + if (_r) + *_r = (BYTE)color; + + if (_a) + *_a = 0xFF; + + break; + + /* 16bpp formats */ + case PIXEL_FORMAT_RGB16: + if (_r) + { + const UINT32 c = (color >> 11) & 0x1F; + const UINT32 val = (c << 3) + c / 4; + *_r = (BYTE)(val > 255 ? 255 : val); + } + + if (_g) + { + const UINT32 c = (color >> 5) & 0x3F; + const UINT32 val = (c << 2) + c / 4 / 2; + *_g = (BYTE)(val > 255 ? 255 : val); + } + + if (_b) + { + const UINT32 c = (color)&0x1F; + const UINT32 val = (c << 3) + c / 4; + *_b = (BYTE)(val > 255 ? 255 : val); + } + + if (_a) + *_a = 0xFF; + + break; + + case PIXEL_FORMAT_BGR16: + if (_r) + { + const UINT32 c = (color)&0x1F; + const UINT32 val = (c << 3) + c / 4; + *_r = (BYTE)(val > 255 ? 255 : val); + } + + if (_g) + { + const UINT32 c = (color >> 5) & 0x3F; + const UINT32 val = (c << 2) + c / 4 / 2; + *_g = (BYTE)(val > 255 ? 255 : val); + } + + if (_b) + { + const UINT32 c = (color >> 11) & 0x1F; + const UINT32 val = (c << 3) + c / 4; + *_b = (BYTE)(val > 255 ? 255 : val); + } + + if (_a) + *_a = 0xFF; + + break; + + case PIXEL_FORMAT_ARGB15: + if (_r) + { + const UINT32 c = (color >> 10) & 0x1F; + const UINT32 val = (c << 3) + c / 4; + *_r = (BYTE)(val > 255 ? 255 : val); + } + + if (_g) + { + const UINT32 c = (color >> 5) & 0x1F; + const UINT32 val = (c << 3) + c / 4; + *_g = (BYTE)(val > 255 ? 255 : val); + } + + if (_b) + { + const UINT32 c = (color)&0x1F; + const UINT32 val = (c << 3) + c / 4; + *_b = (BYTE)(val > 255 ? 255 : val); + } + + if (_a) + *_a = color & 0x8000 ? 0xFF : 0x00; + + break; + + case PIXEL_FORMAT_ABGR15: + if (_r) + { + const UINT32 c = (color)&0x1F; + const UINT32 val = (c << 3) + c / 4; + *_r = (BYTE)(val > 255 ? 255 : val); + } + + if (_g) + { + const UINT32 c = (color >> 5) & 0x1F; + const UINT32 val = (c << 3) + c / 4; + *_g = (BYTE)(val > 255 ? 255 : val); + } + + if (_b) + { + const UINT32 c = (color >> 10) & 0x1F; + const UINT32 val = (c << 3) + c / 4; + *_b = (BYTE)(val > 255 ? 255 : val); + } + + if (_a) + *_a = color & 0x8000 ? 0xFF : 0x00; + + break; + + /* 15bpp formats */ + case PIXEL_FORMAT_RGB15: + if (_r) + { + const UINT32 c = (color >> 10) & 0x1F; + const UINT32 val = (c << 3) + c / 4; + *_r = (BYTE)(val > 255 ? 255 : val); + } + + if (_g) + { + const UINT32 c = (color >> 5) & 0x1F; + const UINT32 val = (c << 3) + c / 4; + *_g = (BYTE)(val > 255 ? 255 : val); + } + + if (_b) + { + const UINT32 c = (color)&0x1F; + const UINT32 val = (c << 3) + c / 4; + *_b = (BYTE)(val > 255 ? 255 : val); + } + + if (_a) + *_a = 0xFF; + + break; + + case PIXEL_FORMAT_BGR15: + if (_r) + { + const UINT32 c = (color)&0x1F; + const UINT32 val = (c << 3) + c / 4; + *_r = (BYTE)(val > 255 ? 255 : val); + } + + if (_g) + { + const UINT32 c = (color >> 5) & 0x1F; + const UINT32 val = (c << 3) + c / 4; + *_g = (BYTE)(val > 255 ? 255 : val); + } + + if (_b) + { + const UINT32 c = (color >> 10) & 0x1F; + const UINT32 val = (c << 3) + c / 4; + *_b = (BYTE)(val > 255 ? 255 : val); + } + + if (_a) + *_a = 0xFF; + + break; + + /* 8bpp formats */ + case PIXEL_FORMAT_RGB8: + if (color <= 0xFF) + { + tmp = palette->palette[color]; + FreeRDPSplitColor(tmp, palette->format, _r, _g, _b, _a, NULL); + } + else + { + if (_r) + *_r = 0x00; + + if (_g) + *_g = 0x00; + + if (_b) + *_b = 0x00; + + if (_a) + *_a = 0x00; + } + + break; + + /* 1bpp formats */ + case PIXEL_FORMAT_MONO: + if (_r) + *_r = (color) ? 0xFF : 0x00; + + if (_g) + *_g = (color) ? 0xFF : 0x00; + + if (_b) + *_b = (color) ? 0xFF : 0x00; + + if (_a) + *_a = (color) ? 0xFF : 0x00; + + break; + + /* 4 bpp formats */ + case PIXEL_FORMAT_A4: + default: + if (_r) + *_r = 0x00; + + if (_g) + *_g = 0x00; + + if (_b) + *_b = 0x00; + + if (_a) + *_a = 0x00; + + WLog_ERR(TAG, "Unsupported format %s", FreeRDPGetColorFormatName(format)); + break; + } +} + +BOOL FreeRDPWriteColorIgnoreAlpha(BYTE* WINPR_RESTRICT dst, UINT32 format, UINT32 color) +{ + switch (format) + { + case PIXEL_FORMAT_XBGR32: + case PIXEL_FORMAT_XRGB32: + case PIXEL_FORMAT_ABGR32: + case PIXEL_FORMAT_ARGB32: + { + const UINT32 tmp = ((UINT32)dst[0] << 24ULL) | (color & 0x00FFFFFFULL); + return FreeRDPWriteColor(dst, format, tmp); + } + case PIXEL_FORMAT_BGRX32: + case PIXEL_FORMAT_RGBX32: + case PIXEL_FORMAT_BGRA32: + case PIXEL_FORMAT_RGBA32: + { + const UINT32 tmp = ((UINT32)dst[3]) | (color & 0xFFFFFF00ULL); + return FreeRDPWriteColor(dst, format, tmp); + } + default: + return FreeRDPWriteColor(dst, format, color); + } +} + +BOOL FreeRDPWriteColor(BYTE* WINPR_RESTRICT dst, UINT32 format, UINT32 color) +{ + switch (FreeRDPGetBitsPerPixel(format)) + { + case 32: + dst[0] = (BYTE)(color >> 24); + dst[1] = (BYTE)(color >> 16); + dst[2] = (BYTE)(color >> 8); + dst[3] = (BYTE)color; + break; + + case 24: + dst[0] = (BYTE)(color >> 16); + dst[1] = (BYTE)(color >> 8); + dst[2] = (BYTE)color; + break; + + case 16: + dst[1] = (BYTE)(color >> 8); + dst[0] = (BYTE)color; + break; + + case 15: + if (!FreeRDPColorHasAlpha(format)) + color = color & 0x7FFF; + + dst[1] = (BYTE)(color >> 8); + dst[0] = (BYTE)color; + break; + + case 8: + dst[0] = (BYTE)color; + break; + + default: + WLog_ERR(TAG, "Unsupported format %s", FreeRDPGetColorFormatName(format)); + return FALSE; + } + + return TRUE; +} + +UINT32 FreeRDPReadColor(const BYTE* WINPR_RESTRICT src, UINT32 format) +{ + UINT32 color = 0; + + switch (FreeRDPGetBitsPerPixel(format)) + { + case 32: + color = + ((UINT32)src[0] << 24) | ((UINT32)src[1] << 16) | ((UINT32)src[2] << 8) | src[3]; + break; + + case 24: + color = ((UINT32)src[0] << 16) | ((UINT32)src[1] << 8) | src[2]; + break; + + case 16: + color = ((UINT32)src[1] << 8) | src[0]; + break; + + case 15: + color = ((UINT32)src[1] << 8) | src[0]; + + if (!FreeRDPColorHasAlpha(format)) + color = color & 0x7FFF; + + break; + + case 8: + case 4: + case 1: + color = *src; + break; + + default: + WLog_ERR(TAG, "Unsupported format %s", FreeRDPGetColorFormatName(format)); + color = 0; + break; + } + + return color; +} + +UINT32 FreeRDPGetColor(UINT32 format, BYTE r, BYTE g, BYTE b, BYTE a) +{ + UINT32 _r = r; + UINT32 _g = g; + UINT32 _b = b; + UINT32 _a = a; + UINT32 t = 0; + + switch (format) + { + /* 32bpp formats */ + case PIXEL_FORMAT_ARGB32: + return (_a << 24) | (_r << 16) | (_g << 8) | _b; + + case PIXEL_FORMAT_XRGB32: + return (_r << 16) | (_g << 8) | _b; + + case PIXEL_FORMAT_ABGR32: + return (_a << 24) | (_b << 16) | (_g << 8) | _r; + + case PIXEL_FORMAT_XBGR32: + return (_b << 16) | (_g << 8) | _r; + + case PIXEL_FORMAT_RGBA32: + return (_r << 24) | (_g << 16) | (_b << 8) | _a; + + case PIXEL_FORMAT_RGBX32: + return (_r << 24) | (_g << 16) | (_b << 8) | _a; + + case PIXEL_FORMAT_BGRA32: + return (_b << 24) | (_g << 16) | (_r << 8) | _a; + + case PIXEL_FORMAT_BGRX32: + return (_b << 24) | (_g << 16) | (_r << 8) | _a; + + case PIXEL_FORMAT_RGBX32_DEPTH30: + // TODO: Not tested + t = (_r << 22) | (_g << 12) | (_b << 2); + // NOTE: Swapping byte-order because FreeRDPWriteColor written UINT32 in big-endian + return ((t & 0xff) << 24) | (((t >> 8) & 0xff) << 16) | (((t >> 16) & 0xff) << 8) | + (t >> 24); + + case PIXEL_FORMAT_BGRX32_DEPTH30: + // NOTE: Swapping b and r channel (unknown reason) + t = (_r << 22) | (_g << 12) | (_b << 2); + // NOTE: Swapping byte-order because FreeRDPWriteColor written UINT32 in big-endian + return ((t & 0xff) << 24) | (((t >> 8) & 0xff) << 16) | (((t >> 16) & 0xff) << 8) | + (t >> 24); + + /* 24bpp formats */ + case PIXEL_FORMAT_RGB24: + return (_r << 16) | (_g << 8) | _b; + + case PIXEL_FORMAT_BGR24: + return (_b << 16) | (_g << 8) | _r; + + /* 16bpp formats */ + case PIXEL_FORMAT_RGB16: + return (((_r >> 3) & 0x1F) << 11) | (((_g >> 2) & 0x3F) << 5) | ((_b >> 3) & 0x1F); + + case PIXEL_FORMAT_BGR16: + return (((_b >> 3) & 0x1F) << 11) | (((_g >> 2) & 0x3F) << 5) | ((_r >> 3) & 0x1F); + + case PIXEL_FORMAT_ARGB15: + return (((_r >> 3) & 0x1F) << 10) | (((_g >> 3) & 0x1F) << 5) | ((_b >> 3) & 0x1F) | + (_a ? 0x8000 : 0x0000); + + case PIXEL_FORMAT_ABGR15: + return (((_b >> 3) & 0x1F) << 10) | (((_g >> 3) & 0x1F) << 5) | ((_r >> 3) & 0x1F) | + (_a ? 0x8000 : 0x0000); + + /* 15bpp formats */ + case PIXEL_FORMAT_RGB15: + return (((_r >> 3) & 0x1F) << 10) | (((_g >> 3) & 0x1F) << 5) | ((_b >> 3) & 0x1F); + + case PIXEL_FORMAT_BGR15: + return (((_b >> 3) & 0x1F) << 10) | (((_g >> 3) & 0x1F) << 5) | ((_r >> 3) & 0x1F); + + /* 8bpp formats */ + case PIXEL_FORMAT_RGB8: + + /* 4 bpp formats */ + case PIXEL_FORMAT_A4: + + /* 1bpp formats */ + case PIXEL_FORMAT_MONO: + default: + WLog_ERR(TAG, "Unsupported format %s", FreeRDPGetColorFormatName(format)); + return 0; + } +} diff --git a/libfreerdp/codec/dsp.c b/libfreerdp/codec/dsp.c new file mode 100644 index 0000000..68800e4 --- /dev/null +++ b/libfreerdp/codec/dsp.c @@ -0,0 +1,1507 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Digital Sound Processing + * + * Copyright 2010-2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <winpr/assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <winpr/crt.h> + +#include <freerdp/types.h> +#include <freerdp/log.h> +#include <freerdp/codec/dsp.h> + +#if !defined(WITH_DSP_FFMPEG) +#if defined(WITH_GSM) +#include <gsm/gsm.h> +#endif + +#if defined(WITH_LAME) +#include <lame/lame.h> +#endif + +#if defined(WITH_OPUS) +#include <opus/opus.h> + +#define OPUS_MAX_FRAMES 5760 +#endif + +#if defined(WITH_FAAD2) +#include <neaacdec.h> +#endif + +#if defined(WITH_FAAC) +#include <faac.h> +#endif + +#if defined(WITH_SOXR) +#include <soxr.h> +#endif + +#else +#include "dsp_ffmpeg.h" +#endif + +#define TAG FREERDP_TAG("dsp") + +#if !defined(WITH_DSP_FFMPEG) + +typedef union +{ + struct + { + size_t packet_size; + INT16 last_sample[2]; + INT16 last_step[2]; + } ima; + struct + { + BYTE predictor[2]; + INT32 delta[2]; + INT32 sample1[2]; + INT32 sample2[2]; + } ms; +} ADPCM; + +struct S_FREERDP_DSP_CONTEXT +{ + BOOL encoder; + + ADPCM adpcm; + AUDIO_FORMAT format; + + wStream* channelmix; + wStream* resample; + wStream* buffer; + +#if defined(WITH_GSM) + gsm gsm; +#endif +#if defined(WITH_LAME) + lame_t lame; + hip_t hip; +#endif +#if defined(WITH_OPUS) + OpusDecoder* opus_decoder; + OpusEncoder* opus_encoder; +#endif +#if defined(WITH_FAAD2) + NeAACDecHandle faad; + BOOL faadSetup; +#endif + +#if defined(WITH_FAAC) + faacEncHandle faac; + unsigned long faacInputSamples; + unsigned long faacMaxOutputBytes; +#endif + +#if defined(WITH_SOXR) + soxr_t sox; +#endif +}; + +#if defined(WITH_OPUS) +static BOOL opus_is_valid_samplerate(const AUDIO_FORMAT* format) +{ + WINPR_ASSERT(format); + + switch (format->nSamplesPerSec) + { + case 8000: + case 12000: + case 16000: + case 24000: + case 48000: + return TRUE; + default: + return FALSE; + } +} +#endif + +static INT16 read_int16(const BYTE* src) +{ + return (INT16)(src[0] | (src[1] << 8)); +} + +static BOOL freerdp_dsp_channel_mix(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size, + const AUDIO_FORMAT* srcFormat, const BYTE** data, + size_t* length) +{ + UINT32 bpp; + size_t samples; + + if (!context || !data || !length) + return FALSE; + + if (srcFormat->wFormatTag != WAVE_FORMAT_PCM) + return FALSE; + + bpp = srcFormat->wBitsPerSample > 8 ? 2 : 1; + samples = size / bpp / srcFormat->nChannels; + + if (context->format.nChannels == srcFormat->nChannels) + { + *data = src; + *length = size; + return TRUE; + } + + Stream_SetPosition(context->channelmix, 0); + + /* Destination has more channels than source */ + if (context->format.nChannels > srcFormat->nChannels) + { + switch (srcFormat->nChannels) + { + case 1: + if (!Stream_EnsureCapacity(context->channelmix, size * 2)) + return FALSE; + + for (size_t x = 0; x < samples; x++) + { + for (size_t y = 0; y < bpp; y++) + Stream_Write_UINT8(context->channelmix, src[x * bpp + y]); + + for (size_t y = 0; y < bpp; y++) + Stream_Write_UINT8(context->channelmix, src[x * bpp + y]); + } + + Stream_SealLength(context->channelmix); + *data = Stream_Buffer(context->channelmix); + *length = Stream_Length(context->channelmix); + return TRUE; + + case 2: /* We only support stereo, so we can not handle this case. */ + default: /* Unsupported number of channels */ + return FALSE; + } + } + + /* Destination has less channels than source */ + switch (srcFormat->nChannels) + { + case 2: + if (!Stream_EnsureCapacity(context->channelmix, size / 2)) + return FALSE; + + /* Simply drop second channel. + * TODO: Calculate average */ + for (size_t x = 0; x < samples; x++) + { + for (size_t y = 0; y < bpp; y++) + Stream_Write_UINT8(context->channelmix, src[2 * x * bpp + y]); + } + + Stream_SealLength(context->channelmix); + *data = Stream_Buffer(context->channelmix); + *length = Stream_Length(context->channelmix); + return TRUE; + + case 1: /* Invalid, do we want to use a 0 channel sound? */ + default: /* Unsupported number of channels */ + return FALSE; + } + + return FALSE; +} + +/** + * Microsoft Multimedia Standards Update + * http://download.microsoft.com/download/9/8/6/9863C72A-A3AA-4DDB-B1BA-CA8D17EFD2D4/RIFFNEW.pdf + */ + +static BOOL freerdp_dsp_resample(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size, + const AUDIO_FORMAT* srcFormat, const BYTE** data, size_t* length) +{ +#if defined(WITH_SOXR) + soxr_error_t error; + size_t idone, odone; + size_t sframes, rframes; + size_t rsize; + size_t sbytes, rbytes; + size_t dstChannels; + size_t srcChannels; + size_t srcBytesPerFrame, dstBytesPerFrame; +#endif + AUDIO_FORMAT format; + + if (srcFormat->wFormatTag != WAVE_FORMAT_PCM) + { + WLog_ERR(TAG, "requires %s for sample input, got %s", + audio_format_get_tag_string(WAVE_FORMAT_PCM), + audio_format_get_tag_string(srcFormat->wFormatTag)); + return FALSE; + } + + /* We want to ignore differences of source and destination format. */ + format = *srcFormat; + format.wFormatTag = WAVE_FORMAT_UNKNOWN; + format.wBitsPerSample = 0; + + if (audio_format_compatible(&format, &context->format)) + { + *data = src; + *length = size; + return TRUE; + } + +#if defined(WITH_SOXR) + srcBytesPerFrame = (srcFormat->wBitsPerSample > 8) ? 2 : 1; + dstBytesPerFrame = (context->format.wBitsPerSample > 8) ? 2 : 1; + srcChannels = srcFormat->nChannels; + dstChannels = context->format.nChannels; + sbytes = srcChannels * srcBytesPerFrame; + sframes = size / sbytes; + rbytes = dstBytesPerFrame * dstChannels; + /* Integer rounding correct division */ + rframes = (sframes * context->format.nSamplesPerSec + (srcFormat->nSamplesPerSec + 1) / 2) / + srcFormat->nSamplesPerSec; + rsize = rframes * rbytes; + + if (!Stream_EnsureCapacity(context->resample, rsize)) + return FALSE; + + error = soxr_process(context->sox, src, sframes, &idone, Stream_Buffer(context->resample), + Stream_Capacity(context->resample) / rbytes, &odone); + Stream_SetLength(context->resample, odone * rbytes); + *data = Stream_Buffer(context->resample); + *length = Stream_Length(context->resample); + return (error == 0) ? TRUE : FALSE; +#else + WLog_ERR(TAG, "Missing resample support, recompile -DWITH_SOXR=ON or -DWITH_DSP_FFMPEG=ON"); + return FALSE; +#endif +} + +/** + * Microsoft IMA ADPCM specification: + * + * http://wiki.multimedia.cx/index.php?title=Microsoft_IMA_ADPCM + * http://wiki.multimedia.cx/index.php?title=IMA_ADPCM + */ + +static const INT16 ima_step_index_table[] = { + -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8 +}; + +static const INT16 ima_step_size_table[] = { + 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, + 25, 28, 31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, + 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, 279, + 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, + 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, + 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, + 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767 +}; + +static UINT16 dsp_decode_ima_adpcm_sample(ADPCM* adpcm, unsigned int channel, BYTE sample) +{ + INT32 ss; + INT32 d; + ss = ima_step_size_table[adpcm->ima.last_step[channel]]; + d = (ss >> 3); + + if (sample & 1) + d += (ss >> 2); + + if (sample & 2) + d += (ss >> 1); + + if (sample & 4) + d += ss; + + if (sample & 8) + d = -d; + + d += adpcm->ima.last_sample[channel]; + + if (d < -32768) + d = -32768; + else if (d > 32767) + d = 32767; + + adpcm->ima.last_sample[channel] = (INT16)d; + adpcm->ima.last_step[channel] += ima_step_index_table[sample]; + + if (adpcm->ima.last_step[channel] < 0) + adpcm->ima.last_step[channel] = 0; + else if (adpcm->ima.last_step[channel] > 88) + adpcm->ima.last_step[channel] = 88; + + return (UINT16)d; +} + +static BOOL freerdp_dsp_decode_ima_adpcm(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size, + wStream* out) +{ + BYTE sample; + UINT16 decoded; + size_t out_size = size * 4; + UINT32 channel; + const UINT32 block_size = context->format.nBlockAlign; + const UINT32 channels = context->format.nChannels; + + if (!Stream_EnsureCapacity(out, out_size)) + return FALSE; + + while (size > 0) + { + if (size % block_size == 0) + { + context->adpcm.ima.last_sample[0] = + (INT16)(((UINT16)(*src)) | (((UINT16)(*(src + 1))) << 8)); + context->adpcm.ima.last_step[0] = (INT16)(*(src + 2)); + src += 4; + size -= 4; + out_size -= 16; + + if (channels > 1) + { + context->adpcm.ima.last_sample[1] = + (INT16)(((UINT16)(*src)) | (((UINT16)(*(src + 1))) << 8)); + context->adpcm.ima.last_step[1] = (INT16)(*(src + 2)); + src += 4; + size -= 4; + out_size -= 16; + } + } + + if (channels > 1) + { + for (size_t i = 0; i < 8; i++) + { + BYTE* dst = Stream_Pointer(out); + + channel = (i < 4 ? 0 : 1); + sample = ((*src) & 0x0f); + decoded = dsp_decode_ima_adpcm_sample(&context->adpcm, channel, sample); + dst[((i & 3) << 3) + (channel << 1)] = (decoded & 0xFF); + dst[((i & 3) << 3) + (channel << 1) + 1] = (decoded >> 8); + sample = ((*src) >> 4); + decoded = dsp_decode_ima_adpcm_sample(&context->adpcm, channel, sample); + dst[((i & 3) << 3) + (channel << 1) + 4] = (decoded & 0xFF); + dst[((i & 3) << 3) + (channel << 1) + 5] = (decoded >> 8); + src++; + } + + if (!Stream_SafeSeek(out, 32)) + return FALSE; + size -= 8; + } + else + { + BYTE* dst = Stream_Pointer(out); + if (!Stream_SafeSeek(out, 4)) + return FALSE; + + sample = ((*src) & 0x0f); + decoded = dsp_decode_ima_adpcm_sample(&context->adpcm, 0, sample); + *dst++ = (decoded & 0xFF); + *dst++ = (decoded >> 8); + sample = ((*src) >> 4); + decoded = dsp_decode_ima_adpcm_sample(&context->adpcm, 0, sample); + *dst++ = (decoded & 0xFF); + *dst++ = (decoded >> 8); + src++; + size--; + } + } + + return TRUE; +} + +#if defined(WITH_GSM) +static BOOL freerdp_dsp_decode_gsm610(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size, + wStream* out) +{ + size_t offset = 0; + + while (offset < size) + { + int rc; + gsm_signal gsmBlockBuffer[160] = { 0 }; + rc = gsm_decode(context->gsm, (gsm_byte*)/* API does not modify */ &src[offset], + gsmBlockBuffer); + + if (rc < 0) + return FALSE; + + if ((offset % 65) == 0) + offset += 33; + else + offset += 32; + + if (!Stream_EnsureRemainingCapacity(out, sizeof(gsmBlockBuffer))) + return FALSE; + + Stream_Write(out, (void*)gsmBlockBuffer, sizeof(gsmBlockBuffer)); + } + + return TRUE; +} + +static BOOL freerdp_dsp_encode_gsm610(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size, + wStream* out) +{ + size_t offset = 0; + + while (offset < size) + { + const gsm_signal* signal = (const gsm_signal*)&src[offset]; + + if (!Stream_EnsureRemainingCapacity(out, sizeof(gsm_frame))) + return FALSE; + + gsm_encode(context->gsm, (gsm_signal*)/* API does not modify */ signal, + Stream_Pointer(out)); + + if ((offset % 65) == 0) + Stream_Seek(out, 33); + else + Stream_Seek(out, 32); + + offset += 160; + } + + return TRUE; +} +#endif + +#if defined(WITH_LAME) +static BOOL freerdp_dsp_decode_mp3(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size, + wStream* out) +{ + int rc; + short* pcm_l; + short* pcm_r; + size_t buffer_size; + + if (!context || !src || !out) + return FALSE; + + buffer_size = 2 * context->format.nChannels * context->format.nSamplesPerSec; + + if (!Stream_EnsureCapacity(context->buffer, 2 * buffer_size)) + return FALSE; + + pcm_l = (short*)Stream_Buffer(context->buffer); + pcm_r = (short*)Stream_Buffer(context->buffer) + buffer_size; + rc = hip_decode(context->hip, (unsigned char*)/* API is not modifying content */ src, size, + pcm_l, pcm_r); + + if (rc <= 0) + return FALSE; + + if (!Stream_EnsureRemainingCapacity(out, (size_t)rc * context->format.nChannels * 2)) + return FALSE; + + for (size_t x = 0; x < rc; x++) + { + Stream_Write_UINT16(out, (UINT16)pcm_l[x]); + Stream_Write_UINT16(out, (UINT16)pcm_r[x]); + } + + return TRUE; +} + +static BOOL freerdp_dsp_encode_mp3(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size, + wStream* out) +{ + size_t samples_per_channel; + int rc; + + if (!context || !src || !out) + return FALSE; + + samples_per_channel = size / context->format.nChannels / context->format.wBitsPerSample / 8; + + /* Ensure worst case buffer size for mp3 stream taken from LAME header */ + if (!Stream_EnsureRemainingCapacity(out, 5 / 4 * samples_per_channel + 7200)) + return FALSE; + + samples_per_channel = size / 2 /* size of a sample */ / context->format.nChannels; + rc = lame_encode_buffer_interleaved(context->lame, (short*)src, samples_per_channel, + Stream_Pointer(out), Stream_GetRemainingCapacity(out)); + + if (rc < 0) + return FALSE; + + Stream_Seek(out, (size_t)rc); + return TRUE; +} +#endif + +#if defined(WITH_FAAC) +static BOOL freerdp_dsp_encode_faac(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size, + wStream* out) +{ + const int16_t* inSamples = (const int16_t*)src; + unsigned int bpp; + size_t nrSamples; + int rc; + + if (!context || !src || !out) + return FALSE; + + bpp = context->format.wBitsPerSample / 8; + nrSamples = size / bpp; + + if (!Stream_EnsureRemainingCapacity(context->buffer, nrSamples * sizeof(int16_t))) + return FALSE; + + for (size_t x = 0; x < nrSamples; x++) + { + Stream_Write_INT16(context->buffer, inSamples[x]); + if (Stream_GetPosition(context->buffer) / bpp >= context->faacInputSamples) + { + if (!Stream_EnsureRemainingCapacity(out, context->faacMaxOutputBytes)) + return FALSE; + rc = faacEncEncode(context->faac, (int32_t*)Stream_Buffer(context->buffer), + context->faacInputSamples, Stream_Pointer(out), + Stream_GetRemainingCapacity(out)); + if (rc < 0) + return FALSE; + if (rc > 0) + Stream_Seek(out, (size_t)rc); + Stream_SetPosition(context->buffer, 0); + } + } + + return TRUE; +} +#endif + +#if defined(WITH_OPUS) +static BOOL freerdp_dsp_decode_opus(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size, + wStream* out) +{ + size_t max_size = 5760; + int frames; + + if (!context || !src || !out) + return FALSE; + + /* Max packet duration is 120ms (5760 at 48KHz) */ + max_size = OPUS_MAX_FRAMES * context->format.nChannels * sizeof(int16_t); + if (!Stream_EnsureRemainingCapacity(context->buffer, max_size)) + return FALSE; + + frames = opus_decode(context->opus_decoder, src, size, Stream_Pointer(out), OPUS_MAX_FRAMES, 0); + if (frames < 0) + return FALSE; + + Stream_Seek(out, frames * context->format.nChannels * sizeof(int16_t)); + + return TRUE; +} + +static BOOL freerdp_dsp_encode_opus(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size, + wStream* out) +{ + if (!context || !src || !out) + return FALSE; + + /* Max packet duration is 120ms (5760 at 48KHz) */ + const size_t max_size = OPUS_MAX_FRAMES * context->format.nChannels * sizeof(int16_t); + if (!Stream_EnsureRemainingCapacity(context->buffer, max_size)) + return FALSE; + + const int src_frames = size / sizeof(opus_int16) / context->format.nChannels; + const opus_int16* src_data = (const opus_int16*)src; + const int frames = + opus_encode(context->opus_encoder, src_data, src_frames, Stream_Pointer(out), max_size); + if (frames < 0) + return FALSE; + return Stream_SafeSeek(out, frames * context->format.nChannels * sizeof(int16_t)); +} +#endif + +#if defined(WITH_FAAD2) +static BOOL freerdp_dsp_decode_faad(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size, + wStream* out) +{ + NeAACDecFrameInfo info; + size_t offset = 0; + + if (!context || !src || !out) + return FALSE; + + if (!context->faadSetup) + { + union + { + const void* cpv; + void* pv; + } cnv; + unsigned long samplerate; + unsigned char channels; + long err; + cnv.cpv = src; + err = NeAACDecInit(context->faad, /* API is not modifying content */ cnv.pv, size, + &samplerate, &channels); + + if (err != 0) + return FALSE; + + if (channels != context->format.nChannels) + return FALSE; + + if (samplerate != context->format.nSamplesPerSec) + return FALSE; + + context->faadSetup = TRUE; + } + + while (offset < size) + { + union + { + const void* cpv; + void* pv; + } cnv; + size_t outSize; + void* sample_buffer; + outSize = context->format.nSamplesPerSec * context->format.nChannels * + context->format.wBitsPerSample / 8; + + if (!Stream_EnsureRemainingCapacity(out, outSize)) + return FALSE; + + sample_buffer = Stream_Pointer(out); + + cnv.cpv = &src[offset]; + NeAACDecDecode2(context->faad, &info, cnv.pv, size - offset, &sample_buffer, + Stream_GetRemainingCapacity(out)); + + if (info.error != 0) + return FALSE; + + offset += info.bytesconsumed; + + if (info.samples == 0) + continue; + + Stream_Seek(out, info.samples * context->format.wBitsPerSample / 8); + } + + return TRUE; +} + +#endif + +/** + * 0 1 2 3 + * 2 0 6 4 10 8 14 12 <left> + * + * 4 5 6 7 + * 3 1 7 5 11 9 15 13 <right> + */ +static const struct +{ + BYTE byte_num; + BYTE byte_shift; +} ima_stereo_encode_map[] = { { 0, 0 }, { 4, 0 }, { 0, 4 }, { 4, 4 }, { 1, 0 }, { 5, 0 }, + { 1, 4 }, { 5, 4 }, { 2, 0 }, { 6, 0 }, { 2, 4 }, { 6, 4 }, + { 3, 0 }, { 7, 0 }, { 3, 4 }, { 7, 4 } }; + +static BYTE dsp_encode_ima_adpcm_sample(ADPCM* adpcm, int channel, INT16 sample) +{ + INT32 e; + INT32 d; + INT32 ss; + BYTE enc; + INT32 diff; + ss = ima_step_size_table[adpcm->ima.last_step[channel]]; + d = e = sample - adpcm->ima.last_sample[channel]; + diff = ss >> 3; + enc = 0; + + if (e < 0) + { + enc = 8; + e = -e; + } + + if (e >= ss) + { + enc |= 4; + e -= ss; + } + + ss >>= 1; + + if (e >= ss) + { + enc |= 2; + e -= ss; + } + + ss >>= 1; + + if (e >= ss) + { + enc |= 1; + e -= ss; + } + + if (d < 0) + diff = d + e - diff; + else + diff = d - e + diff; + + diff += adpcm->ima.last_sample[channel]; + + if (diff < -32768) + diff = -32768; + else if (diff > 32767) + diff = 32767; + + adpcm->ima.last_sample[channel] = (INT16)diff; + adpcm->ima.last_step[channel] += ima_step_index_table[enc]; + + if (adpcm->ima.last_step[channel] < 0) + adpcm->ima.last_step[channel] = 0; + else if (adpcm->ima.last_step[channel] > 88) + adpcm->ima.last_step[channel] = 88; + + return enc; +} + +static BOOL freerdp_dsp_encode_ima_adpcm(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size, + wStream* out) +{ + INT16 sample; + BYTE encoded; + size_t align; + + if (!Stream_EnsureRemainingCapacity(out, size)) + return FALSE; + + align = (context->format.nChannels > 1) ? 32 : 4; + + while (size >= align) + { + if (Stream_GetPosition(context->buffer) % context->format.nBlockAlign == 0) + { + Stream_Write_UINT8(context->buffer, context->adpcm.ima.last_sample[0] & 0xFF); + Stream_Write_UINT8(context->buffer, (context->adpcm.ima.last_sample[0] >> 8) & 0xFF); + Stream_Write_UINT8(context->buffer, (BYTE)context->adpcm.ima.last_step[0]); + Stream_Write_UINT8(context->buffer, 0); + + if (context->format.nChannels > 1) + { + Stream_Write_UINT8(context->buffer, context->adpcm.ima.last_sample[1] & 0xFF); + Stream_Write_UINT8(context->buffer, + (context->adpcm.ima.last_sample[1] >> 8) & 0xFF); + Stream_Write_UINT8(context->buffer, (BYTE)context->adpcm.ima.last_step[1]); + Stream_Write_UINT8(context->buffer, 0); + } + } + + if (context->format.nChannels > 1) + { + BYTE* dst = Stream_Pointer(context->buffer); + ZeroMemory(dst, 8); + + for (size_t i = 0; i < 16; i++) + { + sample = (INT16)(((UINT16)(*src)) | (((UINT16)(*(src + 1))) << 8)); + src += 2; + encoded = dsp_encode_ima_adpcm_sample(&context->adpcm, i % 2, sample); + dst[ima_stereo_encode_map[i].byte_num] |= encoded + << ima_stereo_encode_map[i].byte_shift; + } + + if (!Stream_SafeSeek(context->buffer, 8)) + return FALSE; + size -= 32; + } + else + { + sample = (INT16)(((UINT16)(*src)) | (((UINT16)(*(src + 1))) << 8)); + src += 2; + encoded = dsp_encode_ima_adpcm_sample(&context->adpcm, 0, sample); + sample = (INT16)(((UINT16)(*src)) | (((UINT16)(*(src + 1))) << 8)); + src += 2; + encoded |= dsp_encode_ima_adpcm_sample(&context->adpcm, 0, sample) << 4; + Stream_Write_UINT8(context->buffer, encoded); + size -= 4; + } + + if (Stream_GetPosition(context->buffer) >= context->adpcm.ima.packet_size) + { + BYTE* bsrc = Stream_Buffer(context->buffer); + Stream_Write(out, bsrc, context->adpcm.ima.packet_size); + Stream_SetPosition(context->buffer, 0); + } + } + + return TRUE; +} + +/** + * Microsoft ADPCM Specification: + * + * http://wiki.multimedia.cx/index.php?title=Microsoft_ADPCM + */ + +static const INT32 ms_adpcm_adaptation_table[] = { 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230 }; + +static const INT32 ms_adpcm_coeffs1[7] = { 256, 512, 0, 192, 240, 460, 392 }; + +static const INT32 ms_adpcm_coeffs2[7] = { 0, -256, 0, 64, 0, -208, -232 }; + +static INLINE INT16 freerdp_dsp_decode_ms_adpcm_sample(ADPCM* adpcm, BYTE sample, int channel) +{ + INT8 nibble; + INT32 presample; + nibble = (sample & 0x08 ? (INT8)sample - 16 : (INT8)sample); + presample = ((adpcm->ms.sample1[channel] * ms_adpcm_coeffs1[adpcm->ms.predictor[channel]]) + + (adpcm->ms.sample2[channel] * ms_adpcm_coeffs2[adpcm->ms.predictor[channel]])) / + 256; + presample += nibble * adpcm->ms.delta[channel]; + + if (presample > 32767) + presample = 32767; + else if (presample < -32768) + presample = -32768; + + adpcm->ms.sample2[channel] = adpcm->ms.sample1[channel]; + adpcm->ms.sample1[channel] = presample; + adpcm->ms.delta[channel] = adpcm->ms.delta[channel] * ms_adpcm_adaptation_table[sample] / 256; + + if (adpcm->ms.delta[channel] < 16) + adpcm->ms.delta[channel] = 16; + + return (INT16)presample; +} + +static BOOL freerdp_dsp_decode_ms_adpcm(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size, + wStream* out) +{ + BYTE sample; + const size_t out_size = size * 4; + const UINT32 channels = context->format.nChannels; + const UINT32 block_size = context->format.nBlockAlign; + + if (!Stream_EnsureCapacity(out, out_size)) + return FALSE; + + while (size > 0) + { + if (size % block_size == 0) + { + if (channels > 1) + { + context->adpcm.ms.predictor[0] = *src++; + context->adpcm.ms.predictor[1] = *src++; + context->adpcm.ms.delta[0] = read_int16(src); + src += 2; + context->adpcm.ms.delta[1] = read_int16(src); + src += 2; + context->adpcm.ms.sample1[0] = read_int16(src); + src += 2; + context->adpcm.ms.sample1[1] = read_int16(src); + src += 2; + context->adpcm.ms.sample2[0] = read_int16(src); + src += 2; + context->adpcm.ms.sample2[1] = read_int16(src); + src += 2; + size -= 14; + Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample2[0]); + Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample2[1]); + Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample1[0]); + Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample1[1]); + } + else + { + context->adpcm.ms.predictor[0] = *src++; + context->adpcm.ms.delta[0] = read_int16(src); + src += 2; + context->adpcm.ms.sample1[0] = read_int16(src); + src += 2; + context->adpcm.ms.sample2[0] = read_int16(src); + src += 2; + size -= 7; + Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample2[0]); + Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample1[0]); + } + } + + if (channels > 1) + { + sample = *src++; + size--; + Stream_Write_INT16(out, + freerdp_dsp_decode_ms_adpcm_sample(&context->adpcm, sample >> 4, 0)); + Stream_Write_INT16( + out, freerdp_dsp_decode_ms_adpcm_sample(&context->adpcm, sample & 0x0F, 1)); + sample = *src++; + size--; + Stream_Write_INT16(out, + freerdp_dsp_decode_ms_adpcm_sample(&context->adpcm, sample >> 4, 0)); + Stream_Write_INT16( + out, freerdp_dsp_decode_ms_adpcm_sample(&context->adpcm, sample & 0x0F, 1)); + } + else + { + sample = *src++; + size--; + Stream_Write_INT16(out, + freerdp_dsp_decode_ms_adpcm_sample(&context->adpcm, sample >> 4, 0)); + Stream_Write_INT16( + out, freerdp_dsp_decode_ms_adpcm_sample(&context->adpcm, sample & 0x0F, 0)); + } + } + + return TRUE; +} + +static BYTE freerdp_dsp_encode_ms_adpcm_sample(ADPCM* adpcm, INT32 sample, int channel) +{ + INT32 presample; + INT32 errordelta; + presample = ((adpcm->ms.sample1[channel] * ms_adpcm_coeffs1[adpcm->ms.predictor[channel]]) + + (adpcm->ms.sample2[channel] * ms_adpcm_coeffs2[adpcm->ms.predictor[channel]])) / + 256; + errordelta = (sample - presample) / adpcm->ms.delta[channel]; + + if ((sample - presample) % adpcm->ms.delta[channel] > adpcm->ms.delta[channel] / 2) + errordelta++; + + if (errordelta > 7) + errordelta = 7; + else if (errordelta < -8) + errordelta = -8; + + presample += adpcm->ms.delta[channel] * errordelta; + + if (presample > 32767) + presample = 32767; + else if (presample < -32768) + presample = -32768; + + adpcm->ms.sample2[channel] = adpcm->ms.sample1[channel]; + adpcm->ms.sample1[channel] = presample; + adpcm->ms.delta[channel] = + adpcm->ms.delta[channel] * ms_adpcm_adaptation_table[(((BYTE)errordelta) & 0x0F)] / 256; + + if (adpcm->ms.delta[channel] < 16) + adpcm->ms.delta[channel] = 16; + + return ((BYTE)errordelta) & 0x0F; +} + +static BOOL freerdp_dsp_encode_ms_adpcm(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size, + wStream* out) +{ + size_t start; + INT32 sample; + const size_t step = 8 + ((context->format.nChannels > 1) ? 4 : 0); + + if (!Stream_EnsureRemainingCapacity(out, size)) + return FALSE; + + start = Stream_GetPosition(out); + + if (context->adpcm.ms.delta[0] < 16) + context->adpcm.ms.delta[0] = 16; + + if (context->adpcm.ms.delta[1] < 16) + context->adpcm.ms.delta[1] = 16; + + while (size >= step) + { + BYTE val; + if ((Stream_GetPosition(out) - start) % context->format.nBlockAlign == 0) + { + if (context->format.nChannels > 1) + { + Stream_Write_UINT8(out, context->adpcm.ms.predictor[0]); + Stream_Write_UINT8(out, context->adpcm.ms.predictor[1]); + Stream_Write_UINT8(out, (context->adpcm.ms.delta[0] & 0xFF)); + Stream_Write_UINT8(out, ((context->adpcm.ms.delta[0] >> 8) & 0xFF)); + Stream_Write_UINT8(out, (context->adpcm.ms.delta[1] & 0xFF)); + Stream_Write_UINT8(out, ((context->adpcm.ms.delta[1] >> 8) & 0xFF)); + + context->adpcm.ms.sample1[0] = read_int16(src + 4); + context->adpcm.ms.sample1[1] = read_int16(src + 6); + context->adpcm.ms.sample2[0] = read_int16(src + 0); + context->adpcm.ms.sample2[1] = read_int16(src + 2); + + Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample1[0]); + Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample1[1]); + Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample2[0]); + Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample2[1]); + + src += 8; + size -= 8; + } + else + { + Stream_Write_UINT8(out, context->adpcm.ms.predictor[0]); + Stream_Write_UINT8(out, (BYTE)(context->adpcm.ms.delta[0] & 0xFF)); + Stream_Write_UINT8(out, (BYTE)((context->adpcm.ms.delta[0] >> 8) & 0xFF)); + + context->adpcm.ms.sample1[0] = read_int16(src + 2); + context->adpcm.ms.sample2[0] = read_int16(src + 0); + + Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample1[0]); + Stream_Write_INT16(out, (INT16)context->adpcm.ms.sample2[0]); + src += 4; + size -= 4; + } + } + + sample = read_int16(src); + src += 2; + Stream_Write_UINT8( + out, (freerdp_dsp_encode_ms_adpcm_sample(&context->adpcm, sample, 0) << 4) & 0xFF); + sample = read_int16(src); + src += 2; + + Stream_Read_UINT8(out, val); + val += freerdp_dsp_encode_ms_adpcm_sample(&context->adpcm, sample, + context->format.nChannels > 1 ? 1 : 0); + Stream_Write_UINT8(out, val); + size -= 4; + } + + return TRUE; +} + +#endif + +FREERDP_DSP_CONTEXT* freerdp_dsp_context_new(BOOL encoder) +{ +#if defined(WITH_DSP_FFMPEG) + return freerdp_dsp_ffmpeg_context_new(encoder); +#else + FREERDP_DSP_CONTEXT* context = calloc(1, sizeof(FREERDP_DSP_CONTEXT)); + + if (!context) + return NULL; + + context->channelmix = Stream_New(NULL, 4096); + + if (!context->channelmix) + goto fail; + + context->resample = Stream_New(NULL, 4096); + + if (!context->resample) + goto fail; + + context->buffer = Stream_New(NULL, 4096); + + if (!context->buffer) + goto fail; + + context->encoder = encoder; +#if defined(WITH_GSM) + context->gsm = gsm_create(); + + if (!context->gsm) + goto fail; + + { + int rc; + int val = 1; + rc = gsm_option(context->gsm, GSM_OPT_WAV49, &val); + + if (rc < 0) + goto fail; + } +#endif +#if defined(WITH_LAME) + + if (encoder) + { + context->lame = lame_init(); + + if (!context->lame) + goto fail; + } + else + { + context->hip = hip_decode_init(); + + if (!context->hip) + goto fail; + } + +#endif +#if defined(WITH_FAAD2) + + if (!encoder) + { + context->faad = NeAACDecOpen(); + + if (!context->faad) + goto fail; + } + +#endif + return context; +fail: + freerdp_dsp_context_free(context); + return NULL; +#endif +} + +void freerdp_dsp_context_free(FREERDP_DSP_CONTEXT* context) +{ +#if defined(WITH_DSP_FFMPEG) + freerdp_dsp_ffmpeg_context_free(context); +#else + + if (context) + { + Stream_Free(context->channelmix, TRUE); + Stream_Free(context->resample, TRUE); + Stream_Free(context->buffer, TRUE); +#if defined(WITH_GSM) + gsm_destroy(context->gsm); +#endif +#if defined(WITH_LAME) + + if (context->encoder) + lame_close(context->lame); + else + hip_decode_exit(context->hip); + +#endif +#if defined(WITH_OPUS) + + if (context->opus_decoder) + opus_decoder_destroy(context->opus_decoder); + if (context->opus_encoder) + opus_encoder_destroy(context->opus_encoder); + +#endif +#if defined(WITH_FAAD2) + + if (!context->encoder) + NeAACDecClose(context->faad); + +#endif +#if defined(WITH_FAAC) + + if (context->faac) + faacEncClose(context->faac); + +#endif +#if defined(WITH_SOXR) + soxr_delete(context->sox); +#endif + free(context); + } + +#endif +} + +BOOL freerdp_dsp_encode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* srcFormat, + const BYTE* data, size_t length, wStream* out) +{ +#if defined(WITH_DSP_FFMPEG) + return freerdp_dsp_ffmpeg_encode(context, srcFormat, data, length, out); +#else + const BYTE* resampleData; + size_t resampleLength; + AUDIO_FORMAT format; + + if (!context || !context->encoder || !srcFormat || !data || !out) + return FALSE; + + format = *srcFormat; + + if (!freerdp_dsp_channel_mix(context, data, length, srcFormat, &resampleData, &resampleLength)) + return FALSE; + + format.nChannels = context->format.nChannels; + + if (!freerdp_dsp_resample(context, resampleData, resampleLength, &format, &data, &length)) + return FALSE; + + switch (context->format.wFormatTag) + { + case WAVE_FORMAT_PCM: + if (!Stream_EnsureRemainingCapacity(out, length)) + return FALSE; + + Stream_Write(out, data, length); + return TRUE; + + case WAVE_FORMAT_ADPCM: + return freerdp_dsp_encode_ms_adpcm(context, data, length, out); + + case WAVE_FORMAT_DVI_ADPCM: + return freerdp_dsp_encode_ima_adpcm(context, data, length, out); +#if defined(WITH_GSM) + + case WAVE_FORMAT_GSM610: + return freerdp_dsp_encode_gsm610(context, data, length, out); +#endif +#if defined(WITH_LAME) + + case WAVE_FORMAT_MPEGLAYER3: + return freerdp_dsp_encode_mp3(context, data, length, out); +#endif +#if defined(WITH_FAAC) + + case WAVE_FORMAT_AAC_MS: + return freerdp_dsp_encode_faac(context, data, length, out); +#endif +#if defined(WITH_OPUS) + + case WAVE_FORMAT_OPUS: + return freerdp_dsp_encode_opus(context, data, length, out); +#endif + default: + return FALSE; + } + + return FALSE; +#endif +} + +BOOL freerdp_dsp_decode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* srcFormat, + const BYTE* data, size_t length, wStream* out) +{ +#if defined(WITH_DSP_FFMPEG) + return freerdp_dsp_ffmpeg_decode(context, srcFormat, data, length, out); +#else + + if (!context || context->encoder || !srcFormat || !data || !out) + return FALSE; + + switch (context->format.wFormatTag) + { + case WAVE_FORMAT_PCM: + if (!Stream_EnsureRemainingCapacity(out, length)) + return FALSE; + + Stream_Write(out, data, length); + return TRUE; + + case WAVE_FORMAT_ADPCM: + return freerdp_dsp_decode_ms_adpcm(context, data, length, out); + + case WAVE_FORMAT_DVI_ADPCM: + return freerdp_dsp_decode_ima_adpcm(context, data, length, out); +#if defined(WITH_GSM) + + case WAVE_FORMAT_GSM610: + return freerdp_dsp_decode_gsm610(context, data, length, out); +#endif +#if defined(WITH_LAME) + + case WAVE_FORMAT_MPEGLAYER3: + return freerdp_dsp_decode_mp3(context, data, length, out); +#endif +#if defined(WITH_FAAD2) + + case WAVE_FORMAT_AAC_MS: + return freerdp_dsp_decode_faad(context, data, length, out); +#endif + +#if defined(WITH_OPUS) + case WAVE_FORMAT_OPUS: + return freerdp_dsp_decode_opus(context, data, length, out); +#endif + default: + return FALSE; + } + + return FALSE; +#endif +} + +BOOL freerdp_dsp_supports_format(const AUDIO_FORMAT* format, BOOL encode) +{ +#if defined(WITH_DSP_FFMPEG) + return freerdp_dsp_ffmpeg_supports_format(format, encode); +#else + +#if !defined(WITH_DSP_EXPERIMENTAL) + WINPR_UNUSED(encode); +#endif + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + return TRUE; +#if defined(WITH_DSP_EXPERIMENTAL) + + case WAVE_FORMAT_ADPCM: + return FALSE; + case WAVE_FORMAT_DVI_ADPCM: + return TRUE; +#endif +#if defined(WITH_GSM) + + case WAVE_FORMAT_GSM610: +#if defined(WITH_DSP_EXPERIMENTAL) + return TRUE; +#else + return !encode; +#endif +#endif +#if defined(WITH_LAME) + + case WAVE_FORMAT_MPEGLAYER3: +#if defined(WITH_DSP_EXPERIMENTAL) + return TRUE; +#else + return !encode; +#endif +#endif + + case WAVE_FORMAT_AAC_MS: +#if defined(WITH_FAAD2) + if (!encode) + return TRUE; + +#endif +#if defined(WITH_FAAC) + + if (encode) + return TRUE; + +#endif +#if defined(WITH_OPUS) + WINPR_FALLTHROUGH + case WAVE_FORMAT_OPUS: + return opus_is_valid_samplerate(format); +#endif + WINPR_FALLTHROUGH + default: + return FALSE; + } + + return FALSE; +#endif +} + +BOOL freerdp_dsp_context_reset(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* targetFormat, + UINT32 FramesPerPacket) +{ +#if defined(WITH_DSP_FFMPEG) + return freerdp_dsp_ffmpeg_context_reset(context, targetFormat); +#else + + if (!context || !targetFormat) + return FALSE; + + context->format = *targetFormat; + + if (context->format.wFormatTag == WAVE_FORMAT_DVI_ADPCM) + { + size_t min_frame_data = + 1ull * context->format.wBitsPerSample * context->format.nChannels * FramesPerPacket; + size_t data_per_block = (context->format.nBlockAlign - 4 * context->format.nChannels) * 8; + size_t nb_block_per_packet = min_frame_data / data_per_block; + + if (min_frame_data % data_per_block) + nb_block_per_packet++; + + context->adpcm.ima.packet_size = nb_block_per_packet * context->format.nBlockAlign; + Stream_EnsureCapacity(context->buffer, context->adpcm.ima.packet_size); + Stream_SetPosition(context->buffer, 0); + } + +#if defined(WITH_OPUS) + + if (opus_is_valid_samplerate(&context->format)) + { + if (!context->encoder) + { + int opus_error = OPUS_OK; + + context->opus_decoder = opus_decoder_create(context->format.nSamplesPerSec, + context->format.nChannels, &opus_error); + if (opus_error != OPUS_OK) + return FALSE; + } + else + { + int opus_error = OPUS_OK; + + context->opus_encoder = + opus_encoder_create(context->format.nSamplesPerSec, context->format.nChannels, + OPUS_APPLICATION_VOIP, &opus_error); + if (opus_error != OPUS_OK) + return FALSE; + + opus_error = opus_encoder_ctl(context->opus_encoder, + OPUS_SET_BITRATE(context->format.nAvgBytesPerSec * 8)); + if (opus_error != OPUS_OK) + return FALSE; + } + } + +#endif +#if defined(WITH_FAAD2) + context->faadSetup = FALSE; +#endif +#if defined(WITH_FAAC) + + if (context->encoder) + { + faacEncConfigurationPtr cfg; + + if (context->faac) + faacEncClose(context->faac); + + context->faac = faacEncOpen(targetFormat->nSamplesPerSec, targetFormat->nChannels, + &context->faacInputSamples, &context->faacMaxOutputBytes); + + if (!context->faac) + return FALSE; + + cfg = faacEncGetCurrentConfiguration(context->faac); + cfg->inputFormat = FAAC_INPUT_16BIT; + cfg->outputFormat = 0; + cfg->mpegVersion = MPEG4; + cfg->useTns = 1; + cfg->bandWidth = targetFormat->nAvgBytesPerSec; + faacEncSetConfiguration(context->faac, cfg); + } + +#endif +#if defined(WITH_SOXR) + { + soxr_io_spec_t iospec = soxr_io_spec(SOXR_INT16, SOXR_INT16); + soxr_error_t error; + soxr_delete(context->sox); + context->sox = soxr_create(context->format.nSamplesPerSec, targetFormat->nSamplesPerSec, + targetFormat->nChannels, &error, &iospec, NULL, NULL); + + if (!context->sox || (error != 0)) + return FALSE; + } +#endif + return TRUE; +#endif +} diff --git a/libfreerdp/codec/dsp.h b/libfreerdp/codec/dsp.h new file mode 100644 index 0000000..1325c31 --- /dev/null +++ b/libfreerdp/codec/dsp.h @@ -0,0 +1,34 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Digital Sound Processing - backend + * + * Copyright 2018 Armin Novak <armin.novak@thincast.com> + * Copyright 2018 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_LIB_CODEC_DSP_H +#define FREERDP_LIB_CODEC_DSP_H + +#include <freerdp/api.h> +#include <freerdp/codec/audio.h> +#include <freerdp/codec/dsp.h> + +struct S_FREERDP_DSP_COMMON_CONTEXT +{ + wStream* buffer; + wStream* resample; +}; + +#endif /* FREERDP_LIB_CODEC_DSP_H */ diff --git a/libfreerdp/codec/dsp_ffmpeg.c b/libfreerdp/codec/dsp_ffmpeg.c new file mode 100644 index 0000000..ff12f07 --- /dev/null +++ b/libfreerdp/codec/dsp_ffmpeg.c @@ -0,0 +1,846 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Digital Sound Processing - FFMPEG backend + * + * Copyright 2018 Armin Novak <armin.novak@thincast.com> + * Copyright 2018 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <freerdp/log.h> + +#include <libavcodec/avcodec.h> +#include <libavutil/avutil.h> +#include <libavutil/opt.h> +#if defined(SWRESAMPLE_FOUND) +#include <libswresample/swresample.h> +#elif defined(AVRESAMPLE_FOUND) +#include <libavresample/avresample.h> +#else +#error "libswresample or libavresample required" +#endif + +#include "dsp.h" +#include "dsp_ffmpeg.h" + +#define TAG FREERDP_TAG("dsp.ffmpeg") + +struct S_FREERDP_DSP_CONTEXT +{ + AUDIO_FORMAT format; + + BOOL isOpen; + BOOL encoder; + + UINT32 bufferedSamples; + + enum AVCodecID id; + AVCodec* codec; + AVCodecContext* context; + AVFrame* frame; + AVFrame* resampled; + AVFrame* buffered; + AVPacket* packet; +#if defined(SWRESAMPLE_FOUND) + SwrContext* rcontext; +#else + AVAudioResampleContext* rcontext; +#endif + wStream* channelmix; +}; + +static BOOL ffmpeg_codec_is_filtered(enum AVCodecID id, BOOL encoder) +{ + switch (id) + { +#if !defined(WITH_DSP_EXPERIMENTAL) + + case AV_CODEC_ID_ADPCM_IMA_OKI: + case AV_CODEC_ID_MP3: + case AV_CODEC_ID_ADPCM_MS: + case AV_CODEC_ID_G723_1: + return TRUE; +#endif + + case AV_CODEC_ID_NONE: + return TRUE; + + case AV_CODEC_ID_GSM_MS: + case AV_CODEC_ID_AAC: + case AV_CODEC_ID_AAC_LATM: +#if !defined(WITH_DSP_EXPERIMENTAL) + if (encoder) + return TRUE; +#endif + return FALSE; + + default: + return FALSE; + } +} + +static enum AVCodecID ffmpeg_get_avcodec(const AUDIO_FORMAT* format) +{ + if (!format) + return AV_CODEC_ID_NONE; + + switch (format->wFormatTag) + { + case WAVE_FORMAT_UNKNOWN: + return AV_CODEC_ID_NONE; + + case WAVE_FORMAT_PCM: + switch (format->wBitsPerSample) + { + case 16: + return AV_CODEC_ID_PCM_U16LE; + + case 8: + return AV_CODEC_ID_PCM_U8; + + default: + return AV_CODEC_ID_NONE; + } + + case WAVE_FORMAT_DVI_ADPCM: + return AV_CODEC_ID_ADPCM_IMA_OKI; + + case WAVE_FORMAT_ADPCM: + return AV_CODEC_ID_ADPCM_MS; + + case WAVE_FORMAT_ALAW: + return AV_CODEC_ID_PCM_ALAW; + + case WAVE_FORMAT_MULAW: + return AV_CODEC_ID_PCM_MULAW; + + case WAVE_FORMAT_GSM610: + return AV_CODEC_ID_GSM_MS; + + case WAVE_FORMAT_MSG723: + return AV_CODEC_ID_G723_1; + + case WAVE_FORMAT_AAC_MS: + return AV_CODEC_ID_AAC; + + case WAVE_FORMAT_OPUS: + return AV_CODEC_ID_OPUS; + + default: + return AV_CODEC_ID_NONE; + } +} + +static int ffmpeg_sample_format(const AUDIO_FORMAT* format) +{ + switch (format->wFormatTag) + { + case WAVE_FORMAT_PCM: + switch (format->wBitsPerSample) + { + case 8: + return AV_SAMPLE_FMT_U8; + + case 16: + return AV_SAMPLE_FMT_S16; + + default: + return FALSE; + } + + case WAVE_FORMAT_DVI_ADPCM: + case WAVE_FORMAT_ADPCM: + return AV_SAMPLE_FMT_S16P; + + case WAVE_FORMAT_MPEGLAYER3: + case WAVE_FORMAT_AAC_MS: + return AV_SAMPLE_FMT_FLTP; + + case WAVE_FORMAT_OPUS: + return AV_SAMPLE_FMT_S16; + + case WAVE_FORMAT_MSG723: + case WAVE_FORMAT_GSM610: + return AV_SAMPLE_FMT_S16P; + + case WAVE_FORMAT_ALAW: + return AV_SAMPLE_FMT_S16; + + default: + return FALSE; + } +} + +static void ffmpeg_close_context(FREERDP_DSP_CONTEXT* context) +{ + if (context) + { + if (context->context) + avcodec_free_context(&context->context); + + if (context->frame) + av_frame_free(&context->frame); + + if (context->resampled) + av_frame_free(&context->resampled); + + if (context->buffered) + av_frame_free(&context->buffered); + + if (context->packet) + av_packet_free(&context->packet); + + if (context->rcontext) + { +#if defined(SWRESAMPLE_FOUND) + swr_free(&context->rcontext); +#else + avresample_free(&context->rcontext); +#endif + } + + context->id = AV_CODEC_ID_NONE; + context->codec = NULL; + context->isOpen = FALSE; + context->context = NULL; + context->frame = NULL; + context->resampled = NULL; + context->packet = NULL; + context->rcontext = NULL; + } +} + +static BOOL ffmpeg_open_context(FREERDP_DSP_CONTEXT* context) +{ + int ret = 0; + + if (!context || context->isOpen) + return FALSE; + + const AUDIO_FORMAT* format = &context->format; + + if (!format) + return FALSE; +#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100) + const int layout = av_get_default_channel_layout(format->nChannels); +#endif + context->id = ffmpeg_get_avcodec(format); + + if (ffmpeg_codec_is_filtered(context->id, context->encoder)) + goto fail; + + if (context->encoder) + context->codec = avcodec_find_encoder(context->id); + else + context->codec = avcodec_find_decoder(context->id); + + if (!context->codec) + goto fail; + + context->context = avcodec_alloc_context3(context->codec); + + if (!context->context) + goto fail; + + switch (context->id) + { + /* We need support for multichannel and sample rates != 8000 */ + case AV_CODEC_ID_GSM_MS: + context->context->strict_std_compliance = FF_COMPLIANCE_UNOFFICIAL; + break; + + case AV_CODEC_ID_AAC: + context->context->profile = FF_PROFILE_AAC_MAIN; + break; + + default: + break; + } + + context->context->max_b_frames = 1; + context->context->delay = 0; +#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100) + context->context->channels = format->nChannels; + context->context->channel_layout = layout; +#else + av_channel_layout_default(&context->context->ch_layout, format->nChannels); +#endif + context->context->sample_rate = format->nSamplesPerSec; + context->context->block_align = format->nBlockAlign; + context->context->bit_rate = format->nAvgBytesPerSec * 8; + context->context->sample_fmt = ffmpeg_sample_format(format); + context->context->time_base = av_make_q(1, context->context->sample_rate); + + if ((ret = avcodec_open2(context->context, context->codec, NULL)) < 0) + { + const char* err = av_err2str(ret); + WLog_ERR(TAG, "Error avcodec_open2 %s [%d]", err, ret); + goto fail; + } + + context->packet = av_packet_alloc(); + + if (!context->packet) + goto fail; + + context->frame = av_frame_alloc(); + + if (!context->frame) + goto fail; + + context->resampled = av_frame_alloc(); + + if (!context->resampled) + goto fail; + + context->buffered = av_frame_alloc(); + + if (!context->buffered) + goto fail; + +#if defined(SWRESAMPLE_FOUND) + context->rcontext = swr_alloc(); +#else + context->rcontext = avresample_alloc_context(); +#endif + + if (!context->rcontext) + goto fail; + +#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100) + context->frame->channel_layout = layout; + context->frame->channels = format->nChannels; +#else + av_channel_layout_default(&context->frame->ch_layout, format->nChannels); +#endif + context->frame->sample_rate = format->nSamplesPerSec; + context->frame->format = AV_SAMPLE_FMT_S16; + + if (context->encoder) + { + context->resampled->format = context->context->sample_fmt; + context->resampled->sample_rate = context->context->sample_rate; + } + else + { + context->resampled->format = AV_SAMPLE_FMT_S16; + context->resampled->sample_rate = format->nSamplesPerSec; + } + +#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100) + context->resampled->channel_layout = layout; + context->resampled->channels = format->nChannels; +#else + av_channel_layout_default(&context->resampled->ch_layout, format->nChannels); +#endif + + if (context->context->frame_size > 0) + { +#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100) + context->buffered->channel_layout = context->resampled->channel_layout; + context->buffered->channels = context->resampled->channels; +#else + av_channel_layout_copy(&context->buffered->ch_layout, &context->resampled->ch_layout); +#endif + context->buffered->format = context->resampled->format; + context->buffered->nb_samples = context->context->frame_size; + + if ((ret = av_frame_get_buffer(context->buffered, 1)) < 0) + goto fail; + } + + context->isOpen = TRUE; + return TRUE; +fail: + ffmpeg_close_context(context); + return FALSE; +} + +#if defined(SWRESAMPLE_FOUND) +static BOOL ffmpeg_resample_frame(SwrContext* context, AVFrame* in, AVFrame* out) +{ + int ret = 0; + + if (!swr_is_initialized(context)) + { + if ((ret = swr_config_frame(context, out, in)) < 0) + { + const char* err = av_err2str(ret); + WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret); + return FALSE; + } + + if ((ret = (swr_init(context))) < 0) + { + const char* err = av_err2str(ret); + WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret); + return FALSE; + } + } + + if ((ret = swr_convert_frame(context, out, in)) < 0) + { + const char* err = av_err2str(ret); + WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret); + return FALSE; + } + + return TRUE; +} +#else +static BOOL ffmpeg_resample_frame(AVAudioResampleContext* context, AVFrame* in, AVFrame* out) +{ + int ret; + + if (!avresample_is_open(context)) + { + if ((ret = avresample_config(context, out, in)) < 0) + { + const char* err = av_err2str(ret); + WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret); + return FALSE; + } + + if ((ret = (avresample_open(context))) < 0) + { + const char* err = av_err2str(ret); + WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret); + return FALSE; + } + } + + if ((ret = avresample_convert_frame(context, out, in)) < 0) + { + const char* err = av_err2str(ret); + WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret); + return FALSE; + } + + return TRUE; +} +#endif + +static BOOL ffmpeg_encode_frame(AVCodecContext* context, AVFrame* in, AVPacket* packet, + wStream* out) +{ + if (in->format == AV_SAMPLE_FMT_FLTP) + { + uint8_t** pp = in->extended_data; +#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100) + const int nr_channels = in->channels; +#else + const int nr_channels = in->ch_layout.nb_channels; +#endif + + for (int y = 0; y < nr_channels; y++) + { + float* data = (float*)pp[y]; + for (int x = 0; x < in->nb_samples; x++) + { + const float val1 = data[x]; + if (isnan(val1)) + data[x] = 0.0f; + else if (isinf(val1)) + { + if (val1 < 0.0f) + data[x] = -1.0f; + else + data[x] = 1.0f; + } + } + } + } + /* send the packet with the compressed data to the encoder */ + int ret = avcodec_send_frame(context, in); + + if (ret < 0) + { + const char* err = av_err2str(ret); + WLog_ERR(TAG, "Error submitting the packet to the encoder %s [%d]", err, ret); + return FALSE; + } + + /* read all the output frames (in general there may be any number of them */ + while (ret >= 0) + { + ret = avcodec_receive_packet(context, packet); + + if ((ret == AVERROR(EAGAIN)) || (ret == AVERROR_EOF)) + return TRUE; + else if (ret < 0) + { + const char* err = av_err2str(ret); + WLog_ERR(TAG, "Error during encoding %s [%d]", err, ret); + return FALSE; + } + + if (!Stream_EnsureRemainingCapacity(out, packet->size)) + return FALSE; + + Stream_Write(out, packet->data, packet->size); + av_packet_unref(packet); + } + + return TRUE; +} + +static BOOL ffmpeg_fill_frame(AVFrame* frame, const AUDIO_FORMAT* inputFormat, const BYTE* data, + size_t size) +{ + int ret = 0; + int bpp = 0; +#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100) + frame->channels = inputFormat->nChannels; + frame->channel_layout = av_get_default_channel_layout(frame->channels); +#else + av_channel_layout_default(&frame->ch_layout, inputFormat->nChannels); +#endif + frame->sample_rate = inputFormat->nSamplesPerSec; + frame->format = ffmpeg_sample_format(inputFormat); + + bpp = av_get_bytes_per_sample(frame->format); + frame->nb_samples = size / inputFormat->nChannels / bpp; + + if ((ret = avcodec_fill_audio_frame(frame, inputFormat->nChannels, frame->format, data, size, + 1)) < 0) + { + const char* err = av_err2str(ret); + WLog_ERR(TAG, "Error during audio frame fill %s [%d]", err, ret); + return FALSE; + } + + return TRUE; +} +#if defined(SWRESAMPLE_FOUND) +static BOOL ffmpeg_decode(AVCodecContext* dec_ctx, AVPacket* pkt, AVFrame* frame, + SwrContext* resampleContext, AVFrame* resampled, wStream* out) +#else +static BOOL ffmpeg_decode(AVCodecContext* dec_ctx, AVPacket* pkt, AVFrame* frame, + AVAudioResampleContext* resampleContext, AVFrame* resampled, wStream* out) +#endif +{ + int ret = 0; + /* send the packet with the compressed data to the decoder */ + ret = avcodec_send_packet(dec_ctx, pkt); + + if (ret < 0) + { + const char* err = av_err2str(ret); + WLog_ERR(TAG, "Error submitting the packet to the decoder %s [%d]", err, ret); + return FALSE; + } + + /* read all the output frames (in general there may be any number of them */ + while (ret >= 0) + { + ret = avcodec_receive_frame(dec_ctx, frame); + + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + return TRUE; + else if (ret < 0) + { + const char* err = av_err2str(ret); + WLog_ERR(TAG, "Error during decoding %s [%d]", err, ret); + return FALSE; + } + +#if defined(SWRESAMPLE_FOUND) + if (!swr_is_initialized(resampleContext)) + { + if ((ret = swr_config_frame(resampleContext, resampled, frame)) < 0) + { +#else + if (!avresample_is_open(resampleContext)) + { + if ((ret = avresample_config(resampleContext, resampled, frame)) < 0) + { +#endif + const char* err = av_err2str(ret); + WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret); + return FALSE; + } + +#if defined(SWRESAMPLE_FOUND) + if ((ret = (swr_init(resampleContext))) < 0) +#else + if ((ret = (avresample_open(resampleContext))) < 0) +#endif + { + const char* err = av_err2str(ret); + WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret); + return FALSE; + } + } + +#if defined(SWRESAMPLE_FOUND) + if ((ret = swr_convert_frame(resampleContext, resampled, frame)) < 0) +#else + if ((ret = avresample_convert_frame(resampleContext, resampled, frame)) < 0) +#endif + { + const char* err = av_err2str(ret); + WLog_ERR(TAG, "Error during resampling %s [%d]", err, ret); + return FALSE; + } + + { +#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100) + const size_t channels = resampled->channels; +#else + const size_t channels = resampled->ch_layout.nb_channels; +#endif + const size_t data_size = channels * resampled->nb_samples * 2; + Stream_EnsureRemainingCapacity(out, data_size); + Stream_Write(out, resampled->data[0], data_size); + } + } + + return TRUE; +} + +BOOL freerdp_dsp_ffmpeg_supports_format(const AUDIO_FORMAT* format, BOOL encode) +{ + enum AVCodecID id = ffmpeg_get_avcodec(format); + + if (ffmpeg_codec_is_filtered(id, encode)) + return FALSE; + + if (encode) + return avcodec_find_encoder(id) != NULL; + else + return avcodec_find_decoder(id) != NULL; +} + +FREERDP_DSP_CONTEXT* freerdp_dsp_ffmpeg_context_new(BOOL encode) +{ + FREERDP_DSP_CONTEXT* context = NULL; +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100) + avcodec_register_all(); +#endif + context = calloc(1, sizeof(FREERDP_DSP_CONTEXT)); + + if (!context) + return NULL; + + context->channelmix = Stream_New(NULL, 1024); + if (!context->channelmix) + { + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + freerdp_dsp_ffmpeg_context_free(context); + WINPR_PRAGMA_DIAG_POP + return NULL; + } + context->encoder = encode; + return context; +} + +void freerdp_dsp_ffmpeg_context_free(FREERDP_DSP_CONTEXT* context) +{ + if (context) + { + ffmpeg_close_context(context); + Stream_Free(context->channelmix, TRUE); + free(context); + } +} + +BOOL freerdp_dsp_ffmpeg_context_reset(FREERDP_DSP_CONTEXT* context, + const AUDIO_FORMAT* targetFormat) +{ + if (!context || !targetFormat) + return FALSE; + + ffmpeg_close_context(context); + context->format = *targetFormat; + return ffmpeg_open_context(context); +} + +static BOOL freerdp_dsp_channel_mix(FREERDP_DSP_CONTEXT* context, const BYTE* src, size_t size, + const AUDIO_FORMAT* srcFormat, const BYTE** data, + size_t* length, AUDIO_FORMAT* dstFormat) +{ + UINT32 bpp = 0; + size_t samples = 0; + + if (!context || !data || !length || !dstFormat) + return FALSE; + + if (srcFormat->wFormatTag != WAVE_FORMAT_PCM) + return FALSE; + + bpp = srcFormat->wBitsPerSample > 8 ? 2 : 1; + samples = size / bpp / srcFormat->nChannels; + + *dstFormat = *srcFormat; + if (context->format.nChannels == srcFormat->nChannels) + { + *data = src; + *length = size; + return TRUE; + } + + Stream_SetPosition(context->channelmix, 0); + + /* Destination has more channels than source */ + if (context->format.nChannels > srcFormat->nChannels) + { + switch (srcFormat->nChannels) + { + case 1: + if (!Stream_EnsureCapacity(context->channelmix, size * 2)) + return FALSE; + + for (UINT32 x = 0; x < samples; x++) + { + for (UINT32 y = 0; y < bpp; y++) + Stream_Write_UINT8(context->channelmix, src[x * bpp + y]); + + for (UINT32 y = 0; y < bpp; y++) + Stream_Write_UINT8(context->channelmix, src[x * bpp + y]); + } + + Stream_SealLength(context->channelmix); + *data = Stream_Buffer(context->channelmix); + *length = Stream_Length(context->channelmix); + dstFormat->nChannels = 2; + return TRUE; + + case 2: /* We only support stereo, so we can not handle this case. */ + default: /* Unsupported number of channels */ + WLog_WARN(TAG, "unsupported source channel count %" PRIu16, srcFormat->nChannels); + return FALSE; + } + } + + /* Destination has less channels than source */ + switch (srcFormat->nChannels) + { + case 2: + if (!Stream_EnsureCapacity(context->channelmix, size / 2)) + return FALSE; + + /* Simply drop second channel. + * TODO: Calculate average */ + for (UINT32 x = 0; x < samples; x++) + { + for (UINT32 y = 0; y < bpp; y++) + Stream_Write_UINT8(context->channelmix, src[2 * x * bpp + y]); + } + + Stream_SealLength(context->channelmix); + *data = Stream_Buffer(context->channelmix); + *length = Stream_Length(context->channelmix); + dstFormat->nChannels = 1; + return TRUE; + + case 1: /* Invalid, do we want to use a 0 channel sound? */ + default: /* Unsupported number of channels */ + WLog_WARN(TAG, "unsupported channel count %" PRIu16, srcFormat->nChannels); + return FALSE; + } + + return FALSE; +} + +BOOL freerdp_dsp_ffmpeg_encode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* format, + const BYTE* data, size_t length, wStream* out) +{ + AUDIO_FORMAT fmt = { 0 }; + + if (!context || !format || !data || !out || !context->encoder) + return FALSE; + + if (!context || !data || !out) + return FALSE; + + /* https://github.com/FreeRDP/FreeRDP/issues/7607 + * + * we get noisy data with channel transformation, so do it ourselves. + */ + if (!freerdp_dsp_channel_mix(context, data, length, format, &data, &length, &fmt)) + return FALSE; + + /* Create input frame */ + if (!ffmpeg_fill_frame(context->frame, format, data, length)) + return FALSE; + + /* Resample to desired format. */ + if (!ffmpeg_resample_frame(context->rcontext, context->frame, context->resampled)) + return FALSE; + + if (context->context->frame_size <= 0) + { + return ffmpeg_encode_frame(context->context, context->resampled, context->packet, out); + } + else + { + int copied = 0; + int rest = context->resampled->nb_samples; + + do + { + int inSamples = rest; + + if ((inSamples < 0) || (context->bufferedSamples > (UINT32)(INT_MAX - inSamples))) + return FALSE; + + if (inSamples + (int)context->bufferedSamples > context->context->frame_size) + inSamples = context->context->frame_size - (int)context->bufferedSamples; + +#if LIBAVUTIL_VERSION_INT < AV_VERSION_INT(57, 28, 100) + const int channels = context->context->channels; +#else + const int channels = context->context->ch_layout.nb_channels; +#endif + const int rc = + av_samples_copy(context->buffered->extended_data, context->resampled->extended_data, + (int)context->bufferedSamples, copied, inSamples, channels, + context->context->sample_fmt); + if (rc < 0) + return FALSE; + rest -= inSamples; + copied += inSamples; + context->bufferedSamples += (UINT32)inSamples; + + if (context->context->frame_size <= (int)context->bufferedSamples) + { + /* Encode in desired format. */ + if (!ffmpeg_encode_frame(context->context, context->buffered, context->packet, out)) + return FALSE; + + context->bufferedSamples = 0; + } + } while (rest > 0); + + return TRUE; + } +} + +BOOL freerdp_dsp_ffmpeg_decode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* srcFormat, + const BYTE* data, size_t length, wStream* out) +{ + if (!context || !srcFormat || !data || !out || context->encoder) + return FALSE; + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100) + av_init_packet(context->packet); +#endif + context->packet->data = (uint8_t*)data; + context->packet->size = length; + return ffmpeg_decode(context->context, context->packet, context->frame, context->rcontext, + context->resampled, out); +} diff --git a/libfreerdp/codec/dsp_ffmpeg.h b/libfreerdp/codec/dsp_ffmpeg.h new file mode 100644 index 0000000..973e371 --- /dev/null +++ b/libfreerdp/codec/dsp_ffmpeg.h @@ -0,0 +1,48 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Digital Sound Processing - FFMPEG backend + * + * Copyright 2018 Armin Novak <armin.novak@thincast.com> + * Copyright 2018 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_LIB_CODEC_DSP_FFMPEG_H +#define FREERDP_LIB_CODEC_DSP_FFMPEG_H + +#include <freerdp/api.h> +#include <freerdp/codec/audio.h> +#include <freerdp/codec/dsp.h> + +#include <libavcodec/version.h> + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 48, 101) +#error \ + "DSP module requires libavcodec version >= 57.48.101. Upgrade or set WITH_DSP_FFMPEG=OFF to continue" +#endif + +void freerdp_dsp_ffmpeg_context_free(FREERDP_DSP_CONTEXT* context); + +WINPR_ATTR_MALLOC(freerdp_dsp_ffmpeg_context_free, 1) +FREERDP_DSP_CONTEXT* freerdp_dsp_ffmpeg_context_new(BOOL encode); +BOOL freerdp_dsp_ffmpeg_supports_format(const AUDIO_FORMAT* format, BOOL encode); +BOOL freerdp_dsp_ffmpeg_encode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* srcFormat, + const BYTE* data, size_t length, wStream* out); +BOOL freerdp_dsp_ffmpeg_decode(FREERDP_DSP_CONTEXT* context, const AUDIO_FORMAT* srcFormat, + const BYTE* data, size_t length, wStream* out); + +BOOL freerdp_dsp_ffmpeg_context_reset(FREERDP_DSP_CONTEXT* context, + const AUDIO_FORMAT* targetFormat); + +#endif /* FREERDP_LIB_CODEC_DSP_FFMPEG_H */ diff --git a/libfreerdp/codec/h264.c b/libfreerdp/codec/h264.c new file mode 100644 index 0000000..718bd2c --- /dev/null +++ b/libfreerdp/codec/h264.c @@ -0,0 +1,777 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * H.264 Bitmap Compression + * + * Copyright 2014 Mike McDonald <Mike.McDonald@software.dell.com> + * Copyright 2017 David Fort <contact@hardening-consulting.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <winpr/crt.h> +#include <winpr/print.h> +#include <winpr/library.h> +#include <winpr/bitstream.h> +#include <winpr/synch.h> + +#include <freerdp/primitives.h> +#include <freerdp/codec/h264.h> +#include <freerdp/codec/yuv.h> +#include <freerdp/log.h> + +#include "h264.h" + +#define TAG FREERDP_TAG("codec") + +static BOOL avc444_ensure_buffer(H264_CONTEXT* h264, DWORD nDstHeight); + +BOOL avc420_ensure_buffer(H264_CONTEXT* h264, UINT32 stride, UINT32 width, UINT32 height) +{ + BOOL isNull = FALSE; + UINT32 pheight = height; + + if (!h264) + return FALSE; + + if (stride == 0) + stride = width; + + if (stride % 16 != 0) + stride += 16 - stride % 16; + + if (pheight % 16 != 0) + pheight += 16 - pheight % 16; + + for (size_t x = 0; x < 3; x++) + { + if (!h264->pYUVData[x] || !h264->pOldYUVData[x]) + isNull = TRUE; + } + + if (pheight == 0) + return FALSE; + if (stride == 0) + return FALSE; + + if (isNull || (width != h264->width) || (height != h264->height) || + (stride != h264->iStride[0])) + { + h264->iStride[0] = stride; + h264->iStride[1] = (stride + 1) / 2; + h264->iStride[2] = (stride + 1) / 2; + h264->width = width; + h264->height = height; + + for (size_t x = 0; x < 3; x++) + { + BYTE* tmp1 = winpr_aligned_recalloc(h264->pYUVData[x], h264->iStride[x], pheight, 16); + BYTE* tmp2 = + winpr_aligned_recalloc(h264->pOldYUVData[x], h264->iStride[x], pheight, 16); + if (tmp1) + h264->pYUVData[x] = tmp1; + if (tmp2) + h264->pOldYUVData[x] = tmp2; + if (!tmp1 || !tmp2) + return FALSE; + } + } + + return TRUE; +} + +INT32 avc420_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize, BYTE* pDstData, + DWORD DstFormat, UINT32 nDstStep, UINT32 nDstWidth, UINT32 nDstHeight, + const RECTANGLE_16* regionRects, UINT32 numRegionRects) +{ + int status = 0; + const BYTE* pYUVData[3]; + + if (!h264 || h264->Compressor) + return -1001; + + status = h264->subsystem->Decompress(h264, pSrcData, SrcSize); + + if (status == 0) + return 1; + + if (status < 0) + return status; + + pYUVData[0] = h264->pYUVData[0]; + pYUVData[1] = h264->pYUVData[1]; + pYUVData[2] = h264->pYUVData[2]; + if (!yuv420_context_decode(h264->yuv, pYUVData, h264->iStride, h264->height, DstFormat, + pDstData, nDstStep, regionRects, numRegionRects)) + return -1002; + + return 1; +} + +static BOOL allocate_h264_metablock(UINT32 QP, RECTANGLE_16* rectangles, + RDPGFX_H264_METABLOCK* meta, size_t count) +{ + /* [MS-RDPEGFX] 2.2.4.4.2 RDPGFX_AVC420_QUANT_QUALITY */ + if (!meta || (QP > UINT8_MAX)) + { + free(rectangles); + return FALSE; + } + + meta->regionRects = rectangles; + if (count == 0) + return TRUE; + + if (count > UINT32_MAX) + return FALSE; + + meta->quantQualityVals = calloc(count, sizeof(RDPGFX_H264_QUANT_QUALITY)); + + if (!meta->quantQualityVals || !meta->regionRects) + return FALSE; + meta->numRegionRects = (UINT32)count; + for (size_t x = 0; x < count; x++) + { + RDPGFX_H264_QUANT_QUALITY* cur = &meta->quantQualityVals[x]; + cur->qp = (UINT8)QP; + + /* qpVal bit 6 and 7 are flags, so mask them out here. + * qualityVal is [0-100] so 100 - qpVal [0-64] is always in range */ + cur->qualityVal = 100 - (QP & 0x3F); + } + return TRUE; +} + +static INLINE BOOL diff_tile(const RECTANGLE_16* regionRect, BYTE* pYUVData[3], + BYTE* pOldYUVData[3], UINT32 const iStride[3]) +{ + size_t size = 0; + + if (!regionRect || !pYUVData || !pOldYUVData || !iStride) + return FALSE; + size = regionRect->right - regionRect->left; + if (regionRect->right > iStride[0]) + return FALSE; + if (regionRect->right / 2u > iStride[1]) + return FALSE; + if (regionRect->right / 2u > iStride[2]) + return FALSE; + + for (UINT16 y = regionRect->top; y < regionRect->bottom; y++) + { + const BYTE* cur0 = &pYUVData[0][y * iStride[0]]; + const BYTE* cur1 = &pYUVData[1][y * iStride[1]]; + const BYTE* cur2 = &pYUVData[2][y * iStride[2]]; + const BYTE* old0 = &pOldYUVData[0][y * iStride[0]]; + const BYTE* old1 = &pOldYUVData[1][y * iStride[1]]; + const BYTE* old2 = &pOldYUVData[2][y * iStride[2]]; + + if (memcmp(&cur0[regionRect->left], &old0[regionRect->left], size) != 0) + return TRUE; + if (memcmp(&cur1[regionRect->left / 2], &old1[regionRect->left / 2], size / 2) != 0) + return TRUE; + if (memcmp(&cur2[regionRect->left / 2], &old2[regionRect->left / 2], size / 2) != 0) + return TRUE; + } + return FALSE; +} + +static BOOL detect_changes(BOOL firstFrameDone, const UINT32 QP, const RECTANGLE_16* regionRect, + BYTE* pYUVData[3], BYTE* pOldYUVData[3], UINT32 const iStride[3], + RDPGFX_H264_METABLOCK* meta) +{ + size_t count = 0; + size_t wc = 0; + size_t hc = 0; + RECTANGLE_16* rectangles = NULL; + + if (!regionRect || !pYUVData || !pOldYUVData || !iStride || !meta) + return FALSE; + + wc = (regionRect->right - regionRect->left) / 64 + 1; + hc = (regionRect->bottom - regionRect->top) / 64 + 1; + rectangles = calloc(wc * hc, sizeof(RECTANGLE_16)); + if (!rectangles) + return FALSE; + if (!firstFrameDone) + { + rectangles[0] = *regionRect; + count = 1; + } + else + { + for (size_t y = regionRect->top; y < regionRect->bottom; y += 64) + { + for (size_t x = regionRect->left; x < regionRect->right; x += 64) + { + RECTANGLE_16 rect; + rect.left = (UINT16)MIN(UINT16_MAX, regionRect->left + x); + rect.top = (UINT16)MIN(UINT16_MAX, regionRect->top + y); + rect.right = + (UINT16)MIN(UINT16_MAX, MIN(regionRect->left + x + 64, regionRect->right)); + rect.bottom = + (UINT16)MIN(UINT16_MAX, MIN(regionRect->top + y + 64, regionRect->bottom)); + if (diff_tile(&rect, pYUVData, pOldYUVData, iStride)) + rectangles[count++] = rect; + } + } + } + if (!allocate_h264_metablock(QP, rectangles, meta, count)) + return FALSE; + return TRUE; +} + +INT32 avc420_compress(H264_CONTEXT* h264, const BYTE* pSrcData, DWORD SrcFormat, UINT32 nSrcStep, + UINT32 nSrcWidth, UINT32 nSrcHeight, const RECTANGLE_16* regionRect, + BYTE** ppDstData, UINT32* pDstSize, RDPGFX_H264_METABLOCK* meta) +{ + INT32 rc = -1; + BYTE* pYUVData[3] = { 0 }; + const BYTE* pcYUVData[3] = { 0 }; + BYTE* pOldYUVData[3] = { 0 }; + + if (!h264 || !regionRect || !meta || !h264->Compressor) + return -1; + + if (!h264->subsystem->Compress) + return -1; + + if (!avc420_ensure_buffer(h264, nSrcStep, nSrcWidth, nSrcHeight)) + return -1; + + if (h264->encodingBuffer) + { + for (size_t x = 0; x < 3; x++) + { + pYUVData[x] = h264->pYUVData[x]; + pOldYUVData[x] = h264->pOldYUVData[x]; + } + } + else + { + for (size_t x = 0; x < 3; x++) + { + pYUVData[x] = h264->pOldYUVData[x]; + pOldYUVData[x] = h264->pYUVData[x]; + } + } + h264->encodingBuffer = !h264->encodingBuffer; + + if (!yuv420_context_encode(h264->yuv, pSrcData, nSrcStep, SrcFormat, h264->iStride, pYUVData, + regionRect, 1)) + goto fail; + + if (!detect_changes(h264->firstLumaFrameDone, h264->QP, regionRect, pYUVData, pOldYUVData, + h264->iStride, meta)) + goto fail; + + if (meta->numRegionRects == 0) + { + rc = 0; + goto fail; + } + + for (size_t x = 0; x < 3; x++) + pcYUVData[x] = pYUVData[x]; + + rc = h264->subsystem->Compress(h264, pcYUVData, h264->iStride, ppDstData, pDstSize); + if (rc >= 0) + h264->firstLumaFrameDone = TRUE; + +fail: + if (rc < 0) + free_h264_metablock(meta); + return rc; +} + +INT32 avc444_compress(H264_CONTEXT* h264, const BYTE* pSrcData, DWORD SrcFormat, UINT32 nSrcStep, + UINT32 nSrcWidth, UINT32 nSrcHeight, BYTE version, const RECTANGLE_16* region, + BYTE* op, BYTE** ppDstData, UINT32* pDstSize, BYTE** ppAuxDstData, + UINT32* pAuxDstSize, RDPGFX_H264_METABLOCK* meta, + RDPGFX_H264_METABLOCK* auxMeta) +{ + int rc = -1; + BYTE* coded = NULL; + UINT32 codedSize = 0; + BYTE** pYUV444Data = NULL; + BYTE** pOldYUV444Data = NULL; + BYTE** pYUVData = NULL; + BYTE** pOldYUVData = NULL; + + if (!h264 || !h264->Compressor) + return -1; + + if (!h264->subsystem->Compress) + return -1; + + if (!avc420_ensure_buffer(h264, nSrcStep, nSrcWidth, nSrcHeight)) + return -1; + + if (!avc444_ensure_buffer(h264, nSrcHeight)) + return -1; + + if (h264->encodingBuffer) + { + pYUV444Data = h264->pOldYUV444Data; + pOldYUV444Data = h264->pYUV444Data; + pYUVData = h264->pOldYUVData; + pOldYUVData = h264->pYUVData; + } + else + { + pYUV444Data = h264->pYUV444Data; + pOldYUV444Data = h264->pOldYUV444Data; + pYUVData = h264->pYUVData; + pOldYUVData = h264->pOldYUVData; + } + h264->encodingBuffer = !h264->encodingBuffer; + + if (!yuv444_context_encode(h264->yuv, version, pSrcData, nSrcStep, SrcFormat, h264->iStride, + pYUV444Data, pYUVData, region, 1)) + goto fail; + + if (!detect_changes(h264->firstLumaFrameDone, h264->QP, region, pYUV444Data, pOldYUV444Data, + h264->iStride, meta)) + goto fail; + if (!detect_changes(h264->firstChromaFrameDone, h264->QP, region, pYUVData, pOldYUVData, + h264->iStride, auxMeta)) + goto fail; + + /* [MS-RDPEGFX] 2.2.4.5 RFX_AVC444_BITMAP_STREAM + * LC: + * 0 ... Luma & Chroma + * 1 ... Luma + * 2 ... Chroma + */ + if ((meta->numRegionRects > 0) && (auxMeta->numRegionRects > 0)) + *op = 0; + else if (meta->numRegionRects > 0) + *op = 1; + else if (auxMeta->numRegionRects > 0) + *op = 2; + else + { + WLog_INFO(TAG, "no changes detected for luma or chroma frame"); + rc = 0; + goto fail; + } + + if ((*op == 0) || (*op == 1)) + { + const BYTE* pcYUV444Data[3] = { pYUV444Data[0], pYUV444Data[1], pYUV444Data[2] }; + + if (h264->subsystem->Compress(h264, pcYUV444Data, h264->iStride, &coded, &codedSize) < 0) + goto fail; + h264->firstLumaFrameDone = TRUE; + memcpy(h264->lumaData, coded, codedSize); + *ppDstData = h264->lumaData; + *pDstSize = codedSize; + } + + if ((*op == 0) || (*op == 2)) + { + const BYTE* pcYUVData[3] = { pYUVData[0], pYUVData[1], pYUVData[2] }; + + if (h264->subsystem->Compress(h264, pcYUVData, h264->iStride, &coded, &codedSize) < 0) + goto fail; + h264->firstChromaFrameDone = TRUE; + *ppAuxDstData = coded; + *pAuxDstSize = codedSize; + } + + rc = 1; +fail: + if (rc < 0) + { + free_h264_metablock(meta); + free_h264_metablock(auxMeta); + } + return rc; +} + +static BOOL avc444_ensure_buffer(H264_CONTEXT* h264, DWORD nDstHeight) +{ + WINPR_ASSERT(h264); + + const UINT32* piMainStride = h264->iStride; + UINT32* piDstSize = h264->iYUV444Size; + UINT32* piDstStride = h264->iYUV444Stride; + BYTE** ppYUVDstData = h264->pYUV444Data; + BYTE** ppOldYUVDstData = h264->pOldYUV444Data; + + nDstHeight = MAX(h264->height, nDstHeight); + const UINT32 pad = nDstHeight % 16; + UINT32 padDstHeight = nDstHeight; /* Need alignment to 16x16 blocks */ + + if (pad != 0) + padDstHeight += 16 - pad; + + if ((piMainStride[0] != piDstStride[0]) || + (piDstSize[0] != 1ull * piMainStride[0] * padDstHeight)) + { + for (UINT32 x = 0; x < 3; x++) + { + piDstStride[x] = piMainStride[0]; + piDstSize[x] = piDstStride[x] * padDstHeight; + + if (piDstSize[x] == 0) + return FALSE; + + BYTE* tmp1 = winpr_aligned_recalloc(ppYUVDstData[x], piDstSize[x], 1, 16); + if (!tmp1) + return FALSE; + ppYUVDstData[x] = tmp1; + BYTE* tmp2 = winpr_aligned_recalloc(ppOldYUVDstData[x], piDstSize[x], 1, 16); + if (!tmp2) + return FALSE; + ppOldYUVDstData[x] = tmp2; + } + + { + BYTE* tmp = winpr_aligned_recalloc(h264->lumaData, piDstSize[0], 4, 16); + if (!tmp) + goto fail; + h264->lumaData = tmp; + } + } + + for (UINT32 x = 0; x < 3; x++) + { + if (!ppOldYUVDstData[x] || !ppYUVDstData[x] || (piDstSize[x] == 0) || (piDstStride[x] == 0)) + { + WLog_Print(h264->log, WLOG_ERROR, + "YUV buffer not initialized! check your decoder settings"); + goto fail; + } + } + + if (!h264->lumaData) + goto fail; + + return TRUE; +fail: + return FALSE; +} + +static BOOL avc444_process_rects(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize, + BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep, + UINT32 nDstWidth, UINT32 nDstHeight, const RECTANGLE_16* rects, + UINT32 nrRects, avc444_frame_type type) +{ + const BYTE* pYUVData[3]; + BYTE* pYUVDstData[3]; + UINT32* piDstStride = h264->iYUV444Stride; + BYTE** ppYUVDstData = h264->pYUV444Data; + const UINT32* piStride = h264->iStride; + + if (h264->subsystem->Decompress(h264, pSrcData, SrcSize) < 0) + return FALSE; + + pYUVData[0] = h264->pYUVData[0]; + pYUVData[1] = h264->pYUVData[1]; + pYUVData[2] = h264->pYUVData[2]; + if (!avc444_ensure_buffer(h264, nDstHeight)) + return FALSE; + + pYUVDstData[0] = ppYUVDstData[0]; + pYUVDstData[1] = ppYUVDstData[1]; + pYUVDstData[2] = ppYUVDstData[2]; + if (!yuv444_context_decode(h264->yuv, (BYTE)type, pYUVData, piStride, h264->height, pYUVDstData, + piDstStride, DstFormat, pDstData, nDstStep, rects, nrRects)) + return FALSE; + + return TRUE; +} + +#if defined(AVC444_FRAME_STAT) +static UINT64 op1 = 0; +static double op1sum = 0; +static UINT64 op2 = 0; +static double op2sum = 0; +static UINT64 op3 = 0; +static double op3sum = 0; +static double avg(UINT64* count, double old, double size) +{ + double tmp = size + *count * old; + (*count)++; + tmp = tmp / *count; + return tmp; +} +#endif + +INT32 avc444_decompress(H264_CONTEXT* h264, BYTE op, const RECTANGLE_16* regionRects, + UINT32 numRegionRects, const BYTE* pSrcData, UINT32 SrcSize, + const RECTANGLE_16* auxRegionRects, UINT32 numAuxRegionRect, + const BYTE* pAuxSrcData, UINT32 AuxSrcSize, BYTE* pDstData, DWORD DstFormat, + UINT32 nDstStep, UINT32 nDstWidth, UINT32 nDstHeight, UINT32 codecId) +{ + INT32 status = -1; + avc444_frame_type chroma = + (codecId == RDPGFX_CODECID_AVC444) ? AVC444_CHROMAv1 : AVC444_CHROMAv2; + + if (!h264 || !regionRects || !pSrcData || !pDstData || h264->Compressor) + return -1001; + + switch (op) + { + case 0: /* YUV420 in stream 1 + * Chroma420 in stream 2 */ + if (!avc444_process_rects(h264, pSrcData, SrcSize, pDstData, DstFormat, nDstStep, + nDstWidth, nDstHeight, regionRects, numRegionRects, + AVC444_LUMA)) + status = -1; + else if (!avc444_process_rects(h264, pAuxSrcData, AuxSrcSize, pDstData, DstFormat, + nDstStep, nDstWidth, nDstHeight, auxRegionRects, + numAuxRegionRect, chroma)) + status = -1; + else + status = 0; + + break; + + case 2: /* Chroma420 in stream 1 */ + if (!avc444_process_rects(h264, pSrcData, SrcSize, pDstData, DstFormat, nDstStep, + nDstWidth, nDstHeight, regionRects, numRegionRects, chroma)) + status = -1; + else + status = 0; + + break; + + case 1: /* YUV420 in stream 1 */ + if (!avc444_process_rects(h264, pSrcData, SrcSize, pDstData, DstFormat, nDstStep, + nDstWidth, nDstHeight, regionRects, numRegionRects, + AVC444_LUMA)) + status = -1; + else + status = 0; + + break; + + default: /* WTF? */ + break; + } + +#if defined(AVC444_FRAME_STAT) + + switch (op) + { + case 0: + op1sum = avg(&op1, op1sum, SrcSize + AuxSrcSize); + break; + + case 1: + op2sum = avg(&op2, op2sum, SrcSize); + break; + + case 2: + op3sum = avg(&op3, op3sum, SrcSize); + break; + + default: + break; + } + + WLog_Print(h264->log, WLOG_INFO, + "luma=%" PRIu64 " [avg=%lf] chroma=%" PRIu64 " [avg=%lf] combined=%" PRIu64 + " [avg=%lf]", + op1, op1sum, op2, op2sum, op3, op3sum); +#endif + return status; +} + +#define MAX_SUBSYSTEMS 10 +static INIT_ONCE subsystems_once = INIT_ONCE_STATIC_INIT; +static const H264_CONTEXT_SUBSYSTEM* subSystems[MAX_SUBSYSTEMS] = { 0 }; + +static BOOL CALLBACK h264_register_subsystems(PINIT_ONCE once, PVOID param, PVOID* context) +{ + int i = 0; + +#ifdef WITH_MEDIACODEC + { + subSystems[i] = &g_Subsystem_mediacodec; + i++; + } +#endif +#if defined(_WIN32) && defined(WITH_MEDIA_FOUNDATION) + { + subSystems[i] = &g_Subsystem_MF; + i++; + } +#endif +#ifdef WITH_OPENH264 + { + subSystems[i] = &g_Subsystem_OpenH264; + i++; + } +#endif +#ifdef WITH_VIDEO_FFMPEG + { + subSystems[i] = &g_Subsystem_libavcodec; + i++; + } +#endif + return i > 0; +} + +static BOOL h264_context_init(H264_CONTEXT* h264) +{ + if (!h264) + return FALSE; + + h264->log = WLog_Get(TAG); + + if (!h264->log) + return FALSE; + + h264->subsystem = NULL; + InitOnceExecuteOnce(&subsystems_once, h264_register_subsystems, NULL, NULL); + + for (size_t i = 0; i < MAX_SUBSYSTEMS; i++) + { + const H264_CONTEXT_SUBSYSTEM* subsystem = subSystems[i]; + + if (!subsystem || !subsystem->Init) + break; + + if (subsystem->Init(h264)) + { + h264->subsystem = subsystem; + return TRUE; + } + } + + return FALSE; +} + +BOOL h264_context_reset(H264_CONTEXT* h264, UINT32 width, UINT32 height) +{ + if (!h264) + return FALSE; + + h264->width = width; + h264->height = height; + return yuv_context_reset(h264->yuv, width, height); +} + +H264_CONTEXT* h264_context_new(BOOL Compressor) +{ + H264_CONTEXT* h264 = (H264_CONTEXT*)calloc(1, sizeof(H264_CONTEXT)); + if (!h264) + return NULL; + + h264->Compressor = Compressor; + if (Compressor) + + { + /* Default compressor settings, may be changed by caller */ + h264->BitRate = 1000000; + h264->FrameRate = 30; + } + + if (!h264_context_init(h264)) + goto fail; + + h264->yuv = yuv_context_new(Compressor, 0); + if (!h264->yuv) + goto fail; + + return h264; + +fail: + WINPR_PRAGMA_DIAG_PUSH + WINPR_PRAGMA_DIAG_IGNORED_MISMATCHED_DEALLOC + h264_context_free(h264); + WINPR_PRAGMA_DIAG_POP + return NULL; +} + +void h264_context_free(H264_CONTEXT* h264) +{ + if (h264) + { + if (h264->subsystem) + h264->subsystem->Uninit(h264); + + for (size_t x = 0; x < 3; x++) + { + if (h264->Compressor) + { + winpr_aligned_free(h264->pYUVData[x]); + winpr_aligned_free(h264->pOldYUVData[x]); + } + winpr_aligned_free(h264->pYUV444Data[x]); + winpr_aligned_free(h264->pOldYUV444Data[x]); + } + winpr_aligned_free(h264->lumaData); + + yuv_context_free(h264->yuv); + free(h264); + } +} + +void free_h264_metablock(RDPGFX_H264_METABLOCK* meta) +{ + RDPGFX_H264_METABLOCK m = { 0 }; + if (!meta) + return; + free(meta->quantQualityVals); + free(meta->regionRects); + *meta = m; +} + +BOOL h264_context_set_option(H264_CONTEXT* h264, H264_CONTEXT_OPTION option, UINT32 value) +{ + WINPR_ASSERT(h264); + switch (option) + { + case H264_CONTEXT_OPTION_BITRATE: + h264->BitRate = value; + return TRUE; + case H264_CONTEXT_OPTION_FRAMERATE: + h264->FrameRate = value; + return TRUE; + case H264_CONTEXT_OPTION_RATECONTROL: + h264->RateControlMode = value; + return TRUE; + case H264_CONTEXT_OPTION_QP: + h264->QP = value; + return TRUE; + default: + WLog_Print(h264->log, WLOG_WARN, "Unknown H264_CONTEXT_OPTION[0x%08" PRIx32 "]", + option); + return FALSE; + } +} + +UINT32 h264_context_get_option(H264_CONTEXT* h264, H264_CONTEXT_OPTION option) +{ + WINPR_ASSERT(h264); + switch (option) + { + case H264_CONTEXT_OPTION_BITRATE: + return h264->BitRate; + case H264_CONTEXT_OPTION_FRAMERATE: + return h264->FrameRate; + case H264_CONTEXT_OPTION_RATECONTROL: + return h264->RateControlMode; + case H264_CONTEXT_OPTION_QP: + return h264->QP; + default: + WLog_Print(h264->log, WLOG_WARN, "Unknown H264_CONTEXT_OPTION[0x%08" PRIx32 "]", + option); + return 0; + } +} diff --git a/libfreerdp/codec/h264.h b/libfreerdp/codec/h264.h new file mode 100644 index 0000000..64bda53 --- /dev/null +++ b/libfreerdp/codec/h264.h @@ -0,0 +1,106 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX Codec Library - Decode + * + * Copyright 2018 Armin Novak <anovak@thincast.com> + * Copyright 2018 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_LIB_CODEC_H264_H +#define FREERDP_LIB_CODEC_H264_H + +#include <freerdp/api.h> +#include <freerdp/config.h> +#include <freerdp/codec/h264.h> + +#ifdef __cplusplus +extern "C" +{ +#endif + + typedef BOOL (*pfnH264SubsystemInit)(H264_CONTEXT* h264); + typedef void (*pfnH264SubsystemUninit)(H264_CONTEXT* h264); + + typedef int (*pfnH264SubsystemDecompress)(H264_CONTEXT* h264, const BYTE* pSrcData, + UINT32 SrcSize); + typedef int (*pfnH264SubsystemCompress)(H264_CONTEXT* h264, const BYTE** pSrcYuv, + const UINT32* pStride, BYTE** ppDstData, + UINT32* pDstSize); + + struct S_H264_CONTEXT_SUBSYSTEM + { + const char* name; + pfnH264SubsystemInit Init; + pfnH264SubsystemUninit Uninit; + pfnH264SubsystemDecompress Decompress; + pfnH264SubsystemCompress Compress; + }; + + struct S_H264_CONTEXT + { + BOOL Compressor; + + UINT32 width; + UINT32 height; + + H264_RATECONTROL_MODE RateControlMode; + UINT32 BitRate; + UINT32 FrameRate; + UINT32 QP; + UINT32 NumberOfThreads; + + UINT32 iStride[3]; + BYTE* pOldYUVData[3]; + BYTE* pYUVData[3]; + + UINT32 iYUV444Size[3]; + UINT32 iYUV444Stride[3]; + BYTE* pOldYUV444Data[3]; + BYTE* pYUV444Data[3]; + + UINT32 numSystemData; + void* pSystemData; + const H264_CONTEXT_SUBSYSTEM* subsystem; + YUV_CONTEXT* yuv; + + BOOL encodingBuffer; + BOOL firstLumaFrameDone; + BOOL firstChromaFrameDone; + + void* lumaData; + wLog* log; + }; + + FREERDP_LOCAL BOOL avc420_ensure_buffer(H264_CONTEXT* h264, UINT32 stride, UINT32 width, + UINT32 height); + +#ifdef WITH_MEDIACODEC + extern const H264_CONTEXT_SUBSYSTEM g_Subsystem_mediacodec; +#endif +#if defined(_WIN32) && defined(WITH_MEDIA_FOUNDATION) + extern const H264_CONTEXT_SUBSYSTEM g_Subsystem_MF; +#endif +#ifdef WITH_OPENH264 + extern const H264_CONTEXT_SUBSYSTEM g_Subsystem_OpenH264; +#endif +#ifdef WITH_VIDEO_FFMPEG + extern const H264_CONTEXT_SUBSYSTEM g_Subsystem_libavcodec; +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_LIB_CODEC_H264_H */ diff --git a/libfreerdp/codec/h264_ffmpeg.c b/libfreerdp/codec/h264_ffmpeg.c new file mode 100644 index 0000000..54492e1 --- /dev/null +++ b/libfreerdp/codec/h264_ffmpeg.c @@ -0,0 +1,697 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * H.264 Bitmap Compression + * + * Copyright 2015 Marc-André Moreau <marcandre.moreau@gmail.com> + * Copyright 2014 Mike McDonald <Mike.McDonald@software.dell.com> + * Copyright 2014 erbth <t.erbesdobler@team103.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include <freerdp/config.h> + +#include <winpr/wlog.h> +#include <freerdp/log.h> +#include <freerdp/codec/h264.h> +#include <libavcodec/avcodec.h> +#include <libavutil/opt.h> + +#include "h264.h" + +#ifdef WITH_VAAPI +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(55, 9, 0) +#include <libavutil/hwcontext.h> +#else +#pragma warning You have asked for VA - API decoding, \ + but your version of libavutil is too old !Disabling. +#undef WITH_VAAPI +#endif +#endif + +/* Fallback support for older libavcodec versions */ +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(54, 59, 100) +#define AV_CODEC_ID_H264 CODEC_ID_H264 +#endif + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(56, 34, 2) +#define AV_CODEC_FLAG_LOOP_FILTER CODEC_FLAG_LOOP_FILTER +#define AV_CODEC_CAP_TRUNCATED CODEC_CAP_TRUNCATED +#define AV_CODEC_FLAG_TRUNCATED CODEC_FLAG_TRUNCATED +#endif + +#if LIBAVUTIL_VERSION_MAJOR < 52 +#define AV_PIX_FMT_YUV420P PIX_FMT_YUV420P +#endif + +/* Ubuntu 14.04 ships without the functions provided by avutil, + * so define error to string methods here. */ +#if !defined(av_err2str) +static inline char* error_string(char* errbuf, size_t errbuf_size, int errnum) +{ + av_strerror(errnum, errbuf, errbuf_size); + return errbuf; +} + +#define av_err2str(errnum) error_string((char[64]){ 0 }, 64, errnum) +#endif + +#ifdef WITH_VAAPI +#define VAAPI_DEVICE "/dev/dri/renderD128" +#endif + +typedef struct +{ + const AVCodec* codecDecoder; + AVCodecContext* codecDecoderContext; + const AVCodec* codecEncoder; + AVCodecContext* codecEncoderContext; + AVCodecParserContext* codecParser; + AVFrame* videoFrame; +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100) + AVPacket bufferpacket; +#endif + AVPacket* packet; +#ifdef WITH_VAAPI + AVBufferRef* hwctx; + AVFrame* hwVideoFrame; + enum AVPixelFormat hw_pix_fmt; +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 80, 100) + AVBufferRef* hw_frames_ctx; +#endif +#endif +} H264_CONTEXT_LIBAVCODEC; + +static void libavcodec_destroy_encoder(H264_CONTEXT* h264) +{ + H264_CONTEXT_LIBAVCODEC* sys = NULL; + + if (!h264 || !h264->subsystem) + return; + + sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData; + + if (sys->codecEncoderContext) + { + avcodec_close(sys->codecEncoderContext); +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 69, 100) + avcodec_free_context(&sys->codecEncoderContext); +#else + av_free(sys->codecEncoderContext); +#endif + } + + sys->codecEncoder = NULL; + sys->codecEncoderContext = NULL; +} + +static BOOL libavcodec_create_encoder(H264_CONTEXT* h264) +{ + BOOL recreate = FALSE; + H264_CONTEXT_LIBAVCODEC* sys = NULL; + + if (!h264 || !h264->subsystem) + return FALSE; + + if ((h264->width > INT_MAX) || (h264->height > INT_MAX)) + return FALSE; + + sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData; + if (!sys) + return FALSE; + recreate = !sys->codecEncoder || !sys->codecEncoderContext; + + if (sys->codecEncoderContext) + { + if ((sys->codecEncoderContext->width != (int)h264->width) || + (sys->codecEncoderContext->height != (int)h264->height)) + recreate = TRUE; + } + + if (!recreate) + return TRUE; + + libavcodec_destroy_encoder(h264); + sys->codecEncoder = avcodec_find_encoder(AV_CODEC_ID_H264); + + if (!sys->codecEncoder) + goto EXCEPTION; + + sys->codecEncoderContext = avcodec_alloc_context3(sys->codecEncoder); + + if (!sys->codecEncoderContext) + goto EXCEPTION; + + switch (h264->RateControlMode) + { + case H264_RATECONTROL_VBR: + sys->codecEncoderContext->bit_rate = h264->BitRate; + break; + + case H264_RATECONTROL_CQP: + /* TODO: sys->codecEncoderContext-> = h264->QP; */ + break; + + default: + break; + } + + sys->codecEncoderContext->width = (int)MIN(INT32_MAX, h264->width); + sys->codecEncoderContext->height = (int)MIN(INT32_MAX, h264->height); + sys->codecEncoderContext->delay = 0; +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(56, 13, 100) + sys->codecEncoderContext->framerate = (AVRational){ h264->FrameRate, 1 }; +#endif + sys->codecEncoderContext->time_base = (AVRational){ 1, h264->FrameRate }; + av_opt_set(sys->codecEncoderContext, "preset", "medium", AV_OPT_SEARCH_CHILDREN); + av_opt_set(sys->codecEncoderContext, "tune", "zerolatency", AV_OPT_SEARCH_CHILDREN); + sys->codecEncoderContext->flags |= AV_CODEC_FLAG_LOOP_FILTER; + sys->codecEncoderContext->pix_fmt = AV_PIX_FMT_YUV420P; + + if (avcodec_open2(sys->codecEncoderContext, sys->codecEncoder, NULL) < 0) + goto EXCEPTION; + + return TRUE; +EXCEPTION: + libavcodec_destroy_encoder(h264); + return FALSE; +} + +static int libavcodec_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize) +{ + union + { + const BYTE* cpv; + BYTE* pv; + } cnv; + int rc = -1; + int status = 0; + int gotFrame = 0; + AVPacket* packet = NULL; + + WINPR_ASSERT(h264); + WINPR_ASSERT(pSrcData || (SrcSize == 0)); + + H264_CONTEXT_LIBAVCODEC* sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData; + BYTE** pYUVData = h264->pYUVData; + UINT32* iStride = h264->iStride; + + WINPR_ASSERT(sys); + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100) + packet = &sys->bufferpacket; + WINPR_ASSERT(packet); + av_init_packet(packet); +#else + packet = av_packet_alloc(); +#endif + if (!packet) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to allocate AVPacket"); + goto fail; + } + + cnv.cpv = pSrcData; + packet->data = cnv.pv; + packet->size = (int)MIN(SrcSize, INT32_MAX); + + WINPR_ASSERT(sys->codecDecoderContext); + /* avcodec_decode_video2 is deprecated with libavcodec 57.48.101 */ +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101) + status = avcodec_send_packet(sys->codecDecoderContext, packet); + + if (status < 0) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to decode video frame (status=%d)", status); + goto fail; + } + + sys->videoFrame->format = AV_PIX_FMT_YUV420P; + + do + { +#ifdef WITH_VAAPI + status = avcodec_receive_frame(sys->codecDecoderContext, + sys->hwctx ? sys->hwVideoFrame : sys->videoFrame); +#else + status = avcodec_receive_frame(sys->codecDecoderContext, sys->videoFrame); +#endif + } while (status == AVERROR(EAGAIN)); + + gotFrame = (status == 0); +#else +#ifdef WITH_VAAPI + status = + avcodec_decode_video2(sys->codecDecoderContext, + sys->hwctx ? sys->hwVideoFrame : sys->videoFrame, &gotFrame, packet); +#else + status = avcodec_decode_video2(sys->codecDecoderContext, sys->videoFrame, &gotFrame, packet); +#endif +#endif + if (status < 0) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to decode video frame (status=%d)", status); + goto fail; + } + +#ifdef WITH_VAAPI + + if (sys->hwctx) + { + if (sys->hwVideoFrame->format == sys->hw_pix_fmt) + { + sys->videoFrame->width = sys->hwVideoFrame->width; + sys->videoFrame->height = sys->hwVideoFrame->height; + status = av_hwframe_transfer_data(sys->videoFrame, sys->hwVideoFrame, 0); + } + else + { + status = av_frame_copy(sys->videoFrame, sys->hwVideoFrame); + } + } + + gotFrame = (status == 0); + + if (status < 0) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to transfer video frame (status=%d) (%s)", status, + av_err2str(status)); + goto fail; + } + +#endif +#if 0 + WLog_Print(h264->log, WLOG_INFO, + "libavcodec_decompress: frame decoded (status=%d, gotFrame=%d, width=%d, height=%d, Y=[%p,%d], U=[%p,%d], V=[%p,%d])", + status, gotFrame, sys->videoFrame->width, sys->videoFrame->height, + (void*) sys->videoFrame->data[0], sys->videoFrame->linesize[0], + (void*) sys->videoFrame->data[1], sys->videoFrame->linesize[1], + (void*) sys->videoFrame->data[2], sys->videoFrame->linesize[2]); +#endif + + if (gotFrame) + { + WINPR_ASSERT(sys->videoFrame); + + pYUVData[0] = sys->videoFrame->data[0]; + pYUVData[1] = sys->videoFrame->data[1]; + pYUVData[2] = sys->videoFrame->data[2]; + iStride[0] = (UINT32)MAX(0, sys->videoFrame->linesize[0]); + iStride[1] = (UINT32)MAX(0, sys->videoFrame->linesize[1]); + iStride[2] = (UINT32)MAX(0, sys->videoFrame->linesize[2]); + + rc = 1; + } + else + rc = -2; + +fail: +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100) + av_packet_unref(packet); +#else + av_packet_free(&packet); +#endif + + return rc; +} + +static int libavcodec_compress(H264_CONTEXT* h264, const BYTE** pSrcYuv, const UINT32* pStride, + BYTE** ppDstData, UINT32* pDstSize) +{ + union + { + const BYTE* cpv; + uint8_t* pv; + } cnv; + int rc = -1; + int status = 0; + int gotFrame = 0; + + WINPR_ASSERT(h264); + + H264_CONTEXT_LIBAVCODEC* sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData; + WINPR_ASSERT(sys); + + if (!libavcodec_create_encoder(h264)) + return -1; + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100) + sys->packet = &sys->bufferpacket; + av_packet_unref(sys->packet); + av_init_packet(sys->packet); +#else + av_packet_free(&sys->packet); + sys->packet = av_packet_alloc(); +#endif + if (!sys->packet) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to allocate AVPacket"); + goto fail; + } + + WINPR_ASSERT(sys->packet); + sys->packet->data = NULL; + sys->packet->size = 0; + + WINPR_ASSERT(sys->videoFrame); + WINPR_ASSERT(sys->codecEncoderContext); + sys->videoFrame->format = sys->codecEncoderContext->pix_fmt; + sys->videoFrame->width = sys->codecEncoderContext->width; + sys->videoFrame->height = sys->codecEncoderContext->height; +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(52, 48, 100) + sys->videoFrame->colorspace = AVCOL_SPC_BT709; +#endif +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(52, 92, 100) + sys->videoFrame->chroma_location = AVCHROMA_LOC_LEFT; +#endif + cnv.cpv = pSrcYuv[0]; + sys->videoFrame->data[0] = cnv.pv; + + cnv.cpv = pSrcYuv[1]; + sys->videoFrame->data[1] = cnv.pv; + + cnv.cpv = pSrcYuv[2]; + sys->videoFrame->data[2] = cnv.pv; + + sys->videoFrame->linesize[0] = (int)pStride[0]; + sys->videoFrame->linesize[1] = (int)pStride[1]; + sys->videoFrame->linesize[2] = (int)pStride[2]; + sys->videoFrame->pts++; + /* avcodec_encode_video2 is deprecated with libavcodec 57.48.101 */ +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 48, 101) + status = avcodec_send_frame(sys->codecEncoderContext, sys->videoFrame); + + if (status < 0) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to encode video frame (%s [%d])", + av_err2str(status), status); + goto fail; + } + + status = avcodec_receive_packet(sys->codecEncoderContext, sys->packet); + + if (status < 0) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to encode video frame (%s [%d])", + av_err2str(status), status); + goto fail; + } + + gotFrame = (status == 0); +#elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 59, 100) + + do + { + status = avcodec_encode_video2(sys->codecEncoderContext, sys->packet, sys->videoFrame, + &gotFrame); + } while ((status >= 0) && (gotFrame == 0)); + +#else + sys->packet->size = + avpicture_get_size(sys->codecDecoderContext->pix_fmt, sys->codecDecoderContext->width, + sys->codecDecoderContext->height); + sys->packet->data = av_malloc(sys->packet->size); + + if (!sys->packet->data) + status = -1; + else + { + status = avcodec_encode_video(sys->codecDecoderContext, sys->packet->data, + sys->packet->size, sys->videoFrame); + } + +#endif + + if (status < 0) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to encode video frame (%s [%d])", + av_err2str(status), status); + goto fail; + } + + WINPR_ASSERT(sys->packet); + *ppDstData = sys->packet->data; + *pDstSize = (UINT32)MAX(0, sys->packet->size); + + if (!gotFrame) + { + WLog_Print(h264->log, WLOG_ERROR, "Did not get frame! (%s [%d])", av_err2str(status), + status); + rc = -2; + } + else + rc = 1; +fail: + return rc; +} + +static void libavcodec_uninit(H264_CONTEXT* h264) +{ + WINPR_ASSERT(h264); + + H264_CONTEXT_LIBAVCODEC* sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData; + + if (!sys) + return; + + if (sys->packet) + { +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 133, 100) + av_packet_unref(sys->packet); +#else + av_packet_free(&sys->packet); +#endif + } + + if (sys->videoFrame) + { +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 18, 102) + av_frame_free(&sys->videoFrame); +#else + av_free(sys->videoFrame); +#endif + } + +#ifdef WITH_VAAPI + + if (sys->hwVideoFrame) + { +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 18, 102) + av_frame_free(&sys->hwVideoFrame); +#else + av_free(sys->hwVideoFrame); +#endif + } + + if (sys->hwctx) + av_buffer_unref(&sys->hwctx); + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 80, 100) + + if (sys->hw_frames_ctx) + av_buffer_unref(&sys->hw_frames_ctx); + +#endif +#endif + + if (sys->codecParser) + av_parser_close(sys->codecParser); + + if (sys->codecDecoderContext) + { + avcodec_close(sys->codecDecoderContext); +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 69, 100) + avcodec_free_context(&sys->codecDecoderContext); +#else + av_free(sys->codecDecoderContext); +#endif + } + + libavcodec_destroy_encoder(h264); + free(sys); + h264->pSystemData = NULL; +} + +#ifdef WITH_VAAPI +static enum AVPixelFormat libavcodec_get_format(struct AVCodecContext* ctx, + const enum AVPixelFormat* fmts) +{ + WINPR_ASSERT(ctx); + + H264_CONTEXT* h264 = (H264_CONTEXT*)ctx->opaque; + WINPR_ASSERT(h264); + + H264_CONTEXT_LIBAVCODEC* sys = (H264_CONTEXT_LIBAVCODEC*)h264->pSystemData; + WINPR_ASSERT(sys); + + for (const enum AVPixelFormat* p = fmts; *p != AV_PIX_FMT_NONE; p++) + { + if (*p == sys->hw_pix_fmt) + { +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(57, 80, 100) + sys->hw_frames_ctx = av_hwframe_ctx_alloc(sys->hwctx); + + if (!sys->hw_frames_ctx) + { + return AV_PIX_FMT_NONE; + } + + sys->codecDecoderContext->pix_fmt = *p; + AVHWFramesContext* frames = (AVHWFramesContext*)sys->hw_frames_ctx->data; + frames->format = *p; + frames->height = sys->codecDecoderContext->coded_height; + frames->width = sys->codecDecoderContext->coded_width; + frames->sw_format = + (sys->codecDecoderContext->sw_pix_fmt == AV_PIX_FMT_YUV420P10 ? AV_PIX_FMT_P010 + : AV_PIX_FMT_NV12); + frames->initial_pool_size = 20; + + if (sys->codecDecoderContext->active_thread_type & FF_THREAD_FRAME) + frames->initial_pool_size += sys->codecDecoderContext->thread_count; + + int err = av_hwframe_ctx_init(sys->hw_frames_ctx); + + if (err < 0) + { + WLog_Print(h264->log, WLOG_ERROR, "Could not init hwframes context: %s", + av_err2str(err)); + return AV_PIX_FMT_NONE; + } + + sys->codecDecoderContext->hw_frames_ctx = av_buffer_ref(sys->hw_frames_ctx); +#endif + return *p; + } + } + + return AV_PIX_FMT_NONE; +} +#endif + +static BOOL libavcodec_init(H264_CONTEXT* h264) +{ + H264_CONTEXT_LIBAVCODEC* sys = NULL; + + WINPR_ASSERT(h264); + sys = (H264_CONTEXT_LIBAVCODEC*)calloc(1, sizeof(H264_CONTEXT_LIBAVCODEC)); + + if (!sys) + { + goto EXCEPTION; + } + + h264->pSystemData = (void*)sys; + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(58, 10, 100) + avcodec_register_all(); +#endif + + if (!h264->Compressor) + { + sys->codecDecoder = avcodec_find_decoder(AV_CODEC_ID_H264); + + if (!sys->codecDecoder) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to find libav H.264 codec"); + goto EXCEPTION; + } + + sys->codecDecoderContext = avcodec_alloc_context3(sys->codecDecoder); + + if (!sys->codecDecoderContext) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to allocate libav codec context"); + goto EXCEPTION; + } + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(59, 18, 100) + if (sys->codecDecoder->capabilities & AV_CODEC_CAP_TRUNCATED) + { + sys->codecDecoderContext->flags |= AV_CODEC_FLAG_TRUNCATED; + } +#endif + +#ifdef WITH_VAAPI + + if (!sys->hwctx) + { + int ret = + av_hwdevice_ctx_create(&sys->hwctx, AV_HWDEVICE_TYPE_VAAPI, VAAPI_DEVICE, NULL, 0); + + if (ret < 0) + { + WLog_Print(h264->log, WLOG_ERROR, + "Could not initialize hardware decoder, falling back to software: %s", + av_err2str(ret)); + sys->hwctx = NULL; + goto fail_hwdevice_create; + } + } + + sys->codecDecoderContext->get_format = libavcodec_get_format; + sys->hw_pix_fmt = AV_PIX_FMT_VAAPI; +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(57, 80, 100) + sys->codecDecoderContext->hw_device_ctx = av_buffer_ref(sys->hwctx); +#endif + sys->codecDecoderContext->opaque = (void*)h264; + fail_hwdevice_create: +#endif + + if (avcodec_open2(sys->codecDecoderContext, sys->codecDecoder, NULL) < 0) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to open libav codec"); + goto EXCEPTION; + } + + sys->codecParser = av_parser_init(AV_CODEC_ID_H264); + + if (!sys->codecParser) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to initialize libav parser"); + goto EXCEPTION; + } + } + +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(55, 18, 102) + sys->videoFrame = av_frame_alloc(); +#ifdef WITH_VAAPI + sys->hwVideoFrame = av_frame_alloc(); +#endif +#else + sys->videoFrame = avcodec_alloc_frame(); +#endif + + if (!sys->videoFrame) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to allocate libav frame"); + goto EXCEPTION; + } + +#ifdef WITH_VAAPI + + if (!sys->hwVideoFrame) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to allocate libav hw frame"); + goto EXCEPTION; + } + +#endif + sys->videoFrame->pts = 0; + return TRUE; +EXCEPTION: + libavcodec_uninit(h264); + return FALSE; +} + +const H264_CONTEXT_SUBSYSTEM g_Subsystem_libavcodec = { "libavcodec", libavcodec_init, + libavcodec_uninit, libavcodec_decompress, + libavcodec_compress }; diff --git a/libfreerdp/codec/h264_mediacodec.c b/libfreerdp/codec/h264_mediacodec.c new file mode 100644 index 0000000..ae6d76a --- /dev/null +++ b/libfreerdp/codec/h264_mediacodec.c @@ -0,0 +1,527 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * H.264 Bitmap Compression + * + * Copyright 2022 Ely Ronnen <elyronnen@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/wlog.h> +#include <winpr/assert.h> +#include <winpr/library.h> + +#include <freerdp/log.h> +#include <freerdp/codec/h264.h> + +#include <media/NdkMediaCodec.h> +#include <media/NdkMediaFormat.h> + +#include "h264.h" + +static const char* CODEC_NAME = "video/avc"; + +static const int COLOR_FormatYUV420Planar = 19; +static const int COLOR_FormatYUV420Flexible = 0x7f420888; + +/* https://developer.android.com/reference/android/media/MediaCodec#qualityFloor */ +static const int MEDIACODEC_MINIMUM_WIDTH = 320; +static const int MEDIACODEC_MINIMUM_HEIGHT = 240; + +typedef struct +{ + AMediaCodec* decoder; + AMediaFormat* inputFormat; + AMediaFormat* outputFormat; + int32_t width; + int32_t height; + int32_t outputWidth; + int32_t outputHeight; + ssize_t currentOutputBufferIndex; +} H264_CONTEXT_MEDIACODEC; + +static AMediaFormat* mediacodec_format_new(wLog* log, int width, int height) +{ + const char* media_format; + AMediaFormat* format = AMediaFormat_new(); + if (format == NULL) + { + WLog_Print(log, WLOG_ERROR, "AMediaFormat_new failed"); + return NULL; + } + + AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, CODEC_NAME); + AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, width); + AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, height); + AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT, COLOR_FormatYUV420Planar); + + media_format = AMediaFormat_toString(format); + if (media_format == NULL) + { + WLog_Print(log, WLOG_ERROR, "AMediaFormat_toString failed"); + AMediaFormat_delete(format); + return NULL; + } + + WLog_Print(log, WLOG_DEBUG, "MediaCodec configuring with desired output format [%s]", + media_format); + + return format; +} + +static void set_mediacodec_format(H264_CONTEXT* h264, AMediaFormat** formatVariable, + AMediaFormat* newFormat) +{ + media_status_t status = AMEDIA_OK; + H264_CONTEXT_MEDIACODEC* sys; + + WINPR_ASSERT(h264); + WINPR_ASSERT(formatVariable); + + sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData; + WINPR_ASSERT(sys); + + if (*formatVariable == newFormat) + return; + + if (*formatVariable != NULL) + { + status = AMediaFormat_delete(*formatVariable); + if (status != AMEDIA_OK) + { + WLog_Print(h264->log, WLOG_ERROR, "Error AMediaFormat_delete %d", status); + } + } + + *formatVariable = newFormat; +} + +static int update_mediacodec_inputformat(H264_CONTEXT* h264) +{ + H264_CONTEXT_MEDIACODEC* sys; + AMediaFormat* inputFormat; + const char* mediaFormatName; + + WINPR_ASSERT(h264); + + sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData; + WINPR_ASSERT(sys); + +#if __ANDROID__ >= 21 + inputFormat = AMediaCodec_getInputFormat(sys->decoder); + if (inputFormat == NULL) + { + WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getInputFormat failed"); + return -1; + } +#else + inputFormat = sys->inputFormat; +#endif + set_mediacodec_format(h264, &sys->inputFormat, inputFormat); + + mediaFormatName = AMediaFormat_toString(sys->inputFormat); + if (mediaFormatName == NULL) + { + WLog_Print(h264->log, WLOG_ERROR, "AMediaFormat_toString failed"); + return -1; + } + WLog_Print(h264->log, WLOG_DEBUG, "Using MediaCodec with input MediaFormat [%s]", + mediaFormatName); + + return 1; +} + +static int update_mediacodec_outputformat(H264_CONTEXT* h264) +{ + H264_CONTEXT_MEDIACODEC* sys; + AMediaFormat* outputFormat; + const char* mediaFormatName; + int32_t outputWidth, outputHeight; + + WINPR_ASSERT(h264); + + sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData; + WINPR_ASSERT(sys); + + outputFormat = AMediaCodec_getOutputFormat(sys->decoder); + if (outputFormat == NULL) + { + WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getOutputFormat failed"); + return -1; + } + set_mediacodec_format(h264, &sys->outputFormat, outputFormat); + + mediaFormatName = AMediaFormat_toString(sys->outputFormat); + if (mediaFormatName == NULL) + { + WLog_Print(h264->log, WLOG_ERROR, "AMediaFormat_toString failed"); + return -1; + } + WLog_Print(h264->log, WLOG_DEBUG, "Using MediaCodec with output MediaFormat [%s]", + mediaFormatName); + + if (!AMediaFormat_getInt32(sys->outputFormat, AMEDIAFORMAT_KEY_WIDTH, &outputWidth)) + { + WLog_Print(h264->log, WLOG_ERROR, "fnAMediaFormat_getInt32 failed getting width"); + return -1; + } + + if (!AMediaFormat_getInt32(sys->outputFormat, AMEDIAFORMAT_KEY_HEIGHT, &outputHeight)) + { + WLog_Print(h264->log, WLOG_ERROR, "fnAMediaFormat_getInt32 failed getting height"); + return -1; + } + + sys->outputWidth = outputWidth; + sys->outputHeight = outputHeight; + + return 1; +} + +static void release_current_outputbuffer(H264_CONTEXT* h264) +{ + media_status_t status = AMEDIA_OK; + H264_CONTEXT_MEDIACODEC* sys; + + WINPR_ASSERT(h264); + sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData; + WINPR_ASSERT(sys); + + if (sys->currentOutputBufferIndex < 0) + { + return; + } + + status = AMediaCodec_releaseOutputBuffer(sys->decoder, sys->currentOutputBufferIndex, FALSE); + if (status != AMEDIA_OK) + { + WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_releaseOutputBuffer %d", status); + } + + sys->currentOutputBufferIndex = -1; +} + +static int mediacodec_compress(H264_CONTEXT* h264, const BYTE** pSrcYuv, const UINT32* pStride, + BYTE** ppDstData, UINT32* pDstSize) +{ + WINPR_ASSERT(h264); + WINPR_ASSERT(pSrcYuv); + WINPR_ASSERT(pStride); + WINPR_ASSERT(ppDstData); + WINPR_ASSERT(pDstSize); + + WLog_Print(h264->log, WLOG_ERROR, "MediaCodec is not supported as an encoder"); + return -1; +} + +static int mediacodec_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize) +{ + ssize_t inputBufferId = -1; + size_t inputBufferSize, outputBufferSize; + uint8_t* inputBuffer; + media_status_t status; + BYTE** pYUVData; + UINT32* iStride; + H264_CONTEXT_MEDIACODEC* sys; + + WINPR_ASSERT(h264); + WINPR_ASSERT(pSrcData); + + sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData; + WINPR_ASSERT(sys); + + pYUVData = h264->pYUVData; + WINPR_ASSERT(pYUVData); + + iStride = h264->iStride; + WINPR_ASSERT(iStride); + + release_current_outputbuffer(h264); + + if (sys->width != h264->width || sys->height != h264->height) + { + sys->width = h264->width; + sys->height = h264->height; + + if (sys->width < MEDIACODEC_MINIMUM_WIDTH || sys->height < MEDIACODEC_MINIMUM_HEIGHT) + { + WLog_Print(h264->log, WLOG_ERROR, + "MediaCodec got width or height smaller than minimum [%d,%d]", sys->width, + sys->height); + return -1; + } + + WLog_Print(h264->log, WLOG_DEBUG, "MediaCodec setting new input width and height [%d,%d]", + sys->width, sys->height); + +#if __ANDROID__ >= 26 + AMediaFormat_setInt32(sys->inputFormat, AMEDIAFORMAT_KEY_WIDTH, sys->width); + AMediaFormat_setInt32(sys->inputFormat, AMEDIAFORMAT_KEY_HEIGHT, sys->height); + status = AMediaCodec_setParameters(sys->decoder, sys->inputFormat); + if (status != AMEDIA_OK) + { + WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_setParameters failed: %d", status); + return -1; + } +#else + set_mediacodec_format(h264, &sys->inputFormat, + mediacodec_format_new(h264->log, sys->width, sys->height)); +#endif + + // The codec can change output width and height + if (update_mediacodec_outputformat(h264) < 0) + { + WLog_Print(h264->log, WLOG_ERROR, "MediaCodec failed updating input format"); + return -1; + } + } + + while (true) + { + UINT32 inputBufferCurrnetOffset = 0; + while (inputBufferCurrnetOffset < SrcSize) + { + UINT32 numberOfBytesToCopy = SrcSize - inputBufferCurrnetOffset; + inputBufferId = AMediaCodec_dequeueInputBuffer(sys->decoder, -1); + if (inputBufferId < 0) + { + WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_dequeueInputBuffer failed [%d]", + inputBufferId); + // TODO: sleep? + continue; + } + + inputBuffer = AMediaCodec_getInputBuffer(sys->decoder, inputBufferId, &inputBufferSize); + if (inputBuffer == NULL) + { + WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getInputBuffer failed"); + return -1; + } + + if (numberOfBytesToCopy > inputBufferSize) + { + WLog_Print(h264->log, WLOG_WARN, + "MediaCodec inputBufferSize: got [%d] but wanted [%d]", inputBufferSize, + numberOfBytesToCopy); + numberOfBytesToCopy = inputBufferSize; + } + + memcpy(inputBuffer, pSrcData + inputBufferCurrnetOffset, numberOfBytesToCopy); + inputBufferCurrnetOffset += numberOfBytesToCopy; + + status = AMediaCodec_queueInputBuffer(sys->decoder, inputBufferId, 0, + numberOfBytesToCopy, 0, 0); + if (status != AMEDIA_OK) + { + WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_queueInputBuffer %d", status); + return -1; + } + } + + while (true) + { + AMediaCodecBufferInfo bufferInfo; + ssize_t outputBufferId = AMediaCodec_dequeueOutputBuffer(sys->decoder, &bufferInfo, -1); + if (outputBufferId >= 0) + { + sys->currentOutputBufferIndex = outputBufferId; + + uint8_t* outputBuffer; + outputBuffer = + AMediaCodec_getOutputBuffer(sys->decoder, outputBufferId, &outputBufferSize); + sys->currentOutputBufferIndex = outputBufferId; + + if (outputBufferSize != + (sys->outputWidth * sys->outputHeight + + ((sys->outputWidth + 1) / 2) * ((sys->outputHeight + 1) / 2) * 2)) + { + WLog_Print(h264->log, WLOG_ERROR, + "Error MediaCodec unexpected output buffer size %d", + outputBufferSize); + return -1; + } + + // TODO: work with AImageReader and get AImage object instead of + // COLOR_FormatYUV420Planar buffer. + iStride[0] = sys->outputWidth; + iStride[1] = (sys->outputWidth + 1) / 2; + iStride[2] = (sys->outputWidth + 1) / 2; + pYUVData[0] = outputBuffer; + pYUVData[1] = outputBuffer + iStride[0] * sys->outputHeight; + pYUVData[2] = outputBuffer + iStride[0] * sys->outputHeight + + iStride[1] * ((sys->outputHeight + 1) / 2); + break; + } + else if (outputBufferId == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) + { + if (update_mediacodec_outputformat(h264) < 0) + { + WLog_Print(h264->log, WLOG_ERROR, + "MediaCodec failed updating output format in decompress"); + return -1; + } + } + else if (outputBufferId == AMEDIACODEC_INFO_TRY_AGAIN_LATER) + { + WLog_Print(h264->log, WLOG_WARN, + "AMediaCodec_dequeueOutputBuffer need to try again later"); + // TODO: sleep? + } + else if (outputBufferId == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) + { + WLog_Print(h264->log, WLOG_WARN, + "AMediaCodec_dequeueOutputBuffer returned deprecated value " + "AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED, ignoring"); + } + else + { + WLog_Print(h264->log, WLOG_ERROR, + "AMediaCodec_dequeueOutputBuffer returned unknown value [%d]", + outputBufferId); + return -1; + } + } + + break; + } + + return 1; +} + +static void mediacodec_uninit(H264_CONTEXT* h264) +{ + media_status_t status = AMEDIA_OK; + H264_CONTEXT_MEDIACODEC* sys; + + WINPR_ASSERT(h264); + + sys = (H264_CONTEXT_MEDIACODEC*)h264->pSystemData; + + WLog_Print(h264->log, WLOG_DEBUG, "Uninitializing MediaCodec"); + + if (!sys) + return; + + if (sys->decoder != NULL) + { + release_current_outputbuffer(h264); + status = AMediaCodec_stop(sys->decoder); + if (status != AMEDIA_OK) + { + WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_stop %d", status); + } + + status = AMediaCodec_delete(sys->decoder); + if (status != AMEDIA_OK) + { + WLog_Print(h264->log, WLOG_ERROR, "Error AMediaCodec_delete %d", status); + } + + sys->decoder = NULL; + } + + set_mediacodec_format(h264, &sys->inputFormat, NULL); + set_mediacodec_format(h264, &sys->outputFormat, NULL); + + free(sys); + h264->pSystemData = NULL; +} + +static BOOL mediacodec_init(H264_CONTEXT* h264) +{ + H264_CONTEXT_MEDIACODEC* sys; + media_status_t status; + + WINPR_ASSERT(h264); + + if (h264->Compressor) + { + WLog_Print(h264->log, WLOG_ERROR, "MediaCodec is not supported as an encoder"); + goto EXCEPTION; + } + + WLog_Print(h264->log, WLOG_DEBUG, "Initializing MediaCodec"); + + sys = (H264_CONTEXT_MEDIACODEC*)calloc(1, sizeof(H264_CONTEXT_MEDIACODEC)); + + if (!sys) + { + goto EXCEPTION; + } + + h264->pSystemData = (void*)sys; + + sys->currentOutputBufferIndex = -1; + + // updated when we're given the height and width for the first time + sys->width = sys->outputWidth = MEDIACODEC_MINIMUM_WIDTH; + sys->height = sys->outputHeight = MEDIACODEC_MINIMUM_HEIGHT; + sys->decoder = AMediaCodec_createDecoderByType(CODEC_NAME); + if (sys->decoder == NULL) + { + WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_createCodecByName failed"); + goto EXCEPTION; + } + +#if __ANDROID_API__ >= 28 + char* codec_name; + status = AMediaCodec_getName(sys->decoder, &codec_name); + if (status != AMEDIA_OK) + { + WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_getName failed: %d", status); + goto EXCEPTION; + } + + WLog_Print(h264->log, WLOG_DEBUG, "MediaCodec using %s codec [%s]", CODEC_NAME, codec_name); + AMediaCodec_releaseName(sys->decoder, codec_name); +#endif + + set_mediacodec_format(h264, &sys->inputFormat, + mediacodec_format_new(h264->log, sys->width, sys->height)); + + status = AMediaCodec_configure(sys->decoder, sys->inputFormat, NULL, NULL, 0); + if (status != AMEDIA_OK) + { + WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_configure failed: %d", status); + goto EXCEPTION; + } + + if (update_mediacodec_inputformat(h264) < 0) + { + WLog_Print(h264->log, WLOG_ERROR, "MediaCodec failed updating input format"); + goto EXCEPTION; + } + + if (update_mediacodec_outputformat(h264) < 0) + { + WLog_Print(h264->log, WLOG_ERROR, "MediaCodec failed updating output format"); + goto EXCEPTION; + } + + WLog_Print(h264->log, WLOG_DEBUG, "Starting MediaCodec"); + status = AMediaCodec_start(sys->decoder); + if (status != AMEDIA_OK) + { + WLog_Print(h264->log, WLOG_ERROR, "AMediaCodec_start failed %d", status); + goto EXCEPTION; + } + + return TRUE; +EXCEPTION: + mediacodec_uninit(h264); + return FALSE; +} + +const H264_CONTEXT_SUBSYSTEM g_Subsystem_mediacodec = { "MediaCodec", mediacodec_init, + mediacodec_uninit, mediacodec_decompress, + mediacodec_compress }; diff --git a/libfreerdp/codec/h264_mf.c b/libfreerdp/codec/h264_mf.c new file mode 100644 index 0000000..b5e65f5 --- /dev/null +++ b/libfreerdp/codec/h264_mf.c @@ -0,0 +1,595 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * H.264 Bitmap Compression + * + * Copyright 2014 Mike McDonald <Mike.McDonald@software.dell.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/log.h> +#include <freerdp/codec/h264.h> + +#include <ks.h> +#include <codecapi.h> + +#include <mfapi.h> +#include <mferror.h> +#include <wmcodecdsp.h> +#include <mftransform.h> + +#include "h264.h" + +#define TAG FREERDP_TAG("codec") + +static const GUID sCLSID_CMSH264DecoderMFT = { + 0x62CE7E72, 0x4C71, 0x4d20, { 0xB1, 0x5D, 0x45, 0x28, 0x31, 0xA8, 0x7D, 0x9D } +}; +static const GUID sIID_IMFTransform = { + 0xbf94c121, 0x5b05, 0x4e6f, { 0x80, 0x00, 0xba, 0x59, 0x89, 0x61, 0x41, 0x4d } +}; +static const GUID sMF_MT_MAJOR_TYPE = { + 0x48eba18e, 0xf8c9, 0x4687, { 0xbf, 0x11, 0x0a, 0x74, 0xc9, 0xf9, 0x6a, 0x8f } +}; +static const GUID sMF_MT_FRAME_SIZE = { + 0x1652c33d, 0xd6b2, 0x4012, { 0xb8, 0x34, 0x72, 0x03, 0x08, 0x49, 0xa3, 0x7d } +}; +static const GUID sMF_MT_DEFAULT_STRIDE = { + 0x644b4e48, 0x1e02, 0x4516, { 0xb0, 0xeb, 0xc0, 0x1c, 0xa9, 0xd4, 0x9a, 0xc6 } +}; +static const GUID sMF_MT_SUBTYPE = { + 0xf7e34c9a, 0x42e8, 0x4714, { 0xb7, 0x4b, 0xcb, 0x29, 0xd7, 0x2c, 0x35, 0xe5 } +}; +static const GUID sMFMediaType_Video = { + 0x73646976, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xAA, 0x00, 0x38, 0x9B, 0x71 } +}; +static const GUID sMFVideoFormat_H264 = { + 0x34363248, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } +}; +static const GUID sMFVideoFormat_IYUV = { + 0x56555949, 0x0000, 0x0010, { 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 } +}; +static const GUID sIID_ICodecAPI = { + 0x901db4c7, 0x31ce, 0x41a2, { 0x85, 0xdc, 0x8f, 0xa0, 0xbf, 0x41, 0xb8, 0xda } +}; +static const GUID sCODECAPI_AVLowLatencyMode = { + 0x9c27891a, 0xed7a, 0x40e1, { 0x88, 0xe8, 0xb2, 0x27, 0x27, 0xa0, 0x24, 0xee } +}; + +typedef HRESULT(__stdcall* pfnMFStartup)(ULONG Version, DWORD dwFlags); +typedef HRESULT(__stdcall* pfnMFShutdown)(void); +typedef HRESULT(__stdcall* pfnMFCreateSample)(IMFSample** ppIMFSample); +typedef HRESULT(__stdcall* pfnMFCreateMemoryBuffer)(DWORD cbMaxLength, IMFMediaBuffer** ppBuffer); +typedef HRESULT(__stdcall* pfnMFCreateMediaType)(IMFMediaType** ppMFType); + +typedef struct +{ + ICodecAPI* codecApi; + IMFTransform* transform; + IMFMediaType* inputType; + IMFMediaType* outputType; + IMFSample* sample; + UINT32 frameWidth; + UINT32 frameHeight; + IMFSample* outputSample; + IMFMediaBuffer* outputBuffer; + HMODULE mfplat; + pfnMFStartup MFStartup; + pfnMFShutdown MFShutdown; + pfnMFCreateSample MFCreateSample; + pfnMFCreateMemoryBuffer MFCreateMemoryBuffer; + pfnMFCreateMediaType MFCreateMediaType; +} H264_CONTEXT_MF; + +static HRESULT mf_find_output_type(H264_CONTEXT_MF* sys, const GUID* guid, + IMFMediaType** ppMediaType) +{ + DWORD idx = 0; + GUID mediaGuid; + HRESULT hr = S_OK; + IMFMediaType* pMediaType = NULL; + + while (1) + { + hr = sys->transform->lpVtbl->GetOutputAvailableType(sys->transform, 0, idx, &pMediaType); + + if (FAILED(hr)) + break; + + pMediaType->lpVtbl->GetGUID(pMediaType, &sMF_MT_SUBTYPE, &mediaGuid); + + if (IsEqualGUID(&mediaGuid, guid)) + { + *ppMediaType = pMediaType; + return S_OK; + } + + pMediaType->lpVtbl->Release(pMediaType); + idx++; + } + + return hr; +} + +static HRESULT mf_create_output_sample(H264_CONTEXT* h264, H264_CONTEXT_MF* sys) +{ + HRESULT hr = S_OK; + MFT_OUTPUT_STREAM_INFO streamInfo; + + if (sys->outputSample) + { + sys->outputSample->lpVtbl->Release(sys->outputSample); + sys->outputSample = NULL; + } + + hr = sys->MFCreateSample(&sys->outputSample); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "MFCreateSample failure: 0x%08" PRIX32 "", hr); + goto error; + } + + hr = sys->transform->lpVtbl->GetOutputStreamInfo(sys->transform, 0, &streamInfo); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "GetOutputStreamInfo failure: 0x%08" PRIX32 "", hr); + goto error; + } + + hr = sys->MFCreateMemoryBuffer(streamInfo.cbSize, &sys->outputBuffer); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "MFCreateMemoryBuffer failure: 0x%08" PRIX32 "", hr); + goto error; + } + + sys->outputSample->lpVtbl->AddBuffer(sys->outputSample, sys->outputBuffer); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "AddBuffer failure: 0x%08" PRIX32 "", hr); + goto error; + } + + sys->outputBuffer->lpVtbl->Release(sys->outputBuffer); +error: + return hr; +} + +static int mf_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize) +{ + HRESULT hr; + BYTE* pbBuffer = NULL; + DWORD cbMaxLength = 0; + DWORD cbCurrentLength = 0; + DWORD outputStatus = 0; + IMFSample* inputSample = NULL; + IMFMediaBuffer* inputBuffer = NULL; + IMFMediaBuffer* outputBuffer = NULL; + MFT_OUTPUT_DATA_BUFFER outputDataBuffer; + H264_CONTEXT_MF* sys = (H264_CONTEXT_MF*)h264->pSystemData; + UINT32* iStride = h264->iStride; + BYTE** pYUVData = h264->pYUVData; + hr = sys->MFCreateMemoryBuffer(SrcSize, &inputBuffer); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "MFCreateMemoryBuffer failure: 0x%08" PRIX32 "", hr); + goto error; + } + + hr = inputBuffer->lpVtbl->Lock(inputBuffer, &pbBuffer, &cbMaxLength, &cbCurrentLength); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "Lock failure: 0x%08" PRIX32 "", hr); + goto error; + } + + CopyMemory(pbBuffer, pSrcData, SrcSize); + hr = inputBuffer->lpVtbl->SetCurrentLength(inputBuffer, SrcSize); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "SetCurrentLength failure: 0x%08" PRIX32 "", hr); + goto error; + } + + hr = inputBuffer->lpVtbl->Unlock(inputBuffer); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "Unlock failure: 0x%08" PRIX32 "", hr); + goto error; + } + + hr = sys->MFCreateSample(&inputSample); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "MFCreateSample failure: 0x%08" PRIX32 "", hr); + goto error; + } + + inputSample->lpVtbl->AddBuffer(inputSample, inputBuffer); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "AddBuffer failure: 0x%08" PRIX32 "", hr); + goto error; + } + + inputBuffer->lpVtbl->Release(inputBuffer); + hr = sys->transform->lpVtbl->ProcessInput(sys->transform, 0, inputSample, 0); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "ProcessInput failure: 0x%08" PRIX32 "", hr); + goto error; + } + + hr = mf_create_output_sample(h264, sys); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "mf_create_output_sample failure: 0x%08" PRIX32 "", hr); + goto error; + } + + outputDataBuffer.dwStreamID = 0; + outputDataBuffer.dwStatus = 0; + outputDataBuffer.pEvents = NULL; + outputDataBuffer.pSample = sys->outputSample; + hr = sys->transform->lpVtbl->ProcessOutput(sys->transform, 0, 1, &outputDataBuffer, + &outputStatus); + + if (hr == MF_E_TRANSFORM_STREAM_CHANGE) + { + UINT32 stride = 0; + UINT64 frameSize = 0; + + if (sys->outputType) + { + sys->outputType->lpVtbl->Release(sys->outputType); + sys->outputType = NULL; + } + + hr = mf_find_output_type(sys, &sMFVideoFormat_IYUV, &sys->outputType); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "mf_find_output_type failure: 0x%08" PRIX32 "", hr); + goto error; + } + + hr = sys->transform->lpVtbl->SetOutputType(sys->transform, 0, sys->outputType, 0); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "SetOutputType failure: 0x%08" PRIX32 "", hr); + goto error; + } + + hr = mf_create_output_sample(h264, sys); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "mf_create_output_sample failure: 0x%08" PRIX32 "", + hr); + goto error; + } + + hr = sys->outputType->lpVtbl->GetUINT64(sys->outputType, &sMF_MT_FRAME_SIZE, &frameSize); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, + "GetUINT64(MF_MT_FRAME_SIZE) failure: 0x%08" PRIX32 "", hr); + goto error; + } + + sys->frameWidth = (UINT32)(frameSize >> 32); + sys->frameHeight = (UINT32)frameSize; + hr = sys->outputType->lpVtbl->GetUINT32(sys->outputType, &sMF_MT_DEFAULT_STRIDE, &stride); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, + "GetUINT32(MF_MT_DEFAULT_STRIDE) failure: 0x%08" PRIX32 "", hr); + goto error; + } + + if (!avc420_ensure_buffer(h264, stride, sys->frameWidth, sys->frameHeight)) + goto error; + } + else if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) + { + } + else if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "ProcessOutput failure: 0x%08" PRIX32 "", hr); + goto error; + } + else + { + int offset = 0; + BYTE* buffer = NULL; + DWORD bufferCount = 0; + DWORD cbMaxLength = 0; + DWORD cbCurrentLength = 0; + hr = sys->outputSample->lpVtbl->GetBufferCount(sys->outputSample, &bufferCount); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "GetBufferCount failure: 0x%08" PRIX32 "", hr); + goto error; + } + + hr = sys->outputSample->lpVtbl->GetBufferByIndex(sys->outputSample, 0, &outputBuffer); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "GetBufferByIndex failure: 0x%08" PRIX32 "", hr); + goto error; + } + + hr = outputBuffer->lpVtbl->Lock(outputBuffer, &buffer, &cbMaxLength, &cbCurrentLength); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "Lock failure: 0x%08" PRIX32 "", hr); + goto error; + } + + CopyMemory(pYUVData[0], &buffer[offset], iStride[0] * sys->frameHeight); + offset += iStride[0] * sys->frameHeight; + CopyMemory(pYUVData[1], &buffer[offset], iStride[1] * (sys->frameHeight / 2)); + offset += iStride[1] * (sys->frameHeight / 2); + CopyMemory(pYUVData[2], &buffer[offset], iStride[2] * (sys->frameHeight / 2)); + offset += iStride[2] * (sys->frameHeight / 2); + hr = outputBuffer->lpVtbl->Unlock(outputBuffer); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "Unlock failure: 0x%08" PRIX32 "", hr); + goto error; + } + + outputBuffer->lpVtbl->Release(outputBuffer); + } + + inputSample->lpVtbl->Release(inputSample); + return 1; +error: + fprintf(stderr, "mf_decompress error\n"); + return -1; +} + +static int mf_compress(H264_CONTEXT* h264, const BYTE** ppSrcYuv, const UINT32* pStride, + BYTE** ppDstData, UINT32* pDstSize) +{ + H264_CONTEXT_MF* sys = (H264_CONTEXT_MF*)h264->pSystemData; + return 1; +} + +static BOOL mf_plat_loaded(H264_CONTEXT_MF* sys) +{ + return sys->MFStartup && sys->MFShutdown && sys->MFCreateSample && sys->MFCreateMemoryBuffer && + sys->MFCreateMediaType; +} + +static void mf_uninit(H264_CONTEXT* h264) +{ + H264_CONTEXT_MF* sys = (H264_CONTEXT_MF*)h264->pSystemData; + + if (sys) + { + if (sys->transform) + { + sys->transform->lpVtbl->Release(sys->transform); + sys->transform = NULL; + } + + if (sys->codecApi) + { + sys->codecApi->lpVtbl->Release(sys->codecApi); + sys->codecApi = NULL; + } + + if (sys->inputType) + { + sys->inputType->lpVtbl->Release(sys->inputType); + sys->inputType = NULL; + } + + if (sys->outputType) + { + sys->outputType->lpVtbl->Release(sys->outputType); + sys->outputType = NULL; + } + + if (sys->outputSample) + { + sys->outputSample->lpVtbl->Release(sys->outputSample); + sys->outputSample = NULL; + } + + if (sys->mfplat) + { + if (mf_plat_loaded(sys)) + sys->MFShutdown(); + + FreeLibrary(sys->mfplat); + sys->mfplat = NULL; + + if (mf_plat_loaded(sys)) + CoUninitialize(); + } + + for (size_t x = 0; x < sizeof(h264->pYUVData) / sizeof(h264->pYUVData[0]); x++) + winpr_aligned_free(h264->pYUVData[x]); + + memset(h264->pYUVData, 0, sizeof(h264->pYUVData)); + memset(h264->iStride, 0, sizeof(h264->iStride)); + + free(sys); + h264->pSystemData = NULL; + } +} + +static BOOL mf_init(H264_CONTEXT* h264) +{ + HRESULT hr; + H264_CONTEXT_MF* sys = (H264_CONTEXT_MF*)calloc(1, sizeof(H264_CONTEXT_MF)); + + if (!sys) + goto error; + + h264->pSystemData = (void*)sys; + /* http://decklink-sdk-delphi.googlecode.com/svn/trunk/Blackmagic%20DeckLink%20SDK%209.7/Win/Samples/Streaming/StreamingPreview/DecoderMF.cpp + */ + sys->mfplat = LoadLibraryA("mfplat.dll"); + + if (!sys->mfplat) + goto error; + + sys->MFStartup = (pfnMFStartup)GetProcAddress(sys->mfplat, "MFStartup"); + sys->MFShutdown = (pfnMFShutdown)GetProcAddress(sys->mfplat, "MFShutdown"); + sys->MFCreateSample = (pfnMFCreateSample)GetProcAddress(sys->mfplat, "MFCreateSample"); + sys->MFCreateMemoryBuffer = + (pfnMFCreateMemoryBuffer)GetProcAddress(sys->mfplat, "MFCreateMemoryBuffer"); + sys->MFCreateMediaType = (pfnMFCreateMediaType)GetProcAddress(sys->mfplat, "MFCreateMediaType"); + + if (!mf_plat_loaded(sys)) + goto error; + + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + + if (h264->Compressor) + { + } + else + { + VARIANT var = { 0 }; + hr = sys->MFStartup(MF_VERSION, 0); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "MFStartup failure: 0x%08" PRIX32 "", hr); + goto error; + } + + hr = CoCreateInstance(&sCLSID_CMSH264DecoderMFT, NULL, CLSCTX_INPROC_SERVER, + &sIID_IMFTransform, (void**)&sys->transform); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, + "CoCreateInstance(CLSID_CMSH264DecoderMFT) failure: 0x%08" PRIX32 "", hr); + goto error; + } + + hr = sys->transform->lpVtbl->QueryInterface(sys->transform, &sIID_ICodecAPI, + (void**)&sys->codecApi); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, + "QueryInterface(IID_ICodecAPI) failure: 0x%08" PRIX32 "", hr); + goto error; + } + + var.vt = VT_UI4; + var.ulVal = 1; + hr = sys->codecApi->lpVtbl->SetValue(sys->codecApi, &sCODECAPI_AVLowLatencyMode, &var); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, + "SetValue(CODECAPI_AVLowLatencyMode) failure: 0x%08" PRIX32 "", hr); + goto error; + } + + hr = sys->MFCreateMediaType(&sys->inputType); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "MFCreateMediaType failure: 0x%08" PRIX32 "", hr); + goto error; + } + + hr = sys->inputType->lpVtbl->SetGUID(sys->inputType, &sMF_MT_MAJOR_TYPE, + &sMFMediaType_Video); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "SetGUID(MF_MT_MAJOR_TYPE) failure: 0x%08" PRIX32 "", + hr); + goto error; + } + + hr = sys->inputType->lpVtbl->SetGUID(sys->inputType, &sMF_MT_SUBTYPE, &sMFVideoFormat_H264); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "SetGUID(MF_MT_SUBTYPE) failure: 0x%08" PRIX32 "", + hr); + goto error; + } + + hr = sys->transform->lpVtbl->SetInputType(sys->transform, 0, sys->inputType, 0); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "SetInputType failure: 0x%08" PRIX32 "", hr); + goto error; + } + + hr = mf_find_output_type(sys, &sMFVideoFormat_IYUV, &sys->outputType); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "mf_find_output_type failure: 0x%08" PRIX32 "", hr); + goto error; + } + + hr = sys->transform->lpVtbl->SetOutputType(sys->transform, 0, sys->outputType, 0); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "SetOutputType failure: 0x%08" PRIX32 "", hr); + goto error; + } + + hr = mf_create_output_sample(h264, sys); + + if (FAILED(hr)) + { + WLog_Print(h264->log, WLOG_ERROR, "mf_create_output_sample failure: 0x%08" PRIX32 "", + hr); + goto error; + } + } + + return TRUE; +error: + WLog_Print(h264->log, WLOG_ERROR, "mf_init failure"); + mf_uninit(h264); + return FALSE; +} + +const H264_CONTEXT_SUBSYSTEM g_Subsystem_MF = { "MediaFoundation", mf_init, mf_uninit, + mf_decompress, mf_compress }; diff --git a/libfreerdp/codec/h264_openh264.c b/libfreerdp/codec/h264_openh264.c new file mode 100644 index 0000000..2fa6a03 --- /dev/null +++ b/libfreerdp/codec/h264_openh264.c @@ -0,0 +1,632 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * H.264 Bitmap Compression + * + * Copyright 2014 Mike McDonald <Mike.McDonald@software.dell.com> + * Copyright 2015 Vic Lee <llyzs.vic@gmail.com> + * Copyright 2014 Armin Novak <armin.novak@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include <freerdp/config.h> + +#include <winpr/library.h> +#include <winpr/assert.h> + +#include <freerdp/log.h> +#include <freerdp/codec/h264.h> + +#include <wels/codec_def.h> +#include <wels/codec_api.h> +#include <wels/codec_ver.h> + +#include "h264.h" + +typedef void (*pWelsGetCodecVersionEx)(OpenH264Version* pVersion); + +typedef long (*pWelsCreateDecoder)(ISVCDecoder** ppDecoder); +typedef void (*pWelsDestroyDecoder)(ISVCDecoder* pDecoder); + +typedef int (*pWelsCreateSVCEncoder)(ISVCEncoder** ppEncoder); +typedef void (*pWelsDestroySVCEncoder)(ISVCEncoder* pEncoder); + +typedef struct +{ +#if defined(WITH_OPENH264_LOADING) + HMODULE lib; + OpenH264Version version; +#endif + pWelsGetCodecVersionEx WelsGetCodecVersionEx; + pWelsCreateDecoder WelsCreateDecoder; + pWelsDestroyDecoder WelsDestroyDecoder; + pWelsCreateSVCEncoder WelsCreateSVCEncoder; + pWelsDestroySVCEncoder WelsDestroySVCEncoder; + ISVCDecoder* pDecoder; + ISVCEncoder* pEncoder; + SEncParamExt EncParamExt; +} H264_CONTEXT_OPENH264; + +#if defined(WITH_OPENH264_LOADING) +static const char* openh264_library_names[] = { +#if defined(_WIN32) + "openh264.dll" +#elif defined(__APPLE__) + "libopenh264.dylib" +#else + "libopenh264.so" +#endif +}; +#endif + +static void openh264_trace_callback(H264_CONTEXT* h264, int level, const char* message) +{ + if (h264) + WLog_Print(h264->log, WLOG_TRACE, "%d - %s", level, message); +} + +static int openh264_decompress(H264_CONTEXT* h264, const BYTE* pSrcData, UINT32 SrcSize) +{ + DECODING_STATE state = dsInvalidArgument; + SBufferInfo sBufferInfo = { 0 }; + SSysMEMBuffer* pSystemBuffer = NULL; + H264_CONTEXT_OPENH264* sys = NULL; + UINT32* iStride = NULL; + BYTE** pYUVData = NULL; + + WINPR_ASSERT(h264); + WINPR_ASSERT(pSrcData || (SrcSize == 0)); + + sys = (H264_CONTEXT_OPENH264*)h264->pSystemData; + WINPR_ASSERT(sys); + + iStride = h264->iStride; + WINPR_ASSERT(iStride); + + pYUVData = h264->pYUVData; + WINPR_ASSERT(pYUVData); + + if (!sys->pDecoder) + return -2001; + + /* + * Decompress the image. The RDP host only seems to send I420 format. + */ + pYUVData[0] = NULL; + pYUVData[1] = NULL; + pYUVData[2] = NULL; + + WINPR_ASSERT(sys->pDecoder); + state = + (*sys->pDecoder)->DecodeFrame2(sys->pDecoder, pSrcData, SrcSize, pYUVData, &sBufferInfo); + + if (sBufferInfo.iBufferStatus != 1) + { + if (state == dsNoParamSets) + { + /* this happens on the first frame due to missing parameter sets */ + state = (*sys->pDecoder)->DecodeFrame2(sys->pDecoder, NULL, 0, pYUVData, &sBufferInfo); + } + else if (state == dsErrorFree) + { + /* call DecodeFrame2 again to decode without delay */ + state = (*sys->pDecoder)->DecodeFrame2(sys->pDecoder, NULL, 0, pYUVData, &sBufferInfo); + } + else + { + WLog_Print(h264->log, WLOG_WARN, "DecodeFrame2 state: 0x%04X iBufferStatus: %d", state, + sBufferInfo.iBufferStatus); + return -2002; + } + } + + if (state != dsErrorFree) + { + WLog_Print(h264->log, WLOG_WARN, "DecodeFrame2 state: 0x%02X", state); + return -2003; + } + +#if OPENH264_MAJOR >= 2 + state = (*sys->pDecoder)->FlushFrame(sys->pDecoder, pYUVData, &sBufferInfo); + if (state != dsErrorFree) + { + WLog_Print(h264->log, WLOG_WARN, "FlushFrame state: 0x%02X", state); + return -2003; + } +#endif + + pSystemBuffer = &sBufferInfo.UsrData.sSystemBuffer; + iStride[0] = pSystemBuffer->iStride[0]; + iStride[1] = pSystemBuffer->iStride[1]; + iStride[2] = pSystemBuffer->iStride[1]; + + if (sBufferInfo.iBufferStatus != 1) + { + WLog_Print(h264->log, WLOG_WARN, "DecodeFrame2 iBufferStatus: %d", + sBufferInfo.iBufferStatus); + return 0; + } + + if (state != dsErrorFree) + { + WLog_Print(h264->log, WLOG_WARN, "DecodeFrame2 state: 0x%02X", state); + return -2003; + } + +#if 0 + WLog_Print(h264->log, WLOG_INFO, + "h264_decompress: state=%u, pYUVData=[%p,%p,%p], bufferStatus=%d, width=%d, height=%d, format=%d, stride=[%d,%d]", + state, (void*) pYUVData[0], (void*) pYUVData[1], (void*) pYUVData[2], sBufferInfo.iBufferStatus, + pSystemBuffer->iWidth, pSystemBuffer->iHeight, pSystemBuffer->iFormat, + pSystemBuffer->iStride[0], pSystemBuffer->iStride[1]); +#endif + + if (pSystemBuffer->iFormat != videoFormatI420) + return -2004; + + if (!pYUVData[0] || !pYUVData[1] || !pYUVData[2]) + return -2005; + + return 1; +} + +static int openh264_compress(H264_CONTEXT* h264, const BYTE** pYUVData, const UINT32* iStride, + BYTE** ppDstData, UINT32* pDstSize) +{ + int status = 0; + SFrameBSInfo info = { 0 }; + SSourcePicture pic = { 0 }; + + H264_CONTEXT_OPENH264* sys = NULL; + + WINPR_ASSERT(h264); + WINPR_ASSERT(pYUVData); + WINPR_ASSERT(iStride); + WINPR_ASSERT(ppDstData); + WINPR_ASSERT(pDstSize); + + sys = &((H264_CONTEXT_OPENH264*)h264->pSystemData)[0]; + WINPR_ASSERT(sys); + + if (!sys->pEncoder) + return -1; + + if (!pYUVData[0] || !pYUVData[1] || !pYUVData[2]) + return -1; + + if ((h264->width > INT_MAX) || (h264->height > INT_MAX)) + return -1; + + if ((h264->FrameRate > INT_MAX) || (h264->NumberOfThreads > INT_MAX) || + (h264->BitRate > INT_MAX) || (h264->QP > INT_MAX)) + return -1; + + WINPR_ASSERT(sys->pEncoder); + if ((sys->EncParamExt.iPicWidth != (int)h264->width) || + (sys->EncParamExt.iPicHeight != (int)h264->height)) + { + WINPR_ASSERT((*sys->pEncoder)->GetDefaultParams); + status = (*sys->pEncoder)->GetDefaultParams(sys->pEncoder, &sys->EncParamExt); + + if (status < 0) + { + WLog_Print(h264->log, WLOG_ERROR, + "Failed to get OpenH264 default parameters (status=%d)", status); + return status; + } + + sys->EncParamExt.iUsageType = SCREEN_CONTENT_REAL_TIME; + sys->EncParamExt.iPicWidth = (int)h264->width; + sys->EncParamExt.iPicHeight = (int)h264->height; + sys->EncParamExt.fMaxFrameRate = (int)h264->FrameRate; + sys->EncParamExt.iMaxBitrate = UNSPECIFIED_BIT_RATE; + sys->EncParamExt.bEnableDenoise = 0; + sys->EncParamExt.bEnableLongTermReference = 0; + sys->EncParamExt.bEnableFrameSkip = 0; + sys->EncParamExt.iSpatialLayerNum = 1; + sys->EncParamExt.iMultipleThreadIdc = (int)h264->NumberOfThreads; + sys->EncParamExt.sSpatialLayers[0].fFrameRate = h264->FrameRate; + sys->EncParamExt.sSpatialLayers[0].iVideoWidth = sys->EncParamExt.iPicWidth; + sys->EncParamExt.sSpatialLayers[0].iVideoHeight = sys->EncParamExt.iPicHeight; + sys->EncParamExt.sSpatialLayers[0].iMaxSpatialBitrate = sys->EncParamExt.iMaxBitrate; + + switch (h264->RateControlMode) + { + case H264_RATECONTROL_VBR: + sys->EncParamExt.iRCMode = RC_BITRATE_MODE; + sys->EncParamExt.iTargetBitrate = (int)h264->BitRate; + sys->EncParamExt.sSpatialLayers[0].iSpatialBitrate = + sys->EncParamExt.iTargetBitrate; + break; + + case H264_RATECONTROL_CQP: + sys->EncParamExt.iRCMode = RC_OFF_MODE; + sys->EncParamExt.sSpatialLayers[0].iDLayerQp = (int)h264->QP; + break; + } + + if (sys->EncParamExt.iMultipleThreadIdc > 1) + { +#if (OPENH264_MAJOR == 1) && (OPENH264_MINOR <= 5) + sys->EncParamExt.sSpatialLayers[0].sSliceCfg.uiSliceMode = SM_AUTO_SLICE; +#else + sys->EncParamExt.sSpatialLayers[0].sSliceArgument.uiSliceMode = SM_FIXEDSLCNUM_SLICE; +#endif + } + + WINPR_ASSERT((*sys->pEncoder)->InitializeExt); + status = (*sys->pEncoder)->InitializeExt(sys->pEncoder, &sys->EncParamExt); + + if (status < 0) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to initialize OpenH264 encoder (status=%d)", + status); + return status; + } + + WINPR_ASSERT((*sys->pEncoder)->GetOption); + status = + (*sys->pEncoder) + ->GetOption(sys->pEncoder, ENCODER_OPTION_SVC_ENCODE_PARAM_EXT, &sys->EncParamExt); + + if (status < 0) + { + WLog_Print(h264->log, WLOG_ERROR, + "Failed to get initial OpenH264 encoder parameters (status=%d)", status); + return status; + } + } + else + { + switch (h264->RateControlMode) + { + case H264_RATECONTROL_VBR: + if (sys->EncParamExt.iTargetBitrate != (int)h264->BitRate) + { + SBitrateInfo bitrate = { 0 }; + + sys->EncParamExt.iTargetBitrate = (int)h264->BitRate; + bitrate.iLayer = SPATIAL_LAYER_ALL; + bitrate.iBitrate = (int)h264->BitRate; + + WINPR_ASSERT((*sys->pEncoder)->SetOption); + status = (*sys->pEncoder) + ->SetOption(sys->pEncoder, ENCODER_OPTION_BITRATE, &bitrate); + + if (status < 0) + { + WLog_Print(h264->log, WLOG_ERROR, + "Failed to set encoder bitrate (status=%d)", status); + return status; + } + } + + if (sys->EncParamExt.fMaxFrameRate != (int)h264->FrameRate) + { + sys->EncParamExt.fMaxFrameRate = (int)h264->FrameRate; + + WINPR_ASSERT((*sys->pEncoder)->SetOption); + status = (*sys->pEncoder) + ->SetOption(sys->pEncoder, ENCODER_OPTION_FRAME_RATE, + &sys->EncParamExt.fMaxFrameRate); + + if (status < 0) + { + WLog_Print(h264->log, WLOG_ERROR, + "Failed to set encoder framerate (status=%d)", status); + return status; + } + } + + break; + + case H264_RATECONTROL_CQP: + if (sys->EncParamExt.sSpatialLayers[0].iDLayerQp != (int)h264->QP) + { + sys->EncParamExt.sSpatialLayers[0].iDLayerQp = (int)h264->QP; + + WINPR_ASSERT((*sys->pEncoder)->SetOption); + status = (*sys->pEncoder) + ->SetOption(sys->pEncoder, ENCODER_OPTION_SVC_ENCODE_PARAM_EXT, + &sys->EncParamExt); + + if (status < 0) + { + WLog_Print(h264->log, WLOG_ERROR, + "Failed to set encoder parameters (status=%d)", status); + return status; + } + } + + break; + } + } + + pic.iPicWidth = (int)h264->width; + pic.iPicHeight = (int)h264->height; + pic.iColorFormat = videoFormatI420; + pic.iStride[0] = (int)iStride[0]; + pic.iStride[1] = (int)iStride[1]; + pic.iStride[2] = (int)iStride[2]; + pic.pData[0] = (unsigned char*)pYUVData[0]; + pic.pData[1] = (unsigned char*)pYUVData[1]; + pic.pData[2] = (unsigned char*)pYUVData[2]; + + WINPR_ASSERT((*sys->pEncoder)->EncodeFrame); + status = (*sys->pEncoder)->EncodeFrame(sys->pEncoder, &pic, &info); + + if (status < 0) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to encode frame (status=%d)", status); + return status; + } + + *ppDstData = info.sLayerInfo[0].pBsBuf; + *pDstSize = 0; + + for (int i = 0; i < info.iLayerNum; i++) + { + for (int j = 0; j < info.sLayerInfo[i].iNalCount; j++) + { + *pDstSize += info.sLayerInfo[i].pNalLengthInByte[j]; + } + } + + return 1; +} + +static void openh264_uninit(H264_CONTEXT* h264) +{ + H264_CONTEXT_OPENH264* sysContexts = NULL; + + WINPR_ASSERT(h264); + + sysContexts = (H264_CONTEXT_OPENH264*)h264->pSystemData; + + if (sysContexts) + { + for (UINT32 x = 0; x < h264->numSystemData; x++) + { + H264_CONTEXT_OPENH264* sys = &sysContexts[x]; + + if (sys->pDecoder) + { + (*sys->pDecoder)->Uninitialize(sys->pDecoder); + sysContexts->WelsDestroyDecoder(sys->pDecoder); + sys->pDecoder = NULL; + } + + if (sys->pEncoder) + { + (*sys->pEncoder)->Uninitialize(sys->pEncoder); + sysContexts->WelsDestroySVCEncoder(sys->pEncoder); + sys->pEncoder = NULL; + } + } + +#if defined(WITH_OPENH264_LOADING) + if (sysContexts->lib) + FreeLibrary(sysContexts->lib); +#endif + free(h264->pSystemData); + h264->pSystemData = NULL; + } +} + +#if defined(WITH_OPENH264_LOADING) +static BOOL openh264_load_functionpointers(H264_CONTEXT* h264, const char* name) +{ + H264_CONTEXT_OPENH264* sysContexts; + + WINPR_ASSERT(name); + + if (!h264) + return FALSE; + + sysContexts = h264->pSystemData; + + if (!sysContexts) + return FALSE; + + sysContexts->lib = LoadLibraryA(name); + + if (!sysContexts->lib) + return FALSE; + + sysContexts->WelsGetCodecVersionEx = + (pWelsGetCodecVersionEx)GetProcAddress(sysContexts->lib, "WelsGetCodecVersionEx"); + sysContexts->WelsCreateDecoder = + (pWelsCreateDecoder)GetProcAddress(sysContexts->lib, "WelsCreateDecoder"); + sysContexts->WelsDestroyDecoder = + (pWelsDestroyDecoder)GetProcAddress(sysContexts->lib, "WelsDestroyDecoder"); + sysContexts->WelsCreateSVCEncoder = + (pWelsCreateSVCEncoder)GetProcAddress(sysContexts->lib, "WelsCreateSVCEncoder"); + sysContexts->WelsDestroySVCEncoder = + (pWelsDestroySVCEncoder)GetProcAddress(sysContexts->lib, "WelsDestroySVCEncoder"); + + if (!sysContexts->WelsCreateDecoder || !sysContexts->WelsDestroyDecoder || + !sysContexts->WelsCreateSVCEncoder || !sysContexts->WelsDestroySVCEncoder || + !sysContexts->WelsGetCodecVersionEx) + { + FreeLibrary(sysContexts->lib); + sysContexts->lib = NULL; + return FALSE; + } + + sysContexts->WelsGetCodecVersionEx(&sysContexts->version); + WLog_Print(h264->log, WLOG_INFO, "loaded %s %d.%d.%d", name, sysContexts->version.uMajor, + sysContexts->version.uMinor, sysContexts->version.uRevision); + + if ((sysContexts->version.uMajor < 1) || + ((sysContexts->version.uMajor == 1) && (sysContexts->version.uMinor < 6))) + { + WLog_Print( + h264->log, WLOG_ERROR, + "OpenH264 %s %d.%d.%d is too old, need at least version 1.6.0 for dynamic loading", + name, sysContexts->version.uMajor, sysContexts->version.uMinor, + sysContexts->version.uRevision); + FreeLibrary(sysContexts->lib); + sysContexts->lib = NULL; + return FALSE; + } + + return TRUE; +} +#endif + +static BOOL openh264_init(H264_CONTEXT* h264) +{ +#if defined(WITH_OPENH264_LOADING) + BOOL success = FALSE; +#endif + long status = 0; + H264_CONTEXT_OPENH264* sysContexts = NULL; + static int traceLevel = WELS_LOG_DEBUG; +#if (OPENH264_MAJOR == 1) && (OPENH264_MINOR <= 5) + static EVideoFormatType videoFormat = videoFormatI420; +#endif + static WelsTraceCallback traceCallback = (WelsTraceCallback)openh264_trace_callback; + + WINPR_ASSERT(h264); + + h264->numSystemData = 1; + sysContexts = + (H264_CONTEXT_OPENH264*)calloc(h264->numSystemData, sizeof(H264_CONTEXT_OPENH264)); + + if (!sysContexts) + goto EXCEPTION; + + h264->pSystemData = (void*)sysContexts; +#if defined(WITH_OPENH264_LOADING) + + for (size_t i = 0; i < ARRAYSIZE(openh264_library_names); i++) + { + const char* current = openh264_library_names[i]; + success = openh264_load_functionpointers(h264, current); + + if (success) + break; + } + + if (!success) + goto EXCEPTION; + +#else + sysContexts->WelsGetCodecVersionEx = WelsGetCodecVersionEx; + sysContexts->WelsCreateDecoder = WelsCreateDecoder; + sysContexts->WelsDestroyDecoder = WelsDestroyDecoder; + sysContexts->WelsCreateSVCEncoder = WelsCreateSVCEncoder; + sysContexts->WelsDestroySVCEncoder = WelsDestroySVCEncoder; +#endif + + for (UINT32 x = 0; x < h264->numSystemData; x++) + { + SDecodingParam sDecParam = { 0 }; + H264_CONTEXT_OPENH264* sys = &sysContexts[x]; + + if (h264->Compressor) + { + sysContexts->WelsCreateSVCEncoder(&sys->pEncoder); + + if (!sys->pEncoder) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to create OpenH264 encoder"); + goto EXCEPTION; + } + } + else + { + sysContexts->WelsCreateDecoder(&sys->pDecoder); + + if (!sys->pDecoder) + { + WLog_Print(h264->log, WLOG_ERROR, "Failed to create OpenH264 decoder"); + goto EXCEPTION; + } + +#if (OPENH264_MAJOR == 1) && (OPENH264_MINOR <= 5) + sDecParam.eOutputColorFormat = videoFormatI420; +#endif + sDecParam.eEcActiveIdc = ERROR_CON_FRAME_COPY; + sDecParam.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_AVC; + status = (*sys->pDecoder)->Initialize(sys->pDecoder, &sDecParam); + + if (status != 0) + { + WLog_Print(h264->log, WLOG_ERROR, + "Failed to initialize OpenH264 decoder (status=%ld)", status); + goto EXCEPTION; + } + +#if (OPENH264_MAJOR == 1) && (OPENH264_MINOR <= 5) + status = + (*sys->pDecoder)->SetOption(sys->pDecoder, DECODER_OPTION_DATAFORMAT, &videoFormat); +#endif + + if (status != 0) + { + WLog_Print(h264->log, WLOG_ERROR, + "Failed to set data format option on OpenH264 decoder (status=%ld)", + status); + goto EXCEPTION; + } + + if (WLog_GetLogLevel(h264->log) == WLOG_TRACE) + { + status = (*sys->pDecoder) + ->SetOption(sys->pDecoder, DECODER_OPTION_TRACE_LEVEL, &traceLevel); + + if (status != 0) + { + WLog_Print(h264->log, WLOG_ERROR, + "Failed to set trace level option on OpenH264 decoder (status=%ld)", + status); + goto EXCEPTION; + } + + status = + (*sys->pDecoder) + ->SetOption(sys->pDecoder, DECODER_OPTION_TRACE_CALLBACK_CONTEXT, &h264); + + if (status != 0) + { + WLog_Print(h264->log, WLOG_ERROR, + "Failed to set trace callback context option on OpenH264 decoder " + "(status=%ld)", + status); + goto EXCEPTION; + } + + status = + (*sys->pDecoder) + ->SetOption(sys->pDecoder, DECODER_OPTION_TRACE_CALLBACK, &traceCallback); + + if (status != 0) + { + WLog_Print( + h264->log, WLOG_ERROR, + "Failed to set trace callback option on OpenH264 decoder (status=%ld)", + status); + goto EXCEPTION; + } + } + } + } + + return TRUE; +EXCEPTION: + openh264_uninit(h264); + return FALSE; +} + +const H264_CONTEXT_SUBSYSTEM g_Subsystem_OpenH264 = { "OpenH264", openh264_init, openh264_uninit, + openh264_decompress, openh264_compress }; diff --git a/libfreerdp/codec/include/bitmap.c b/libfreerdp/codec/include/bitmap.c new file mode 100644 index 0000000..af83387 --- /dev/null +++ b/libfreerdp/codec/include/bitmap.c @@ -0,0 +1,449 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RLE Compressed Bitmap Stream + * + * Copyright 2011 Jay Sorg <jay.sorg@gmail.com> + * Copyright 2016 Armin Novak <armin.novak@thincast.com> + * Copyright 2016 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* do not compile the file directly */ + +/** + * Write a foreground/background image to a destination buffer. + */ +static INLINE BYTE* WRITEFGBGIMAGE(BYTE* pbDest, const BYTE* pbDestEnd, UINT32 rowDelta, + BYTE bitmask, PIXEL fgPel, INT32 cBits) +{ + PIXEL xorPixel; + BYTE mask = 0x01; + + if (cBits > 8) + { + WLog_ERR(TAG, "cBits %d > 8", cBits); + return NULL; + } + + if (!ENSURE_CAPACITY(pbDest, pbDestEnd, cBits)) + return NULL; + + UNROLL(cBits, { + UINT32 data; + DESTREADPIXEL(xorPixel, pbDest - rowDelta); + + if (bitmask & mask) + data = xorPixel ^ fgPel; + else + data = xorPixel; + + DESTWRITEPIXEL(pbDest, data); + mask = mask << 1; + }); + return pbDest; +} + +/** + * Write a foreground/background image to a destination buffer + * for the first line of compressed data. + */ +static INLINE BYTE* WRITEFIRSTLINEFGBGIMAGE(BYTE* pbDest, const BYTE* pbDestEnd, BYTE bitmask, + PIXEL fgPel, UINT32 cBits) +{ + BYTE mask = 0x01; + + if (cBits > 8) + { + WLog_ERR(TAG, "cBits %d > 8", cBits); + return NULL; + } + + if (!ENSURE_CAPACITY(pbDest, pbDestEnd, cBits)) + return NULL; + + UNROLL(cBits, { + UINT32 data; + + if (bitmask & mask) + data = fgPel; + else + data = BLACK_PIXEL; + + DESTWRITEPIXEL(pbDest, data); + mask = mask << 1; + }); + return pbDest; +} + +/** + * Decompress an RLE compressed bitmap. + */ +static INLINE BOOL RLEDECOMPRESS(const BYTE* pbSrcBuffer, UINT32 cbSrcBuffer, BYTE* pbDestBuffer, + UINT32 rowDelta, UINT32 width, UINT32 height) +{ +#if defined(WITH_DEBUG_CODECS) + char sbuffer[128] = { 0 }; +#endif + const BYTE* pbSrc = pbSrcBuffer; + const BYTE* pbEnd; + const BYTE* pbDestEnd; + BYTE* pbDest = pbDestBuffer; + PIXEL temp; + PIXEL fgPel = WHITE_PIXEL; + BOOL fInsertFgPel = FALSE; + BOOL fFirstLine = TRUE; + BYTE bitmask; + PIXEL pixelA, pixelB; + UINT32 runLength; + UINT32 code; + UINT32 advance = 0; + RLEEXTRA + + if ((rowDelta == 0) || (rowDelta < width)) + { + WLog_ERR(TAG, "Invalid arguments: rowDelta=%" PRIu32 " == 0 || < width=%" PRIu32, rowDelta, + width); + return FALSE; + } + + if (!pbSrcBuffer || !pbDestBuffer) + { + WLog_ERR(TAG, "Invalid arguments: pbSrcBuffer=%p, pbDestBuffer=%p", pbSrcBuffer, + pbDestBuffer); + return FALSE; + } + + pbEnd = pbSrcBuffer + cbSrcBuffer; + pbDestEnd = pbDestBuffer + rowDelta * height; + + while (pbSrc < pbEnd) + { + /* Watch out for the end of the first scanline. */ + if (fFirstLine) + { + if ((UINT32)(pbDest - pbDestBuffer) >= rowDelta) + { + fFirstLine = FALSE; + fInsertFgPel = FALSE; + } + } + + /* + Extract the compression order code ID from the compression + order header. + */ + code = ExtractCodeId(*pbSrc); + +#if defined(WITH_DEBUG_CODECS) + WLog_VRB(TAG, "pbSrc=%p code=%s, rem=%" PRIuz, pbSrc, + rle_code_str_buffer(code, sbuffer, sizeof(sbuffer)), pbEnd - pbSrc); +#endif + + /* Handle Background Run Orders. */ + if ((code == REGULAR_BG_RUN) || (code == MEGA_MEGA_BG_RUN)) + { + runLength = ExtractRunLength(code, pbSrc, pbEnd, &advance); + if (advance == 0) + return FALSE; + pbSrc = pbSrc + advance; + + if (fFirstLine) + { + if (fInsertFgPel) + { + if (!ENSURE_CAPACITY(pbDest, pbDestEnd, 1)) + return FALSE; + + DESTWRITEPIXEL(pbDest, fgPel); + runLength = runLength - 1; + } + + if (!ENSURE_CAPACITY(pbDest, pbDestEnd, runLength)) + return FALSE; + + UNROLL(runLength, { DESTWRITEPIXEL(pbDest, BLACK_PIXEL); }); + } + else + { + if (fInsertFgPel) + { + DESTREADPIXEL(temp, pbDest - rowDelta); + + if (!ENSURE_CAPACITY(pbDest, pbDestEnd, 1)) + return FALSE; + + DESTWRITEPIXEL(pbDest, temp ^ fgPel); + runLength--; + } + + if (!ENSURE_CAPACITY(pbDest, pbDestEnd, runLength)) + return FALSE; + + UNROLL(runLength, { + DESTREADPIXEL(temp, pbDest - rowDelta); + DESTWRITEPIXEL(pbDest, temp); + }); + } + + /* A follow-on background run order will need a foreground pel inserted. */ + fInsertFgPel = TRUE; + continue; + } + + /* For any of the other run-types a follow-on background run + order does not need a foreground pel inserted. */ + fInsertFgPel = FALSE; + + switch (code) + { + /* Handle Foreground Run Orders. */ + case REGULAR_FG_RUN: + case MEGA_MEGA_FG_RUN: + case LITE_SET_FG_FG_RUN: + case MEGA_MEGA_SET_FG_RUN: + runLength = ExtractRunLength(code, pbSrc, pbEnd, &advance); + if (advance == 0) + return FALSE; + pbSrc = pbSrc + advance; + + if (code == LITE_SET_FG_FG_RUN || code == MEGA_MEGA_SET_FG_RUN) + { + if (!buffer_within_range(pbSrc, PIXEL_SIZE, pbEnd)) + return FALSE; + SRCREADPIXEL(fgPel, pbSrc); + } + + if (!ENSURE_CAPACITY(pbDest, pbDestEnd, runLength)) + return FALSE; + + if (fFirstLine) + { + UNROLL(runLength, { DESTWRITEPIXEL(pbDest, fgPel); }); + } + else + { + UNROLL(runLength, { + DESTREADPIXEL(temp, pbDest - rowDelta); + DESTWRITEPIXEL(pbDest, temp ^ fgPel); + }); + } + + break; + + /* Handle Dithered Run Orders. */ + case LITE_DITHERED_RUN: + case MEGA_MEGA_DITHERED_RUN: + runLength = ExtractRunLength(code, pbSrc, pbEnd, &advance); + if (advance == 0) + return FALSE; + pbSrc = pbSrc + advance; + if (!buffer_within_range(pbSrc, PIXEL_SIZE, pbEnd)) + return FALSE; + SRCREADPIXEL(pixelA, pbSrc); + if (!buffer_within_range(pbSrc, PIXEL_SIZE, pbEnd)) + return FALSE; + SRCREADPIXEL(pixelB, pbSrc); + + if (!ENSURE_CAPACITY(pbDest, pbDestEnd, runLength * 2)) + return FALSE; + + UNROLL(runLength, { + DESTWRITEPIXEL(pbDest, pixelA); + DESTWRITEPIXEL(pbDest, pixelB); + }); + break; + + /* Handle Color Run Orders. */ + case REGULAR_COLOR_RUN: + case MEGA_MEGA_COLOR_RUN: + runLength = ExtractRunLength(code, pbSrc, pbEnd, &advance); + if (advance == 0) + return FALSE; + pbSrc = pbSrc + advance; + if (!buffer_within_range(pbSrc, PIXEL_SIZE, pbEnd)) + return FALSE; + SRCREADPIXEL(pixelA, pbSrc); + + if (!ENSURE_CAPACITY(pbDest, pbDestEnd, runLength)) + return FALSE; + + UNROLL(runLength, { DESTWRITEPIXEL(pbDest, pixelA); }); + break; + + /* Handle Foreground/Background Image Orders. */ + case REGULAR_FGBG_IMAGE: + case MEGA_MEGA_FGBG_IMAGE: + case LITE_SET_FG_FGBG_IMAGE: + case MEGA_MEGA_SET_FGBG_IMAGE: + runLength = ExtractRunLength(code, pbSrc, pbEnd, &advance); + if (advance == 0) + return FALSE; + pbSrc = pbSrc + advance; + + if (code == LITE_SET_FG_FGBG_IMAGE || code == MEGA_MEGA_SET_FGBG_IMAGE) + { + if (!buffer_within_range(pbSrc, PIXEL_SIZE, pbEnd)) + return FALSE; + SRCREADPIXEL(fgPel, pbSrc); + } + + if (!buffer_within_range(pbSrc, runLength / 8, pbEnd)) + return FALSE; + if (fFirstLine) + { + while (runLength > 8) + { + bitmask = *pbSrc; + pbSrc = pbSrc + 1; + pbDest = WRITEFIRSTLINEFGBGIMAGE(pbDest, pbDestEnd, bitmask, fgPel, 8); + + if (!pbDest) + return FALSE; + + runLength = runLength - 8; + } + } + else + { + while (runLength > 8) + { + bitmask = *pbSrc++; + + pbDest = WRITEFGBGIMAGE(pbDest, pbDestEnd, rowDelta, bitmask, fgPel, 8); + + if (!pbDest) + return FALSE; + + runLength = runLength - 8; + } + } + + if (runLength > 0) + { + if (!buffer_within_range(pbSrc, 1, pbEnd)) + return FALSE; + bitmask = *pbSrc++; + + if (fFirstLine) + { + pbDest = + WRITEFIRSTLINEFGBGIMAGE(pbDest, pbDestEnd, bitmask, fgPel, runLength); + } + else + { + pbDest = + WRITEFGBGIMAGE(pbDest, pbDestEnd, rowDelta, bitmask, fgPel, runLength); + } + + if (!pbDest) + return FALSE; + } + + break; + + /* Handle Color Image Orders. */ + case REGULAR_COLOR_IMAGE: + case MEGA_MEGA_COLOR_IMAGE: + runLength = ExtractRunLength(code, pbSrc, pbEnd, &advance); + if (advance == 0) + return FALSE; + pbSrc = pbSrc + advance; + if (!ENSURE_CAPACITY(pbDest, pbDestEnd, runLength)) + return FALSE; + if (!ENSURE_CAPACITY(pbSrc, pbEnd, runLength)) + return FALSE; + + UNROLL(runLength, { + SRCREADPIXEL(temp, pbSrc); + DESTWRITEPIXEL(pbDest, temp); + }); + break; + + /* Handle Special Order 1. */ + case SPECIAL_FGBG_1: + if (!buffer_within_range(pbSrc, 1, pbEnd)) + return FALSE; + pbSrc = pbSrc + 1; + + if (fFirstLine) + { + pbDest = + WRITEFIRSTLINEFGBGIMAGE(pbDest, pbDestEnd, g_MaskSpecialFgBg1, fgPel, 8); + } + else + { + pbDest = + WRITEFGBGIMAGE(pbDest, pbDestEnd, rowDelta, g_MaskSpecialFgBg1, fgPel, 8); + } + + if (!pbDest) + return FALSE; + + break; + + /* Handle Special Order 2. */ + case SPECIAL_FGBG_2: + if (!buffer_within_range(pbSrc, 1, pbEnd)) + return FALSE; + pbSrc = pbSrc + 1; + + if (fFirstLine) + { + pbDest = + WRITEFIRSTLINEFGBGIMAGE(pbDest, pbDestEnd, g_MaskSpecialFgBg2, fgPel, 8); + } + else + { + pbDest = + WRITEFGBGIMAGE(pbDest, pbDestEnd, rowDelta, g_MaskSpecialFgBg2, fgPel, 8); + } + + if (!pbDest) + return FALSE; + + break; + + /* Handle White Order. */ + case SPECIAL_WHITE: + if (!buffer_within_range(pbSrc, 1, pbEnd)) + return FALSE; + pbSrc = pbSrc + 1; + + if (!ENSURE_CAPACITY(pbDest, pbDestEnd, 1)) + return FALSE; + + DESTWRITEPIXEL(pbDest, WHITE_PIXEL); + break; + + /* Handle Black Order. */ + case SPECIAL_BLACK: + if (!buffer_within_range(pbSrc, 1, pbEnd)) + return FALSE; + pbSrc = pbSrc + 1; + + if (!ENSURE_CAPACITY(pbDest, pbDestEnd, 1)) + return FALSE; + + DESTWRITEPIXEL(pbDest, BLACK_PIXEL); + break; + + default: + WLog_ERR(TAG, "invalid code 0x%08" PRIx32 ", pbSrcBuffer=%p, pbSrc=%p, pbEnd=%p", + code, pbSrcBuffer, pbSrc, pbEnd); + return FALSE; + } + } + + return TRUE; +} diff --git a/libfreerdp/codec/interleaved.c b/libfreerdp/codec/interleaved.c new file mode 100644 index 0000000..75b2e27 --- /dev/null +++ b/libfreerdp/codec/interleaved.c @@ -0,0 +1,750 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Interleaved RLE Bitmap Codec + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 DI (FH) Martin Haimberger <martin.haimberger@thincast.com> + * Copyright 2016 Armin Novak <armin.novak@thincast.com> + * Copyright 2016 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/assert.h> +#include <freerdp/config.h> + +#include <freerdp/codec/interleaved.h> +#include <freerdp/log.h> + +#define TAG FREERDP_TAG("codec") + +#define UNROLL_BODY(_exp, _count) \ + do \ + { \ + for (size_t x = 0; x < (_count); x++) \ + { \ + do \ + { \ + _exp \ + } while (FALSE); \ + } \ + } while (FALSE) + +#define UNROLL_MULTIPLE(_condition, _exp, _count) \ + do \ + { \ + while ((_condition) >= _count) \ + { \ + UNROLL_BODY(_exp, _count); \ + (_condition) -= _count; \ + } \ + } while (FALSE) + +#define UNROLL(_condition, _exp) \ + do \ + { \ + UNROLL_MULTIPLE(_condition, _exp, 16); \ + UNROLL_MULTIPLE(_condition, _exp, 4); \ + UNROLL_MULTIPLE(_condition, _exp, 1); \ + } while (FALSE) + +/* + RLE Compressed Bitmap Stream (RLE_BITMAP_STREAM) + http://msdn.microsoft.com/en-us/library/cc240895%28v=prot.10%29.aspx + pseudo-code + http://msdn.microsoft.com/en-us/library/dd240593%28v=prot.10%29.aspx +*/ + +#define REGULAR_BG_RUN 0x00 +#define MEGA_MEGA_BG_RUN 0xF0 +#define REGULAR_FG_RUN 0x01 +#define MEGA_MEGA_FG_RUN 0xF1 +#define LITE_SET_FG_FG_RUN 0x0C +#define MEGA_MEGA_SET_FG_RUN 0xF6 +#define LITE_DITHERED_RUN 0x0E +#define MEGA_MEGA_DITHERED_RUN 0xF8 +#define REGULAR_COLOR_RUN 0x03 +#define MEGA_MEGA_COLOR_RUN 0xF3 +#define REGULAR_FGBG_IMAGE 0x02 +#define MEGA_MEGA_FGBG_IMAGE 0xF2 +#define LITE_SET_FG_FGBG_IMAGE 0x0D +#define MEGA_MEGA_SET_FGBG_IMAGE 0xF7 +#define REGULAR_COLOR_IMAGE 0x04 +#define MEGA_MEGA_COLOR_IMAGE 0xF4 +#define SPECIAL_FGBG_1 0xF9 +#define SPECIAL_FGBG_2 0xFA +#define SPECIAL_WHITE 0xFD +#define SPECIAL_BLACK 0xFE + +#define BLACK_PIXEL 0x000000 + +typedef UINT32 PIXEL; + +static const BYTE g_MaskSpecialFgBg1 = 0x03; +static const BYTE g_MaskSpecialFgBg2 = 0x05; + +static const BYTE g_MaskRegularRunLength = 0x1F; +static const BYTE g_MaskLiteRunLength = 0x0F; + +static const char* rle_code_str(UINT32 code) +{ + switch (code) + { + case REGULAR_BG_RUN: + return "REGULAR_BG_RUN"; + case MEGA_MEGA_BG_RUN: + return "MEGA_MEGA_BG_RUN"; + case REGULAR_FG_RUN: + return "REGULAR_FG_RUN"; + case MEGA_MEGA_FG_RUN: + return "MEGA_MEGA_FG_RUN"; + case LITE_SET_FG_FG_RUN: + return "LITE_SET_FG_FG_RUN"; + case MEGA_MEGA_SET_FG_RUN: + return "MEGA_MEGA_SET_FG_RUN"; + case LITE_DITHERED_RUN: + return "LITE_DITHERED_RUN"; + case MEGA_MEGA_DITHERED_RUN: + return "MEGA_MEGA_DITHERED_RUN"; + case REGULAR_COLOR_RUN: + return "REGULAR_COLOR_RUN"; + case MEGA_MEGA_COLOR_RUN: + return "MEGA_MEGA_COLOR_RUN"; + case REGULAR_FGBG_IMAGE: + return "REGULAR_FGBG_IMAGE"; + case MEGA_MEGA_FGBG_IMAGE: + return "MEGA_MEGA_FGBG_IMAGE"; + case LITE_SET_FG_FGBG_IMAGE: + return "LITE_SET_FG_FGBG_IMAGE"; + case MEGA_MEGA_SET_FGBG_IMAGE: + return "MEGA_MEGA_SET_FGBG_IMAGE"; + case REGULAR_COLOR_IMAGE: + return "REGULAR_COLOR_IMAGE"; + case MEGA_MEGA_COLOR_IMAGE: + return "MEGA_MEGA_COLOR_IMAGE"; + case SPECIAL_FGBG_1: + return "SPECIAL_FGBG_1"; + case SPECIAL_FGBG_2: + return "SPECIAL_FGBG_2"; + case SPECIAL_WHITE: + return "SPECIAL_WHITE"; + case SPECIAL_BLACK: + return "SPECIAL_BLACK"; + default: + return "UNKNOWN"; + } +} + +static const char* rle_code_str_buffer(UINT32 code, char* buffer, size_t size) +{ + const char* str = rle_code_str(code); + _snprintf(buffer, size, "%s [0x%08" PRIx32 "]", str, code); + return buffer; +} + +#define buffer_within_range(pbSrc, size, pbEnd) \ + buffer_within_range_((pbSrc), (size), (pbEnd), __func__, __FILE__, __LINE__) +static INLINE BOOL buffer_within_range_(const void* pbSrc, size_t size, const void* pbEnd, + const char* fkt, const char* file, size_t line) +{ + WINPR_UNUSED(file); + WINPR_ASSERT(pbSrc); + WINPR_ASSERT(pbEnd); + + if ((const char*)pbSrc + size > (const char*)pbEnd) + { + WLog_ERR(TAG, "[%s:%" PRIuz "] pbSrc=%p + %" PRIuz " > pbEnd=%p", fkt, line, pbSrc, size, + pbEnd); + return FALSE; + } + return TRUE; +} + +/** + * Reads the supplied order header and extracts the compression + * order code ID. + */ +static INLINE UINT32 ExtractCodeId(BYTE bOrderHdr) +{ + if ((bOrderHdr & 0xC0U) != 0xC0U) + { + /* REGULAR orders + * (000x xxxx, 001x xxxx, 010x xxxx, 011x xxxx, 100x xxxx) + */ + return bOrderHdr >> 5; + } + else if ((bOrderHdr & 0xF0U) == 0xF0U) + { + /* MEGA and SPECIAL orders (0xF*) */ + return bOrderHdr; + } + else + { + /* LITE orders + * 1100 xxxx, 1101 xxxx, 1110 xxxx) + */ + return bOrderHdr >> 4; + } +} + +/** + * Extract the run length of a compression order. + */ +static UINT ExtractRunLengthRegularFgBg(const BYTE* pbOrderHdr, const BYTE* pbEnd, UINT32* advance) +{ + UINT runLength = 0; + + WINPR_ASSERT(pbOrderHdr); + WINPR_ASSERT(pbEnd); + WINPR_ASSERT(advance); + + runLength = (*pbOrderHdr) & g_MaskRegularRunLength; + if (runLength == 0) + { + if (!buffer_within_range(pbOrderHdr, 1, pbEnd)) + { + *advance = 0; + return 0; + } + runLength = *(pbOrderHdr + 1) + 1; + (*advance)++; + } + else + runLength = runLength * 8; + + return runLength; +} + +static UINT ExtractRunLengthLiteFgBg(const BYTE* pbOrderHdr, const BYTE* pbEnd, UINT32* advance) +{ + UINT runLength = 0; + + WINPR_ASSERT(pbOrderHdr); + WINPR_ASSERT(pbEnd); + WINPR_ASSERT(advance); + + runLength = *pbOrderHdr & g_MaskLiteRunLength; + if (runLength == 0) + { + if (!buffer_within_range(pbOrderHdr, 1, pbEnd)) + { + *advance = 0; + return 0; + } + runLength = *(pbOrderHdr + 1) + 1; + (*advance)++; + } + else + runLength = runLength * 8; + + return runLength; +} + +static UINT ExtractRunLengthRegular(const BYTE* pbOrderHdr, const BYTE* pbEnd, UINT32* advance) +{ + UINT runLength = 0; + + WINPR_ASSERT(pbOrderHdr); + WINPR_ASSERT(pbEnd); + WINPR_ASSERT(advance); + + runLength = *pbOrderHdr & g_MaskRegularRunLength; + if (runLength == 0) + { + if (!buffer_within_range(pbOrderHdr, 1, pbEnd)) + { + *advance = 0; + return 0; + } + runLength = *(pbOrderHdr + 1) + 32; + (*advance)++; + } + + return runLength; +} + +static UINT ExtractRunLengthMegaMega(const BYTE* pbOrderHdr, const BYTE* pbEnd, UINT32* advance) +{ + UINT runLength = 0; + + WINPR_ASSERT(pbOrderHdr); + WINPR_ASSERT(pbEnd); + WINPR_ASSERT(advance); + + if (!buffer_within_range(pbOrderHdr, 2, pbEnd)) + { + *advance = 0; + return 0; + } + + runLength = ((UINT16)pbOrderHdr[1]) | (((UINT16)pbOrderHdr[2]) << 8); + (*advance) += 2; + + return runLength; +} + +static UINT ExtractRunLengthLite(const BYTE* pbOrderHdr, const BYTE* pbEnd, UINT32* advance) +{ + UINT runLength = 0; + + WINPR_ASSERT(pbOrderHdr); + WINPR_ASSERT(pbEnd); + WINPR_ASSERT(advance); + + runLength = *pbOrderHdr & g_MaskLiteRunLength; + if (runLength == 0) + { + if (!buffer_within_range(pbOrderHdr, 1, pbEnd)) + { + *advance = 0; + return 0; + } + runLength = *(pbOrderHdr + 1) + 16; + (*advance)++; + } + return runLength; +} + +static INLINE UINT32 ExtractRunLength(UINT32 code, const BYTE* pbOrderHdr, const BYTE* pbEnd, + UINT32* advance) +{ + UINT32 runLength = 0; + UINT32 ladvance = 1; + + WINPR_ASSERT(pbOrderHdr); + WINPR_ASSERT(pbEnd); + WINPR_ASSERT(advance); + + *advance = 0; + if (!buffer_within_range(pbOrderHdr, 0, pbEnd)) + return 0; + + switch (code) + { + case REGULAR_FGBG_IMAGE: + runLength = ExtractRunLengthRegularFgBg(pbOrderHdr, pbEnd, &ladvance); + break; + + case LITE_SET_FG_FGBG_IMAGE: + runLength = ExtractRunLengthLiteFgBg(pbOrderHdr, pbEnd, &ladvance); + break; + + case REGULAR_BG_RUN: + case REGULAR_FG_RUN: + case REGULAR_COLOR_RUN: + case REGULAR_COLOR_IMAGE: + runLength = ExtractRunLengthRegular(pbOrderHdr, pbEnd, &ladvance); + break; + + case LITE_SET_FG_FG_RUN: + case LITE_DITHERED_RUN: + runLength = ExtractRunLengthLite(pbOrderHdr, pbEnd, &ladvance); + break; + + case MEGA_MEGA_BG_RUN: + case MEGA_MEGA_FG_RUN: + case MEGA_MEGA_SET_FG_RUN: + case MEGA_MEGA_DITHERED_RUN: + case MEGA_MEGA_COLOR_RUN: + case MEGA_MEGA_FGBG_IMAGE: + case MEGA_MEGA_SET_FGBG_IMAGE: + case MEGA_MEGA_COLOR_IMAGE: + runLength = ExtractRunLengthMegaMega(pbOrderHdr, pbEnd, &ladvance); + break; + + default: + runLength = 0; + ladvance = 0; + break; + } + + *advance = ladvance; + return runLength; +} + +#define ensure_capacity(start, end, size, base) \ + ensure_capacity_((start), (end), (size), (base), __func__, __FILE__, __LINE__) +static INLINE BOOL ensure_capacity_(const BYTE* start, const BYTE* end, size_t size, size_t base, + const char* fkt, const char* file, size_t line) +{ + const size_t available = (uintptr_t)end - (uintptr_t)start; + const BOOL rc = available >= size * base; + const BOOL res = rc && (start <= end); + + if (!res) + WLog_ERR(TAG, + "[%s:%" PRIuz "] failed: start=%p <= end=%p, available=%" PRIuz " >= size=%" PRIuz + " * base=%" PRIuz, + fkt, line, start, end, available, size, base); + return res; +} + +static INLINE void write_pixel_8(BYTE* _buf, BYTE _pix) +{ + WINPR_ASSERT(_buf); + *_buf = _pix; +} + +static INLINE void write_pixel_24(BYTE* _buf, UINT32 _pix) +{ + WINPR_ASSERT(_buf); + (_buf)[0] = (BYTE)(_pix); + (_buf)[1] = (BYTE)((_pix) >> 8); + (_buf)[2] = (BYTE)((_pix) >> 16); +} + +static INLINE void write_pixel_16(BYTE* _buf, UINT16 _pix) +{ + WINPR_ASSERT(_buf); + _buf[0] = _pix & 0xFF; + _buf[1] = (_pix >> 8) & 0xFF; +} + +#undef DESTWRITEPIXEL +#undef DESTREADPIXEL +#undef SRCREADPIXEL +#undef WRITEFGBGIMAGE +#undef WRITEFIRSTLINEFGBGIMAGE +#undef RLEDECOMPRESS +#undef RLEEXTRA +#undef WHITE_PIXEL +#undef PIXEL_SIZE +#undef PIXEL +#define PIXEL_SIZE 1 +#define PIXEL BYTE +#define WHITE_PIXEL 0xFF +#define DESTWRITEPIXEL(_buf, _pix) \ + do \ + { \ + write_pixel_8(_buf, _pix); \ + _buf += 1; \ + } while (0) +#define DESTREADPIXEL(_pix, _buf) _pix = (_buf)[0] +#define SRCREADPIXEL(_pix, _buf) \ + do \ + { \ + _pix = (_buf)[0]; \ + _buf += 1; \ + } while (0) + +#define WRITEFGBGIMAGE WriteFgBgImage8to8 +#define WRITEFIRSTLINEFGBGIMAGE WriteFirstLineFgBgImage8to8 +#define RLEDECOMPRESS RleDecompress8to8 +#define RLEEXTRA +#undef ENSURE_CAPACITY +#define ENSURE_CAPACITY(_start, _end, _size) ensure_capacity(_start, _end, _size, 1) +#include "include/bitmap.c" + +#undef DESTWRITEPIXEL +#undef DESTREADPIXEL +#undef SRCREADPIXEL +#undef WRITEFGBGIMAGE +#undef WRITEFIRSTLINEFGBGIMAGE +#undef RLEDECOMPRESS +#undef RLEEXTRA +#undef WHITE_PIXEL +#undef PIXEL_SIZE +#undef PIXEL +#define PIXEL_SIZE 2 +#define PIXEL UINT16 +#define WHITE_PIXEL 0xFFFF +#define DESTWRITEPIXEL(_buf, _pix) \ + do \ + { \ + write_pixel_16(_buf, _pix); \ + _buf += 2; \ + } while (0) +#define DESTREADPIXEL(_pix, _buf) _pix = ((UINT16*)(_buf))[0] +#define SRCREADPIXEL(_pix, _buf) \ + do \ + { \ + _pix = (_buf)[0] | ((_buf)[1] << 8); \ + _buf += 2; \ + } while (0) +#define WRITEFGBGIMAGE WriteFgBgImage16to16 +#define WRITEFIRSTLINEFGBGIMAGE WriteFirstLineFgBgImage16to16 +#define RLEDECOMPRESS RleDecompress16to16 +#define RLEEXTRA +#undef ENSURE_CAPACITY +#define ENSURE_CAPACITY(_start, _end, _size) ensure_capacity(_start, _end, _size, 2) +#include "include/bitmap.c" + +#undef DESTWRITEPIXEL +#undef DESTREADPIXEL +#undef SRCREADPIXEL +#undef WRITEFGBGIMAGE +#undef WRITEFIRSTLINEFGBGIMAGE +#undef RLEDECOMPRESS +#undef RLEEXTRA +#undef WHITE_PIXEL +#undef PIXEL_SIZE +#undef PIXEL +#define PIXEL_SIZE 3 +#define PIXEL UINT32 +#define WHITE_PIXEL 0xffffff +#define DESTWRITEPIXEL(_buf, _pix) \ + do \ + { \ + write_pixel_24(_buf, _pix); \ + _buf += 3; \ + } while (0) +#define DESTREADPIXEL(_pix, _buf) _pix = (_buf)[0] | ((_buf)[1] << 8) | ((_buf)[2] << 16) +#define SRCREADPIXEL(_pix, _buf) \ + do \ + { \ + _pix = (_buf)[0] | ((_buf)[1] << 8) | ((_buf)[2] << 16); \ + _buf += 3; \ + } while (0) + +#define WRITEFGBGIMAGE WriteFgBgImage24to24 +#define WRITEFIRSTLINEFGBGIMAGE WriteFirstLineFgBgImage24to24 +#define RLEDECOMPRESS RleDecompress24to24 +#define RLEEXTRA +#undef ENSURE_CAPACITY +#define ENSURE_CAPACITY(_start, _end, _size) ensure_capacity(_start, _end, _size, 3) +#include "include/bitmap.c" + +struct S_BITMAP_INTERLEAVED_CONTEXT +{ + BOOL Compressor; + + UINT32 TempSize; + BYTE* TempBuffer; + + wStream* bts; +}; + +BOOL interleaved_decompress(BITMAP_INTERLEAVED_CONTEXT* interleaved, const BYTE* pSrcData, + UINT32 SrcSize, UINT32 nSrcWidth, UINT32 nSrcHeight, UINT32 bpp, + BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst, + UINT32 nYDst, UINT32 nDstWidth, UINT32 nDstHeight, + const gdiPalette* palette) +{ + UINT32 scanline = 0; + UINT32 SrcFormat = 0; + UINT32 BufferSize = 0; + + if (!interleaved || !pSrcData || !pDstData) + { + WLog_ERR(TAG, "invalid arguments: interleaved=%p, pSrcData=%p, pDstData=%p", interleaved, + pSrcData, pDstData); + return FALSE; + } + + switch (bpp) + { + case 24: + scanline = nSrcWidth * 3; + SrcFormat = PIXEL_FORMAT_BGR24; + break; + + case 16: + scanline = nSrcWidth * 2; + SrcFormat = PIXEL_FORMAT_RGB16; + break; + + case 15: + scanline = nSrcWidth * 2; + SrcFormat = PIXEL_FORMAT_RGB15; + break; + + case 8: + scanline = nSrcWidth; + SrcFormat = PIXEL_FORMAT_RGB8; + break; + + default: + WLog_ERR(TAG, "Invalid color depth %" PRIu32 "", bpp); + return FALSE; + } + + BufferSize = scanline * nSrcHeight; + + if (BufferSize > interleaved->TempSize) + { + interleaved->TempBuffer = + winpr_aligned_recalloc(interleaved->TempBuffer, BufferSize, sizeof(BYTE), 16); + interleaved->TempSize = BufferSize; + } + + if (!interleaved->TempBuffer) + { + WLog_ERR(TAG, "interleaved->TempBuffer=%p", interleaved->TempBuffer); + return FALSE; + } + + switch (bpp) + { + case 24: + if (!RleDecompress24to24(pSrcData, SrcSize, interleaved->TempBuffer, scanline, + nSrcWidth, nSrcHeight)) + { + WLog_ERR(TAG, "RleDecompress24to24 failed"); + return FALSE; + } + + break; + + case 16: + case 15: + if (!RleDecompress16to16(pSrcData, SrcSize, interleaved->TempBuffer, scanline, + nSrcWidth, nSrcHeight)) + { + WLog_ERR(TAG, "RleDecompress16to16 failed"); + return FALSE; + } + + break; + + case 8: + if (!RleDecompress8to8(pSrcData, SrcSize, interleaved->TempBuffer, scanline, nSrcWidth, + nSrcHeight)) + { + WLog_ERR(TAG, "RleDecompress8to8 failed"); + return FALSE; + } + + break; + + default: + WLog_ERR(TAG, "Invalid color depth %" PRIu32 "", bpp); + return FALSE; + } + + if (!freerdp_image_copy(pDstData, DstFormat, nDstStep, nXDst, nYDst, nDstWidth, nDstHeight, + interleaved->TempBuffer, SrcFormat, scanline, 0, 0, palette, + FREERDP_FLIP_VERTICAL | FREERDP_KEEP_DST_ALPHA)) + { + WLog_ERR(TAG, "freerdp_image_copy failed"); + return FALSE; + } + return TRUE; +} + +BOOL interleaved_compress(BITMAP_INTERLEAVED_CONTEXT* interleaved, BYTE* pDstData, UINT32* pDstSize, + UINT32 nWidth, UINT32 nHeight, const BYTE* pSrcData, UINT32 SrcFormat, + UINT32 nSrcStep, UINT32 nXSrc, UINT32 nYSrc, const gdiPalette* palette, + UINT32 bpp) +{ + BOOL status = 0; + wStream* s = NULL; + UINT32 DstFormat = 0; + const UINT32 maxSize = 64 * 64 * 4; + + if (!interleaved || !pDstData || !pSrcData) + return FALSE; + + if ((nWidth == 0) || (nHeight == 0)) + return FALSE; + + if (nWidth % 4) + { + WLog_ERR(TAG, "interleaved_compress: width is not a multiple of 4"); + return FALSE; + } + + if ((nWidth > 64) || (nHeight > 64)) + { + WLog_ERR(TAG, + "interleaved_compress: width (%" PRIu32 ") or height (%" PRIu32 + ") is greater than 64", + nWidth, nHeight); + return FALSE; + } + + switch (bpp) + { + case 24: + DstFormat = PIXEL_FORMAT_BGRX32; + break; + + case 16: + DstFormat = PIXEL_FORMAT_RGB16; + break; + + case 15: + DstFormat = PIXEL_FORMAT_RGB15; + break; + + default: + return FALSE; + } + + if (!freerdp_image_copy(interleaved->TempBuffer, DstFormat, 0, 0, 0, nWidth, nHeight, pSrcData, + SrcFormat, nSrcStep, nXSrc, nYSrc, palette, FREERDP_KEEP_DST_ALPHA)) + return FALSE; + + s = Stream_New(pDstData, *pDstSize); + + if (!s) + return FALSE; + + Stream_SetPosition(interleaved->bts, 0); + + if (freerdp_bitmap_compress(interleaved->TempBuffer, nWidth, nHeight, s, bpp, maxSize, + nHeight - 1, interleaved->bts, 0) < 0) + status = FALSE; + else + status = TRUE; + + Stream_SealLength(s); + *pDstSize = (UINT32)Stream_Length(s); + Stream_Free(s, FALSE); + return status; +} + +BOOL bitmap_interleaved_context_reset(BITMAP_INTERLEAVED_CONTEXT* interleaved) +{ + if (!interleaved) + return FALSE; + + return TRUE; +} + +BITMAP_INTERLEAVED_CONTEXT* bitmap_interleaved_context_new(BOOL Compressor) +{ + BITMAP_INTERLEAVED_CONTEXT* interleaved = NULL; + interleaved = (BITMAP_INTERLEAVED_CONTEXT*)winpr_aligned_recalloc( + NULL, 1, sizeof(BITMAP_INTERLEAVED_CONTEXT), 32); + + if (interleaved) + { + interleaved->TempSize = 64 * 64 * 4; + interleaved->TempBuffer = winpr_aligned_calloc(interleaved->TempSize, sizeof(BYTE), 16); + + if (!interleaved->TempBuffer) + goto fail; + + interleaved->bts = Stream_New(NULL, interleaved->TempSize); + + if (!interleaved->bts) + goto fail; + } + + return interleaved; + +fail: + bitmap_interleaved_context_free(interleaved); + return NULL; +} + +void bitmap_interleaved_context_free(BITMAP_INTERLEAVED_CONTEXT* interleaved) +{ + if (!interleaved) + return; + + winpr_aligned_free(interleaved->TempBuffer); + Stream_Free(interleaved->bts, TRUE); + winpr_aligned_free(interleaved); +} diff --git a/libfreerdp/codec/jpeg.c b/libfreerdp/codec/jpeg.c new file mode 100644 index 0000000..65c8d28 --- /dev/null +++ b/libfreerdp/codec/jpeg.c @@ -0,0 +1,64 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Compressed jpeg + * + * Copyright 2012 Jay Sorg <jay.sorg@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <winpr/stream.h> +#include <winpr/image.h> + +#include <freerdp/codec/color.h> + +#include <freerdp/codec/jpeg.h> + +#ifdef WITH_JPEG + +/* jpeg decompress */ +BOOL jpeg_decompress(const BYTE* input, BYTE* output, int width, int height, int size, int bpp) +{ + BOOL rc = FALSE; + + if (bpp != 24) + return FALSE; + + wImage* image = winpr_image_new(); + if (!image) + goto fail; + + if (winpr_image_read_buffer(image, input, size) <= 0) + goto fail; + + if ((image->width != width) || (image->height != height) || (image->bitsPerPixel != bpp)) + goto fail; + + memcpy(output, image->data, 1ull * image->scanline * image->height); + rc = TRUE; + +fail: + winpr_image_free(image, TRUE); + return rc; +} + +#else + +BOOL jpeg_decompress(const BYTE* input, BYTE* output, int width, int height, int size, int bpp) +{ + return 0; +} + +#endif diff --git a/libfreerdp/codec/mppc.c b/libfreerdp/codec/mppc.c new file mode 100644 index 0000000..e4f57a7 --- /dev/null +++ b/libfreerdp/codec/mppc.c @@ -0,0 +1,857 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MPPC Bulk Data Compression + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/assert.h> +#include <freerdp/config.h> + +#include <winpr/crt.h> +#include <winpr/print.h> +#include <winpr/stream.h> +#include <winpr/bitstream.h> + +#include <freerdp/log.h> +#include "mppc.h" + +#define TAG FREERDP_TAG("codec.mppc") + +//#define DEBUG_MPPC 1 + +#define MPPC_MATCH_INDEX(_sym1, _sym2, _sym3) \ + ((((MPPC_MATCH_TABLE[_sym3] << 16) + (MPPC_MATCH_TABLE[_sym2] << 8) + \ + MPPC_MATCH_TABLE[_sym1]) & \ + 0x07FFF000) >> \ + 12) + +struct s_MPPC_CONTEXT +{ + ALIGN64 wBitStream* bs; + ALIGN64 BOOL Compressor; + ALIGN64 BYTE* HistoryPtr; + ALIGN64 UINT32 HistoryOffset; + ALIGN64 UINT32 HistoryBufferSize; + ALIGN64 BYTE HistoryBuffer[65536]; + ALIGN64 UINT16 MatchBuffer[32768]; + ALIGN64 UINT32 CompressionLevel; +}; + +static const UINT32 MPPC_MATCH_TABLE[256] = { + 0x00000000, 0x009CCF93, 0x01399F26, 0x01D66EB9, 0x02733E4C, 0x03100DDF, 0x03ACDD72, 0x0449AD05, + 0x04E67C98, 0x05834C2B, 0x06201BBE, 0x06BCEB51, 0x0759BAE4, 0x07F68A77, 0x08935A0A, 0x0930299D, + 0x09CCF930, 0x0A69C8C3, 0x0B069856, 0x0BA367E9, 0x0C40377C, 0x0CDD070F, 0x0D79D6A2, 0x0E16A635, + 0x0EB375C8, 0x0F50455B, 0x0FED14EE, 0x1089E481, 0x1126B414, 0x11C383A7, 0x1260533A, 0x12FD22CD, + 0x1399F260, 0x1436C1F3, 0x14D39186, 0x15706119, 0x160D30AC, 0x16AA003F, 0x1746CFD2, 0x17E39F65, + 0x18806EF8, 0x191D3E8B, 0x19BA0E1E, 0x1A56DDB1, 0x1AF3AD44, 0x1B907CD7, 0x1C2D4C6A, 0x1CCA1BFD, + 0x1D66EB90, 0x1E03BB23, 0x1EA08AB6, 0x1F3D5A49, 0x1FDA29DC, 0x2076F96F, 0x2113C902, 0x21B09895, + 0x224D6828, 0x22EA37BB, 0x2387074E, 0x2423D6E1, 0x24C0A674, 0x255D7607, 0x25FA459A, 0x2697152D, + 0x2733E4C0, 0x27D0B453, 0x286D83E6, 0x290A5379, 0x29A7230C, 0x2A43F29F, 0x2AE0C232, 0x2B7D91C5, + 0x2C1A6158, 0x2CB730EB, 0x2D54007E, 0x2DF0D011, 0x2E8D9FA4, 0x2F2A6F37, 0x2FC73ECA, 0x30640E5D, + 0x3100DDF0, 0x319DAD83, 0x323A7D16, 0x32D74CA9, 0x33741C3C, 0x3410EBCF, 0x34ADBB62, 0x354A8AF5, + 0x35E75A88, 0x36842A1B, 0x3720F9AE, 0x37BDC941, 0x385A98D4, 0x38F76867, 0x399437FA, 0x3A31078D, + 0x3ACDD720, 0x3B6AA6B3, 0x3C077646, 0x3CA445D9, 0x3D41156C, 0x3DDDE4FF, 0x3E7AB492, 0x3F178425, + 0x3FB453B8, 0x4051234B, 0x40EDF2DE, 0x418AC271, 0x42279204, 0x42C46197, 0x4361312A, 0x43FE00BD, + 0x449AD050, 0x45379FE3, 0x45D46F76, 0x46713F09, 0x470E0E9C, 0x47AADE2F, 0x4847ADC2, 0x48E47D55, + 0x49814CE8, 0x4A1E1C7B, 0x4ABAEC0E, 0x4B57BBA1, 0x4BF48B34, 0x4C915AC7, 0x4D2E2A5A, 0x4DCAF9ED, + 0x4E67C980, 0x4F049913, 0x4FA168A6, 0x503E3839, 0x50DB07CC, 0x5177D75F, 0x5214A6F2, 0x52B17685, + 0x534E4618, 0x53EB15AB, 0x5487E53E, 0x5524B4D1, 0x55C18464, 0x565E53F7, 0x56FB238A, 0x5797F31D, + 0x5834C2B0, 0x58D19243, 0x596E61D6, 0x5A0B3169, 0x5AA800FC, 0x5B44D08F, 0x5BE1A022, 0x5C7E6FB5, + 0x5D1B3F48, 0x5DB80EDB, 0x5E54DE6E, 0x5EF1AE01, 0x5F8E7D94, 0x602B4D27, 0x60C81CBA, 0x6164EC4D, + 0x6201BBE0, 0x629E8B73, 0x633B5B06, 0x63D82A99, 0x6474FA2C, 0x6511C9BF, 0x65AE9952, 0x664B68E5, + 0x66E83878, 0x6785080B, 0x6821D79E, 0x68BEA731, 0x695B76C4, 0x69F84657, 0x6A9515EA, 0x6B31E57D, + 0x6BCEB510, 0x6C6B84A3, 0x6D085436, 0x6DA523C9, 0x6E41F35C, 0x6EDEC2EF, 0x6F7B9282, 0x70186215, + 0x70B531A8, 0x7152013B, 0x71EED0CE, 0x728BA061, 0x73286FF4, 0x73C53F87, 0x74620F1A, 0x74FEDEAD, + 0x759BAE40, 0x76387DD3, 0x76D54D66, 0x77721CF9, 0x780EEC8C, 0x78ABBC1F, 0x79488BB2, 0x79E55B45, + 0x7A822AD8, 0x7B1EFA6B, 0x7BBBC9FE, 0x7C589991, 0x7CF56924, 0x7D9238B7, 0x7E2F084A, 0x7ECBD7DD, + 0x7F68A770, 0x80057703, 0x80A24696, 0x813F1629, 0x81DBE5BC, 0x8278B54F, 0x831584E2, 0x83B25475, + 0x844F2408, 0x84EBF39B, 0x8588C32E, 0x862592C1, 0x86C26254, 0x875F31E7, 0x87FC017A, 0x8898D10D, + 0x8935A0A0, 0x89D27033, 0x8A6F3FC6, 0x8B0C0F59, 0x8BA8DEEC, 0x8C45AE7F, 0x8CE27E12, 0x8D7F4DA5, + 0x8E1C1D38, 0x8EB8ECCB, 0x8F55BC5E, 0x8FF28BF1, 0x908F5B84, 0x912C2B17, 0x91C8FAAA, 0x9265CA3D, + 0x930299D0, 0x939F6963, 0x943C38F6, 0x94D90889, 0x9575D81C, 0x9612A7AF, 0x96AF7742, 0x974C46D5, + 0x97E91668, 0x9885E5FB, 0x9922B58E, 0x99BF8521, 0x9A5C54B4, 0x9AF92447, 0x9B95F3DA, 0x9C32C36D +}; + +int mppc_decompress(MPPC_CONTEXT* mppc, const BYTE* pSrcData, UINT32 SrcSize, + const BYTE** ppDstData, UINT32* pDstSize, UINT32 flags) +{ + BYTE Literal = 0; + BYTE* SrcPtr = NULL; + UINT32 CopyOffset = 0; + UINT32 LengthOfMatch = 0; + UINT32 accumulator = 0; + BYTE* HistoryPtr = NULL; + BYTE* HistoryBuffer = NULL; + BYTE* HistoryBufferEnd = NULL; + UINT32 HistoryBufferSize = 0; + UINT32 CompressionLevel = 0; + wBitStream* bs = NULL; + + WINPR_ASSERT(mppc); + WINPR_ASSERT(pSrcData); + WINPR_ASSERT(ppDstData); + WINPR_ASSERT(pDstSize); + + bs = mppc->bs; + WINPR_ASSERT(bs); + + HistoryBuffer = mppc->HistoryBuffer; + WINPR_ASSERT(HistoryBuffer); + + HistoryBufferSize = mppc->HistoryBufferSize; + HistoryBufferEnd = &HistoryBuffer[HistoryBufferSize - 1]; + CompressionLevel = mppc->CompressionLevel; + BitStream_Attach(bs, pSrcData, SrcSize); + BitStream_Fetch(bs); + + if (flags & PACKET_AT_FRONT) + { + mppc->HistoryOffset = 0; + mppc->HistoryPtr = HistoryBuffer; + } + + if (flags & PACKET_FLUSHED) + { + mppc->HistoryOffset = 0; + mppc->HistoryPtr = HistoryBuffer; + ZeroMemory(HistoryBuffer, mppc->HistoryBufferSize); + } + + HistoryPtr = mppc->HistoryPtr; + + if (!(flags & PACKET_COMPRESSED)) + { + *pDstSize = SrcSize; + *ppDstData = pSrcData; + return 1; + } + + while ((bs->length - bs->position) >= 8) + { + accumulator = bs->accumulator; + + /** + * Literal Encoding + */ + + if (HistoryPtr > HistoryBufferEnd) + { + WLog_ERR(TAG, "history buffer index out of range"); + return -1004; + } + + if ((accumulator & 0x80000000) == 0x00000000) + { + /** + * Literal, less than 0x80 + * bit 0 followed by the lower 7 bits of the literal + */ + Literal = ((accumulator & 0x7F000000) >> 24); + *(HistoryPtr) = Literal; + HistoryPtr++; + BitStream_Shift(bs, 8); + continue; + } + else if ((accumulator & 0xC0000000) == 0x80000000) + { + /** + * Literal, greater than 0x7F + * bits 10 followed by the lower 7 bits of the literal + */ + Literal = ((accumulator & 0x3F800000) >> 23) + 0x80; + *(HistoryPtr) = Literal; + HistoryPtr++; + BitStream_Shift(bs, 9); + continue; + } + + /** + * CopyOffset Encoding + */ + if (CompressionLevel) /* RDP5 */ + { + if ((accumulator & 0xF8000000) == 0xF8000000) + { + /** + * CopyOffset, range [0, 63] + * bits 11111 + lower 6 bits of CopyOffset + */ + CopyOffset = ((accumulator >> 21) & 0x3F); + BitStream_Shift(bs, 11); + } + else if ((accumulator & 0xF8000000) == 0xF0000000) + { + /** + * CopyOffset, range [64, 319] + * bits 11110 + lower 8 bits of (CopyOffset - 64) + */ + CopyOffset = ((accumulator >> 19) & 0xFF) + 64; + BitStream_Shift(bs, 13); + } + else if ((accumulator & 0xF0000000) == 0xE0000000) + { + /** + * CopyOffset, range [320, 2367] + * bits 1110 + lower 11 bits of (CopyOffset - 320) + */ + CopyOffset = ((accumulator >> 17) & 0x7FF) + 320; + BitStream_Shift(bs, 15); + } + else if ((accumulator & 0xE0000000) == 0xC0000000) + { + /** + * CopyOffset, range [2368, ] + * bits 110 + lower 16 bits of (CopyOffset - 2368) + */ + CopyOffset = ((accumulator >> 13) & 0xFFFF) + 2368; + BitStream_Shift(bs, 19); + } + else + { + /* Invalid CopyOffset Encoding */ + return -1001; + } + } + else /* RDP4 */ + { + if ((accumulator & 0xF0000000) == 0xF0000000) + { + /** + * CopyOffset, range [0, 63] + * bits 1111 + lower 6 bits of CopyOffset + */ + CopyOffset = ((accumulator >> 22) & 0x3F); + BitStream_Shift(bs, 10); + } + else if ((accumulator & 0xF0000000) == 0xE0000000) + { + /** + * CopyOffset, range [64, 319] + * bits 1110 + lower 8 bits of (CopyOffset - 64) + */ + CopyOffset = ((accumulator >> 20) & 0xFF) + 64; + BitStream_Shift(bs, 12); + } + else if ((accumulator & 0xE0000000) == 0xC0000000) + { + /** + * CopyOffset, range [320, 8191] + * bits 110 + lower 13 bits of (CopyOffset - 320) + */ + CopyOffset = ((accumulator >> 16) & 0x1FFF) + 320; + BitStream_Shift(bs, 16); + } + else + { + /* Invalid CopyOffset Encoding */ + return -1002; + } + } + + /** + * LengthOfMatch Encoding + */ + accumulator = bs->accumulator; + + if ((accumulator & 0x80000000) == 0x00000000) + { + /** + * LengthOfMatch [3] + * bit 0 + 0 lower bits of LengthOfMatch + */ + LengthOfMatch = 3; + BitStream_Shift(bs, 1); + } + else if ((accumulator & 0xC0000000) == 0x80000000) + { + /** + * LengthOfMatch [4, 7] + * bits 10 + 2 lower bits of LengthOfMatch + */ + LengthOfMatch = ((accumulator >> 28) & 0x0003) + 0x0004; + BitStream_Shift(bs, 4); + } + else if ((accumulator & 0xE0000000) == 0xC0000000) + { + /** + * LengthOfMatch [8, 15] + * bits 110 + 3 lower bits of LengthOfMatch + */ + LengthOfMatch = ((accumulator >> 26) & 0x0007) + 0x0008; + BitStream_Shift(bs, 6); + } + else if ((accumulator & 0xF0000000) == 0xE0000000) + { + /** + * LengthOfMatch [16, 31] + * bits 1110 + 4 lower bits of LengthOfMatch + */ + LengthOfMatch = ((accumulator >> 24) & 0x000F) + 0x0010; + BitStream_Shift(bs, 8); + } + else if ((accumulator & 0xF8000000) == 0xF0000000) + { + /** + * LengthOfMatch [32, 63] + * bits 11110 + 5 lower bits of LengthOfMatch + */ + LengthOfMatch = ((accumulator >> 22) & 0x001F) + 0x0020; + BitStream_Shift(bs, 10); + } + else if ((accumulator & 0xFC000000) == 0xF8000000) + { + /** + * LengthOfMatch [64, 127] + * bits 111110 + 6 lower bits of LengthOfMatch + */ + LengthOfMatch = ((accumulator >> 20) & 0x003F) + 0x0040; + BitStream_Shift(bs, 12); + } + else if ((accumulator & 0xFE000000) == 0xFC000000) + { + /** + * LengthOfMatch [128, 255] + * bits 1111110 + 7 lower bits of LengthOfMatch + */ + LengthOfMatch = ((accumulator >> 18) & 0x007F) + 0x0080; + BitStream_Shift(bs, 14); + } + else if ((accumulator & 0xFF000000) == 0xFE000000) + { + /** + * LengthOfMatch [256, 511] + * bits 11111110 + 8 lower bits of LengthOfMatch + */ + LengthOfMatch = ((accumulator >> 16) & 0x00FF) + 0x0100; + BitStream_Shift(bs, 16); + } + else if ((accumulator & 0xFF800000) == 0xFF000000) + { + /** + * LengthOfMatch [512, 1023] + * bits 111111110 + 9 lower bits of LengthOfMatch + */ + LengthOfMatch = ((accumulator >> 14) & 0x01FF) + 0x0200; + BitStream_Shift(bs, 18); + } + else if ((accumulator & 0xFFC00000) == 0xFF800000) + { + /** + * LengthOfMatch [1024, 2047] + * bits 1111111110 + 10 lower bits of LengthOfMatch + */ + LengthOfMatch = ((accumulator >> 12) & 0x03FF) + 0x0400; + BitStream_Shift(bs, 20); + } + else if ((accumulator & 0xFFE00000) == 0xFFC00000) + { + /** + * LengthOfMatch [2048, 4095] + * bits 11111111110 + 11 lower bits of LengthOfMatch + */ + LengthOfMatch = ((accumulator >> 10) & 0x07FF) + 0x0800; + BitStream_Shift(bs, 22); + } + else if ((accumulator & 0xFFF00000) == 0xFFE00000) + { + /** + * LengthOfMatch [4096, 8191] + * bits 111111111110 + 12 lower bits of LengthOfMatch + */ + LengthOfMatch = ((accumulator >> 8) & 0x0FFF) + 0x1000; + BitStream_Shift(bs, 24); + } + else if (((accumulator & 0xFFF80000) == 0xFFF00000) && CompressionLevel) /* RDP5 */ + { + /** + * LengthOfMatch [8192, 16383] + * bits 1111111111110 + 13 lower bits of LengthOfMatch + */ + LengthOfMatch = ((accumulator >> 6) & 0x1FFF) + 0x2000; + BitStream_Shift(bs, 26); + } + else if (((accumulator & 0xFFFC0000) == 0xFFF80000) && CompressionLevel) /* RDP5 */ + { + /** + * LengthOfMatch [16384, 32767] + * bits 11111111111110 + 14 lower bits of LengthOfMatch + */ + LengthOfMatch = ((accumulator >> 4) & 0x3FFF) + 0x4000; + BitStream_Shift(bs, 28); + } + else if (((accumulator & 0xFFFE0000) == 0xFFFC0000) && CompressionLevel) /* RDP5 */ + { + /** + * LengthOfMatch [32768, 65535] + * bits 111111111111110 + 15 lower bits of LengthOfMatch + */ + LengthOfMatch = ((accumulator >> 2) & 0x7FFF) + 0x8000; + BitStream_Shift(bs, 30); + } + else + { + /* Invalid LengthOfMatch Encoding */ + return -1003; + } + +#if defined(DEBUG_MPPC) + WLog_DBG(TAG, "<%" PRIu32 ",%" PRIu32 ">", CopyOffset, LengthOfMatch); +#endif + + if ((HistoryPtr + LengthOfMatch - 1) > HistoryBufferEnd) + { + WLog_ERR(TAG, "history buffer overflow"); + return -1005; + } + + SrcPtr = &HistoryBuffer[(HistoryPtr - HistoryBuffer - CopyOffset) & + (CompressionLevel ? 0xFFFF : 0x1FFF)]; + + do + { + *HistoryPtr++ = *SrcPtr++; + } while (--LengthOfMatch); + } + + *pDstSize = (UINT32)(HistoryPtr - mppc->HistoryPtr); + *ppDstData = mppc->HistoryPtr; + mppc->HistoryPtr = HistoryPtr; + return 1; +} + +int mppc_compress(MPPC_CONTEXT* mppc, const BYTE* pSrcData, UINT32 SrcSize, BYTE* pDstBuffer, + const BYTE** ppDstData, UINT32* pDstSize, UINT32* pFlags) +{ + const BYTE* pSrcPtr = NULL; + const BYTE* pSrcEnd = NULL; + BYTE* MatchPtr = NULL; + UINT32 DstSize = 0; + BYTE* pDstData = NULL; + UINT32 MatchIndex = 0; + UINT32 accumulator = 0; + BOOL PacketFlushed = 0; + BOOL PacketAtFront = 0; + DWORD CopyOffset = 0; + DWORD LengthOfMatch = 0; + BYTE* HistoryBuffer = NULL; + BYTE* HistoryPtr = NULL; + UINT32 HistoryOffset = 0; + UINT32 HistoryBufferSize = 0; + BYTE Sym1 = 0; + BYTE Sym2 = 0; + BYTE Sym3 = 0; + UINT32 CompressionLevel = 0; + wBitStream* bs = NULL; + + WINPR_ASSERT(mppc); + WINPR_ASSERT(pSrcData); + WINPR_ASSERT(pDstBuffer); + WINPR_ASSERT(ppDstData); + WINPR_ASSERT(pDstSize); + WINPR_ASSERT(pFlags); + + bs = mppc->bs; + WINPR_ASSERT(bs); + + HistoryBuffer = mppc->HistoryBuffer; + WINPR_ASSERT(HistoryBuffer); + + HistoryBufferSize = mppc->HistoryBufferSize; + CompressionLevel = mppc->CompressionLevel; + HistoryOffset = mppc->HistoryOffset; + *pFlags = 0; + PacketFlushed = FALSE; + + if (((HistoryOffset + SrcSize) < (HistoryBufferSize - 3)) && HistoryOffset) + { + PacketAtFront = FALSE; + } + else + { + if (HistoryOffset == (HistoryBufferSize + 1)) + PacketFlushed = TRUE; + + HistoryOffset = 0; + PacketAtFront = TRUE; + } + + HistoryPtr = &(HistoryBuffer[HistoryOffset]); + pDstData = pDstBuffer; + *ppDstData = pDstBuffer; + + if (!pDstData) + return -1; + + if (*pDstSize > SrcSize) + DstSize = SrcSize; + else + DstSize = *pDstSize; + + BitStream_Attach(bs, pDstData, DstSize); + pSrcPtr = pSrcData; + pSrcEnd = &(pSrcData[SrcSize - 1]); + + while (pSrcPtr < (pSrcEnd - 2)) + { + Sym1 = pSrcPtr[0]; + Sym2 = pSrcPtr[1]; + Sym3 = pSrcPtr[2]; + *HistoryPtr++ = *pSrcPtr++; + MatchIndex = MPPC_MATCH_INDEX(Sym1, Sym2, Sym3); + MatchPtr = &(HistoryBuffer[mppc->MatchBuffer[MatchIndex]]); + + if (MatchPtr != (HistoryPtr - 1)) + mppc->MatchBuffer[MatchIndex] = (UINT16)(HistoryPtr - HistoryBuffer); + + if (mppc->HistoryPtr < HistoryPtr) + mppc->HistoryPtr = HistoryPtr; + + if ((Sym1 != *(MatchPtr - 1)) || (Sym2 != MatchPtr[0]) || (Sym3 != MatchPtr[1]) || + (&MatchPtr[1] > mppc->HistoryPtr) || (MatchPtr == HistoryBuffer) || + (MatchPtr == (HistoryPtr - 1)) || (MatchPtr == HistoryPtr)) + { + if (((bs->position / 8) + 2) > (DstSize - 1)) + { + mppc_context_reset(mppc, TRUE); + *pFlags |= PACKET_FLUSHED; + *pFlags |= CompressionLevel; + *ppDstData = pSrcData; + *pDstSize = SrcSize; + return 1; + } + + accumulator = Sym1; +#if defined(DEBUG_MPPC) + WLog_DBG(TAG, "%" PRIu32 "", accumulator); +#endif + + if (accumulator < 0x80) + { + /* 8 bits of literal are encoded as-is */ + BitStream_Write_Bits(bs, accumulator, 8); + } + else + { + /* bits 10 followed by lower 7 bits of literal */ + accumulator = 0x100 | (accumulator & 0x7F); + BitStream_Write_Bits(bs, accumulator, 9); + } + } + else + { + CopyOffset = (HistoryBufferSize - 1) & (HistoryPtr - MatchPtr); + *HistoryPtr++ = Sym2; + *HistoryPtr++ = Sym3; + pSrcPtr += 2; + LengthOfMatch = 3; + MatchPtr += 2; + + while ((*pSrcPtr == *MatchPtr) && (pSrcPtr < pSrcEnd) && (MatchPtr <= mppc->HistoryPtr)) + { + MatchPtr++; + *HistoryPtr++ = *pSrcPtr++; + LengthOfMatch++; + } + +#if defined(DEBUG_MPPC) + WLog_DBG(TAG, "<%" PRIu32 ",%" PRIu32 ">", CopyOffset, LengthOfMatch); +#endif + + /* Encode CopyOffset */ + + if (((bs->position / 8) + 7) > (DstSize - 1)) + { + mppc_context_reset(mppc, TRUE); + *pFlags |= PACKET_FLUSHED; + *pFlags |= CompressionLevel; + *ppDstData = pSrcData; + *pDstSize = SrcSize; + return 1; + } + + if (CompressionLevel) /* RDP5 */ + { + if (CopyOffset < 64) + { + /* bits 11111 + lower 6 bits of CopyOffset */ + accumulator = 0x07C0 | (CopyOffset & 0x003F); + BitStream_Write_Bits(bs, accumulator, 11); + } + else if ((CopyOffset >= 64) && (CopyOffset < 320)) + { + /* bits 11110 + lower 8 bits of (CopyOffset - 64) */ + accumulator = 0x1E00 | ((CopyOffset - 64) & 0x00FF); + BitStream_Write_Bits(bs, accumulator, 13); + } + else if ((CopyOffset >= 320) && (CopyOffset < 2368)) + { + /* bits 1110 + lower 11 bits of (CopyOffset - 320) */ + accumulator = 0x7000 | ((CopyOffset - 320) & 0x07FF); + BitStream_Write_Bits(bs, accumulator, 15); + } + else + { + /* bits 110 + lower 16 bits of (CopyOffset - 2368) */ + accumulator = 0x060000 | ((CopyOffset - 2368) & 0xFFFF); + BitStream_Write_Bits(bs, accumulator, 19); + } + } + else /* RDP4 */ + { + if (CopyOffset < 64) + { + /* bits 1111 + lower 6 bits of CopyOffset */ + accumulator = 0x03C0 | (CopyOffset & 0x003F); + BitStream_Write_Bits(bs, accumulator, 10); + } + else if ((CopyOffset >= 64) && (CopyOffset < 320)) + { + /* bits 1110 + lower 8 bits of (CopyOffset - 64) */ + accumulator = 0x0E00 | ((CopyOffset - 64) & 0x00FF); + BitStream_Write_Bits(bs, accumulator, 12); + } + else if ((CopyOffset >= 320) && (CopyOffset < 8192)) + { + /* bits 110 + lower 13 bits of (CopyOffset - 320) */ + accumulator = 0xC000 | ((CopyOffset - 320) & 0x1FFF); + BitStream_Write_Bits(bs, accumulator, 16); + } + } + + /* Encode LengthOfMatch */ + + if (LengthOfMatch == 3) + { + /* 0 + 0 lower bits of LengthOfMatch */ + BitStream_Write_Bits(bs, 0, 1); + } + else if ((LengthOfMatch >= 4) && (LengthOfMatch < 8)) + { + /* 10 + 2 lower bits of LengthOfMatch */ + accumulator = 0x0008 | (LengthOfMatch & 0x0003); + BitStream_Write_Bits(bs, accumulator, 4); + } + else if ((LengthOfMatch >= 8) && (LengthOfMatch < 16)) + { + /* 110 + 3 lower bits of LengthOfMatch */ + accumulator = 0x0030 | (LengthOfMatch & 0x0007); + BitStream_Write_Bits(bs, accumulator, 6); + } + else if ((LengthOfMatch >= 16) && (LengthOfMatch < 32)) + { + /* 1110 + 4 lower bits of LengthOfMatch */ + accumulator = 0x00E0 | (LengthOfMatch & 0x000F); + BitStream_Write_Bits(bs, accumulator, 8); + } + else if ((LengthOfMatch >= 32) && (LengthOfMatch < 64)) + { + /* 11110 + 5 lower bits of LengthOfMatch */ + accumulator = 0x03C0 | (LengthOfMatch & 0x001F); + BitStream_Write_Bits(bs, accumulator, 10); + } + else if ((LengthOfMatch >= 64) && (LengthOfMatch < 128)) + { + /* 111110 + 6 lower bits of LengthOfMatch */ + accumulator = 0x0F80 | (LengthOfMatch & 0x003F); + BitStream_Write_Bits(bs, accumulator, 12); + } + else if ((LengthOfMatch >= 128) && (LengthOfMatch < 256)) + { + /* 1111110 + 7 lower bits of LengthOfMatch */ + accumulator = 0x3F00 | (LengthOfMatch & 0x007F); + BitStream_Write_Bits(bs, accumulator, 14); + } + else if ((LengthOfMatch >= 256) && (LengthOfMatch < 512)) + { + /* 11111110 + 8 lower bits of LengthOfMatch */ + accumulator = 0xFE00 | (LengthOfMatch & 0x00FF); + BitStream_Write_Bits(bs, accumulator, 16); + } + else if ((LengthOfMatch >= 512) && (LengthOfMatch < 1024)) + { + /* 111111110 + 9 lower bits of LengthOfMatch */ + accumulator = 0x3FC00 | (LengthOfMatch & 0x01FF); + BitStream_Write_Bits(bs, accumulator, 18); + } + else if ((LengthOfMatch >= 1024) && (LengthOfMatch < 2048)) + { + /* 1111111110 + 10 lower bits of LengthOfMatch */ + accumulator = 0xFF800 | (LengthOfMatch & 0x03FF); + BitStream_Write_Bits(bs, accumulator, 20); + } + else if ((LengthOfMatch >= 2048) && (LengthOfMatch < 4096)) + { + /* 11111111110 + 11 lower bits of LengthOfMatch */ + accumulator = 0x3FF000 | (LengthOfMatch & 0x07FF); + BitStream_Write_Bits(bs, accumulator, 22); + } + else if ((LengthOfMatch >= 4096) && (LengthOfMatch < 8192)) + { + /* 111111111110 + 12 lower bits of LengthOfMatch */ + accumulator = 0xFFE000 | (LengthOfMatch & 0x0FFF); + BitStream_Write_Bits(bs, accumulator, 24); + } + else if (((LengthOfMatch >= 8192) && (LengthOfMatch < 16384)) && + CompressionLevel) /* RDP5 */ + { + /* 1111111111110 + 13 lower bits of LengthOfMatch */ + accumulator = 0x3FFC000 | (LengthOfMatch & 0x1FFF); + BitStream_Write_Bits(bs, accumulator, 26); + } + else if (((LengthOfMatch >= 16384) && (LengthOfMatch < 32768)) && + CompressionLevel) /* RDP5 */ + { + /* 11111111111110 + 14 lower bits of LengthOfMatch */ + accumulator = 0xFFF8000 | (LengthOfMatch & 0x3FFF); + BitStream_Write_Bits(bs, accumulator, 28); + } + else if (((LengthOfMatch >= 32768) && (LengthOfMatch < 65536)) && + CompressionLevel) /* RDP5 */ + { + /* 111111111111110 + 15 lower bits of LengthOfMatch */ + accumulator = 0x3FFF0000 | (LengthOfMatch & 0x7FFF); + BitStream_Write_Bits(bs, accumulator, 30); + } + } + } + + /* Encode trailing symbols as literals */ + + while (pSrcPtr <= pSrcEnd) + { + if (((bs->position / 8) + 2) > (DstSize - 1)) + { + mppc_context_reset(mppc, TRUE); + *pFlags |= PACKET_FLUSHED; + *pFlags |= CompressionLevel; + *ppDstData = pSrcData; + *pDstSize = SrcSize; + return 1; + } + + accumulator = *pSrcPtr; +#if defined(DEBUG_MPPC) + WLog_DBG(TAG, "%" PRIu32 "", accumulator); +#endif + + if (accumulator < 0x80) + { + /* 8 bits of literal are encoded as-is */ + BitStream_Write_Bits(bs, accumulator, 8); + } + else + { + /* bits 10 followed by lower 7 bits of literal */ + accumulator = 0x100 | (accumulator & 0x7F); + BitStream_Write_Bits(bs, accumulator, 9); + } + + *HistoryPtr++ = *pSrcPtr++; + } + + BitStream_Flush(bs); + *pFlags |= PACKET_COMPRESSED; + *pFlags |= CompressionLevel; + + if (PacketAtFront) + *pFlags |= PACKET_AT_FRONT; + + if (PacketFlushed) + *pFlags |= PACKET_FLUSHED; + + *pDstSize = ((bs->position + 7) / 8); + mppc->HistoryPtr = HistoryPtr; + mppc->HistoryOffset = HistoryPtr - HistoryBuffer; + return 1; +} + +void mppc_set_compression_level(MPPC_CONTEXT* mppc, DWORD CompressionLevel) +{ + WINPR_ASSERT(mppc); + + if (CompressionLevel < 1) + { + mppc->CompressionLevel = 0; + mppc->HistoryBufferSize = 8192; + } + else + { + mppc->CompressionLevel = 1; + mppc->HistoryBufferSize = 65536; + } +} + +void mppc_context_reset(MPPC_CONTEXT* mppc, BOOL flush) +{ + WINPR_ASSERT(mppc); + + ZeroMemory(&(mppc->HistoryBuffer), sizeof(mppc->HistoryBuffer)); + ZeroMemory(&(mppc->MatchBuffer), sizeof(mppc->MatchBuffer)); + + if (flush) + { + mppc->HistoryOffset = mppc->HistoryBufferSize + 1; + mppc->HistoryPtr = mppc->HistoryBuffer; + } + else + { + mppc->HistoryOffset = 0; + mppc->HistoryPtr = &(mppc->HistoryBuffer[mppc->HistoryOffset]); + } +} + +MPPC_CONTEXT* mppc_context_new(DWORD CompressionLevel, BOOL Compressor) +{ + MPPC_CONTEXT* mppc = calloc(1, sizeof(MPPC_CONTEXT)); + + if (!mppc) + goto fail; + + mppc->Compressor = Compressor; + + if (CompressionLevel < 1) + { + mppc->CompressionLevel = 0; + mppc->HistoryBufferSize = 8192; + } + else + { + mppc->CompressionLevel = 1; + mppc->HistoryBufferSize = 65536; + } + + mppc->bs = BitStream_New(); + + if (!mppc->bs) + goto fail; + + mppc_context_reset(mppc, FALSE); + + return mppc; + +fail: + mppc_context_free(mppc); + return NULL; +} + +void mppc_context_free(MPPC_CONTEXT* mppc) +{ + if (mppc) + { + BitStream_Free(mppc->bs); + free(mppc); + } +} diff --git a/libfreerdp/codec/mppc.h b/libfreerdp/codec/mppc.h new file mode 100644 index 0000000..a4b3c48 --- /dev/null +++ b/libfreerdp/codec/mppc.h @@ -0,0 +1,54 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * MPPC Bulk Data Compression + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_MPPC_H +#define FREERDP_MPPC_H + +#include <freerdp/api.h> +#include <freerdp/types.h> + +#include <winpr/bitstream.h> + +#include <freerdp/codec/bulk.h> + +typedef struct s_MPPC_CONTEXT MPPC_CONTEXT; + +#ifdef __cplusplus +extern "C" +{ +#endif + + FREERDP_LOCAL int mppc_compress(MPPC_CONTEXT* mppc, const BYTE* pSrcData, UINT32 SrcSize, + BYTE* pDstBuffer, const BYTE** ppDstData, UINT32* pDstSize, + UINT32* pFlags); + FREERDP_LOCAL int mppc_decompress(MPPC_CONTEXT* mppc, const BYTE* pSrcData, UINT32 SrcSize, + const BYTE** ppDstData, UINT32* pDstSize, UINT32 flags); + + FREERDP_LOCAL void mppc_set_compression_level(MPPC_CONTEXT* mppc, DWORD CompressionLevel); + + FREERDP_LOCAL void mppc_context_reset(MPPC_CONTEXT* mppc, BOOL flush); + + FREERDP_LOCAL MPPC_CONTEXT* mppc_context_new(DWORD CompressionLevel, BOOL Compressor); + FREERDP_LOCAL void mppc_context_free(MPPC_CONTEXT* mppc); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_MPPC_H */ diff --git a/libfreerdp/codec/ncrush.c b/libfreerdp/codec/ncrush.c new file mode 100644 index 0000000..4a7162c --- /dev/null +++ b/libfreerdp/codec/ncrush.c @@ -0,0 +1,3045 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * NCrush (RDP6) Bulk Data Compression + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2017 Armin Novak <armin.novak@thincast.com> + * Copyright 2017 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/assert.h> + +#include <freerdp/config.h> + +#include <winpr/crt.h> +#include <winpr/print.h> +#include <winpr/bitstream.h> + +#include <freerdp/log.h> +#include <freerdp/types.h> + +#include "ncrush.h" + +#define TAG FREERDP_TAG("codec") + +struct s_NCRUSH_CONTEXT +{ + ALIGN64 BOOL Compressor; + ALIGN64 BYTE* HistoryPtr; + ALIGN64 UINT32 HistoryOffset; + ALIGN64 UINT32 HistoryEndOffset; + ALIGN64 UINT32 HistoryBufferSize; + ALIGN64 BYTE HistoryBuffer[65536]; + ALIGN64 UINT32 HistoryBufferFence; + ALIGN64 UINT32 OffsetCache[4]; + ALIGN64 UINT16 HashTable[65536]; + ALIGN64 UINT16 MatchTable[65536]; + ALIGN64 BYTE HuffTableCopyOffset[1024]; + ALIGN64 BYTE HuffTableLOM[4096]; +}; + +static const UINT16 HuffTableLEC[8192] = { + 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA068, 0x5111, 0x7007, 0x6113, 0x90C0, + 0x6108, 0x8018, 0x611B, 0xA0B3, 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA091, + 0x5121, 0x7080, 0x6115, 0xA03A, 0x610A, 0x9012, 0x611D, 0xA0D7, 0x510B, 0x6122, 0x610E, 0x9035, + 0x6001, 0x7123, 0x6118, 0xA07A, 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C4, + 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A1, 0x5121, 0x7102, 0x6116, 0xA056, + 0x610C, 0x901D, 0x611E, 0xA0E8, 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA071, + 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BB, 0x510F, 0x7004, 0x6110, 0x9049, + 0x6002, 0x800D, 0x6119, 0xA099, 0x5121, 0x70FF, 0x6115, 0xA04C, 0x610A, 0x9017, 0x611D, 0xA0DF, + 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA087, 0x5111, 0x700A, 0x6114, 0xA023, + 0x6109, 0x80FE, 0x611C, 0xA0CE, 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0A9, + 0x5121, 0x7103, 0x6116, 0xA05F, 0x610C, 0x9022, 0x611E, 0xA0F5, 0x510B, 0x611F, 0x610D, 0x9029, + 0x6000, 0x7105, 0x6117, 0xA06C, 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B7, + 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA095, 0x5121, 0x7080, 0x6115, 0xA046, + 0x610A, 0x9015, 0x611D, 0xA0DB, 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA07E, + 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0C9, 0x510F, 0x7005, 0x6112, 0x907F, + 0x6107, 0x8010, 0x611A, 0xA0A5, 0x5121, 0x7102, 0x6116, 0xA05B, 0x610C, 0x901F, 0x611E, 0xA0EC, + 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA075, 0x5111, 0x7008, 0x6113, 0x90F1, + 0x6108, 0x8040, 0x611B, 0xA0BF, 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09D, + 0x5121, 0x70FF, 0x6115, 0xA052, 0x610A, 0x901B, 0x611D, 0xA0E4, 0x510B, 0x6122, 0x610E, 0x903F, + 0x6001, 0x7124, 0x6118, 0xA08C, 0x5111, 0x700A, 0x6114, 0xA02F, 0x6109, 0x8120, 0x611C, 0xA0D3, + 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AE, 0x5121, 0x7103, 0x6116, 0xA064, + 0x610C, 0x9025, 0x611E, 0xA0FA, 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06A, + 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B5, 0x510F, 0x7003, 0x6110, 0x9043, + 0x6002, 0x800B, 0x6119, 0xA093, 0x5121, 0x7080, 0x6115, 0xA03D, 0x610A, 0x9014, 0x611D, 0xA0D9, + 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07C, 0x5111, 0x7009, 0x6114, 0x90F8, + 0x6109, 0x8060, 0x611C, 0xA0C7, 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A3, + 0x5121, 0x7102, 0x6116, 0xA058, 0x610C, 0x901E, 0x611E, 0xA0EA, 0x510B, 0x611F, 0x610D, 0x9030, + 0x6000, 0x7106, 0x6117, 0xA073, 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BD, + 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09B, 0x5121, 0x70FF, 0x6115, 0xA04E, + 0x610A, 0x901A, 0x611D, 0xA0E2, 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08A, + 0x5111, 0x700A, 0x6114, 0xA02D, 0x6109, 0x80FE, 0x611C, 0xA0D1, 0x510F, 0x7006, 0x6112, 0x9084, + 0x6107, 0x8011, 0x611A, 0xA0AC, 0x5121, 0x7103, 0x6116, 0xA062, 0x610C, 0x9024, 0x611E, 0xA0F7, + 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06E, 0x5111, 0x7007, 0x6113, 0x90D0, + 0x6108, 0x8019, 0x611B, 0xA0B9, 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA097, + 0x5121, 0x7080, 0x6115, 0xA04A, 0x610A, 0x9016, 0x611D, 0xA0DD, 0x510B, 0x6122, 0x610E, 0x9039, + 0x6001, 0x7123, 0x6118, 0xA085, 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CB, + 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A7, 0x5121, 0x7102, 0x6116, 0xA05D, + 0x610C, 0x9021, 0x611E, 0xA0EF, 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA077, + 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C2, 0x510F, 0x7004, 0x6110, 0x9059, + 0x6002, 0x800E, 0x6119, 0xA09F, 0x5121, 0x70FF, 0x6115, 0xA054, 0x610A, 0x901C, 0x611D, 0xA0E6, + 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08E, 0x5111, 0x700A, 0x6114, 0xA034, + 0x6109, 0x8120, 0x611C, 0xA0D5, 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B0, + 0x5121, 0x7103, 0x6116, 0xA066, 0x610C, 0x9026, 0x611E, 0xA104, 0x510B, 0x611F, 0x610D, 0x9027, + 0x6000, 0x7105, 0x6117, 0xA069, 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B4, + 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA092, 0x5121, 0x7080, 0x6115, 0xA03B, + 0x610A, 0x9012, 0x611D, 0xA0D8, 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07B, + 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C5, 0x510F, 0x7005, 0x6112, 0x9070, + 0x6107, 0x800F, 0x611A, 0xA0A2, 0x5121, 0x7102, 0x6116, 0xA057, 0x610C, 0x901D, 0x611E, 0xA0E9, + 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA072, 0x5111, 0x7008, 0x6113, 0x90E0, + 0x6108, 0x8020, 0x611B, 0xA0BC, 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA09A, + 0x5121, 0x70FF, 0x6115, 0xA04D, 0x610A, 0x9017, 0x611D, 0xA0E1, 0x510B, 0x6122, 0x610E, 0x903C, + 0x6001, 0x7124, 0x6118, 0xA089, 0x5111, 0x700A, 0x6114, 0xA02B, 0x6109, 0x80FE, 0x611C, 0xA0CF, + 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0AA, 0x5121, 0x7103, 0x6116, 0xA061, + 0x610C, 0x9022, 0x611E, 0xA0F6, 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06D, + 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B8, 0x510F, 0x7003, 0x6110, 0x9044, + 0x6002, 0x800C, 0x6119, 0xA096, 0x5121, 0x7080, 0x6115, 0xA047, 0x610A, 0x9015, 0x611D, 0xA0DC, + 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA083, 0x5111, 0x7009, 0x6114, 0x90FC, + 0x6109, 0x80F0, 0x611C, 0xA0CA, 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A6, + 0x5121, 0x7102, 0x6116, 0xA05C, 0x610C, 0x901F, 0x611E, 0xA0ED, 0x510B, 0x611F, 0x610D, 0x9031, + 0x6000, 0x7106, 0x6117, 0xA076, 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0C1, + 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09E, 0x5121, 0x70FF, 0x6115, 0xA053, + 0x610A, 0x901B, 0x611D, 0xA0E5, 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08D, + 0x5111, 0x700A, 0x6114, 0xA032, 0x6109, 0x8120, 0x611C, 0xA0D4, 0x510F, 0x7006, 0x6112, 0x9088, + 0x6107, 0x8013, 0x611A, 0xA0AF, 0x5121, 0x7103, 0x6116, 0xA065, 0x610C, 0x9025, 0x611E, 0xA0FB, + 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06B, 0x5111, 0x7007, 0x6113, 0x90C6, + 0x6108, 0x8018, 0x611B, 0xA0B6, 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA094, + 0x5121, 0x7080, 0x6115, 0xA045, 0x610A, 0x9014, 0x611D, 0xA0DA, 0x510B, 0x6122, 0x610E, 0x9037, + 0x6001, 0x7123, 0x6118, 0xA07D, 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C8, + 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A4, 0x5121, 0x7102, 0x6116, 0xA05A, + 0x610C, 0x901E, 0x611E, 0xA0EB, 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA074, + 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BE, 0x510F, 0x7004, 0x6110, 0x9050, + 0x6002, 0x800D, 0x6119, 0xA09C, 0x5121, 0x70FF, 0x6115, 0xA04F, 0x610A, 0x901A, 0x611D, 0xA0E3, + 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08B, 0x5111, 0x700A, 0x6114, 0xA02E, + 0x6109, 0x80FE, 0x611C, 0xA0D2, 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AD, + 0x5121, 0x7103, 0x6116, 0xA063, 0x610C, 0x9024, 0x611E, 0xA0F9, 0x510B, 0x611F, 0x610D, 0x902A, + 0x6000, 0x7105, 0x6117, 0xA06F, 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0BA, + 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA098, 0x5121, 0x7080, 0x6115, 0xA04B, + 0x610A, 0x9016, 0x611D, 0xA0DE, 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA086, + 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CD, 0x510F, 0x7005, 0x6112, 0x9081, + 0x6107, 0x8010, 0x611A, 0xA0A8, 0x5121, 0x7102, 0x6116, 0xA05E, 0x610C, 0x9021, 0x611E, 0xA0F3, + 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA079, 0x5111, 0x7008, 0x6113, 0x90F2, + 0x6108, 0x8040, 0x611B, 0xA0C3, 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA0A0, + 0x5121, 0x70FF, 0x6115, 0xA055, 0x610A, 0x901C, 0x611D, 0xA0E7, 0x510B, 0x6122, 0x610E, 0x9041, + 0x6001, 0x7124, 0x6118, 0xA08F, 0x5111, 0x700A, 0x6114, 0xA036, 0x6109, 0x8120, 0x611C, 0xA0D6, + 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B1, 0x5121, 0x7103, 0x6116, 0xA067, + 0x610C, 0x9026, 0x611E, 0xB0B2, 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA068, + 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B3, 0x510F, 0x7003, 0x6110, 0x9042, + 0x6002, 0x800B, 0x6119, 0xA091, 0x5121, 0x7080, 0x6115, 0xA03A, 0x610A, 0x9012, 0x611D, 0xA0D7, + 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07A, 0x5111, 0x7009, 0x6114, 0x90F4, + 0x6109, 0x8060, 0x611C, 0xA0C4, 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A1, + 0x5121, 0x7102, 0x6116, 0xA056, 0x610C, 0x901D, 0x611E, 0xA0E8, 0x510B, 0x611F, 0x610D, 0x902C, + 0x6000, 0x7106, 0x6117, 0xA071, 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BB, + 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA099, 0x5121, 0x70FF, 0x6115, 0xA04C, + 0x610A, 0x9017, 0x611D, 0xA0DF, 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA087, + 0x5111, 0x700A, 0x6114, 0xA023, 0x6109, 0x80FE, 0x611C, 0xA0CE, 0x510F, 0x7006, 0x6112, 0x9082, + 0x6107, 0x8011, 0x611A, 0xA0A9, 0x5121, 0x7103, 0x6116, 0xA05F, 0x610C, 0x9022, 0x611E, 0xA0F5, + 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06C, 0x5111, 0x7007, 0x6113, 0x90CC, + 0x6108, 0x8019, 0x611B, 0xA0B7, 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA095, + 0x5121, 0x7080, 0x6115, 0xA046, 0x610A, 0x9015, 0x611D, 0xA0DB, 0x510B, 0x6122, 0x610E, 0x9038, + 0x6001, 0x7123, 0x6118, 0xA07E, 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0C9, + 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A5, 0x5121, 0x7102, 0x6116, 0xA05B, + 0x610C, 0x901F, 0x611E, 0xA0EC, 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA075, + 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0BF, 0x510F, 0x7004, 0x6110, 0x9051, + 0x6002, 0x800E, 0x6119, 0xA09D, 0x5121, 0x70FF, 0x6115, 0xA052, 0x610A, 0x901B, 0x611D, 0xA0E4, + 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08C, 0x5111, 0x700A, 0x6114, 0xA02F, + 0x6109, 0x8120, 0x611C, 0xA0D3, 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AE, + 0x5121, 0x7103, 0x6116, 0xA064, 0x610C, 0x9025, 0x611E, 0xA0FA, 0x510B, 0x611F, 0x610D, 0x9028, + 0x6000, 0x7105, 0x6117, 0xA06A, 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B5, + 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA093, 0x5121, 0x7080, 0x6115, 0xA03D, + 0x610A, 0x9014, 0x611D, 0xA0D9, 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07C, + 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C7, 0x510F, 0x7005, 0x6112, 0x9078, + 0x6107, 0x800F, 0x611A, 0xA0A3, 0x5121, 0x7102, 0x6116, 0xA058, 0x610C, 0x901E, 0x611E, 0xA0EA, + 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA073, 0x5111, 0x7008, 0x6113, 0x90EE, + 0x6108, 0x8020, 0x611B, 0xA0BD, 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09B, + 0x5121, 0x70FF, 0x6115, 0xA04E, 0x610A, 0x901A, 0x611D, 0xA0E2, 0x510B, 0x6122, 0x610E, 0x903E, + 0x6001, 0x7124, 0x6118, 0xA08A, 0x5111, 0x700A, 0x6114, 0xA02D, 0x6109, 0x80FE, 0x611C, 0xA0D1, + 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AC, 0x5121, 0x7103, 0x6116, 0xA062, + 0x610C, 0x9024, 0x611E, 0xA0F7, 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06E, + 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0B9, 0x510F, 0x7003, 0x6110, 0x9048, + 0x6002, 0x800C, 0x6119, 0xA097, 0x5121, 0x7080, 0x6115, 0xA04A, 0x610A, 0x9016, 0x611D, 0xA0DD, + 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA085, 0x5111, 0x7009, 0x6114, 0x90FD, + 0x6109, 0x80F0, 0x611C, 0xA0CB, 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A7, + 0x5121, 0x7102, 0x6116, 0xA05D, 0x610C, 0x9021, 0x611E, 0xA0EF, 0x510B, 0x611F, 0x610D, 0x9033, + 0x6000, 0x7106, 0x6117, 0xA077, 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C2, + 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA09F, 0x5121, 0x70FF, 0x6115, 0xA054, + 0x610A, 0x901C, 0x611D, 0xA0E6, 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08E, + 0x5111, 0x700A, 0x6114, 0xA034, 0x6109, 0x8120, 0x611C, 0xA0D5, 0x510F, 0x7006, 0x6112, 0x9090, + 0x6107, 0x8013, 0x611A, 0xA0B0, 0x5121, 0x7103, 0x6116, 0xA066, 0x610C, 0x9026, 0x611E, 0xA104, + 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA069, 0x5111, 0x7007, 0x6113, 0x90C0, + 0x6108, 0x8018, 0x611B, 0xA0B4, 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA092, + 0x5121, 0x7080, 0x6115, 0xA03B, 0x610A, 0x9012, 0x611D, 0xA0D8, 0x510B, 0x6122, 0x610E, 0x9035, + 0x6001, 0x7123, 0x6118, 0xA07B, 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C5, + 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A2, 0x5121, 0x7102, 0x6116, 0xA057, + 0x610C, 0x901D, 0x611E, 0xA0E9, 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA072, + 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BC, 0x510F, 0x7004, 0x6110, 0x9049, + 0x6002, 0x800D, 0x6119, 0xA09A, 0x5121, 0x70FF, 0x6115, 0xA04D, 0x610A, 0x9017, 0x611D, 0xA0E1, + 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA089, 0x5111, 0x700A, 0x6114, 0xA02B, + 0x6109, 0x80FE, 0x611C, 0xA0CF, 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0AA, + 0x5121, 0x7103, 0x6116, 0xA061, 0x610C, 0x9022, 0x611E, 0xA0F6, 0x510B, 0x611F, 0x610D, 0x9029, + 0x6000, 0x7105, 0x6117, 0xA06D, 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B8, + 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA096, 0x5121, 0x7080, 0x6115, 0xA047, + 0x610A, 0x9015, 0x611D, 0xA0DC, 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA083, + 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0CA, 0x510F, 0x7005, 0x6112, 0x907F, + 0x6107, 0x8010, 0x611A, 0xA0A6, 0x5121, 0x7102, 0x6116, 0xA05C, 0x610C, 0x901F, 0x611E, 0xA0ED, + 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA076, 0x5111, 0x7008, 0x6113, 0x90F1, + 0x6108, 0x8040, 0x611B, 0xA0C1, 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09E, + 0x5121, 0x70FF, 0x6115, 0xA053, 0x610A, 0x901B, 0x611D, 0xA0E5, 0x510B, 0x6122, 0x610E, 0x903F, + 0x6001, 0x7124, 0x6118, 0xA08D, 0x5111, 0x700A, 0x6114, 0xA032, 0x6109, 0x8120, 0x611C, 0xA0D4, + 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AF, 0x5121, 0x7103, 0x6116, 0xA065, + 0x610C, 0x9025, 0x611E, 0xA0FB, 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06B, + 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B6, 0x510F, 0x7003, 0x6110, 0x9043, + 0x6002, 0x800B, 0x6119, 0xA094, 0x5121, 0x7080, 0x6115, 0xA045, 0x610A, 0x9014, 0x611D, 0xA0DA, + 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07D, 0x5111, 0x7009, 0x6114, 0x90F8, + 0x6109, 0x8060, 0x611C, 0xA0C8, 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A4, + 0x5121, 0x7102, 0x6116, 0xA05A, 0x610C, 0x901E, 0x611E, 0xA0EB, 0x510B, 0x611F, 0x610D, 0x9030, + 0x6000, 0x7106, 0x6117, 0xA074, 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BE, + 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09C, 0x5121, 0x70FF, 0x6115, 0xA04F, + 0x610A, 0x901A, 0x611D, 0xA0E3, 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08B, + 0x5111, 0x700A, 0x6114, 0xA02E, 0x6109, 0x80FE, 0x611C, 0xA0D2, 0x510F, 0x7006, 0x6112, 0x9084, + 0x6107, 0x8011, 0x611A, 0xA0AD, 0x5121, 0x7103, 0x6116, 0xA063, 0x610C, 0x9024, 0x611E, 0xA0F9, + 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06F, 0x5111, 0x7007, 0x6113, 0x90D0, + 0x6108, 0x8019, 0x611B, 0xA0BA, 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA098, + 0x5121, 0x7080, 0x6115, 0xA04B, 0x610A, 0x9016, 0x611D, 0xA0DE, 0x510B, 0x6122, 0x610E, 0x9039, + 0x6001, 0x7123, 0x6118, 0xA086, 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CD, + 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A8, 0x5121, 0x7102, 0x6116, 0xA05E, + 0x610C, 0x9021, 0x611E, 0xA0F3, 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA079, + 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C3, 0x510F, 0x7004, 0x6110, 0x9059, + 0x6002, 0x800E, 0x6119, 0xA0A0, 0x5121, 0x70FF, 0x6115, 0xA055, 0x610A, 0x901C, 0x611D, 0xA0E7, + 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08F, 0x5111, 0x700A, 0x6114, 0xA036, + 0x6109, 0x8120, 0x611C, 0xA0D6, 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B1, + 0x5121, 0x7103, 0x6116, 0xA067, 0x610C, 0x9026, 0x611E, 0xD0AB, 0x510B, 0x611F, 0x610D, 0x9027, + 0x6000, 0x7105, 0x6117, 0xA068, 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B3, + 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA091, 0x5121, 0x7080, 0x6115, 0xA03A, + 0x610A, 0x9012, 0x611D, 0xA0D7, 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07A, + 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C4, 0x510F, 0x7005, 0x6112, 0x9070, + 0x6107, 0x800F, 0x611A, 0xA0A1, 0x5121, 0x7102, 0x6116, 0xA056, 0x610C, 0x901D, 0x611E, 0xA0E8, + 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA071, 0x5111, 0x7008, 0x6113, 0x90E0, + 0x6108, 0x8020, 0x611B, 0xA0BB, 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA099, + 0x5121, 0x70FF, 0x6115, 0xA04C, 0x610A, 0x9017, 0x611D, 0xA0DF, 0x510B, 0x6122, 0x610E, 0x903C, + 0x6001, 0x7124, 0x6118, 0xA087, 0x5111, 0x700A, 0x6114, 0xA023, 0x6109, 0x80FE, 0x611C, 0xA0CE, + 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0A9, 0x5121, 0x7103, 0x6116, 0xA05F, + 0x610C, 0x9022, 0x611E, 0xA0F5, 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06C, + 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B7, 0x510F, 0x7003, 0x6110, 0x9044, + 0x6002, 0x800C, 0x6119, 0xA095, 0x5121, 0x7080, 0x6115, 0xA046, 0x610A, 0x9015, 0x611D, 0xA0DB, + 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA07E, 0x5111, 0x7009, 0x6114, 0x90FC, + 0x6109, 0x80F0, 0x611C, 0xA0C9, 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A5, + 0x5121, 0x7102, 0x6116, 0xA05B, 0x610C, 0x901F, 0x611E, 0xA0EC, 0x510B, 0x611F, 0x610D, 0x9031, + 0x6000, 0x7106, 0x6117, 0xA075, 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0BF, + 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09D, 0x5121, 0x70FF, 0x6115, 0xA052, + 0x610A, 0x901B, 0x611D, 0xA0E4, 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08C, + 0x5111, 0x700A, 0x6114, 0xA02F, 0x6109, 0x8120, 0x611C, 0xA0D3, 0x510F, 0x7006, 0x6112, 0x9088, + 0x6107, 0x8013, 0x611A, 0xA0AE, 0x5121, 0x7103, 0x6116, 0xA064, 0x610C, 0x9025, 0x611E, 0xA0FA, + 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06A, 0x5111, 0x7007, 0x6113, 0x90C6, + 0x6108, 0x8018, 0x611B, 0xA0B5, 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA093, + 0x5121, 0x7080, 0x6115, 0xA03D, 0x610A, 0x9014, 0x611D, 0xA0D9, 0x510B, 0x6122, 0x610E, 0x9037, + 0x6001, 0x7123, 0x6118, 0xA07C, 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C7, + 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A3, 0x5121, 0x7102, 0x6116, 0xA058, + 0x610C, 0x901E, 0x611E, 0xA0EA, 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA073, + 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BD, 0x510F, 0x7004, 0x6110, 0x9050, + 0x6002, 0x800D, 0x6119, 0xA09B, 0x5121, 0x70FF, 0x6115, 0xA04E, 0x610A, 0x901A, 0x611D, 0xA0E2, + 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08A, 0x5111, 0x700A, 0x6114, 0xA02D, + 0x6109, 0x80FE, 0x611C, 0xA0D1, 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AC, + 0x5121, 0x7103, 0x6116, 0xA062, 0x610C, 0x9024, 0x611E, 0xA0F7, 0x510B, 0x611F, 0x610D, 0x902A, + 0x6000, 0x7105, 0x6117, 0xA06E, 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0B9, + 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA097, 0x5121, 0x7080, 0x6115, 0xA04A, + 0x610A, 0x9016, 0x611D, 0xA0DD, 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA085, + 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CB, 0x510F, 0x7005, 0x6112, 0x9081, + 0x6107, 0x8010, 0x611A, 0xA0A7, 0x5121, 0x7102, 0x6116, 0xA05D, 0x610C, 0x9021, 0x611E, 0xA0EF, + 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA077, 0x5111, 0x7008, 0x6113, 0x90F2, + 0x6108, 0x8040, 0x611B, 0xA0C2, 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA09F, + 0x5121, 0x70FF, 0x6115, 0xA054, 0x610A, 0x901C, 0x611D, 0xA0E6, 0x510B, 0x6122, 0x610E, 0x9041, + 0x6001, 0x7124, 0x6118, 0xA08E, 0x5111, 0x700A, 0x6114, 0xA034, 0x6109, 0x8120, 0x611C, 0xA0D5, + 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B0, 0x5121, 0x7103, 0x6116, 0xA066, + 0x610C, 0x9026, 0x611E, 0xA104, 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA069, + 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B4, 0x510F, 0x7003, 0x6110, 0x9042, + 0x6002, 0x800B, 0x6119, 0xA092, 0x5121, 0x7080, 0x6115, 0xA03B, 0x610A, 0x9012, 0x611D, 0xA0D8, + 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07B, 0x5111, 0x7009, 0x6114, 0x90F4, + 0x6109, 0x8060, 0x611C, 0xA0C5, 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A2, + 0x5121, 0x7102, 0x6116, 0xA057, 0x610C, 0x901D, 0x611E, 0xA0E9, 0x510B, 0x611F, 0x610D, 0x902C, + 0x6000, 0x7106, 0x6117, 0xA072, 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BC, + 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA09A, 0x5121, 0x70FF, 0x6115, 0xA04D, + 0x610A, 0x9017, 0x611D, 0xA0E1, 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA089, + 0x5111, 0x700A, 0x6114, 0xA02B, 0x6109, 0x80FE, 0x611C, 0xA0CF, 0x510F, 0x7006, 0x6112, 0x9082, + 0x6107, 0x8011, 0x611A, 0xA0AA, 0x5121, 0x7103, 0x6116, 0xA061, 0x610C, 0x9022, 0x611E, 0xA0F6, + 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06D, 0x5111, 0x7007, 0x6113, 0x90CC, + 0x6108, 0x8019, 0x611B, 0xA0B8, 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA096, + 0x5121, 0x7080, 0x6115, 0xA047, 0x610A, 0x9015, 0x611D, 0xA0DC, 0x510B, 0x6122, 0x610E, 0x9038, + 0x6001, 0x7123, 0x6118, 0xA083, 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0CA, + 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A6, 0x5121, 0x7102, 0x6116, 0xA05C, + 0x610C, 0x901F, 0x611E, 0xA0ED, 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA076, + 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0C1, 0x510F, 0x7004, 0x6110, 0x9051, + 0x6002, 0x800E, 0x6119, 0xA09E, 0x5121, 0x70FF, 0x6115, 0xA053, 0x610A, 0x901B, 0x611D, 0xA0E5, + 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08D, 0x5111, 0x700A, 0x6114, 0xA032, + 0x6109, 0x8120, 0x611C, 0xA0D4, 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AF, + 0x5121, 0x7103, 0x6116, 0xA065, 0x610C, 0x9025, 0x611E, 0xA0FB, 0x510B, 0x611F, 0x610D, 0x9028, + 0x6000, 0x7105, 0x6117, 0xA06B, 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B6, + 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA094, 0x5121, 0x7080, 0x6115, 0xA045, + 0x610A, 0x9014, 0x611D, 0xA0DA, 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07D, + 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C8, 0x510F, 0x7005, 0x6112, 0x9078, + 0x6107, 0x800F, 0x611A, 0xA0A4, 0x5121, 0x7102, 0x6116, 0xA05A, 0x610C, 0x901E, 0x611E, 0xA0EB, + 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA074, 0x5111, 0x7008, 0x6113, 0x90EE, + 0x6108, 0x8020, 0x611B, 0xA0BE, 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09C, + 0x5121, 0x70FF, 0x6115, 0xA04F, 0x610A, 0x901A, 0x611D, 0xA0E3, 0x510B, 0x6122, 0x610E, 0x903E, + 0x6001, 0x7124, 0x6118, 0xA08B, 0x5111, 0x700A, 0x6114, 0xA02E, 0x6109, 0x80FE, 0x611C, 0xA0D2, + 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AD, 0x5121, 0x7103, 0x6116, 0xA063, + 0x610C, 0x9024, 0x611E, 0xA0F9, 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06F, + 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0BA, 0x510F, 0x7003, 0x6110, 0x9048, + 0x6002, 0x800C, 0x6119, 0xA098, 0x5121, 0x7080, 0x6115, 0xA04B, 0x610A, 0x9016, 0x611D, 0xA0DE, + 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA086, 0x5111, 0x7009, 0x6114, 0x90FD, + 0x6109, 0x80F0, 0x611C, 0xA0CD, 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A8, + 0x5121, 0x7102, 0x6116, 0xA05E, 0x610C, 0x9021, 0x611E, 0xA0F3, 0x510B, 0x611F, 0x610D, 0x9033, + 0x6000, 0x7106, 0x6117, 0xA079, 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C3, + 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA0A0, 0x5121, 0x70FF, 0x6115, 0xA055, + 0x610A, 0x901C, 0x611D, 0xA0E7, 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08F, + 0x5111, 0x700A, 0x6114, 0xA036, 0x6109, 0x8120, 0x611C, 0xA0D6, 0x510F, 0x7006, 0x6112, 0x9090, + 0x6107, 0x8013, 0x611A, 0xA0B1, 0x5121, 0x7103, 0x6116, 0xA067, 0x610C, 0x9026, 0x611E, 0xB0B2, + 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA068, 0x5111, 0x7007, 0x6113, 0x90C0, + 0x6108, 0x8018, 0x611B, 0xA0B3, 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA091, + 0x5121, 0x7080, 0x6115, 0xA03A, 0x610A, 0x9012, 0x611D, 0xA0D7, 0x510B, 0x6122, 0x610E, 0x9035, + 0x6001, 0x7123, 0x6118, 0xA07A, 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C4, + 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A1, 0x5121, 0x7102, 0x6116, 0xA056, + 0x610C, 0x901D, 0x611E, 0xA0E8, 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA071, + 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BB, 0x510F, 0x7004, 0x6110, 0x9049, + 0x6002, 0x800D, 0x6119, 0xA099, 0x5121, 0x70FF, 0x6115, 0xA04C, 0x610A, 0x9017, 0x611D, 0xA0DF, + 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA087, 0x5111, 0x700A, 0x6114, 0xA023, + 0x6109, 0x80FE, 0x611C, 0xA0CE, 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0A9, + 0x5121, 0x7103, 0x6116, 0xA05F, 0x610C, 0x9022, 0x611E, 0xA0F5, 0x510B, 0x611F, 0x610D, 0x9029, + 0x6000, 0x7105, 0x6117, 0xA06C, 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B7, + 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA095, 0x5121, 0x7080, 0x6115, 0xA046, + 0x610A, 0x9015, 0x611D, 0xA0DB, 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA07E, + 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0C9, 0x510F, 0x7005, 0x6112, 0x907F, + 0x6107, 0x8010, 0x611A, 0xA0A5, 0x5121, 0x7102, 0x6116, 0xA05B, 0x610C, 0x901F, 0x611E, 0xA0EC, + 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA075, 0x5111, 0x7008, 0x6113, 0x90F1, + 0x6108, 0x8040, 0x611B, 0xA0BF, 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09D, + 0x5121, 0x70FF, 0x6115, 0xA052, 0x610A, 0x901B, 0x611D, 0xA0E4, 0x510B, 0x6122, 0x610E, 0x903F, + 0x6001, 0x7124, 0x6118, 0xA08C, 0x5111, 0x700A, 0x6114, 0xA02F, 0x6109, 0x8120, 0x611C, 0xA0D3, + 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AE, 0x5121, 0x7103, 0x6116, 0xA064, + 0x610C, 0x9025, 0x611E, 0xA0FA, 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06A, + 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B5, 0x510F, 0x7003, 0x6110, 0x9043, + 0x6002, 0x800B, 0x6119, 0xA093, 0x5121, 0x7080, 0x6115, 0xA03D, 0x610A, 0x9014, 0x611D, 0xA0D9, + 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07C, 0x5111, 0x7009, 0x6114, 0x90F8, + 0x6109, 0x8060, 0x611C, 0xA0C7, 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A3, + 0x5121, 0x7102, 0x6116, 0xA058, 0x610C, 0x901E, 0x611E, 0xA0EA, 0x510B, 0x611F, 0x610D, 0x9030, + 0x6000, 0x7106, 0x6117, 0xA073, 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BD, + 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09B, 0x5121, 0x70FF, 0x6115, 0xA04E, + 0x610A, 0x901A, 0x611D, 0xA0E2, 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08A, + 0x5111, 0x700A, 0x6114, 0xA02D, 0x6109, 0x80FE, 0x611C, 0xA0D1, 0x510F, 0x7006, 0x6112, 0x9084, + 0x6107, 0x8011, 0x611A, 0xA0AC, 0x5121, 0x7103, 0x6116, 0xA062, 0x610C, 0x9024, 0x611E, 0xA0F7, + 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06E, 0x5111, 0x7007, 0x6113, 0x90D0, + 0x6108, 0x8019, 0x611B, 0xA0B9, 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA097, + 0x5121, 0x7080, 0x6115, 0xA04A, 0x610A, 0x9016, 0x611D, 0xA0DD, 0x510B, 0x6122, 0x610E, 0x9039, + 0x6001, 0x7123, 0x6118, 0xA085, 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CB, + 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A7, 0x5121, 0x7102, 0x6116, 0xA05D, + 0x610C, 0x9021, 0x611E, 0xA0EF, 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA077, + 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C2, 0x510F, 0x7004, 0x6110, 0x9059, + 0x6002, 0x800E, 0x6119, 0xA09F, 0x5121, 0x70FF, 0x6115, 0xA054, 0x610A, 0x901C, 0x611D, 0xA0E6, + 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08E, 0x5111, 0x700A, 0x6114, 0xA034, + 0x6109, 0x8120, 0x611C, 0xA0D5, 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B0, + 0x5121, 0x7103, 0x6116, 0xA066, 0x610C, 0x9026, 0x611E, 0xA104, 0x510B, 0x611F, 0x610D, 0x9027, + 0x6000, 0x7105, 0x6117, 0xA069, 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B4, + 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA092, 0x5121, 0x7080, 0x6115, 0xA03B, + 0x610A, 0x9012, 0x611D, 0xA0D8, 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07B, + 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C5, 0x510F, 0x7005, 0x6112, 0x9070, + 0x6107, 0x800F, 0x611A, 0xA0A2, 0x5121, 0x7102, 0x6116, 0xA057, 0x610C, 0x901D, 0x611E, 0xA0E9, + 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA072, 0x5111, 0x7008, 0x6113, 0x90E0, + 0x6108, 0x8020, 0x611B, 0xA0BC, 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA09A, + 0x5121, 0x70FF, 0x6115, 0xA04D, 0x610A, 0x9017, 0x611D, 0xA0E1, 0x510B, 0x6122, 0x610E, 0x903C, + 0x6001, 0x7124, 0x6118, 0xA089, 0x5111, 0x700A, 0x6114, 0xA02B, 0x6109, 0x80FE, 0x611C, 0xA0CF, + 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0AA, 0x5121, 0x7103, 0x6116, 0xA061, + 0x610C, 0x9022, 0x611E, 0xA0F6, 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06D, + 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B8, 0x510F, 0x7003, 0x6110, 0x9044, + 0x6002, 0x800C, 0x6119, 0xA096, 0x5121, 0x7080, 0x6115, 0xA047, 0x610A, 0x9015, 0x611D, 0xA0DC, + 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA083, 0x5111, 0x7009, 0x6114, 0x90FC, + 0x6109, 0x80F0, 0x611C, 0xA0CA, 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A6, + 0x5121, 0x7102, 0x6116, 0xA05C, 0x610C, 0x901F, 0x611E, 0xA0ED, 0x510B, 0x611F, 0x610D, 0x9031, + 0x6000, 0x7106, 0x6117, 0xA076, 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0C1, + 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09E, 0x5121, 0x70FF, 0x6115, 0xA053, + 0x610A, 0x901B, 0x611D, 0xA0E5, 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08D, + 0x5111, 0x700A, 0x6114, 0xA032, 0x6109, 0x8120, 0x611C, 0xA0D4, 0x510F, 0x7006, 0x6112, 0x9088, + 0x6107, 0x8013, 0x611A, 0xA0AF, 0x5121, 0x7103, 0x6116, 0xA065, 0x610C, 0x9025, 0x611E, 0xA0FB, + 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06B, 0x5111, 0x7007, 0x6113, 0x90C6, + 0x6108, 0x8018, 0x611B, 0xA0B6, 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA094, + 0x5121, 0x7080, 0x6115, 0xA045, 0x610A, 0x9014, 0x611D, 0xA0DA, 0x510B, 0x6122, 0x610E, 0x9037, + 0x6001, 0x7123, 0x6118, 0xA07D, 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C8, + 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A4, 0x5121, 0x7102, 0x6116, 0xA05A, + 0x610C, 0x901E, 0x611E, 0xA0EB, 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA074, + 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BE, 0x510F, 0x7004, 0x6110, 0x9050, + 0x6002, 0x800D, 0x6119, 0xA09C, 0x5121, 0x70FF, 0x6115, 0xA04F, 0x610A, 0x901A, 0x611D, 0xA0E3, + 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08B, 0x5111, 0x700A, 0x6114, 0xA02E, + 0x6109, 0x80FE, 0x611C, 0xA0D2, 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AD, + 0x5121, 0x7103, 0x6116, 0xA063, 0x610C, 0x9024, 0x611E, 0xA0F9, 0x510B, 0x611F, 0x610D, 0x902A, + 0x6000, 0x7105, 0x6117, 0xA06F, 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0BA, + 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA098, 0x5121, 0x7080, 0x6115, 0xA04B, + 0x610A, 0x9016, 0x611D, 0xA0DE, 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA086, + 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CD, 0x510F, 0x7005, 0x6112, 0x9081, + 0x6107, 0x8010, 0x611A, 0xA0A8, 0x5121, 0x7102, 0x6116, 0xA05E, 0x610C, 0x9021, 0x611E, 0xA0F3, + 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA079, 0x5111, 0x7008, 0x6113, 0x90F2, + 0x6108, 0x8040, 0x611B, 0xA0C3, 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA0A0, + 0x5121, 0x70FF, 0x6115, 0xA055, 0x610A, 0x901C, 0x611D, 0xA0E7, 0x510B, 0x6122, 0x610E, 0x9041, + 0x6001, 0x7124, 0x6118, 0xA08F, 0x5111, 0x700A, 0x6114, 0xA036, 0x6109, 0x8120, 0x611C, 0xA0D6, + 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B1, 0x5121, 0x7103, 0x6116, 0xA067, + 0x610C, 0x9026, 0x611E, 0xD101, 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA068, + 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B3, 0x510F, 0x7003, 0x6110, 0x9042, + 0x6002, 0x800B, 0x6119, 0xA091, 0x5121, 0x7080, 0x6115, 0xA03A, 0x610A, 0x9012, 0x611D, 0xA0D7, + 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07A, 0x5111, 0x7009, 0x6114, 0x90F4, + 0x6109, 0x8060, 0x611C, 0xA0C4, 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A1, + 0x5121, 0x7102, 0x6116, 0xA056, 0x610C, 0x901D, 0x611E, 0xA0E8, 0x510B, 0x611F, 0x610D, 0x902C, + 0x6000, 0x7106, 0x6117, 0xA071, 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BB, + 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA099, 0x5121, 0x70FF, 0x6115, 0xA04C, + 0x610A, 0x9017, 0x611D, 0xA0DF, 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA087, + 0x5111, 0x700A, 0x6114, 0xA023, 0x6109, 0x80FE, 0x611C, 0xA0CE, 0x510F, 0x7006, 0x6112, 0x9082, + 0x6107, 0x8011, 0x611A, 0xA0A9, 0x5121, 0x7103, 0x6116, 0xA05F, 0x610C, 0x9022, 0x611E, 0xA0F5, + 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06C, 0x5111, 0x7007, 0x6113, 0x90CC, + 0x6108, 0x8019, 0x611B, 0xA0B7, 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA095, + 0x5121, 0x7080, 0x6115, 0xA046, 0x610A, 0x9015, 0x611D, 0xA0DB, 0x510B, 0x6122, 0x610E, 0x9038, + 0x6001, 0x7123, 0x6118, 0xA07E, 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0C9, + 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A5, 0x5121, 0x7102, 0x6116, 0xA05B, + 0x610C, 0x901F, 0x611E, 0xA0EC, 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA075, + 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0BF, 0x510F, 0x7004, 0x6110, 0x9051, + 0x6002, 0x800E, 0x6119, 0xA09D, 0x5121, 0x70FF, 0x6115, 0xA052, 0x610A, 0x901B, 0x611D, 0xA0E4, + 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08C, 0x5111, 0x700A, 0x6114, 0xA02F, + 0x6109, 0x8120, 0x611C, 0xA0D3, 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AE, + 0x5121, 0x7103, 0x6116, 0xA064, 0x610C, 0x9025, 0x611E, 0xA0FA, 0x510B, 0x611F, 0x610D, 0x9028, + 0x6000, 0x7105, 0x6117, 0xA06A, 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B5, + 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA093, 0x5121, 0x7080, 0x6115, 0xA03D, + 0x610A, 0x9014, 0x611D, 0xA0D9, 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07C, + 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C7, 0x510F, 0x7005, 0x6112, 0x9078, + 0x6107, 0x800F, 0x611A, 0xA0A3, 0x5121, 0x7102, 0x6116, 0xA058, 0x610C, 0x901E, 0x611E, 0xA0EA, + 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA073, 0x5111, 0x7008, 0x6113, 0x90EE, + 0x6108, 0x8020, 0x611B, 0xA0BD, 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09B, + 0x5121, 0x70FF, 0x6115, 0xA04E, 0x610A, 0x901A, 0x611D, 0xA0E2, 0x510B, 0x6122, 0x610E, 0x903E, + 0x6001, 0x7124, 0x6118, 0xA08A, 0x5111, 0x700A, 0x6114, 0xA02D, 0x6109, 0x80FE, 0x611C, 0xA0D1, + 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AC, 0x5121, 0x7103, 0x6116, 0xA062, + 0x610C, 0x9024, 0x611E, 0xA0F7, 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06E, + 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0B9, 0x510F, 0x7003, 0x6110, 0x9048, + 0x6002, 0x800C, 0x6119, 0xA097, 0x5121, 0x7080, 0x6115, 0xA04A, 0x610A, 0x9016, 0x611D, 0xA0DD, + 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA085, 0x5111, 0x7009, 0x6114, 0x90FD, + 0x6109, 0x80F0, 0x611C, 0xA0CB, 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A7, + 0x5121, 0x7102, 0x6116, 0xA05D, 0x610C, 0x9021, 0x611E, 0xA0EF, 0x510B, 0x611F, 0x610D, 0x9033, + 0x6000, 0x7106, 0x6117, 0xA077, 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C2, + 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA09F, 0x5121, 0x70FF, 0x6115, 0xA054, + 0x610A, 0x901C, 0x611D, 0xA0E6, 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08E, + 0x5111, 0x700A, 0x6114, 0xA034, 0x6109, 0x8120, 0x611C, 0xA0D5, 0x510F, 0x7006, 0x6112, 0x9090, + 0x6107, 0x8013, 0x611A, 0xA0B0, 0x5121, 0x7103, 0x6116, 0xA066, 0x610C, 0x9026, 0x611E, 0xA104, + 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA069, 0x5111, 0x7007, 0x6113, 0x90C0, + 0x6108, 0x8018, 0x611B, 0xA0B4, 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA092, + 0x5121, 0x7080, 0x6115, 0xA03B, 0x610A, 0x9012, 0x611D, 0xA0D8, 0x510B, 0x6122, 0x610E, 0x9035, + 0x6001, 0x7123, 0x6118, 0xA07B, 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C5, + 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A2, 0x5121, 0x7102, 0x6116, 0xA057, + 0x610C, 0x901D, 0x611E, 0xA0E9, 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA072, + 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BC, 0x510F, 0x7004, 0x6110, 0x9049, + 0x6002, 0x800D, 0x6119, 0xA09A, 0x5121, 0x70FF, 0x6115, 0xA04D, 0x610A, 0x9017, 0x611D, 0xA0E1, + 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA089, 0x5111, 0x700A, 0x6114, 0xA02B, + 0x6109, 0x80FE, 0x611C, 0xA0CF, 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0AA, + 0x5121, 0x7103, 0x6116, 0xA061, 0x610C, 0x9022, 0x611E, 0xA0F6, 0x510B, 0x611F, 0x610D, 0x9029, + 0x6000, 0x7105, 0x6117, 0xA06D, 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B8, + 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA096, 0x5121, 0x7080, 0x6115, 0xA047, + 0x610A, 0x9015, 0x611D, 0xA0DC, 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA083, + 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0CA, 0x510F, 0x7005, 0x6112, 0x907F, + 0x6107, 0x8010, 0x611A, 0xA0A6, 0x5121, 0x7102, 0x6116, 0xA05C, 0x610C, 0x901F, 0x611E, 0xA0ED, + 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA076, 0x5111, 0x7008, 0x6113, 0x90F1, + 0x6108, 0x8040, 0x611B, 0xA0C1, 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09E, + 0x5121, 0x70FF, 0x6115, 0xA053, 0x610A, 0x901B, 0x611D, 0xA0E5, 0x510B, 0x6122, 0x610E, 0x903F, + 0x6001, 0x7124, 0x6118, 0xA08D, 0x5111, 0x700A, 0x6114, 0xA032, 0x6109, 0x8120, 0x611C, 0xA0D4, + 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AF, 0x5121, 0x7103, 0x6116, 0xA065, + 0x610C, 0x9025, 0x611E, 0xA0FB, 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06B, + 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B6, 0x510F, 0x7003, 0x6110, 0x9043, + 0x6002, 0x800B, 0x6119, 0xA094, 0x5121, 0x7080, 0x6115, 0xA045, 0x610A, 0x9014, 0x611D, 0xA0DA, + 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07D, 0x5111, 0x7009, 0x6114, 0x90F8, + 0x6109, 0x8060, 0x611C, 0xA0C8, 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A4, + 0x5121, 0x7102, 0x6116, 0xA05A, 0x610C, 0x901E, 0x611E, 0xA0EB, 0x510B, 0x611F, 0x610D, 0x9030, + 0x6000, 0x7106, 0x6117, 0xA074, 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BE, + 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09C, 0x5121, 0x70FF, 0x6115, 0xA04F, + 0x610A, 0x901A, 0x611D, 0xA0E3, 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08B, + 0x5111, 0x700A, 0x6114, 0xA02E, 0x6109, 0x80FE, 0x611C, 0xA0D2, 0x510F, 0x7006, 0x6112, 0x9084, + 0x6107, 0x8011, 0x611A, 0xA0AD, 0x5121, 0x7103, 0x6116, 0xA063, 0x610C, 0x9024, 0x611E, 0xA0F9, + 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06F, 0x5111, 0x7007, 0x6113, 0x90D0, + 0x6108, 0x8019, 0x611B, 0xA0BA, 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA098, + 0x5121, 0x7080, 0x6115, 0xA04B, 0x610A, 0x9016, 0x611D, 0xA0DE, 0x510B, 0x6122, 0x610E, 0x9039, + 0x6001, 0x7123, 0x6118, 0xA086, 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CD, + 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A8, 0x5121, 0x7102, 0x6116, 0xA05E, + 0x610C, 0x9021, 0x611E, 0xA0F3, 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA079, + 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C3, 0x510F, 0x7004, 0x6110, 0x9059, + 0x6002, 0x800E, 0x6119, 0xA0A0, 0x5121, 0x70FF, 0x6115, 0xA055, 0x610A, 0x901C, 0x611D, 0xA0E7, + 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08F, 0x5111, 0x700A, 0x6114, 0xA036, + 0x6109, 0x8120, 0x611C, 0xA0D6, 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B1, + 0x5121, 0x7103, 0x6116, 0xA067, 0x610C, 0x9026, 0x611E, 0xB0B2, 0x510B, 0x611F, 0x610D, 0x9027, + 0x6000, 0x7105, 0x6117, 0xA068, 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B3, + 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA091, 0x5121, 0x7080, 0x6115, 0xA03A, + 0x610A, 0x9012, 0x611D, 0xA0D7, 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07A, + 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C4, 0x510F, 0x7005, 0x6112, 0x9070, + 0x6107, 0x800F, 0x611A, 0xA0A1, 0x5121, 0x7102, 0x6116, 0xA056, 0x610C, 0x901D, 0x611E, 0xA0E8, + 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA071, 0x5111, 0x7008, 0x6113, 0x90E0, + 0x6108, 0x8020, 0x611B, 0xA0BB, 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA099, + 0x5121, 0x70FF, 0x6115, 0xA04C, 0x610A, 0x9017, 0x611D, 0xA0DF, 0x510B, 0x6122, 0x610E, 0x903C, + 0x6001, 0x7124, 0x6118, 0xA087, 0x5111, 0x700A, 0x6114, 0xA023, 0x6109, 0x80FE, 0x611C, 0xA0CE, + 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0A9, 0x5121, 0x7103, 0x6116, 0xA05F, + 0x610C, 0x9022, 0x611E, 0xA0F5, 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06C, + 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B7, 0x510F, 0x7003, 0x6110, 0x9044, + 0x6002, 0x800C, 0x6119, 0xA095, 0x5121, 0x7080, 0x6115, 0xA046, 0x610A, 0x9015, 0x611D, 0xA0DB, + 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA07E, 0x5111, 0x7009, 0x6114, 0x90FC, + 0x6109, 0x80F0, 0x611C, 0xA0C9, 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A5, + 0x5121, 0x7102, 0x6116, 0xA05B, 0x610C, 0x901F, 0x611E, 0xA0EC, 0x510B, 0x611F, 0x610D, 0x9031, + 0x6000, 0x7106, 0x6117, 0xA075, 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0BF, + 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09D, 0x5121, 0x70FF, 0x6115, 0xA052, + 0x610A, 0x901B, 0x611D, 0xA0E4, 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08C, + 0x5111, 0x700A, 0x6114, 0xA02F, 0x6109, 0x8120, 0x611C, 0xA0D3, 0x510F, 0x7006, 0x6112, 0x9088, + 0x6107, 0x8013, 0x611A, 0xA0AE, 0x5121, 0x7103, 0x6116, 0xA064, 0x610C, 0x9025, 0x611E, 0xA0FA, + 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06A, 0x5111, 0x7007, 0x6113, 0x90C6, + 0x6108, 0x8018, 0x611B, 0xA0B5, 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA093, + 0x5121, 0x7080, 0x6115, 0xA03D, 0x610A, 0x9014, 0x611D, 0xA0D9, 0x510B, 0x6122, 0x610E, 0x9037, + 0x6001, 0x7123, 0x6118, 0xA07C, 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C7, + 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A3, 0x5121, 0x7102, 0x6116, 0xA058, + 0x610C, 0x901E, 0x611E, 0xA0EA, 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA073, + 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BD, 0x510F, 0x7004, 0x6110, 0x9050, + 0x6002, 0x800D, 0x6119, 0xA09B, 0x5121, 0x70FF, 0x6115, 0xA04E, 0x610A, 0x901A, 0x611D, 0xA0E2, + 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08A, 0x5111, 0x700A, 0x6114, 0xA02D, + 0x6109, 0x80FE, 0x611C, 0xA0D1, 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AC, + 0x5121, 0x7103, 0x6116, 0xA062, 0x610C, 0x9024, 0x611E, 0xA0F7, 0x510B, 0x611F, 0x610D, 0x902A, + 0x6000, 0x7105, 0x6117, 0xA06E, 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0B9, + 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA097, 0x5121, 0x7080, 0x6115, 0xA04A, + 0x610A, 0x9016, 0x611D, 0xA0DD, 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA085, + 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CB, 0x510F, 0x7005, 0x6112, 0x9081, + 0x6107, 0x8010, 0x611A, 0xA0A7, 0x5121, 0x7102, 0x6116, 0xA05D, 0x610C, 0x9021, 0x611E, 0xA0EF, + 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA077, 0x5111, 0x7008, 0x6113, 0x90F2, + 0x6108, 0x8040, 0x611B, 0xA0C2, 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA09F, + 0x5121, 0x70FF, 0x6115, 0xA054, 0x610A, 0x901C, 0x611D, 0xA0E6, 0x510B, 0x6122, 0x610E, 0x9041, + 0x6001, 0x7124, 0x6118, 0xA08E, 0x5111, 0x700A, 0x6114, 0xA034, 0x6109, 0x8120, 0x611C, 0xA0D5, + 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B0, 0x5121, 0x7103, 0x6116, 0xA066, + 0x610C, 0x9026, 0x611E, 0xA104, 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA069, + 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B4, 0x510F, 0x7003, 0x6110, 0x9042, + 0x6002, 0x800B, 0x6119, 0xA092, 0x5121, 0x7080, 0x6115, 0xA03B, 0x610A, 0x9012, 0x611D, 0xA0D8, + 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07B, 0x5111, 0x7009, 0x6114, 0x90F4, + 0x6109, 0x8060, 0x611C, 0xA0C5, 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A2, + 0x5121, 0x7102, 0x6116, 0xA057, 0x610C, 0x901D, 0x611E, 0xA0E9, 0x510B, 0x611F, 0x610D, 0x902C, + 0x6000, 0x7106, 0x6117, 0xA072, 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BC, + 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA09A, 0x5121, 0x70FF, 0x6115, 0xA04D, + 0x610A, 0x9017, 0x611D, 0xA0E1, 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA089, + 0x5111, 0x700A, 0x6114, 0xA02B, 0x6109, 0x80FE, 0x611C, 0xA0CF, 0x510F, 0x7006, 0x6112, 0x9082, + 0x6107, 0x8011, 0x611A, 0xA0AA, 0x5121, 0x7103, 0x6116, 0xA061, 0x610C, 0x9022, 0x611E, 0xA0F6, + 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06D, 0x5111, 0x7007, 0x6113, 0x90CC, + 0x6108, 0x8019, 0x611B, 0xA0B8, 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA096, + 0x5121, 0x7080, 0x6115, 0xA047, 0x610A, 0x9015, 0x611D, 0xA0DC, 0x510B, 0x6122, 0x610E, 0x9038, + 0x6001, 0x7123, 0x6118, 0xA083, 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0CA, + 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A6, 0x5121, 0x7102, 0x6116, 0xA05C, + 0x610C, 0x901F, 0x611E, 0xA0ED, 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA076, + 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0C1, 0x510F, 0x7004, 0x6110, 0x9051, + 0x6002, 0x800E, 0x6119, 0xA09E, 0x5121, 0x70FF, 0x6115, 0xA053, 0x610A, 0x901B, 0x611D, 0xA0E5, + 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08D, 0x5111, 0x700A, 0x6114, 0xA032, + 0x6109, 0x8120, 0x611C, 0xA0D4, 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AF, + 0x5121, 0x7103, 0x6116, 0xA065, 0x610C, 0x9025, 0x611E, 0xA0FB, 0x510B, 0x611F, 0x610D, 0x9028, + 0x6000, 0x7105, 0x6117, 0xA06B, 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B6, + 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA094, 0x5121, 0x7080, 0x6115, 0xA045, + 0x610A, 0x9014, 0x611D, 0xA0DA, 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07D, + 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C8, 0x510F, 0x7005, 0x6112, 0x9078, + 0x6107, 0x800F, 0x611A, 0xA0A4, 0x5121, 0x7102, 0x6116, 0xA05A, 0x610C, 0x901E, 0x611E, 0xA0EB, + 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA074, 0x5111, 0x7008, 0x6113, 0x90EE, + 0x6108, 0x8020, 0x611B, 0xA0BE, 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09C, + 0x5121, 0x70FF, 0x6115, 0xA04F, 0x610A, 0x901A, 0x611D, 0xA0E3, 0x510B, 0x6122, 0x610E, 0x903E, + 0x6001, 0x7124, 0x6118, 0xA08B, 0x5111, 0x700A, 0x6114, 0xA02E, 0x6109, 0x80FE, 0x611C, 0xA0D2, + 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AD, 0x5121, 0x7103, 0x6116, 0xA063, + 0x610C, 0x9024, 0x611E, 0xA0F9, 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06F, + 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0BA, 0x510F, 0x7003, 0x6110, 0x9048, + 0x6002, 0x800C, 0x6119, 0xA098, 0x5121, 0x7080, 0x6115, 0xA04B, 0x610A, 0x9016, 0x611D, 0xA0DE, + 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA086, 0x5111, 0x7009, 0x6114, 0x90FD, + 0x6109, 0x80F0, 0x611C, 0xA0CD, 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A8, + 0x5121, 0x7102, 0x6116, 0xA05E, 0x610C, 0x9021, 0x611E, 0xA0F3, 0x510B, 0x611F, 0x610D, 0x9033, + 0x6000, 0x7106, 0x6117, 0xA079, 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C3, + 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA0A0, 0x5121, 0x70FF, 0x6115, 0xA055, + 0x610A, 0x901C, 0x611D, 0xA0E7, 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08F, + 0x5111, 0x700A, 0x6114, 0xA036, 0x6109, 0x8120, 0x611C, 0xA0D6, 0x510F, 0x7006, 0x6112, 0x9090, + 0x6107, 0x8013, 0x611A, 0xA0B1, 0x5121, 0x7103, 0x6116, 0xA067, 0x610C, 0x9026, 0x611E, 0xD100, + 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA068, 0x5111, 0x7007, 0x6113, 0x90C0, + 0x6108, 0x8018, 0x611B, 0xA0B3, 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA091, + 0x5121, 0x7080, 0x6115, 0xA03A, 0x610A, 0x9012, 0x611D, 0xA0D7, 0x510B, 0x6122, 0x610E, 0x9035, + 0x6001, 0x7123, 0x6118, 0xA07A, 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C4, + 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A1, 0x5121, 0x7102, 0x6116, 0xA056, + 0x610C, 0x901D, 0x611E, 0xA0E8, 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA071, + 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BB, 0x510F, 0x7004, 0x6110, 0x9049, + 0x6002, 0x800D, 0x6119, 0xA099, 0x5121, 0x70FF, 0x6115, 0xA04C, 0x610A, 0x9017, 0x611D, 0xA0DF, + 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA087, 0x5111, 0x700A, 0x6114, 0xA023, + 0x6109, 0x80FE, 0x611C, 0xA0CE, 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0A9, + 0x5121, 0x7103, 0x6116, 0xA05F, 0x610C, 0x9022, 0x611E, 0xA0F5, 0x510B, 0x611F, 0x610D, 0x9029, + 0x6000, 0x7105, 0x6117, 0xA06C, 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B7, + 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA095, 0x5121, 0x7080, 0x6115, 0xA046, + 0x610A, 0x9015, 0x611D, 0xA0DB, 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA07E, + 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0C9, 0x510F, 0x7005, 0x6112, 0x907F, + 0x6107, 0x8010, 0x611A, 0xA0A5, 0x5121, 0x7102, 0x6116, 0xA05B, 0x610C, 0x901F, 0x611E, 0xA0EC, + 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA075, 0x5111, 0x7008, 0x6113, 0x90F1, + 0x6108, 0x8040, 0x611B, 0xA0BF, 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09D, + 0x5121, 0x70FF, 0x6115, 0xA052, 0x610A, 0x901B, 0x611D, 0xA0E4, 0x510B, 0x6122, 0x610E, 0x903F, + 0x6001, 0x7124, 0x6118, 0xA08C, 0x5111, 0x700A, 0x6114, 0xA02F, 0x6109, 0x8120, 0x611C, 0xA0D3, + 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AE, 0x5121, 0x7103, 0x6116, 0xA064, + 0x610C, 0x9025, 0x611E, 0xA0FA, 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06A, + 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B5, 0x510F, 0x7003, 0x6110, 0x9043, + 0x6002, 0x800B, 0x6119, 0xA093, 0x5121, 0x7080, 0x6115, 0xA03D, 0x610A, 0x9014, 0x611D, 0xA0D9, + 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07C, 0x5111, 0x7009, 0x6114, 0x90F8, + 0x6109, 0x8060, 0x611C, 0xA0C7, 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A3, + 0x5121, 0x7102, 0x6116, 0xA058, 0x610C, 0x901E, 0x611E, 0xA0EA, 0x510B, 0x611F, 0x610D, 0x9030, + 0x6000, 0x7106, 0x6117, 0xA073, 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BD, + 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09B, 0x5121, 0x70FF, 0x6115, 0xA04E, + 0x610A, 0x901A, 0x611D, 0xA0E2, 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08A, + 0x5111, 0x700A, 0x6114, 0xA02D, 0x6109, 0x80FE, 0x611C, 0xA0D1, 0x510F, 0x7006, 0x6112, 0x9084, + 0x6107, 0x8011, 0x611A, 0xA0AC, 0x5121, 0x7103, 0x6116, 0xA062, 0x610C, 0x9024, 0x611E, 0xA0F7, + 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06E, 0x5111, 0x7007, 0x6113, 0x90D0, + 0x6108, 0x8019, 0x611B, 0xA0B9, 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA097, + 0x5121, 0x7080, 0x6115, 0xA04A, 0x610A, 0x9016, 0x611D, 0xA0DD, 0x510B, 0x6122, 0x610E, 0x9039, + 0x6001, 0x7123, 0x6118, 0xA085, 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CB, + 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A7, 0x5121, 0x7102, 0x6116, 0xA05D, + 0x610C, 0x9021, 0x611E, 0xA0EF, 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA077, + 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C2, 0x510F, 0x7004, 0x6110, 0x9059, + 0x6002, 0x800E, 0x6119, 0xA09F, 0x5121, 0x70FF, 0x6115, 0xA054, 0x610A, 0x901C, 0x611D, 0xA0E6, + 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08E, 0x5111, 0x700A, 0x6114, 0xA034, + 0x6109, 0x8120, 0x611C, 0xA0D5, 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B0, + 0x5121, 0x7103, 0x6116, 0xA066, 0x610C, 0x9026, 0x611E, 0xA104, 0x510B, 0x611F, 0x610D, 0x9027, + 0x6000, 0x7105, 0x6117, 0xA069, 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B4, + 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA092, 0x5121, 0x7080, 0x6115, 0xA03B, + 0x610A, 0x9012, 0x611D, 0xA0D8, 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07B, + 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C5, 0x510F, 0x7005, 0x6112, 0x9070, + 0x6107, 0x800F, 0x611A, 0xA0A2, 0x5121, 0x7102, 0x6116, 0xA057, 0x610C, 0x901D, 0x611E, 0xA0E9, + 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA072, 0x5111, 0x7008, 0x6113, 0x90E0, + 0x6108, 0x8020, 0x611B, 0xA0BC, 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA09A, + 0x5121, 0x70FF, 0x6115, 0xA04D, 0x610A, 0x9017, 0x611D, 0xA0E1, 0x510B, 0x6122, 0x610E, 0x903C, + 0x6001, 0x7124, 0x6118, 0xA089, 0x5111, 0x700A, 0x6114, 0xA02B, 0x6109, 0x80FE, 0x611C, 0xA0CF, + 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0AA, 0x5121, 0x7103, 0x6116, 0xA061, + 0x610C, 0x9022, 0x611E, 0xA0F6, 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06D, + 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B8, 0x510F, 0x7003, 0x6110, 0x9044, + 0x6002, 0x800C, 0x6119, 0xA096, 0x5121, 0x7080, 0x6115, 0xA047, 0x610A, 0x9015, 0x611D, 0xA0DC, + 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA083, 0x5111, 0x7009, 0x6114, 0x90FC, + 0x6109, 0x80F0, 0x611C, 0xA0CA, 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A6, + 0x5121, 0x7102, 0x6116, 0xA05C, 0x610C, 0x901F, 0x611E, 0xA0ED, 0x510B, 0x611F, 0x610D, 0x9031, + 0x6000, 0x7106, 0x6117, 0xA076, 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0C1, + 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09E, 0x5121, 0x70FF, 0x6115, 0xA053, + 0x610A, 0x901B, 0x611D, 0xA0E5, 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08D, + 0x5111, 0x700A, 0x6114, 0xA032, 0x6109, 0x8120, 0x611C, 0xA0D4, 0x510F, 0x7006, 0x6112, 0x9088, + 0x6107, 0x8013, 0x611A, 0xA0AF, 0x5121, 0x7103, 0x6116, 0xA065, 0x610C, 0x9025, 0x611E, 0xA0FB, + 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06B, 0x5111, 0x7007, 0x6113, 0x90C6, + 0x6108, 0x8018, 0x611B, 0xA0B6, 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA094, + 0x5121, 0x7080, 0x6115, 0xA045, 0x610A, 0x9014, 0x611D, 0xA0DA, 0x510B, 0x6122, 0x610E, 0x9037, + 0x6001, 0x7123, 0x6118, 0xA07D, 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C8, + 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A4, 0x5121, 0x7102, 0x6116, 0xA05A, + 0x610C, 0x901E, 0x611E, 0xA0EB, 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA074, + 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BE, 0x510F, 0x7004, 0x6110, 0x9050, + 0x6002, 0x800D, 0x6119, 0xA09C, 0x5121, 0x70FF, 0x6115, 0xA04F, 0x610A, 0x901A, 0x611D, 0xA0E3, + 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08B, 0x5111, 0x700A, 0x6114, 0xA02E, + 0x6109, 0x80FE, 0x611C, 0xA0D2, 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AD, + 0x5121, 0x7103, 0x6116, 0xA063, 0x610C, 0x9024, 0x611E, 0xA0F9, 0x510B, 0x611F, 0x610D, 0x902A, + 0x6000, 0x7105, 0x6117, 0xA06F, 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0BA, + 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA098, 0x5121, 0x7080, 0x6115, 0xA04B, + 0x610A, 0x9016, 0x611D, 0xA0DE, 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA086, + 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CD, 0x510F, 0x7005, 0x6112, 0x9081, + 0x6107, 0x8010, 0x611A, 0xA0A8, 0x5121, 0x7102, 0x6116, 0xA05E, 0x610C, 0x9021, 0x611E, 0xA0F3, + 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA079, 0x5111, 0x7008, 0x6113, 0x90F2, + 0x6108, 0x8040, 0x611B, 0xA0C3, 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA0A0, + 0x5121, 0x70FF, 0x6115, 0xA055, 0x610A, 0x901C, 0x611D, 0xA0E7, 0x510B, 0x6122, 0x610E, 0x9041, + 0x6001, 0x7124, 0x6118, 0xA08F, 0x5111, 0x700A, 0x6114, 0xA036, 0x6109, 0x8120, 0x611C, 0xA0D6, + 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B1, 0x5121, 0x7103, 0x6116, 0xA067, + 0x610C, 0x9026, 0x611E, 0xB0B2, 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA068, + 0x5111, 0x7007, 0x6113, 0x90C0, 0x6108, 0x8018, 0x611B, 0xA0B3, 0x510F, 0x7003, 0x6110, 0x9042, + 0x6002, 0x800B, 0x6119, 0xA091, 0x5121, 0x7080, 0x6115, 0xA03A, 0x610A, 0x9012, 0x611D, 0xA0D7, + 0x510B, 0x6122, 0x610E, 0x9035, 0x6001, 0x7123, 0x6118, 0xA07A, 0x5111, 0x7009, 0x6114, 0x90F4, + 0x6109, 0x8060, 0x611C, 0xA0C4, 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A1, + 0x5121, 0x7102, 0x6116, 0xA056, 0x610C, 0x901D, 0x611E, 0xA0E8, 0x510B, 0x611F, 0x610D, 0x902C, + 0x6000, 0x7106, 0x6117, 0xA071, 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BB, + 0x510F, 0x7004, 0x6110, 0x9049, 0x6002, 0x800D, 0x6119, 0xA099, 0x5121, 0x70FF, 0x6115, 0xA04C, + 0x610A, 0x9017, 0x611D, 0xA0DF, 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA087, + 0x5111, 0x700A, 0x6114, 0xA023, 0x6109, 0x80FE, 0x611C, 0xA0CE, 0x510F, 0x7006, 0x6112, 0x9082, + 0x6107, 0x8011, 0x611A, 0xA0A9, 0x5121, 0x7103, 0x6116, 0xA05F, 0x610C, 0x9022, 0x611E, 0xA0F5, + 0x510B, 0x611F, 0x610D, 0x9029, 0x6000, 0x7105, 0x6117, 0xA06C, 0x5111, 0x7007, 0x6113, 0x90CC, + 0x6108, 0x8019, 0x611B, 0xA0B7, 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA095, + 0x5121, 0x7080, 0x6115, 0xA046, 0x610A, 0x9015, 0x611D, 0xA0DB, 0x510B, 0x6122, 0x610E, 0x9038, + 0x6001, 0x7123, 0x6118, 0xA07E, 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0C9, + 0x510F, 0x7005, 0x6112, 0x907F, 0x6107, 0x8010, 0x611A, 0xA0A5, 0x5121, 0x7102, 0x6116, 0xA05B, + 0x610C, 0x901F, 0x611E, 0xA0EC, 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA075, + 0x5111, 0x7008, 0x6113, 0x90F1, 0x6108, 0x8040, 0x611B, 0xA0BF, 0x510F, 0x7004, 0x6110, 0x9051, + 0x6002, 0x800E, 0x6119, 0xA09D, 0x5121, 0x70FF, 0x6115, 0xA052, 0x610A, 0x901B, 0x611D, 0xA0E4, + 0x510B, 0x6122, 0x610E, 0x903F, 0x6001, 0x7124, 0x6118, 0xA08C, 0x5111, 0x700A, 0x6114, 0xA02F, + 0x6109, 0x8120, 0x611C, 0xA0D3, 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AE, + 0x5121, 0x7103, 0x6116, 0xA064, 0x610C, 0x9025, 0x611E, 0xA0FA, 0x510B, 0x611F, 0x610D, 0x9028, + 0x6000, 0x7105, 0x6117, 0xA06A, 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B5, + 0x510F, 0x7003, 0x6110, 0x9043, 0x6002, 0x800B, 0x6119, 0xA093, 0x5121, 0x7080, 0x6115, 0xA03D, + 0x610A, 0x9014, 0x611D, 0xA0D9, 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07C, + 0x5111, 0x7009, 0x6114, 0x90F8, 0x6109, 0x8060, 0x611C, 0xA0C7, 0x510F, 0x7005, 0x6112, 0x9078, + 0x6107, 0x800F, 0x611A, 0xA0A3, 0x5121, 0x7102, 0x6116, 0xA058, 0x610C, 0x901E, 0x611E, 0xA0EA, + 0x510B, 0x611F, 0x610D, 0x9030, 0x6000, 0x7106, 0x6117, 0xA073, 0x5111, 0x7008, 0x6113, 0x90EE, + 0x6108, 0x8020, 0x611B, 0xA0BD, 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09B, + 0x5121, 0x70FF, 0x6115, 0xA04E, 0x610A, 0x901A, 0x611D, 0xA0E2, 0x510B, 0x6122, 0x610E, 0x903E, + 0x6001, 0x7124, 0x6118, 0xA08A, 0x5111, 0x700A, 0x6114, 0xA02D, 0x6109, 0x80FE, 0x611C, 0xA0D1, + 0x510F, 0x7006, 0x6112, 0x9084, 0x6107, 0x8011, 0x611A, 0xA0AC, 0x5121, 0x7103, 0x6116, 0xA062, + 0x610C, 0x9024, 0x611E, 0xA0F7, 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06E, + 0x5111, 0x7007, 0x6113, 0x90D0, 0x6108, 0x8019, 0x611B, 0xA0B9, 0x510F, 0x7003, 0x6110, 0x9048, + 0x6002, 0x800C, 0x6119, 0xA097, 0x5121, 0x7080, 0x6115, 0xA04A, 0x610A, 0x9016, 0x611D, 0xA0DD, + 0x510B, 0x6122, 0x610E, 0x9039, 0x6001, 0x7123, 0x6118, 0xA085, 0x5111, 0x7009, 0x6114, 0x90FD, + 0x6109, 0x80F0, 0x611C, 0xA0CB, 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A7, + 0x5121, 0x7102, 0x6116, 0xA05D, 0x610C, 0x9021, 0x611E, 0xA0EF, 0x510B, 0x611F, 0x610D, 0x9033, + 0x6000, 0x7106, 0x6117, 0xA077, 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C2, + 0x510F, 0x7004, 0x6110, 0x9059, 0x6002, 0x800E, 0x6119, 0xA09F, 0x5121, 0x70FF, 0x6115, 0xA054, + 0x610A, 0x901C, 0x611D, 0xA0E6, 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08E, + 0x5111, 0x700A, 0x6114, 0xA034, 0x6109, 0x8120, 0x611C, 0xA0D5, 0x510F, 0x7006, 0x6112, 0x9090, + 0x6107, 0x8013, 0x611A, 0xA0B0, 0x5121, 0x7103, 0x6116, 0xA066, 0x610C, 0x9026, 0x611E, 0xA104, + 0x510B, 0x611F, 0x610D, 0x9027, 0x6000, 0x7105, 0x6117, 0xA069, 0x5111, 0x7007, 0x6113, 0x90C0, + 0x6108, 0x8018, 0x611B, 0xA0B4, 0x510F, 0x7003, 0x6110, 0x9042, 0x6002, 0x800B, 0x6119, 0xA092, + 0x5121, 0x7080, 0x6115, 0xA03B, 0x610A, 0x9012, 0x611D, 0xA0D8, 0x510B, 0x6122, 0x610E, 0x9035, + 0x6001, 0x7123, 0x6118, 0xA07B, 0x5111, 0x7009, 0x6114, 0x90F4, 0x6109, 0x8060, 0x611C, 0xA0C5, + 0x510F, 0x7005, 0x6112, 0x9070, 0x6107, 0x800F, 0x611A, 0xA0A2, 0x5121, 0x7102, 0x6116, 0xA057, + 0x610C, 0x901D, 0x611E, 0xA0E9, 0x510B, 0x611F, 0x610D, 0x902C, 0x6000, 0x7106, 0x6117, 0xA072, + 0x5111, 0x7008, 0x6113, 0x90E0, 0x6108, 0x8020, 0x611B, 0xA0BC, 0x510F, 0x7004, 0x6110, 0x9049, + 0x6002, 0x800D, 0x6119, 0xA09A, 0x5121, 0x70FF, 0x6115, 0xA04D, 0x610A, 0x9017, 0x611D, 0xA0E1, + 0x510B, 0x6122, 0x610E, 0x903C, 0x6001, 0x7124, 0x6118, 0xA089, 0x5111, 0x700A, 0x6114, 0xA02B, + 0x6109, 0x80FE, 0x611C, 0xA0CF, 0x510F, 0x7006, 0x6112, 0x9082, 0x6107, 0x8011, 0x611A, 0xA0AA, + 0x5121, 0x7103, 0x6116, 0xA061, 0x610C, 0x9022, 0x611E, 0xA0F6, 0x510B, 0x611F, 0x610D, 0x9029, + 0x6000, 0x7105, 0x6117, 0xA06D, 0x5111, 0x7007, 0x6113, 0x90CC, 0x6108, 0x8019, 0x611B, 0xA0B8, + 0x510F, 0x7003, 0x6110, 0x9044, 0x6002, 0x800C, 0x6119, 0xA096, 0x5121, 0x7080, 0x6115, 0xA047, + 0x610A, 0x9015, 0x611D, 0xA0DC, 0x510B, 0x6122, 0x610E, 0x9038, 0x6001, 0x7123, 0x6118, 0xA083, + 0x5111, 0x7009, 0x6114, 0x90FC, 0x6109, 0x80F0, 0x611C, 0xA0CA, 0x510F, 0x7005, 0x6112, 0x907F, + 0x6107, 0x8010, 0x611A, 0xA0A6, 0x5121, 0x7102, 0x6116, 0xA05C, 0x610C, 0x901F, 0x611E, 0xA0ED, + 0x510B, 0x611F, 0x610D, 0x9031, 0x6000, 0x7106, 0x6117, 0xA076, 0x5111, 0x7008, 0x6113, 0x90F1, + 0x6108, 0x8040, 0x611B, 0xA0C1, 0x510F, 0x7004, 0x6110, 0x9051, 0x6002, 0x800E, 0x6119, 0xA09E, + 0x5121, 0x70FF, 0x6115, 0xA053, 0x610A, 0x901B, 0x611D, 0xA0E5, 0x510B, 0x6122, 0x610E, 0x903F, + 0x6001, 0x7124, 0x6118, 0xA08D, 0x5111, 0x700A, 0x6114, 0xA032, 0x6109, 0x8120, 0x611C, 0xA0D4, + 0x510F, 0x7006, 0x6112, 0x9088, 0x6107, 0x8013, 0x611A, 0xA0AF, 0x5121, 0x7103, 0x6116, 0xA065, + 0x610C, 0x9025, 0x611E, 0xA0FB, 0x510B, 0x611F, 0x610D, 0x9028, 0x6000, 0x7105, 0x6117, 0xA06B, + 0x5111, 0x7007, 0x6113, 0x90C6, 0x6108, 0x8018, 0x611B, 0xA0B6, 0x510F, 0x7003, 0x6110, 0x9043, + 0x6002, 0x800B, 0x6119, 0xA094, 0x5121, 0x7080, 0x6115, 0xA045, 0x610A, 0x9014, 0x611D, 0xA0DA, + 0x510B, 0x6122, 0x610E, 0x9037, 0x6001, 0x7123, 0x6118, 0xA07D, 0x5111, 0x7009, 0x6114, 0x90F8, + 0x6109, 0x8060, 0x611C, 0xA0C8, 0x510F, 0x7005, 0x6112, 0x9078, 0x6107, 0x800F, 0x611A, 0xA0A4, + 0x5121, 0x7102, 0x6116, 0xA05A, 0x610C, 0x901E, 0x611E, 0xA0EB, 0x510B, 0x611F, 0x610D, 0x9030, + 0x6000, 0x7106, 0x6117, 0xA074, 0x5111, 0x7008, 0x6113, 0x90EE, 0x6108, 0x8020, 0x611B, 0xA0BE, + 0x510F, 0x7004, 0x6110, 0x9050, 0x6002, 0x800D, 0x6119, 0xA09C, 0x5121, 0x70FF, 0x6115, 0xA04F, + 0x610A, 0x901A, 0x611D, 0xA0E3, 0x510B, 0x6122, 0x610E, 0x903E, 0x6001, 0x7124, 0x6118, 0xA08B, + 0x5111, 0x700A, 0x6114, 0xA02E, 0x6109, 0x80FE, 0x611C, 0xA0D2, 0x510F, 0x7006, 0x6112, 0x9084, + 0x6107, 0x8011, 0x611A, 0xA0AD, 0x5121, 0x7103, 0x6116, 0xA063, 0x610C, 0x9024, 0x611E, 0xA0F9, + 0x510B, 0x611F, 0x610D, 0x902A, 0x6000, 0x7105, 0x6117, 0xA06F, 0x5111, 0x7007, 0x6113, 0x90D0, + 0x6108, 0x8019, 0x611B, 0xA0BA, 0x510F, 0x7003, 0x6110, 0x9048, 0x6002, 0x800C, 0x6119, 0xA098, + 0x5121, 0x7080, 0x6115, 0xA04B, 0x610A, 0x9016, 0x611D, 0xA0DE, 0x510B, 0x6122, 0x610E, 0x9039, + 0x6001, 0x7123, 0x6118, 0xA086, 0x5111, 0x7009, 0x6114, 0x90FD, 0x6109, 0x80F0, 0x611C, 0xA0CD, + 0x510F, 0x7005, 0x6112, 0x9081, 0x6107, 0x8010, 0x611A, 0xA0A8, 0x5121, 0x7102, 0x6116, 0xA05E, + 0x610C, 0x9021, 0x611E, 0xA0F3, 0x510B, 0x611F, 0x610D, 0x9033, 0x6000, 0x7106, 0x6117, 0xA079, + 0x5111, 0x7008, 0x6113, 0x90F2, 0x6108, 0x8040, 0x611B, 0xA0C3, 0x510F, 0x7004, 0x6110, 0x9059, + 0x6002, 0x800E, 0x6119, 0xA0A0, 0x5121, 0x70FF, 0x6115, 0xA055, 0x610A, 0x901C, 0x611D, 0xA0E7, + 0x510B, 0x6122, 0x610E, 0x9041, 0x6001, 0x7124, 0x6118, 0xA08F, 0x5111, 0x700A, 0x6114, 0xA036, + 0x6109, 0x8120, 0x611C, 0xA0D6, 0x510F, 0x7006, 0x6112, 0x9090, 0x6107, 0x8013, 0x611A, 0xA0B1, + 0x5121, 0x7103, 0x6116, 0xA067, 0x610C, 0x9026, 0x611E, 0xD125 +}; + +static const UINT16 HuffTableLOM[512] = { + 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, + 0x2001, 0x4006, 0x3004, 0x700D, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600B, + 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x8012, 0x2001, 0x4000, 0x3002, 0x4008, + 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x7010, + 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600C, 0x2001, 0x4003, 0x3002, 0x5009, + 0x2001, 0x4006, 0x3004, 0x9018, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, + 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x700E, 0x2001, 0x4000, 0x3002, 0x4008, + 0x2001, 0x4005, 0x3004, 0x600B, 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x9013, + 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, + 0x2001, 0x4006, 0x3004, 0x800F, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600C, + 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x901C, 0x2001, 0x4000, 0x3002, 0x4008, + 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x700D, + 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600B, 0x2001, 0x4003, 0x3002, 0x5009, + 0x2001, 0x4006, 0x3004, 0x8015, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, + 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x7010, 0x2001, 0x4000, 0x3002, 0x4008, + 0x2001, 0x4005, 0x3004, 0x600C, 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x901A, + 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, + 0x2001, 0x4006, 0x3004, 0x700E, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600B, + 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x9016, 0x2001, 0x4000, 0x3002, 0x4008, + 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x8011, + 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600C, 0x2001, 0x4003, 0x3002, 0x5009, + 0x2001, 0x4006, 0x3004, 0x901E, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, + 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x700D, 0x2001, 0x4000, 0x3002, 0x4008, + 0x2001, 0x4005, 0x3004, 0x600B, 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x8012, + 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, + 0x2001, 0x4006, 0x3004, 0x7010, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600C, + 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x9019, 0x2001, 0x4000, 0x3002, 0x4008, + 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x700E, + 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600B, 0x2001, 0x4003, 0x3002, 0x5009, + 0x2001, 0x4006, 0x3004, 0x9014, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, + 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x800F, 0x2001, 0x4000, 0x3002, 0x4008, + 0x2001, 0x4005, 0x3004, 0x600C, 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x901D, + 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, + 0x2001, 0x4006, 0x3004, 0x700D, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600B, + 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x8015, 0x2001, 0x4000, 0x3002, 0x4008, + 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x7010, + 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600C, 0x2001, 0x4003, 0x3002, 0x5009, + 0x2001, 0x4006, 0x3004, 0x901B, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, + 0x2001, 0x4003, 0x3002, 0x5007, 0x2001, 0x4006, 0x3004, 0x700E, 0x2001, 0x4000, 0x3002, 0x4008, + 0x2001, 0x4005, 0x3004, 0x600B, 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x9017, + 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x500A, 0x2001, 0x4003, 0x3002, 0x5007, + 0x2001, 0x4006, 0x3004, 0x8011, 0x2001, 0x4000, 0x3002, 0x4008, 0x2001, 0x4005, 0x3004, 0x600C, + 0x2001, 0x4003, 0x3002, 0x5009, 0x2001, 0x4006, 0x3004, 0x901F +}; + +static const BYTE HuffTableMask[39] = { + 0x11, /* 0 */ + 0x9E, /* 1 */ + 0xA1, /* 2 */ + 0x00, /* 3 */ + 0x00, /* 4 */ + 0x01, /* 5 */ + 0x00, /* 6 */ + 0x03, /* 7 */ + 0x00, /* 8 */ + 0x07, /* 9 */ + 0x00, /* 10 */ + 0x0F, /* 11 */ + 0x00, /* 12 */ + 0x1F, /* 13 */ + 0x00, /* 14 */ + 0x3F, /* 15 */ + 0x00, /* 16 */ + 0x7F, /* 17 */ + 0x00, /* 18 */ + 0xFF, /* 19 */ + 0x00, /* 20 */ + 0xFF, /* 21 */ + 0x01, /* 22 */ + 0xFF, /* 23 */ + 0x03, /* 24 */ + 0xFF, /* 25 */ + 0x07, /* 26 */ + 0xFF, /* 27 */ + 0x0F, /* 28 */ + 0xFF, /* 29 */ + 0x1F, /* 30 */ + 0xFF, /* 31 */ + 0x3F, /* 32 */ + 0xFF, /* 33 */ + 0x7F, /* 34 */ + 0xFF, /* 35 */ + 0xFF, /* 36 */ + 0x00, /* 37 */ + 0x00 /* 38 */ +}; + +static const BYTE HuffLengthLEC[294] = { + 6, /* 0 */ + 6, /* 1 */ + 6, /* 2 */ + 7, /* 3 */ + 7, /* 4 */ + 7, /* 5 */ + 7, /* 6 */ + 7, /* 7 */ + 7, /* 8 */ + 7, /* 9 */ + 7, /* 10 */ + 8, /* 11 */ + 8, /* 12 */ + 8, /* 13 */ + 8, /* 14 */ + 8, /* 15 */ + 8, /* 16 */ + 8, /* 17 */ + 9, /* 18 */ + 8, /* 19 */ + 9, /* 20 */ + 9, /* 21 */ + 9, /* 22 */ + 9, /* 23 */ + 8, /* 24 */ + 8, /* 25 */ + 9, /* 26 */ + 9, /* 27 */ + 9, /* 28 */ + 9, /* 29 */ + 9, /* 30 */ + 9, /* 31 */ + 8, /* 32 */ + 9, /* 33 */ + 9, /* 34 */ + 10, /* 35 */ + 9, /* 36 */ + 9, /* 37 */ + 9, /* 38 */ + 9, /* 39 */ + 9, /* 40 */ + 9, /* 41 */ + 9, /* 42 */ + 10, /* 43 */ + 9, /* 44 */ + 10, /* 45 */ + 10, /* 46 */ + 10, /* 47 */ + 9, /* 48 */ + 9, /* 49 */ + 10, /* 50 */ + 9, /* 51 */ + 10, /* 52 */ + 9, /* 53 */ + 10, /* 54 */ + 9, /* 55 */ + 9, /* 56 */ + 9, /* 57 */ + 10, /* 58 */ + 10, /* 59 */ + 9, /* 60 */ + 10, /* 61 */ + 9, /* 62 */ + 9, /* 63 */ + 8, /* 64 */ + 9, /* 65 */ + 9, /* 66 */ + 9, /* 67 */ + 9, /* 68 */ + 10, /* 69 */ + 10, /* 70 */ + 10, /* 71 */ + 9, /* 72 */ + 9, /* 73 */ + 10, /* 74 */ + 10, /* 75 */ + 10, /* 76 */ + 10, /* 77 */ + 10, /* 78 */ + 10, /* 79 */ + 9, /* 80 */ + 9, /* 81 */ + 10, /* 82 */ + 10, /* 83 */ + 10, /* 84 */ + 10, /* 85 */ + 10, /* 86 */ + 10, /* 87 */ + 10, /* 88 */ + 9, /* 89 */ + 10, /* 90 */ + 10, /* 91 */ + 10, /* 92 */ + 10, /* 93 */ + 10, /* 94 */ + 10, /* 95 */ + 8, /* 96 */ + 10, /* 97 */ + 10, /* 98 */ + 10, /* 99 */ + 10, /* 100 */ + 10, /* 101 */ + 10, /* 102 */ + 10, /* 103 */ + 10, /* 104 */ + 10, /* 105 */ + 10, /* 106 */ + 10, /* 107 */ + 10, /* 108 */ + 10, /* 109 */ + 10, /* 110 */ + 10, /* 111 */ + 9, /* 112 */ + 10, /* 113 */ + 10, /* 114 */ + 10, /* 115 */ + 10, /* 116 */ + 10, /* 117 */ + 10, /* 118 */ + 10, /* 119 */ + 9, /* 120 */ + 10, /* 121 */ + 10, /* 122 */ + 10, /* 123 */ + 10, /* 124 */ + 10, /* 125 */ + 10, /* 126 */ + 9, /* 127 */ + 7, /* 128 */ + 9, /* 129 */ + 9, /* 130 */ + 10, /* 131 */ + 9, /* 132 */ + 10, /* 133 */ + 10, /* 134 */ + 10, /* 135 */ + 9, /* 136 */ + 10, /* 137 */ + 10, /* 138 */ + 10, /* 139 */ + 10, /* 140 */ + 10, /* 141 */ + 10, /* 142 */ + 10, /* 143 */ + 9, /* 144 */ + 10, /* 145 */ + 10, /* 146 */ + 10, /* 147 */ + 10, /* 148 */ + 10, /* 149 */ + 10, /* 150 */ + 10, /* 151 */ + 10, /* 152 */ + 10, /* 153 */ + 10, /* 154 */ + 10, /* 155 */ + 10, /* 156 */ + 10, /* 157 */ + 10, /* 158 */ + 10, /* 159 */ + 10, /* 160 */ + 10, /* 161 */ + 10, /* 162 */ + 10, /* 163 */ + 10, /* 164 */ + 10, /* 165 */ + 10, /* 166 */ + 10, /* 167 */ + 10, /* 168 */ + 10, /* 169 */ + 10, /* 170 */ + 13, /* 171 */ + 10, /* 172 */ + 10, /* 173 */ + 10, /* 174 */ + 10, /* 175 */ + 10, /* 176 */ + 10, /* 177 */ + 11, /* 178 */ + 10, /* 179 */ + 10, /* 180 */ + 10, /* 181 */ + 10, /* 182 */ + 10, /* 183 */ + 10, /* 184 */ + 10, /* 185 */ + 10, /* 186 */ + 10, /* 187 */ + 10, /* 188 */ + 10, /* 189 */ + 10, /* 190 */ + 10, /* 191 */ + 9, /* 192 */ + 10, /* 193 */ + 10, /* 194 */ + 10, /* 195 */ + 10, /* 196 */ + 10, /* 197 */ + 9, /* 198 */ + 10, /* 199 */ + 10, /* 200 */ + 10, /* 201 */ + 10, /* 202 */ + 10, /* 203 */ + 9, /* 204 */ + 10, /* 205 */ + 10, /* 206 */ + 10, /* 207 */ + 9, /* 208 */ + 10, /* 209 */ + 10, /* 210 */ + 10, /* 211 */ + 10, /* 212 */ + 10, /* 213 */ + 10, /* 214 */ + 10, /* 215 */ + 10, /* 216 */ + 10, /* 217 */ + 10, /* 218 */ + 10, /* 219 */ + 10, /* 220 */ + 10, /* 221 */ + 10, /* 222 */ + 10, /* 223 */ + 9, /* 224 */ + 10, /* 225 */ + 10, /* 226 */ + 10, /* 227 */ + 10, /* 228 */ + 10, /* 229 */ + 10, /* 230 */ + 10, /* 231 */ + 10, /* 232 */ + 10, /* 233 */ + 10, /* 234 */ + 10, /* 235 */ + 10, /* 236 */ + 10, /* 237 */ + 9, /* 238 */ + 10, /* 239 */ + 8, /* 240 */ + 9, /* 241 */ + 9, /* 242 */ + 10, /* 243 */ + 9, /* 244 */ + 10, /* 245 */ + 10, /* 246 */ + 10, /* 247 */ + 9, /* 248 */ + 10, /* 249 */ + 10, /* 250 */ + 10, /* 251 */ + 9, /* 252 */ + 9, /* 253 */ + 8, /* 254 */ + 7, /* 255 */ + 13, /* 256 */ + 13, /* 257 */ + 7, /* 258 */ + 7, /* 259 */ + 10, /* 260 */ + 7, /* 261 */ + 7, /* 262 */ + 6, /* 263 */ + 6, /* 264 */ + 6, /* 265 */ + 6, /* 266 */ + 5, /* 267 */ + 6, /* 268 */ + 6, /* 269 */ + 6, /* 270 */ + 5, /* 271 */ + 6, /* 272 */ + 5, /* 273 */ + 6, /* 274 */ + 6, /* 275 */ + 6, /* 276 */ + 6, /* 277 */ + 6, /* 278 */ + 6, /* 279 */ + 6, /* 280 */ + 6, /* 281 */ + 6, /* 282 */ + 6, /* 283 */ + 6, /* 284 */ + 6, /* 285 */ + 6, /* 286 */ + 6, /* 287 */ + 8, /* 288 */ + 5, /* 289 */ + 6, /* 290 */ + 7, /* 291 */ + 7, /* 292 */ + 13 /* 293 */ +}; + +static const BYTE HuffCodeLEC[588] = { + 0x04, /* 0 */ + 0x00, /* 1 */ + 0x24, /* 2 */ + 0x00, /* 3 */ + 0x14, /* 4 */ + 0x00, /* 5 */ + 0x11, /* 6 */ + 0x00, /* 7 */ + 0x51, /* 8 */ + 0x00, /* 9 */ + 0x31, /* 10 */ + 0x00, /* 11 */ + 0x71, /* 12 */ + 0x00, /* 13 */ + 0x09, /* 14 */ + 0x00, /* 15 */ + 0x49, /* 16 */ + 0x00, /* 17 */ + 0x29, /* 18 */ + 0x00, /* 19 */ + 0x69, /* 20 */ + 0x00, /* 21 */ + 0x15, /* 22 */ + 0x00, /* 23 */ + 0x95, /* 24 */ + 0x00, /* 25 */ + 0x55, /* 26 */ + 0x00, /* 27 */ + 0xD5, /* 28 */ + 0x00, /* 29 */ + 0x35, /* 30 */ + 0x00, /* 31 */ + 0xB5, /* 32 */ + 0x00, /* 33 */ + 0x75, /* 34 */ + 0x00, /* 35 */ + 0x1D, /* 36 */ + 0x00, /* 37 */ + 0xF5, /* 38 */ + 0x00, /* 39 */ + 0x1D, /* 40 */ + 0x01, /* 41 */ + 0x9D, /* 42 */ + 0x00, /* 43 */ + 0x9D, /* 44 */ + 0x01, /* 45 */ + 0x5D, /* 46 */ + 0x00, /* 47 */ + 0x0D, /* 48 */ + 0x00, /* 49 */ + 0x8D, /* 50 */ + 0x00, /* 51 */ + 0x5D, /* 52 */ + 0x01, /* 53 */ + 0xDD, /* 54 */ + 0x00, /* 55 */ + 0xDD, /* 56 */ + 0x01, /* 57 */ + 0x3D, /* 58 */ + 0x00, /* 59 */ + 0x3D, /* 60 */ + 0x01, /* 61 */ + 0xBD, /* 62 */ + 0x00, /* 63 */ + 0x4D, /* 64 */ + 0x00, /* 65 */ + 0xBD, /* 66 */ + 0x01, /* 67 */ + 0x7D, /* 68 */ + 0x00, /* 69 */ + 0x6B, /* 70 */ + 0x00, /* 71 */ + 0x7D, /* 72 */ + 0x01, /* 73 */ + 0xFD, /* 74 */ + 0x00, /* 75 */ + 0xFD, /* 76 */ + 0x01, /* 77 */ + 0x03, /* 78 */ + 0x00, /* 79 */ + 0x03, /* 80 */ + 0x01, /* 81 */ + 0x83, /* 82 */ + 0x00, /* 83 */ + 0x83, /* 84 */ + 0x01, /* 85 */ + 0x6B, /* 86 */ + 0x02, /* 87 */ + 0x43, /* 88 */ + 0x00, /* 89 */ + 0x6B, /* 90 */ + 0x01, /* 91 */ + 0x6B, /* 92 */ + 0x03, /* 93 */ + 0xEB, /* 94 */ + 0x00, /* 95 */ + 0x43, /* 96 */ + 0x01, /* 97 */ + 0xC3, /* 98 */ + 0x00, /* 99 */ + 0xEB, /* 100 */ + 0x02, /* 101 */ + 0xC3, /* 102 */ + 0x01, /* 103 */ + 0xEB, /* 104 */ + 0x01, /* 105 */ + 0x23, /* 106 */ + 0x00, /* 107 */ + 0xEB, /* 108 */ + 0x03, /* 109 */ + 0x23, /* 110 */ + 0x01, /* 111 */ + 0xA3, /* 112 */ + 0x00, /* 113 */ + 0xA3, /* 114 */ + 0x01, /* 115 */ + 0x1B, /* 116 */ + 0x00, /* 117 */ + 0x1B, /* 118 */ + 0x02, /* 119 */ + 0x63, /* 120 */ + 0x00, /* 121 */ + 0x1B, /* 122 */ + 0x01, /* 123 */ + 0x63, /* 124 */ + 0x01, /* 125 */ + 0xE3, /* 126 */ + 0x00, /* 127 */ + 0xCD, /* 128 */ + 0x00, /* 129 */ + 0xE3, /* 130 */ + 0x01, /* 131 */ + 0x13, /* 132 */ + 0x00, /* 133 */ + 0x13, /* 134 */ + 0x01, /* 135 */ + 0x93, /* 136 */ + 0x00, /* 137 */ + 0x1B, /* 138 */ + 0x03, /* 139 */ + 0x9B, /* 140 */ + 0x00, /* 141 */ + 0x9B, /* 142 */ + 0x02, /* 143 */ + 0x93, /* 144 */ + 0x01, /* 145 */ + 0x53, /* 146 */ + 0x00, /* 147 */ + 0x9B, /* 148 */ + 0x01, /* 149 */ + 0x9B, /* 150 */ + 0x03, /* 151 */ + 0x5B, /* 152 */ + 0x00, /* 153 */ + 0x5B, /* 154 */ + 0x02, /* 155 */ + 0x5B, /* 156 */ + 0x01, /* 157 */ + 0x5B, /* 158 */ + 0x03, /* 159 */ + 0x53, /* 160 */ + 0x01, /* 161 */ + 0xD3, /* 162 */ + 0x00, /* 163 */ + 0xDB, /* 164 */ + 0x00, /* 165 */ + 0xDB, /* 166 */ + 0x02, /* 167 */ + 0xDB, /* 168 */ + 0x01, /* 169 */ + 0xDB, /* 170 */ + 0x03, /* 171 */ + 0x3B, /* 172 */ + 0x00, /* 173 */ + 0x3B, /* 174 */ + 0x02, /* 175 */ + 0x3B, /* 176 */ + 0x01, /* 177 */ + 0xD3, /* 178 */ + 0x01, /* 179 */ + 0x3B, /* 180 */ + 0x03, /* 181 */ + 0xBB, /* 182 */ + 0x00, /* 183 */ + 0xBB, /* 184 */ + 0x02, /* 185 */ + 0xBB, /* 186 */ + 0x01, /* 187 */ + 0xBB, /* 188 */ + 0x03, /* 189 */ + 0x7B, /* 190 */ + 0x00, /* 191 */ + 0x2D, /* 192 */ + 0x00, /* 193 */ + 0x7B, /* 194 */ + 0x02, /* 195 */ + 0x7B, /* 196 */ + 0x01, /* 197 */ + 0x7B, /* 198 */ + 0x03, /* 199 */ + 0xFB, /* 200 */ + 0x00, /* 201 */ + 0xFB, /* 202 */ + 0x02, /* 203 */ + 0xFB, /* 204 */ + 0x01, /* 205 */ + 0xFB, /* 206 */ + 0x03, /* 207 */ + 0x07, /* 208 */ + 0x00, /* 209 */ + 0x07, /* 210 */ + 0x02, /* 211 */ + 0x07, /* 212 */ + 0x01, /* 213 */ + 0x07, /* 214 */ + 0x03, /* 215 */ + 0x87, /* 216 */ + 0x00, /* 217 */ + 0x87, /* 218 */ + 0x02, /* 219 */ + 0x87, /* 220 */ + 0x01, /* 221 */ + 0x87, /* 222 */ + 0x03, /* 223 */ + 0x33, /* 224 */ + 0x00, /* 225 */ + 0x47, /* 226 */ + 0x00, /* 227 */ + 0x47, /* 228 */ + 0x02, /* 229 */ + 0x47, /* 230 */ + 0x01, /* 231 */ + 0x47, /* 232 */ + 0x03, /* 233 */ + 0xC7, /* 234 */ + 0x00, /* 235 */ + 0xC7, /* 236 */ + 0x02, /* 237 */ + 0xC7, /* 238 */ + 0x01, /* 239 */ + 0x33, /* 240 */ + 0x01, /* 241 */ + 0xC7, /* 242 */ + 0x03, /* 243 */ + 0x27, /* 244 */ + 0x00, /* 245 */ + 0x27, /* 246 */ + 0x02, /* 247 */ + 0x27, /* 248 */ + 0x01, /* 249 */ + 0x27, /* 250 */ + 0x03, /* 251 */ + 0xA7, /* 252 */ + 0x00, /* 253 */ + 0xB3, /* 254 */ + 0x00, /* 255 */ + 0x19, /* 256 */ + 0x00, /* 257 */ + 0xB3, /* 258 */ + 0x01, /* 259 */ + 0x73, /* 260 */ + 0x00, /* 261 */ + 0xA7, /* 262 */ + 0x02, /* 263 */ + 0x73, /* 264 */ + 0x01, /* 265 */ + 0xA7, /* 266 */ + 0x01, /* 267 */ + 0xA7, /* 268 */ + 0x03, /* 269 */ + 0x67, /* 270 */ + 0x00, /* 271 */ + 0xF3, /* 272 */ + 0x00, /* 273 */ + 0x67, /* 274 */ + 0x02, /* 275 */ + 0x67, /* 276 */ + 0x01, /* 277 */ + 0x67, /* 278 */ + 0x03, /* 279 */ + 0xE7, /* 280 */ + 0x00, /* 281 */ + 0xE7, /* 282 */ + 0x02, /* 283 */ + 0xE7, /* 284 */ + 0x01, /* 285 */ + 0xE7, /* 286 */ + 0x03, /* 287 */ + 0xF3, /* 288 */ + 0x01, /* 289 */ + 0x17, /* 290 */ + 0x00, /* 291 */ + 0x17, /* 292 */ + 0x02, /* 293 */ + 0x17, /* 294 */ + 0x01, /* 295 */ + 0x17, /* 296 */ + 0x03, /* 297 */ + 0x97, /* 298 */ + 0x00, /* 299 */ + 0x97, /* 300 */ + 0x02, /* 301 */ + 0x97, /* 302 */ + 0x01, /* 303 */ + 0x97, /* 304 */ + 0x03, /* 305 */ + 0x57, /* 306 */ + 0x00, /* 307 */ + 0x57, /* 308 */ + 0x02, /* 309 */ + 0x57, /* 310 */ + 0x01, /* 311 */ + 0x57, /* 312 */ + 0x03, /* 313 */ + 0xD7, /* 314 */ + 0x00, /* 315 */ + 0xD7, /* 316 */ + 0x02, /* 317 */ + 0xD7, /* 318 */ + 0x01, /* 319 */ + 0xD7, /* 320 */ + 0x03, /* 321 */ + 0x37, /* 322 */ + 0x00, /* 323 */ + 0x37, /* 324 */ + 0x02, /* 325 */ + 0x37, /* 326 */ + 0x01, /* 327 */ + 0x37, /* 328 */ + 0x03, /* 329 */ + 0xB7, /* 330 */ + 0x00, /* 331 */ + 0xB7, /* 332 */ + 0x02, /* 333 */ + 0xB7, /* 334 */ + 0x01, /* 335 */ + 0xB7, /* 336 */ + 0x03, /* 337 */ + 0x77, /* 338 */ + 0x00, /* 339 */ + 0x77, /* 340 */ + 0x02, /* 341 */ + 0xFF, /* 342 */ + 0x07, /* 343 */ + 0x77, /* 344 */ + 0x01, /* 345 */ + 0x77, /* 346 */ + 0x03, /* 347 */ + 0xF7, /* 348 */ + 0x00, /* 349 */ + 0xF7, /* 350 */ + 0x02, /* 351 */ + 0xF7, /* 352 */ + 0x01, /* 353 */ + 0xF7, /* 354 */ + 0x03, /* 355 */ + 0xFF, /* 356 */ + 0x03, /* 357 */ + 0x0F, /* 358 */ + 0x00, /* 359 */ + 0x0F, /* 360 */ + 0x02, /* 361 */ + 0x0F, /* 362 */ + 0x01, /* 363 */ + 0x0F, /* 364 */ + 0x03, /* 365 */ + 0x8F, /* 366 */ + 0x00, /* 367 */ + 0x8F, /* 368 */ + 0x02, /* 369 */ + 0x8F, /* 370 */ + 0x01, /* 371 */ + 0x8F, /* 372 */ + 0x03, /* 373 */ + 0x4F, /* 374 */ + 0x00, /* 375 */ + 0x4F, /* 376 */ + 0x02, /* 377 */ + 0x4F, /* 378 */ + 0x01, /* 379 */ + 0x4F, /* 380 */ + 0x03, /* 381 */ + 0xCF, /* 382 */ + 0x00, /* 383 */ + 0x0B, /* 384 */ + 0x00, /* 385 */ + 0xCF, /* 386 */ + 0x02, /* 387 */ + 0xCF, /* 388 */ + 0x01, /* 389 */ + 0xCF, /* 390 */ + 0x03, /* 391 */ + 0x2F, /* 392 */ + 0x00, /* 393 */ + 0x2F, /* 394 */ + 0x02, /* 395 */ + 0x0B, /* 396 */ + 0x01, /* 397 */ + 0x2F, /* 398 */ + 0x01, /* 399 */ + 0x2F, /* 400 */ + 0x03, /* 401 */ + 0xAF, /* 402 */ + 0x00, /* 403 */ + 0xAF, /* 404 */ + 0x02, /* 405 */ + 0xAF, /* 406 */ + 0x01, /* 407 */ + 0x8B, /* 408 */ + 0x00, /* 409 */ + 0xAF, /* 410 */ + 0x03, /* 411 */ + 0x6F, /* 412 */ + 0x00, /* 413 */ + 0x6F, /* 414 */ + 0x02, /* 415 */ + 0x8B, /* 416 */ + 0x01, /* 417 */ + 0x6F, /* 418 */ + 0x01, /* 419 */ + 0x6F, /* 420 */ + 0x03, /* 421 */ + 0xEF, /* 422 */ + 0x00, /* 423 */ + 0xEF, /* 424 */ + 0x02, /* 425 */ + 0xEF, /* 426 */ + 0x01, /* 427 */ + 0xEF, /* 428 */ + 0x03, /* 429 */ + 0x1F, /* 430 */ + 0x00, /* 431 */ + 0x1F, /* 432 */ + 0x02, /* 433 */ + 0x1F, /* 434 */ + 0x01, /* 435 */ + 0x1F, /* 436 */ + 0x03, /* 437 */ + 0x9F, /* 438 */ + 0x00, /* 439 */ + 0x9F, /* 440 */ + 0x02, /* 441 */ + 0x9F, /* 442 */ + 0x01, /* 443 */ + 0x9F, /* 444 */ + 0x03, /* 445 */ + 0x5F, /* 446 */ + 0x00, /* 447 */ + 0x4B, /* 448 */ + 0x00, /* 449 */ + 0x5F, /* 450 */ + 0x02, /* 451 */ + 0x5F, /* 452 */ + 0x01, /* 453 */ + 0x5F, /* 454 */ + 0x03, /* 455 */ + 0xDF, /* 456 */ + 0x00, /* 457 */ + 0xDF, /* 458 */ + 0x02, /* 459 */ + 0xDF, /* 460 */ + 0x01, /* 461 */ + 0xDF, /* 462 */ + 0x03, /* 463 */ + 0x3F, /* 464 */ + 0x00, /* 465 */ + 0x3F, /* 466 */ + 0x02, /* 467 */ + 0x3F, /* 468 */ + 0x01, /* 469 */ + 0x3F, /* 470 */ + 0x03, /* 471 */ + 0xBF, /* 472 */ + 0x00, /* 473 */ + 0xBF, /* 474 */ + 0x02, /* 475 */ + 0x4B, /* 476 */ + 0x01, /* 477 */ + 0xBF, /* 478 */ + 0x01, /* 479 */ + 0xAD, /* 480 */ + 0x00, /* 481 */ + 0xCB, /* 482 */ + 0x00, /* 483 */ + 0xCB, /* 484 */ + 0x01, /* 485 */ + 0xBF, /* 486 */ + 0x03, /* 487 */ + 0x2B, /* 488 */ + 0x00, /* 489 */ + 0x7F, /* 490 */ + 0x00, /* 491 */ + 0x7F, /* 492 */ + 0x02, /* 493 */ + 0x7F, /* 494 */ + 0x01, /* 495 */ + 0x2B, /* 496 */ + 0x01, /* 497 */ + 0x7F, /* 498 */ + 0x03, /* 499 */ + 0xFF, /* 500 */ + 0x00, /* 501 */ + 0xFF, /* 502 */ + 0x02, /* 503 */ + 0xAB, /* 504 */ + 0x00, /* 505 */ + 0xAB, /* 506 */ + 0x01, /* 507 */ + 0x6D, /* 508 */ + 0x00, /* 509 */ + 0x59, /* 510 */ + 0x00, /* 511 */ + 0xFF, /* 512 */ + 0x17, /* 513 */ + 0xFF, /* 514 */ + 0x0F, /* 515 */ + 0x39, /* 516 */ + 0x00, /* 517 */ + 0x79, /* 518 */ + 0x00, /* 519 */ + 0xFF, /* 520 */ + 0x01, /* 521 */ + 0x05, /* 522 */ + 0x00, /* 523 */ + 0x45, /* 524 */ + 0x00, /* 525 */ + 0x34, /* 526 */ + 0x00, /* 527 */ + 0x0C, /* 528 */ + 0x00, /* 529 */ + 0x2C, /* 530 */ + 0x00, /* 531 */ + 0x1C, /* 532 */ + 0x00, /* 533 */ + 0x00, /* 534 */ + 0x00, /* 535 */ + 0x3C, /* 536 */ + 0x00, /* 537 */ + 0x02, /* 538 */ + 0x00, /* 539 */ + 0x22, /* 540 */ + 0x00, /* 541 */ + 0x10, /* 542 */ + 0x00, /* 543 */ + 0x12, /* 544 */ + 0x00, /* 545 */ + 0x08, /* 546 */ + 0x00, /* 547 */ + 0x32, /* 548 */ + 0x00, /* 549 */ + 0x0A, /* 550 */ + 0x00, /* 551 */ + 0x2A, /* 552 */ + 0x00, /* 553 */ + 0x1A, /* 554 */ + 0x00, /* 555 */ + 0x3A, /* 556 */ + 0x00, /* 557 */ + 0x06, /* 558 */ + 0x00, /* 559 */ + 0x26, /* 560 */ + 0x00, /* 561 */ + 0x16, /* 562 */ + 0x00, /* 563 */ + 0x36, /* 564 */ + 0x00, /* 565 */ + 0x0E, /* 566 */ + 0x00, /* 567 */ + 0x2E, /* 568 */ + 0x00, /* 569 */ + 0x1E, /* 570 */ + 0x00, /* 571 */ + 0x3E, /* 572 */ + 0x00, /* 573 */ + 0x01, /* 574 */ + 0x00, /* 575 */ + 0xED, /* 576 */ + 0x00, /* 577 */ + 0x18, /* 578 */ + 0x00, /* 579 */ + 0x21, /* 580 */ + 0x00, /* 581 */ + 0x25, /* 582 */ + 0x00, /* 583 */ + 0x65, /* 584 */ + 0x00, /* 585 */ + 0xFF, /* 586 */ + 0x1F /* 587 */ +}; + +static const BYTE HuffLengthLOM[32] = { + 4, /* 0 */ + 2, /* 1 */ + 3, /* 2 */ + 4, /* 3 */ + 3, /* 4 */ + 4, /* 5 */ + 4, /* 6 */ + 5, /* 7 */ + 4, /* 8 */ + 5, /* 9 */ + 5, /* 10 */ + 6, /* 11 */ + 6, /* 12 */ + 7, /* 13 */ + 7, /* 14 */ + 8, /* 15 */ + 7, /* 16 */ + 8, /* 17 */ + 8, /* 18 */ + 9, /* 19 */ + 9, /* 20 */ + 8, /* 21 */ + 9, /* 22 */ + 9, /* 23 */ + 9, /* 24 */ + 9, /* 25 */ + 9, /* 26 */ + 9, /* 27 */ + 9, /* 28 */ + 9, /* 29 */ + 9, /* 30 */ + 9 /* 31 */ +}; + +static const UINT16 HuffCodeLOM[32] = { + 0x0001, /* 0 */ + 0x0000, /* 1 */ + 0x0002, /* 2 */ + 0x0009, /* 3 */ + 0x0006, /* 4 */ + 0x0005, /* 5 */ + 0x000D, /* 6 */ + 0x000B, /* 7 */ + 0x0003, /* 8 */ + 0x001B, /* 9 */ + 0x0007, /* 10 */ + 0x0017, /* 11 */ + 0x0037, /* 12 */ + 0x000F, /* 13 */ + 0x004F, /* 14 */ + 0x006F, /* 15 */ + 0x002F, /* 16 */ + 0x00EF, /* 17 */ + 0x001F, /* 18 */ + 0x005F, /* 19 */ + 0x015F, /* 20 */ + 0x009F, /* 21 */ + 0x00DF, /* 22 */ + 0x01DF, /* 23 */ + 0x003F, /* 24 */ + 0x013F, /* 25 */ + 0x00BF, /* 26 */ + 0x01BF, /* 27 */ + 0x007F, /* 28 */ + 0x017F, /* 29 */ + 0x00FF, /* 30 */ + 0x01FF /* 31 */ +}; + +static const UINT32 CopyOffsetBitsLUT[32] = { + 0x0, /* 0 */ + 0x0, /* 1 */ + 0x0, /* 2 */ + 0x0, /* 3 */ + 0x1, /* 4 */ + 0x1, /* 5 */ + 0x2, /* 6 */ + 0x2, /* 7 */ + 0x3, /* 8 */ + 0x3, /* 9 */ + 0x4, /* 10 */ + 0x4, /* 11 */ + 0x5, /* 12 */ + 0x5, /* 13 */ + 0x6, /* 14 */ + 0x6, /* 15 */ + 0x7, /* 16 */ + 0x7, /* 17 */ + 0x8, /* 18 */ + 0x8, /* 19 */ + 0x9, /* 20 */ + 0x9, /* 21 */ + 0xA, /* 22 */ + 0xA, /* 23 */ + 0xB, /* 24 */ + 0xB, /* 25 */ + 0xC, /* 26 */ + 0xC, /* 27 */ + 0xD, /* 28 */ + 0xD, /* 29 */ + 0xE, /* 30 */ + 0xE /* 31 */ +}; + +static const UINT32 CopyOffsetBaseLUT[32] = { + 0x1, /* 0 */ + 0x2, /* 1 */ + 0x3, /* 2 */ + 0x4, /* 3 */ + 0x5, /* 4 */ + 0x7, /* 5 */ + 0x9, /* 6 */ + 0xD, /* 7 */ + 0x11, /* 8 */ + 0x19, /* 9 */ + 0x21, /* 10 */ + 0x31, /* 11 */ + 0x41, /* 12 */ + 0x61, /* 13 */ + 0x81, /* 14 */ + 0xC1, /* 15 */ + 0x101, /* 16 */ + 0x181, /* 17 */ + 0x201, /* 18 */ + 0x301, /* 19 */ + 0x401, /* 20 */ + 0x601, /* 21 */ + 0x801, /* 22 */ + 0xC01, /* 23 */ + 0x1001, /* 24 */ + 0x1801, /* 25 */ + 0x2001, /* 26 */ + 0x3001, /* 27 */ + 0x4001, /* 28 */ + 0x6001, /* 29 */ + 0x8001, /* 30 */ + 0xC001 /* 31 */ +}; + +static const UINT32 LOMBitsLUT[30] = { + 0x0, /* 0 */ + 0x0, /* 1 */ + 0x0, /* 2 */ + 0x0, /* 3 */ + 0x0, /* 4 */ + 0x0, /* 5 */ + 0x0, /* 6 */ + 0x0, /* 7 */ + 0x1, /* 8 */ + 0x1, /* 9 */ + 0x1, /* 10 */ + 0x1, /* 11 */ + 0x2, /* 12 */ + 0x2, /* 13 */ + 0x2, /* 14 */ + 0x2, /* 15 */ + 0x3, /* 16 */ + 0x3, /* 17 */ + 0x3, /* 18 */ + 0x3, /* 19 */ + 0x4, /* 20 */ + 0x4, /* 21 */ + 0x4, /* 22 */ + 0x4, /* 23 */ + 0x6, /* 24 */ + 0x6, /* 25 */ + 0x8, /* 26 */ + 0x8, /* 27 */ + 0xE, /* 28 */ + 0xE /* 29 */ +}; + +static const UINT32 LOMBaseLUT[30] = { + 0x2, /* 0 */ + 0x3, /* 1 */ + 0x4, /* 2 */ + 0x5, /* 3 */ + 0x6, /* 4 */ + 0x7, /* 5 */ + 0x8, /* 6 */ + 0x9, /* 7 */ + 0xA, /* 8 */ + 0xC, /* 9 */ + 0xE, /* 10 */ + 0x10, /* 11 */ + 0x12, /* 12 */ + 0x16, /* 13 */ + 0x1A, /* 14 */ + 0x1E, /* 15 */ + 0x22, /* 16 */ + 0x2A, /* 17 */ + 0x32, /* 18 */ + 0x3A, /* 19 */ + 0x42, /* 20 */ + 0x52, /* 21 */ + 0x62, /* 22 */ + 0x72, /* 23 */ + 0x82, /* 24 */ + 0xC2, /* 25 */ + 0x102, /* 26 */ + 0x202, /* 27 */ + 0x2, /* 28 */ + 0x2 /* 29 */ +}; + +static INLINE UINT16 get_word(const BYTE* data) +{ + UINT16 tmp = 0; + + WINPR_ASSERT(data); + tmp = *data++; + tmp |= *data << 8; + return tmp; +} + +static INLINE UINT32 get_dword(const BYTE* data) +{ + UINT32 tmp = 0; + WINPR_ASSERT(data); + tmp = *data++; + tmp |= (UINT32)*data++ << 8U; + tmp |= (UINT32)*data++ << 16U; + tmp |= (UINT32)*data++ << 24U; + return tmp; +} + +static INLINE BOOL NCrushFetchBits(const BYTE** SrcPtr, const BYTE** SrcEnd, INT32* nbits, + UINT32* bits) +{ + WINPR_ASSERT(SrcPtr); + WINPR_ASSERT(SrcEnd); + WINPR_ASSERT(nbits); + WINPR_ASSERT(bits); + + if (*nbits < 16) + { + if ((*SrcPtr + 1) >= *SrcEnd) + { + if (*SrcPtr >= *SrcEnd) + { + if (*nbits < 0) + return FALSE; + } + else + { + *bits += *(*SrcPtr)++ << *nbits; + *nbits += 8; + } + } + else + { + UINT16 tmp = *(*SrcPtr)++; + tmp |= (*(*SrcPtr)++) << 8; + *bits += tmp << *nbits; + *nbits += 16; + } + } + + return TRUE; +} + +static INLINE void NCrushWriteStart(UINT32* bits, UINT32* offset, UINT32* accumulator) +{ + WINPR_ASSERT(bits); + WINPR_ASSERT(offset); + WINPR_ASSERT(accumulator); + + *bits = 0; + *offset = 0; + *accumulator = 0; +} + +static INLINE void NCrushWriteBits(BYTE** DstPtr, UINT32* accumulator, UINT32* offset, UINT32 _bits, + UINT32 _nbits) +{ + WINPR_ASSERT(DstPtr); + WINPR_ASSERT(accumulator); + WINPR_ASSERT(offset); + + *accumulator |= _bits << *offset; + *offset += _nbits; + + if (*offset >= 16) + { + *(*DstPtr)++ = (*accumulator & 0xFF); + *(*DstPtr)++ = ((*accumulator >> 8) & 0xFF); + *accumulator >>= 16; + *offset -= 16; + } +} + +static INLINE void NCrushWriteFinish(BYTE** DstPtr, UINT32 accumulator) +{ + WINPR_ASSERT(DstPtr); + + *(*DstPtr)++ = accumulator & 0xFF; + *(*DstPtr)++ = (accumulator >> 8) & 0xFF; +} + +int ncrush_decompress(NCRUSH_CONTEXT* ncrush, const BYTE* pSrcData, UINT32 SrcSize, + const BYTE** ppDstData, UINT32* pDstSize, UINT32 flags) +{ + UINT32 index = 0; + BYTE Literal = 0; + UINT32 IndexLEC = 0; + UINT32 BitLength = 0; + UINT32 CopyOffset = 0; + UINT32 CopyLength = 0; + UINT32 OldCopyOffset = 0; + const BYTE* CopyOffsetPtr = NULL; + UINT32 LengthOfMatch = 0; + UINT32 CopyOffsetIndex = 0; + UINT32 OffsetCacheIndex = 0; + UINT32 CopyOffsetBits = 0; + UINT32 CopyOffsetBase = 0; + UINT32 LengthOfMatchBits = 0; + UINT32 LengthOfMatchBase = 0; + + WINPR_ASSERT(ncrush); + WINPR_ASSERT(pSrcData); + WINPR_ASSERT(ppDstData); + WINPR_ASSERT(pDstSize); + + if (ncrush->HistoryEndOffset != 65535) + return -1001; + + BYTE* HistoryBuffer = ncrush->HistoryBuffer; + const BYTE* HistoryBufferEnd = &HistoryBuffer[ncrush->HistoryEndOffset]; + + if (flags & PACKET_AT_FRONT) + { + if ((ncrush->HistoryPtr - 32768) <= HistoryBuffer) + return -1002; + + MoveMemory(HistoryBuffer, (ncrush->HistoryPtr - 32768), 32768); + ncrush->HistoryPtr = &(HistoryBuffer[32768]); + ZeroMemory(&HistoryBuffer[32768], 32768); + } + + if (flags & PACKET_FLUSHED) + { + ncrush->HistoryPtr = HistoryBuffer; + ZeroMemory(HistoryBuffer, sizeof(ncrush->HistoryBuffer)); + ZeroMemory(&(ncrush->OffsetCache), sizeof(ncrush->OffsetCache)); + } + + BYTE* HistoryPtr = ncrush->HistoryPtr; + + if (!(flags & PACKET_COMPRESSED)) + { + *ppDstData = pSrcData; + *pDstSize = SrcSize; + return 1; + } + + const BYTE* SrcEnd = &pSrcData[SrcSize]; + const BYTE* SrcPtr = pSrcData + 4; + + INT32 nbits = 32; + UINT32 bits = get_dword(pSrcData); + while (1) + { + while (1) + { + const UINT16 Mask = get_word(&HuffTableMask[29]); + const UINT32 MaskedBits = bits & Mask; + if (MaskedBits >= ARRAYSIZE(HuffTableLEC)) + return -1; + IndexLEC = HuffTableLEC[MaskedBits] & 0xFFF; + BitLength = HuffTableLEC[MaskedBits] >> 12; + bits >>= BitLength; + nbits -= BitLength; + + if (!NCrushFetchBits(&SrcPtr, &SrcEnd, &nbits, &bits)) + return -1; + + if (IndexLEC >= 256) + break; + + if (HistoryPtr >= HistoryBufferEnd) + { + WLog_ERR(TAG, "ncrush_decompress error: HistoryPtr (%p) >= HistoryBufferEnd (%p)", + (const void*)HistoryPtr, (const void*)HistoryBufferEnd); + return -1003; + } + + Literal = (HuffTableLEC[MaskedBits] & 0xFF); + *HistoryPtr++ = Literal; + } + + if (IndexLEC == 256) + break; /* EOS */ + + CopyOffsetIndex = IndexLEC - 257; + + if (CopyOffsetIndex >= 32) + { + OffsetCacheIndex = IndexLEC - 289; + + if (OffsetCacheIndex >= 4) + return -1004; + + { + CopyOffset = ncrush->OffsetCache[OffsetCacheIndex]; + const UINT16 Mask = get_word(&HuffTableMask[21]); + const UINT32 MaskedBits = bits & Mask; + if (MaskedBits > ARRAYSIZE(HuffTableLOM)) + return -1; + LengthOfMatch = HuffTableLOM[MaskedBits] & 0xFFF; + BitLength = HuffTableLOM[MaskedBits] >> 12; + bits >>= BitLength; + nbits -= BitLength; + } + + if (!NCrushFetchBits(&SrcPtr, &SrcEnd, &nbits, &bits)) + return -1; + + if (LengthOfMatch >= ARRAYSIZE(LOMBitsLUT)) + return -1; + + LengthOfMatchBits = LOMBitsLUT[LengthOfMatch]; + + if (LengthOfMatch >= ARRAYSIZE(LOMBaseLUT)) + return -1; + LengthOfMatchBase = LOMBaseLUT[LengthOfMatch]; + + if (LengthOfMatchBits) + { + const size_t idx = (2ull * LengthOfMatchBits) + 3ull; + if (idx >= ARRAYSIZE(HuffTableMask)) + return -1; + + const UINT16 Mask = get_word(&HuffTableMask[idx]); + const UINT32 MaskedBits = bits & Mask; + bits >>= LengthOfMatchBits; + nbits -= LengthOfMatchBits; + LengthOfMatchBase += MaskedBits; + + if (!NCrushFetchBits(&SrcPtr, &SrcEnd, &nbits, &bits)) + return -1; + } + + OldCopyOffset = ncrush->OffsetCache[OffsetCacheIndex]; + ncrush->OffsetCache[OffsetCacheIndex] = ncrush->OffsetCache[0]; + ncrush->OffsetCache[0] = OldCopyOffset; + } + else + { + if (CopyOffsetIndex >= ARRAYSIZE(CopyOffsetBitsLUT)) + return -1; + + CopyOffsetBits = CopyOffsetBitsLUT[CopyOffsetIndex]; + + if (CopyOffsetIndex >= ARRAYSIZE(CopyOffsetBaseLUT)) + return -1; + CopyOffsetBase = CopyOffsetBaseLUT[CopyOffsetIndex]; + CopyOffset = CopyOffsetBase - 1; + + if (CopyOffsetBits) + { + const size_t idx = (2ull * CopyOffsetBits) + 3ull; + if (idx >= ARRAYSIZE(HuffTableMask)) + return -1; + + { + const UINT16 Mask = get_word(&HuffTableMask[idx]); + const UINT32 MaskedBits = bits & Mask; + const UINT32 tmp = CopyOffsetBase + MaskedBits; + if (tmp < 1) + return -1; + CopyOffset = tmp - 1; + } + bits >>= CopyOffsetBits; + nbits -= CopyOffsetBits; + + if (!NCrushFetchBits(&SrcPtr, &SrcEnd, &nbits, &bits)) + return -1; + } + { + const UINT16 Mask = get_word(&HuffTableMask[21]); + const UINT32 MaskedBits = bits & Mask; + if (MaskedBits >= ARRAYSIZE(HuffTableLOM)) + return -1; + + LengthOfMatch = HuffTableLOM[MaskedBits] & 0xFFF; + BitLength = HuffTableLOM[MaskedBits] >> 12; + bits >>= BitLength; + nbits -= BitLength; + } + if (!NCrushFetchBits(&SrcPtr, &SrcEnd, &nbits, &bits)) + return -1; + + if (LengthOfMatch >= ARRAYSIZE(LOMBitsLUT)) + return -1; + + LengthOfMatchBits = LOMBitsLUT[LengthOfMatch]; + + if (LengthOfMatch >= ARRAYSIZE(LOMBaseLUT)) + return -1; + LengthOfMatchBase = LOMBaseLUT[LengthOfMatch]; + + if (LengthOfMatchBits) + { + const size_t idx = (2ull * LengthOfMatchBits) + 3ull; + if (idx >= ARRAYSIZE(HuffTableMask)) + return -1; + + const UINT16 Mask = get_word(&HuffTableMask[idx]); + const UINT32 MaskedBits = bits & Mask; + bits >>= LengthOfMatchBits; + nbits -= LengthOfMatchBits; + LengthOfMatchBase += MaskedBits; + + if (!NCrushFetchBits(&SrcPtr, &SrcEnd, &nbits, &bits)) + return -1; + } + + ncrush->OffsetCache[3] = ncrush->OffsetCache[2]; + ncrush->OffsetCache[2] = ncrush->OffsetCache[1]; + ncrush->OffsetCache[1] = ncrush->OffsetCache[0]; + ncrush->OffsetCache[0] = CopyOffset; + } + + CopyOffsetPtr = &HistoryBuffer[(HistoryPtr - HistoryBuffer - CopyOffset) & 0xFFFF]; + LengthOfMatch = LengthOfMatchBase; + + if (LengthOfMatch < 2) + return -1005; + + if ((CopyOffsetPtr >= (HistoryBufferEnd - LengthOfMatch)) || + (HistoryPtr >= (HistoryBufferEnd - LengthOfMatch))) + return -1006; + + CopyOffsetPtr = HistoryPtr - CopyOffset; + index = 0; + CopyLength = (LengthOfMatch > CopyOffset) ? CopyOffset : LengthOfMatch; + + if (CopyOffsetPtr >= HistoryBuffer) + { + while (CopyLength > 0) + { + *HistoryPtr++ = *CopyOffsetPtr++; + CopyLength--; + } + + while (LengthOfMatch > CopyOffset) + { + index = ((index >= CopyOffset)) ? 0 : index; + *HistoryPtr++ = *(CopyOffsetPtr + index++); + LengthOfMatch--; + } + } + else + { + CopyOffsetPtr = HistoryBufferEnd - (CopyOffset - (HistoryPtr - HistoryBuffer)); + CopyOffsetPtr++; + + while (CopyLength && (CopyOffsetPtr <= HistoryBufferEnd)) + { + *HistoryPtr++ = *CopyOffsetPtr++; + CopyLength--; + } + + CopyOffsetPtr = HistoryBuffer; + + while (LengthOfMatch > CopyOffset) + { + index = ((index >= CopyOffset)) ? 0 : index; + *HistoryPtr++ = *(CopyOffsetPtr + index++); + LengthOfMatch--; + } + } + + LengthOfMatch = LengthOfMatchBase; + + if (LengthOfMatch == 2) + continue; + } + + if (IndexLEC != 256) + return -1; + + if (ncrush->HistoryBufferFence != 0xABABABAB) + { + WLog_ERR(TAG, "NCrushDecompress: history buffer fence was overwritten, potential buffer " + "overflow detected!"); + return -1007; + } + + const intptr_t hsize = HistoryPtr - ncrush->HistoryPtr; + WINPR_ASSERT(hsize >= 0); + WINPR_ASSERT(hsize <= UINT32_MAX); + *pDstSize = (UINT32)hsize; + *ppDstData = ncrush->HistoryPtr; + ncrush->HistoryPtr = HistoryPtr; + return 1; +} + +static int ncrush_hash_table_add(NCRUSH_CONTEXT* ncrush, const BYTE* pSrcData, UINT32 SrcSize, + UINT32 HistoryOffset) +{ + const BYTE* SrcPtr = pSrcData; + UINT32 Hash = 0; + UINT32 Offset = HistoryOffset; + UINT32 EndOffset = Offset + SrcSize - 8; + + WINPR_ASSERT(ncrush); + WINPR_ASSERT(pSrcData); + WINPR_ASSERT(Offset + SrcSize >= 8); + + while (Offset < EndOffset) + { + Hash = ncrush->HashTable[get_word(SrcPtr)]; + ncrush->HashTable[get_word(SrcPtr)] = Offset; + ncrush->MatchTable[Offset] = Hash; + SrcPtr++; + Offset++; + } + + return 1; +} + +static int ncrush_find_match_length(const BYTE* Ptr1, const BYTE* Ptr2, BYTE* HistoryPtr) +{ + BYTE val1 = 0; + BYTE val2 = 0; + const BYTE* Ptr = Ptr1; + + WINPR_ASSERT(Ptr1); + WINPR_ASSERT(Ptr2); + WINPR_ASSERT(HistoryPtr); + + do + { + if (Ptr1 > HistoryPtr) + break; + + val1 = *Ptr1++; + val2 = *Ptr2++; + } while (val1 == val2); + + const intptr_t psize = Ptr1 - (Ptr + 1); + WINPR_ASSERT(psize <= INT_MAX); + WINPR_ASSERT(psize >= -INT_MAX); + return (int)psize; +} + +static int ncrush_find_best_match(NCRUSH_CONTEXT* ncrush, UINT16 HistoryOffset, + UINT32* pMatchOffset) +{ + int Length = 0; + int MatchLength = 0; + BYTE* MatchPtr = NULL; + UINT16 Offset = 0; + UINT16 NextOffset = 0; + UINT16 MatchOffset = 0; + BYTE* HistoryBuffer = NULL; + + WINPR_ASSERT(ncrush); + WINPR_ASSERT(pMatchOffset); + + if (!ncrush->MatchTable[HistoryOffset]) + return -1; + + MatchLength = 2; + Offset = HistoryOffset; + HistoryBuffer = (BYTE*)ncrush->HistoryBuffer; + ncrush->MatchTable[0] = HistoryOffset; + MatchOffset = ncrush->MatchTable[HistoryOffset]; + NextOffset = ncrush->MatchTable[Offset]; + MatchPtr = &HistoryBuffer[MatchLength]; + + for (int i = 0; i < 4; i++) + { + int j = -1; + + if (j < 0) + { + Offset = ncrush->MatchTable[NextOffset]; + + if (MatchPtr[NextOffset] == HistoryBuffer[HistoryOffset + MatchLength]) + j = 0; + } + + if (j < 0) + { + NextOffset = ncrush->MatchTable[Offset]; + + if (MatchPtr[Offset] == HistoryBuffer[HistoryOffset + MatchLength]) + j = 1; + } + + if (j < 0) + { + Offset = ncrush->MatchTable[NextOffset]; + + if (MatchPtr[NextOffset] == HistoryBuffer[HistoryOffset + MatchLength]) + j = 2; + } + + if (j < 0) + { + NextOffset = ncrush->MatchTable[Offset]; + + if (MatchPtr[Offset] == HistoryBuffer[HistoryOffset + MatchLength]) + j = 3; + } + + if (j < 0) + { + Offset = ncrush->MatchTable[NextOffset]; + + if (MatchPtr[NextOffset] == HistoryBuffer[HistoryOffset + MatchLength]) + j = 4; + } + + if (j < 0) + { + NextOffset = ncrush->MatchTable[Offset]; + + if (MatchPtr[Offset] == HistoryBuffer[HistoryOffset + MatchLength]) + j = 5; + } + + if (j >= 0) + { + if ((j % 2) == 0) + Offset = NextOffset; + + if ((Offset != HistoryOffset) && Offset) + { + Length = ncrush_find_match_length(&HistoryBuffer[HistoryOffset + 2], + &HistoryBuffer[Offset + 2], ncrush->HistoryPtr) + + 2; + + if (Length < 2) + return -1; + + if (Length > 16) + break; + + if (Length > MatchLength) + { + MatchLength = Length; + MatchOffset = Offset; + } + + if ((Length <= MatchLength) || + (&HistoryBuffer[HistoryOffset + 2] < ncrush->HistoryPtr)) + { + NextOffset = ncrush->MatchTable[Offset]; + MatchPtr = &HistoryBuffer[MatchLength]; + continue; + } + } + + break; + } + } + + ncrush->MatchTable[0] = 0; + *pMatchOffset = MatchOffset; + return MatchLength; +} + +static int ncrush_move_encoder_windows(NCRUSH_CONTEXT* ncrush, BYTE* HistoryPtr) +{ + int NewHash = 0; + int NewMatch = 0; + UINT32 HistoryOffset = 0; + + WINPR_ASSERT(ncrush); + WINPR_ASSERT(HistoryPtr); + + if (HistoryPtr < &ncrush->HistoryBuffer[32768]) + return -1; + + if (HistoryPtr > &ncrush->HistoryBuffer[65536]) + return -1; + + MoveMemory(ncrush->HistoryBuffer, HistoryPtr - 32768, 32768); + const intptr_t hsize = HistoryPtr - 32768 - ncrush->HistoryBuffer; + WINPR_ASSERT(hsize <= UINT32_MAX); + WINPR_ASSERT(hsize >= 0); + HistoryOffset = (UINT32)hsize; + + for (int i = 0; i < 65536; i += 4) + { + NewHash = ncrush->HashTable[i + 0] - HistoryOffset; + ncrush->HashTable[i + 0] = (NewHash <= 0) ? 0 : NewHash; + NewHash = ncrush->HashTable[i + 1] - HistoryOffset; + ncrush->HashTable[i + 1] = (NewHash <= 0) ? 0 : NewHash; + NewHash = ncrush->HashTable[i + 2] - HistoryOffset; + ncrush->HashTable[i + 2] = (NewHash <= 0) ? 0 : NewHash; + NewHash = ncrush->HashTable[i + 3] - HistoryOffset; + ncrush->HashTable[i + 3] = (NewHash <= 0) ? 0 : NewHash; + } + + for (int j = 0; j < 32768; j += 4) + { + NewMatch = ncrush->MatchTable[HistoryOffset + j + 0] - HistoryOffset; + ncrush->MatchTable[j + 0] = (NewMatch <= 0) ? 0 : NewMatch; + NewMatch = ncrush->MatchTable[HistoryOffset + j + 1] - HistoryOffset; + ncrush->MatchTable[j + 1] = (NewMatch <= 0) ? 0 : NewMatch; + NewMatch = ncrush->MatchTable[HistoryOffset + j + 2] - HistoryOffset; + ncrush->MatchTable[j + 2] = (NewMatch <= 0) ? 0 : NewMatch; + NewMatch = ncrush->MatchTable[HistoryOffset + j + 3] - HistoryOffset; + ncrush->MatchTable[j + 3] = (NewMatch <= 0) ? 0 : NewMatch; + } + + ZeroMemory(&ncrush->MatchTable[32768], 65536); + return 1; +} + +int ncrush_compress(NCRUSH_CONTEXT* ncrush, const BYTE* pSrcData, UINT32 SrcSize, BYTE* pDstBuffer, + const BYTE** ppDstData, UINT32* pDstSize, UINT32* pFlags) +{ + BYTE Literal = 0; + const BYTE* SrcPtr = NULL; + BYTE* DstPtr = NULL; + UINT32 bits = 0; + UINT32 offset = 0; + UINT16 Mask = 0; + UINT32 MaskedBits = 0; + UINT32 accumulator = 0; + const BYTE* SrcEndPtr = NULL; + BYTE* DstEndPtr = NULL; + BYTE* HistoryPtr = NULL; + BYTE* pDstData = NULL; + UINT32 DstSize = 0; + BOOL PacketAtFront = FALSE; + BOOL PacketFlushed = FALSE; + UINT32 MatchLength = 0; + UINT32 IndexLEC = 0; + UINT32 IndexLOM = 0; + UINT32 IndexCO = 0; + UINT32 CodeLEC = 0; + UINT32 BitLength = 0; + UINT32 CopyOffset = 0; + UINT32 MatchOffset = 0; + UINT32 OldCopyOffset = 0; + UINT32* OffsetCache = NULL; + UINT32 OffsetCacheIndex = 0; + UINT32 HistoryOffset = 0; + BYTE* HistoryBuffer = NULL; + UINT32 HistoryBufferSize = 0; + BYTE* HistoryBufferEndPtr = NULL; + UINT32 CopyOffsetIndex = 0; + UINT32 CopyOffsetBits = 0; + UINT32 CompressionLevel = 2; + + WINPR_ASSERT(ncrush); + + WINPR_ASSERT(ncrush); + WINPR_ASSERT(pSrcData); + WINPR_ASSERT(pDstBuffer); + WINPR_ASSERT(ppDstData); + WINPR_ASSERT(pDstSize); + WINPR_ASSERT(pFlags); + + HistoryBuffer = ncrush->HistoryBuffer; + *pFlags = 0; + + if ((SrcSize + ncrush->HistoryOffset) >= 65529) + { + if (ncrush->HistoryOffset == (ncrush->HistoryBufferSize + 1)) + { + ncrush->HistoryOffset = 0; + ncrush->HistoryPtr = HistoryBuffer; + PacketFlushed = TRUE; + } + else + { + if (ncrush_move_encoder_windows(ncrush, &(HistoryBuffer[ncrush->HistoryOffset])) < 0) + return -1001; + + ncrush->HistoryPtr = &HistoryBuffer[32768]; + ncrush->HistoryOffset = 32768; + PacketAtFront = TRUE; + } + } + else + { + *pFlags = 0; + } + + pDstData = pDstBuffer; + *ppDstData = pDstBuffer; + + if (!pDstData) + return -1002; + + DstSize = *pDstSize; + + if (DstSize < SrcSize) + return -1003; + + DstSize = SrcSize; + NCrushWriteStart(&bits, &offset, &accumulator); + DstPtr = pDstData; + SrcPtr = pSrcData; + SrcEndPtr = &pSrcData[SrcSize]; + DstEndPtr = &pDstData[DstSize - 1]; + OffsetCache = ncrush->OffsetCache; + HistoryPtr = &HistoryBuffer[ncrush->HistoryOffset]; + HistoryBufferEndPtr = &HistoryBuffer[65536]; + HistoryBufferSize = ncrush->HistoryBufferSize; + CopyOffset = 0; + MatchOffset = 0; + const intptr_t thsize = HistoryPtr - HistoryBuffer; + WINPR_ASSERT(thsize >= 0); + WINPR_ASSERT(thsize <= UINT32_MAX); + ncrush_hash_table_add(ncrush, pSrcData, SrcSize, (UINT32)thsize); + CopyMemory(HistoryPtr, pSrcData, SrcSize); + ncrush->HistoryPtr = &HistoryPtr[SrcSize]; + + while (SrcPtr < (SrcEndPtr - 2)) + { + MatchLength = 0; + const intptr_t hsize = HistoryPtr - HistoryBuffer; + WINPR_ASSERT(hsize <= UINT32_MAX); + WINPR_ASSERT(hsize >= 0); + HistoryOffset = (UINT32)hsize; + + if (ncrush->HistoryPtr && (HistoryPtr > ncrush->HistoryPtr)) + return -1; + + if (HistoryOffset >= 65536) + return -1004; + + if (ncrush->MatchTable[HistoryOffset]) + { + int rc = 0; + + MatchOffset = 0; + rc = ncrush_find_best_match(ncrush, HistoryOffset, &MatchOffset); + + if (rc < 0) + return -1005; + MatchLength = (UINT32)rc; + } + + if (MatchLength) + CopyOffset = (HistoryBufferSize - 1) & (HistoryPtr - &HistoryBuffer[MatchOffset]); + + if ((MatchLength == 2) && (CopyOffset >= 64)) + MatchLength = 0; + + if (MatchLength == 0) + { + /* Literal */ + Literal = *SrcPtr++; + HistoryPtr++; + + if ((DstPtr + 2) > DstEndPtr) /* PACKET_FLUSH #1 */ + { + ncrush_context_reset(ncrush, TRUE); + *pFlags = PACKET_FLUSHED; + *pFlags |= CompressionLevel; + *ppDstData = pSrcData; + *pDstSize = SrcSize; + return 1; + } + + IndexLEC = Literal; + if (IndexLEC >= ARRAYSIZE(HuffLengthLEC)) + return -1; + BitLength = HuffLengthLEC[IndexLEC]; + + if (IndexLEC * 2ull >= ARRAYSIZE(HuffCodeLEC)) + return -1; + CodeLEC = get_word(&HuffCodeLEC[IndexLEC * 2]); + + if (BitLength > 15) + return -1006; + + NCrushWriteBits(&DstPtr, &accumulator, &offset, CodeLEC, BitLength); + } + else + { + HistoryPtr += MatchLength; + SrcPtr += MatchLength; + + if (!MatchLength) + return -1007; + + if ((DstPtr + 8) > DstEndPtr) /* PACKET_FLUSH #2 */ + { + ncrush_context_reset(ncrush, TRUE); + *pFlags = PACKET_FLUSHED; + *pFlags |= CompressionLevel; + *ppDstData = pSrcData; + *pDstSize = SrcSize; + return 1; + } + + OffsetCacheIndex = 5; + + if ((CopyOffset == OffsetCache[0]) || (CopyOffset == OffsetCache[1]) || + (CopyOffset == OffsetCache[2]) || (CopyOffset == OffsetCache[3])) + { + if (CopyOffset == OffsetCache[3]) + { + OldCopyOffset = OffsetCache[3]; + OffsetCache[3] = OffsetCache[0]; + OffsetCache[0] = OldCopyOffset; + OffsetCacheIndex = 3; + } + else + { + if (CopyOffset == OffsetCache[2]) + { + OldCopyOffset = OffsetCache[2]; + OffsetCache[2] = OffsetCache[0]; + OffsetCache[0] = OldCopyOffset; + OffsetCacheIndex = 2; + } + else + { + if (CopyOffset == OffsetCache[1]) + { + OldCopyOffset = OffsetCache[1]; + OffsetCache[1] = OffsetCache[0]; + OffsetCache[0] = OldCopyOffset; + OffsetCacheIndex = 1; + } + else + { + if (CopyOffset == OffsetCache[0]) + { + OffsetCacheIndex = 0; + } + } + } + } + } + else + { + OffsetCache[3] = OffsetCache[2]; + OffsetCache[2] = OffsetCache[1]; + OffsetCache[1] = OffsetCache[0]; + OffsetCache[0] = CopyOffset; + } + + if (OffsetCacheIndex >= 4) + { + /* CopyOffset not in OffsetCache */ + if (CopyOffset >= 256) + bits = (CopyOffset >> 7) + 256; + else + bits = CopyOffset; + + CopyOffsetIndex = ncrush->HuffTableCopyOffset[bits + 2]; + + if (CopyOffsetIndex >= ARRAYSIZE(CopyOffsetBitsLUT)) + return -1; + + CopyOffsetBits = CopyOffsetBitsLUT[CopyOffsetIndex]; + IndexLEC = 257 + CopyOffsetIndex; + if (IndexLEC >= ARRAYSIZE(HuffLengthLEC)) + return -1; + BitLength = HuffLengthLEC[IndexLEC]; + + if (IndexLEC * 2ull >= ARRAYSIZE(HuffCodeLEC)) + return -1; + CodeLEC = get_word(&HuffCodeLEC[IndexLEC * 2]); + + if (BitLength > 15) + return -1008; + + if (CopyOffsetBits > 18) + return -1009; + + NCrushWriteBits(&DstPtr, &accumulator, &offset, CodeLEC, BitLength); + Mask = ((1 << CopyOffsetBits) - 1); + MaskedBits = CopyOffset & Mask; + NCrushWriteBits(&DstPtr, &accumulator, &offset, MaskedBits, CopyOffsetBits); + + if ((MatchLength - 2) >= 768) + IndexCO = 28; + else + IndexCO = ncrush->HuffTableLOM[MatchLength]; + + if (IndexCO >= ARRAYSIZE(HuffLengthLOM)) + return -1; + BitLength = HuffLengthLOM[IndexCO]; + + if (IndexCO >= ARRAYSIZE(LOMBitsLUT)) + return -1; + IndexLOM = LOMBitsLUT[IndexCO]; + + if (IndexCO >= ARRAYSIZE(HuffCodeLOM)) + return -1; + NCrushWriteBits(&DstPtr, &accumulator, &offset, HuffCodeLOM[IndexCO], BitLength); + Mask = ((1 << IndexLOM) - 1); + MaskedBits = (MatchLength - 2) & Mask; + NCrushWriteBits(&DstPtr, &accumulator, &offset, MaskedBits, IndexLOM); + + if (IndexCO >= ARRAYSIZE(LOMBaseLUT)) + return -1; + if ((MaskedBits + LOMBaseLUT[IndexCO]) != MatchLength) + return -1010; + } + else + { + /* CopyOffset in OffsetCache */ + IndexLEC = 289 + OffsetCacheIndex; + if (IndexLEC >= ARRAYSIZE(HuffLengthLEC)) + return -1; + BitLength = HuffLengthLEC[IndexLEC]; + if (IndexLEC * 2ull >= ARRAYSIZE(HuffCodeLEC)) + return -1; + CodeLEC = get_word(&HuffCodeLEC[IndexLEC * 2]); + + if (BitLength >= 15) + return -1011; + + NCrushWriteBits(&DstPtr, &accumulator, &offset, CodeLEC, BitLength); + + if ((MatchLength - 2) >= 768) + IndexCO = 28; + else + IndexCO = ncrush->HuffTableLOM[MatchLength]; + + if (IndexCO >= ARRAYSIZE(HuffLengthLOM)) + return -1; + + BitLength = HuffLengthLOM[IndexCO]; + + if (IndexCO >= ARRAYSIZE(LOMBitsLUT)) + return -1; + IndexLOM = LOMBitsLUT[IndexCO]; + + if (IndexCO >= ARRAYSIZE(HuffCodeLOM)) + return -1; + NCrushWriteBits(&DstPtr, &accumulator, &offset, HuffCodeLOM[IndexCO], BitLength); + Mask = ((1 << IndexLOM) - 1); + MaskedBits = (MatchLength - 2) & Mask; + NCrushWriteBits(&DstPtr, &accumulator, &offset, MaskedBits, IndexLOM); + + if (IndexCO >= ARRAYSIZE(LOMBaseLUT)) + return -1; + if ((MaskedBits + LOMBaseLUT[IndexCO]) != MatchLength) + return -1012; + } + } + + if (HistoryPtr >= HistoryBufferEndPtr) + return -1013; + } + + while (SrcPtr < SrcEndPtr) + { + if ((DstPtr + 2) > DstEndPtr) /* PACKET_FLUSH #3 */ + { + ncrush_context_reset(ncrush, TRUE); + *pFlags = PACKET_FLUSHED; + *pFlags |= CompressionLevel; + *ppDstData = pSrcData; + *pDstSize = SrcSize; + return 1; + } + + Literal = *SrcPtr++; + HistoryPtr++; + IndexLEC = Literal; + if (IndexLEC >= ARRAYSIZE(HuffLengthLEC)) + return -1; + if (IndexLEC * 2ull >= ARRAYSIZE(HuffCodeLEC)) + return -1; + BitLength = HuffLengthLEC[IndexLEC]; + CodeLEC = get_word(&HuffCodeLEC[IndexLEC * 2]); + + if (BitLength > 15) + return -1014; + + NCrushWriteBits(&DstPtr, &accumulator, &offset, CodeLEC, BitLength); + } + + if ((DstPtr + 4) >= DstEndPtr) /* PACKET_FLUSH #4 */ + { + ncrush_context_reset(ncrush, TRUE); + *pFlags = PACKET_FLUSHED; + *pFlags |= CompressionLevel; + *ppDstData = pSrcData; + *pDstSize = SrcSize; + return 1; + } + + IndexLEC = 256; + BitLength = HuffLengthLEC[IndexLEC]; + + if (BitLength > 15) + return -1015; + + bits = get_word(&HuffCodeLEC[IndexLEC * 2]); + NCrushWriteBits(&DstPtr, &accumulator, &offset, bits, BitLength); + NCrushWriteFinish(&DstPtr, accumulator); + const intptr_t dsize = DstPtr - pDstData; + WINPR_ASSERT(dsize <= UINT32_MAX); + WINPR_ASSERT(dsize >= 0); + *pDstSize = (UINT32)dsize; + + if (*pDstSize > SrcSize) + return -1016; + + *pFlags |= PACKET_COMPRESSED; + *pFlags |= CompressionLevel; + + if (PacketAtFront) + *pFlags |= PACKET_AT_FRONT; + + if (PacketFlushed) + *pFlags |= PACKET_FLUSHED; + + ncrush->HistoryOffset = HistoryPtr - HistoryBuffer; + + if (ncrush->HistoryOffset >= ncrush->HistoryBufferSize) + return -1; + + return 1; +} + +static int ncrush_generate_tables(NCRUSH_CONTEXT* context) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(28 < ARRAYSIZE(LOMBitsLUT)); + + UINT32 k = 0; + for (int i = 0; i < 28; i++) + { + for (int j = 0; j < 1 << LOMBitsLUT[i]; j++) + { + size_t l = (k++) + 2ull; + context->HuffTableLOM[l] = (int)i; + } + } + + for (UINT32 k = 2; k < 4096; k++) + { + size_t i = 28; + if ((k - 2) < 768) + i = context->HuffTableLOM[k]; + + if (i >= ARRAYSIZE(LOMBitsLUT)) + return -1; + if (i >= ARRAYSIZE(LOMBaseLUT)) + return -1; + + if (((((1 << LOMBitsLUT[i]) - 1) & (k - 2)) + LOMBaseLUT[i]) != k) + return -1; + } + + k = 0; + + for (int i = 0; i < 16; i++) + { + for (int j = 0; j < 1 << CopyOffsetBitsLUT[i]; j++) + { + size_t l = k++ + 2ull; + context->HuffTableCopyOffset[l] = i; + } + } + + k /= 128; + + for (int i = 16; i < 32; i++) + { + for (int j = 0; j < 1 << (CopyOffsetBitsLUT[i] - 7); j++) + { + size_t l = k++ + 2 + 256ull; + context->HuffTableCopyOffset[l] = i; + } + } + + if ((k + 256) > 1024) + return -1; + + return 1; +} + +void ncrush_context_reset(NCRUSH_CONTEXT* ncrush, BOOL flush) +{ + WINPR_ASSERT(ncrush); + + ZeroMemory(&(ncrush->HistoryBuffer), sizeof(ncrush->HistoryBuffer)); + ZeroMemory(&(ncrush->OffsetCache), sizeof(ncrush->OffsetCache)); + ZeroMemory(&(ncrush->MatchTable), sizeof(ncrush->MatchTable)); + ZeroMemory(&(ncrush->HashTable), sizeof(ncrush->HashTable)); + + if (flush) + ncrush->HistoryOffset = ncrush->HistoryBufferSize + 1; + else + ncrush->HistoryOffset = 0; + + ncrush->HistoryPtr = &(ncrush->HistoryBuffer[ncrush->HistoryOffset]); +} + +NCRUSH_CONTEXT* ncrush_context_new(BOOL Compressor) +{ + NCRUSH_CONTEXT* ncrush = (NCRUSH_CONTEXT*)calloc(1, sizeof(NCRUSH_CONTEXT)); + + if (!ncrush) + goto fail; + + ncrush->Compressor = Compressor; + ncrush->HistoryBufferSize = 65536; + ncrush->HistoryEndOffset = ncrush->HistoryBufferSize - 1; + ncrush->HistoryBufferFence = 0xABABABAB; + ncrush->HistoryOffset = 0; + ncrush->HistoryPtr = &(ncrush->HistoryBuffer[ncrush->HistoryOffset]); + + if (ncrush_generate_tables(ncrush) < 0) + { + WLog_DBG(TAG, "ncrush_context_new: failed to initialize tables"); + goto fail; + } + + ncrush_context_reset(ncrush, FALSE); + + return ncrush; +fail: + ncrush_context_free(ncrush); + return NULL; +} + +void ncrush_context_free(NCRUSH_CONTEXT* ncrush) +{ + free(ncrush); +} diff --git a/libfreerdp/codec/ncrush.h b/libfreerdp/codec/ncrush.h new file mode 100644 index 0000000..bc752ce --- /dev/null +++ b/libfreerdp/codec/ncrush.h @@ -0,0 +1,53 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * NCrush (RDP6) Bulk Data Compression + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CODEC_NCRUSH_H +#define FREERDP_CODEC_NCRUSH_H + +#include <freerdp/api.h> +#include <freerdp/types.h> + +#include "mppc.h" + +#include <winpr/bitstream.h> + +typedef struct s_NCRUSH_CONTEXT NCRUSH_CONTEXT; + +#ifdef __cplusplus +extern "C" +{ +#endif + + FREERDP_LOCAL int ncrush_compress(NCRUSH_CONTEXT* ncrush, const BYTE* pSrcData, UINT32 SrcSize, + BYTE* pDstBuffer, const BYTE** ppDstData, UINT32* pDstSize, + UINT32* pFlags); + FREERDP_LOCAL int ncrush_decompress(NCRUSH_CONTEXT* ncrush, const BYTE* pSrcData, + UINT32 SrcSize, const BYTE** ppDstData, UINT32* pDstSize, + UINT32 flags); + + FREERDP_LOCAL void ncrush_context_reset(NCRUSH_CONTEXT* ncrush, BOOL flush); + + FREERDP_LOCAL NCRUSH_CONTEXT* ncrush_context_new(BOOL Compressor); + FREERDP_LOCAL void ncrush_context_free(NCRUSH_CONTEXT* ncrush); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_CODEC_NCRUSH_H */ diff --git a/libfreerdp/codec/nsc.c b/libfreerdp/codec/nsc.c new file mode 100644 index 0000000..049b541 --- /dev/null +++ b/libfreerdp/codec/nsc.c @@ -0,0 +1,502 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * NSCodec Codec + * + * Copyright 2011 Samsung, Author Jiten Pathy + * Copyright 2012 Vic Lee + * Copyright 2016 Armin Novak <armin.novak@thincast.com> + * Copyright 2016 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <winpr/crt.h> + +#include <freerdp/codec/nsc.h> +#include <freerdp/codec/color.h> + +#include "nsc_types.h" +#include "nsc_encode.h" + +#include "nsc_sse2.h" + +#include <freerdp/log.h> +#define TAG FREERDP_TAG("codec.nsc") + +#ifndef NSC_INIT_SIMD +#define NSC_INIT_SIMD(_nsc_context) \ + do \ + { \ + } while (0) +#endif + +static BOOL nsc_decode(NSC_CONTEXT* context) +{ + UINT16 rw = 0; + BYTE shift = 0; + BYTE* bmpdata = NULL; + size_t pos = 0; + + if (!context) + return FALSE; + + rw = ROUND_UP_TO(context->width, 8); + shift = context->ColorLossLevel - 1; /* colorloss recovery + YCoCg shift */ + bmpdata = context->BitmapData; + + if (!bmpdata) + return FALSE; + + for (UINT32 y = 0; y < context->height; y++) + { + const BYTE* yplane = NULL; + const BYTE* coplane = NULL; + const BYTE* cgplane = NULL; + const BYTE* aplane = context->priv->PlaneBuffers[3] + y * context->width; /* A */ + + if (context->ChromaSubsamplingLevel) + { + yplane = context->priv->PlaneBuffers[0] + y * rw; /* Y */ + coplane = context->priv->PlaneBuffers[1] + (y >> 1) * (rw >> 1); /* Co, supersampled */ + cgplane = context->priv->PlaneBuffers[2] + (y >> 1) * (rw >> 1); /* Cg, supersampled */ + } + else + { + yplane = context->priv->PlaneBuffers[0] + y * context->width; /* Y */ + coplane = context->priv->PlaneBuffers[1] + y * context->width; /* Co */ + cgplane = context->priv->PlaneBuffers[2] + y * context->width; /* Cg */ + } + + for (UINT32 x = 0; x < context->width; x++) + { + INT16 y_val = (INT16)*yplane; + INT16 co_val = (INT16)(INT8)(*coplane << shift); + INT16 cg_val = (INT16)(INT8)(*cgplane << shift); + INT16 r_val = y_val + co_val - cg_val; + INT16 g_val = y_val + cg_val; + INT16 b_val = y_val - co_val - cg_val; + + if (pos + 4 > context->BitmapDataLength) + return FALSE; + + pos += 4; + *bmpdata++ = MINMAX(b_val, 0, 0xFF); + *bmpdata++ = MINMAX(g_val, 0, 0xFF); + *bmpdata++ = MINMAX(r_val, 0, 0xFF); + *bmpdata++ = *aplane; + yplane++; + coplane += (context->ChromaSubsamplingLevel ? x % 2 : 1); + cgplane += (context->ChromaSubsamplingLevel ? x % 2 : 1); + aplane++; + } + } + + return TRUE; +} + +static BOOL nsc_rle_decode(const BYTE* in, size_t inSize, BYTE* out, UINT32 outSize, + UINT32 originalSize) +{ + UINT32 left = originalSize; + + while (left > 4) + { + if (inSize < 1) + return FALSE; + inSize--; + + const BYTE value = *in++; + UINT32 len = 0; + + if (left == 5) + { + if (outSize < 1) + return FALSE; + + outSize--; + *out++ = value; + left--; + } + else if (inSize < 1) + return FALSE; + else if (value == *in) + { + inSize--; + in++; + + if (inSize < 1) + return FALSE; + else if (*in < 0xFF) + { + inSize--; + len = (UINT32)*in++; + len += 2; + } + else + { + if (inSize < 5) + return FALSE; + inSize -= 5; + in++; + len = ((UINT32)(*in++)); + len |= ((UINT32)(*in++)) << 8U; + len |= ((UINT32)(*in++)) << 16U; + len |= ((UINT32)(*in++)) << 24U; + } + + if (outSize < len) + return FALSE; + + outSize -= len; + FillMemory(out, len, value); + out += len; + left -= len; + } + else + { + if (outSize < 1) + return FALSE; + + outSize--; + *out++ = value; + left--; + } + } + + if ((outSize < 4) || (left < 4)) + return FALSE; + + if (inSize < 4) + return FALSE; + memcpy(out, in, 4); + return TRUE; +} + +static BOOL nsc_rle_decompress_data(NSC_CONTEXT* context) +{ + if (!context) + return FALSE; + + const BYTE* rle = context->Planes; + size_t rleSize = context->PlanesSize; + WINPR_ASSERT(rle); + + for (size_t i = 0; i < 4; i++) + { + const UINT32 originalSize = context->OrgByteCount[i]; + const UINT32 planeSize = context->PlaneByteCount[i]; + + if (rleSize < planeSize) + return FALSE; + + if (planeSize == 0) + { + if (context->priv->PlaneBuffersLength < originalSize) + return FALSE; + + FillMemory(context->priv->PlaneBuffers[i], originalSize, 0xFF); + } + else if (planeSize < originalSize) + { + if (!nsc_rle_decode(rle, rleSize, context->priv->PlaneBuffers[i], + context->priv->PlaneBuffersLength, originalSize)) + return FALSE; + } + else + { + if (context->priv->PlaneBuffersLength < originalSize) + return FALSE; + + if (rleSize < originalSize) + return FALSE; + + CopyMemory(context->priv->PlaneBuffers[i], rle, originalSize); + } + + rle += planeSize; + } + + return TRUE; +} + +static BOOL nsc_stream_initialize(NSC_CONTEXT* context, wStream* s) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 20)) + return FALSE; + + size_t total = 0; + for (size_t i = 0; i < 4; i++) + { + Stream_Read_UINT32(s, context->PlaneByteCount[i]); + total += context->PlaneByteCount[i]; + } + + Stream_Read_UINT8(s, context->ColorLossLevel); /* ColorLossLevel (1 byte) */ + Stream_Read_UINT8(s, context->ChromaSubsamplingLevel); /* ChromaSubsamplingLevel (1 byte) */ + Stream_Seek(s, 2); /* Reserved (2 bytes) */ + context->Planes = Stream_Pointer(s); + context->PlanesSize = total; + return Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, total); +} + +static BOOL nsc_context_initialize(NSC_CONTEXT* context, wStream* s) +{ + if (!nsc_stream_initialize(context, s)) + return FALSE; + + const size_t blength = context->width * context->height * 4ull; + + if (!context->BitmapData || (blength > context->BitmapDataLength)) + { + void* tmp = winpr_aligned_recalloc(context->BitmapData, blength + 16, sizeof(BYTE), 32); + + if (!tmp) + return FALSE; + + context->BitmapData = tmp; + context->BitmapDataLength = blength; + } + + const UINT32 tempWidth = ROUND_UP_TO(context->width, 8); + const UINT32 tempHeight = ROUND_UP_TO(context->height, 2); + /* The maximum length a decoded plane can reach in all cases */ + const size_t plength = 1ull * tempWidth * tempHeight; + + if (plength > context->priv->PlaneBuffersLength) + { + for (size_t i = 0; i < 4; i++) + { + void* tmp = (BYTE*)winpr_aligned_recalloc(context->priv->PlaneBuffers[i], plength, + sizeof(BYTE), 32); + + if (!tmp) + return FALSE; + + context->priv->PlaneBuffers[i] = tmp; + } + + context->priv->PlaneBuffersLength = plength; + } + + for (size_t i = 0; i < 4; i++) + context->OrgByteCount[i] = context->width * context->height; + + if (context->ChromaSubsamplingLevel) + { + context->OrgByteCount[0] = tempWidth * context->height; + context->OrgByteCount[1] = (tempWidth >> 1) * (tempHeight >> 1); + context->OrgByteCount[2] = context->OrgByteCount[1]; + } + + return TRUE; +} + +static void nsc_profiler_print(NSC_CONTEXT_PRIV* priv) +{ + WINPR_UNUSED(priv); + + PROFILER_PRINT_HEADER + PROFILER_PRINT(priv->prof_nsc_rle_decompress_data) + PROFILER_PRINT(priv->prof_nsc_decode) + PROFILER_PRINT(priv->prof_nsc_rle_compress_data) + PROFILER_PRINT(priv->prof_nsc_encode) + PROFILER_PRINT_FOOTER +} + +BOOL nsc_context_reset(NSC_CONTEXT* context, UINT32 width, UINT32 height) +{ + if (!context) + return FALSE; + + if ((width > UINT16_MAX) || (height > UINT16_MAX)) + return FALSE; + + context->width = (UINT16)width; + context->height = (UINT16)height; + return TRUE; +} + +NSC_CONTEXT* nsc_context_new(void) +{ + NSC_CONTEXT* context = (NSC_CONTEXT*)winpr_aligned_calloc(1, sizeof(NSC_CONTEXT), 32); + + if (!context) + return NULL; + + context->priv = (NSC_CONTEXT_PRIV*)winpr_aligned_calloc(1, sizeof(NSC_CONTEXT_PRIV), 32); + + if (!context->priv) + goto error; + + context->priv->log = WLog_Get("com.freerdp.codec.nsc"); + WLog_OpenAppender(context->priv->log); + context->BitmapData = NULL; + context->decode = nsc_decode; + context->encode = nsc_encode; + + PROFILER_CREATE(context->priv->prof_nsc_rle_decompress_data, "nsc_rle_decompress_data") + PROFILER_CREATE(context->priv->prof_nsc_decode, "nsc_decode") + PROFILER_CREATE(context->priv->prof_nsc_rle_compress_data, "nsc_rle_compress_data") + PROFILER_CREATE(context->priv->prof_nsc_encode, "nsc_encode") + /* Default encoding parameters */ + context->ColorLossLevel = 3; + context->ChromaSubsamplingLevel = 1; + /* init optimized methods */ + NSC_INIT_SIMD(context); + return context; +error: + nsc_context_free(context); + return NULL; +} + +void nsc_context_free(NSC_CONTEXT* context) +{ + if (!context) + return; + + if (context->priv) + { + for (size_t i = 0; i < 5; i++) + winpr_aligned_free(context->priv->PlaneBuffers[i]); + + nsc_profiler_print(context->priv); + PROFILER_FREE(context->priv->prof_nsc_rle_decompress_data) + PROFILER_FREE(context->priv->prof_nsc_decode) + PROFILER_FREE(context->priv->prof_nsc_rle_compress_data) + PROFILER_FREE(context->priv->prof_nsc_encode) + winpr_aligned_free(context->priv); + } + + winpr_aligned_free(context->BitmapData); + winpr_aligned_free(context); +} + +#if defined(WITH_FREERDP_DEPRECATED) +BOOL nsc_context_set_pixel_format(NSC_CONTEXT* context, UINT32 pixel_format) +{ + return nsc_context_set_parameters(context, NSC_COLOR_FORMAT, pixel_format); +} +#endif + +BOOL nsc_context_set_parameters(NSC_CONTEXT* context, NSC_PARAMETER what, UINT32 value) +{ + if (!context) + return FALSE; + + switch (what) + { + case NSC_COLOR_LOSS_LEVEL: + context->ColorLossLevel = value; + break; + case NSC_ALLOW_SUBSAMPLING: + context->ChromaSubsamplingLevel = value; + break; + case NSC_DYNAMIC_COLOR_FIDELITY: + context->DynamicColorFidelity = value != 0; + break; + case NSC_COLOR_FORMAT: + context->format = value; + break; + default: + return FALSE; + } + return TRUE; +} + +BOOL nsc_process_message(NSC_CONTEXT* context, UINT16 bpp, UINT32 width, UINT32 height, + const BYTE* data, UINT32 length, BYTE* pDstData, UINT32 DstFormat, + UINT32 nDstStride, UINT32 nXDst, UINT32 nYDst, UINT32 nWidth, + UINT32 nHeight, UINT32 flip) +{ + wStream* s = NULL; + wStream sbuffer = { 0 }; + BOOL ret = 0; + if (!context || !data || !pDstData) + return FALSE; + + s = Stream_StaticConstInit(&sbuffer, data, length); + + if (!s) + return FALSE; + + if (nDstStride == 0) + nDstStride = nWidth * FreeRDPGetBytesPerPixel(DstFormat); + + switch (bpp) + { + case 32: + context->format = PIXEL_FORMAT_BGRA32; + break; + + case 24: + context->format = PIXEL_FORMAT_BGR24; + break; + + case 16: + context->format = PIXEL_FORMAT_BGR16; + break; + + case 8: + context->format = PIXEL_FORMAT_RGB8; + break; + + case 4: + context->format = PIXEL_FORMAT_A4; + break; + + default: + return FALSE; + } + + context->width = width; + context->height = height; + ret = nsc_context_initialize(context, s); + + if (!ret) + return FALSE; + + /* RLE decode */ + { + BOOL rc = 0; + PROFILER_ENTER(context->priv->prof_nsc_rle_decompress_data) + rc = nsc_rle_decompress_data(context); + PROFILER_EXIT(context->priv->prof_nsc_rle_decompress_data) + + if (!rc) + return FALSE; + } + /* Colorloss recover, Chroma supersample and AYCoCg to ARGB Conversion in one step */ + { + BOOL rc = 0; + PROFILER_ENTER(context->priv->prof_nsc_decode) + rc = context->decode(context); + PROFILER_EXIT(context->priv->prof_nsc_decode) + + if (!rc) + return FALSE; + } + + if (!freerdp_image_copy(pDstData, DstFormat, nDstStride, nXDst, nYDst, width, height, + context->BitmapData, PIXEL_FORMAT_BGRA32, 0, 0, 0, NULL, flip)) + return FALSE; + + return TRUE; +} diff --git a/libfreerdp/codec/nsc_encode.c b/libfreerdp/codec/nsc_encode.c new file mode 100644 index 0000000..71393d9 --- /dev/null +++ b/libfreerdp/codec/nsc_encode.c @@ -0,0 +1,533 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * NSCodec Encoder + * + * Copyright 2012 Vic Lee + * Copyright 2016 Armin Novak <armin.novak@thincast.com> + * Copyright 2016 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <winpr/crt.h> + +#include <freerdp/codec/nsc.h> +#include <freerdp/codec/color.h> + +#include "nsc_types.h" +#include "nsc_encode.h" + +typedef struct +{ + UINT32 x; + UINT32 y; + UINT32 width; + UINT32 height; + const BYTE* data; + UINT32 scanline; + BYTE* PlaneBuffer; + UINT32 MaxPlaneSize; + BYTE* PlaneBuffers[5]; + UINT32 OrgByteCount[4]; + + UINT32 LumaPlaneByteCount; + UINT32 OrangeChromaPlaneByteCount; + UINT32 GreenChromaPlaneByteCount; + UINT32 AlphaPlaneByteCount; + UINT8 ColorLossLevel; + UINT8 ChromaSubsamplingLevel; +} NSC_MESSAGE; + +static BOOL nsc_write_message(NSC_CONTEXT* context, wStream* s, const NSC_MESSAGE* message); + +static BOOL nsc_context_initialize_encode(NSC_CONTEXT* context) +{ + UINT32 length = 0; + UINT32 tempWidth = 0; + UINT32 tempHeight = 0; + tempWidth = ROUND_UP_TO(context->width, 8); + tempHeight = ROUND_UP_TO(context->height, 2); + /* The maximum length a decoded plane can reach in all cases */ + length = tempWidth * tempHeight + 16; + + if (length > context->priv->PlaneBuffersLength) + { + for (int i = 0; i < 5; i++) + { + BYTE* tmp = (BYTE*)winpr_aligned_recalloc(context->priv->PlaneBuffers[i], length, + sizeof(BYTE), 32); + + if (!tmp) + goto fail; + + context->priv->PlaneBuffers[i] = tmp; + } + + context->priv->PlaneBuffersLength = length; + } + + if (context->ChromaSubsamplingLevel) + { + context->OrgByteCount[0] = tempWidth * context->height; + context->OrgByteCount[1] = tempWidth * tempHeight / 4; + context->OrgByteCount[2] = tempWidth * tempHeight / 4; + context->OrgByteCount[3] = context->width * context->height; + } + else + { + context->OrgByteCount[0] = context->width * context->height; + context->OrgByteCount[1] = context->width * context->height; + context->OrgByteCount[2] = context->width * context->height; + context->OrgByteCount[3] = context->width * context->height; + } + + return TRUE; +fail: + + if (length > context->priv->PlaneBuffersLength) + { + for (int i = 0; i < 5; i++) + winpr_aligned_free(context->priv->PlaneBuffers[i]); + } + + return FALSE; +} + +static BOOL nsc_encode_argb_to_aycocg(NSC_CONTEXT* context, const BYTE* data, UINT32 scanline) +{ + UINT16 x = 0; + UINT16 y = 0; + UINT16 rw = 0; + BYTE ccl = 0; + const BYTE* src = NULL; + BYTE* yplane = NULL; + BYTE* coplane = NULL; + BYTE* cgplane = NULL; + BYTE* aplane = NULL; + INT16 r_val = 0; + INT16 g_val = 0; + INT16 b_val = 0; + BYTE a_val = 0; + UINT32 tempWidth = 0; + + tempWidth = ROUND_UP_TO(context->width, 8); + rw = (context->ChromaSubsamplingLevel ? tempWidth : context->width); + ccl = context->ColorLossLevel; + + for (; y < context->height; y++) + { + src = data + (context->height - 1 - y) * scanline; + yplane = context->priv->PlaneBuffers[0] + y * rw; + coplane = context->priv->PlaneBuffers[1] + y * rw; + cgplane = context->priv->PlaneBuffers[2] + y * rw; + aplane = context->priv->PlaneBuffers[3] + y * context->width; + + for (UINT16 x = 0; x < context->width; x++) + { + switch (context->format) + { + case PIXEL_FORMAT_BGRX32: + b_val = *src++; + g_val = *src++; + r_val = *src++; + src++; + a_val = 0xFF; + break; + + case PIXEL_FORMAT_BGRA32: + b_val = *src++; + g_val = *src++; + r_val = *src++; + a_val = *src++; + break; + + case PIXEL_FORMAT_RGBX32: + r_val = *src++; + g_val = *src++; + b_val = *src++; + src++; + a_val = 0xFF; + break; + + case PIXEL_FORMAT_RGBA32: + r_val = *src++; + g_val = *src++; + b_val = *src++; + a_val = *src++; + break; + + case PIXEL_FORMAT_BGR24: + b_val = *src++; + g_val = *src++; + r_val = *src++; + a_val = 0xFF; + break; + + case PIXEL_FORMAT_RGB24: + r_val = *src++; + g_val = *src++; + b_val = *src++; + a_val = 0xFF; + break; + + case PIXEL_FORMAT_BGR16: + b_val = (INT16)(((*(src + 1)) & 0xF8) | ((*(src + 1)) >> 5)); + g_val = (INT16)((((*(src + 1)) & 0x07) << 5) | (((*src) & 0xE0) >> 3)); + r_val = (INT16)((((*src) & 0x1F) << 3) | (((*src) >> 2) & 0x07)); + a_val = 0xFF; + src += 2; + break; + + case PIXEL_FORMAT_RGB16: + r_val = (INT16)(((*(src + 1)) & 0xF8) | ((*(src + 1)) >> 5)); + g_val = (INT16)((((*(src + 1)) & 0x07) << 5) | (((*src) & 0xE0) >> 3)); + b_val = (INT16)((((*src) & 0x1F) << 3) | (((*src) >> 2) & 0x07)); + a_val = 0xFF; + src += 2; + break; + + case PIXEL_FORMAT_A4: + { + int shift = 0; + BYTE idx = 0; + shift = (7 - (x % 8)); + idx = ((*src) >> shift) & 1; + idx |= (((*(src + 1)) >> shift) & 1) << 1; + idx |= (((*(src + 2)) >> shift) & 1) << 2; + idx |= (((*(src + 3)) >> shift) & 1) << 3; + idx *= 3; + r_val = (INT16)context->palette[idx]; + g_val = (INT16)context->palette[idx + 1]; + b_val = (INT16)context->palette[idx + 2]; + + if (shift == 0) + src += 4; + } + + a_val = 0xFF; + break; + + case PIXEL_FORMAT_RGB8: + { + int idx = (*src) * 3; + r_val = (INT16)context->palette[idx]; + g_val = (INT16)context->palette[idx + 1]; + b_val = (INT16)context->palette[idx + 2]; + src++; + } + + a_val = 0xFF; + break; + + default: + r_val = g_val = b_val = a_val = 0; + break; + } + + *yplane++ = (BYTE)((r_val >> 2) + (g_val >> 1) + (b_val >> 2)); + /* Perform color loss reduction here */ + *coplane++ = (BYTE)((r_val - b_val) >> ccl); + *cgplane++ = (BYTE)((-(r_val >> 1) + g_val - (b_val >> 1)) >> ccl); + *aplane++ = a_val; + } + + if (context->ChromaSubsamplingLevel && (x % 2) == 1) + { + *yplane = *(yplane - 1); + *coplane = *(coplane - 1); + *cgplane = *(cgplane - 1); + } + } + + if (context->ChromaSubsamplingLevel && (y % 2) == 1) + { + yplane = context->priv->PlaneBuffers[0] + y * rw; + coplane = context->priv->PlaneBuffers[1] + y * rw; + cgplane = context->priv->PlaneBuffers[2] + y * rw; + CopyMemory(yplane, yplane - rw, rw); + CopyMemory(coplane, coplane - rw, rw); + CopyMemory(cgplane, cgplane - rw, rw); + } + + return TRUE; +} + +static BOOL nsc_encode_subsampling(NSC_CONTEXT* context) +{ + UINT32 tempWidth = 0; + UINT32 tempHeight = 0; + + if (!context) + return FALSE; + + tempWidth = ROUND_UP_TO(context->width, 8); + tempHeight = ROUND_UP_TO(context->height, 2); + + if (tempHeight == 0) + return FALSE; + + if (tempWidth > context->priv->PlaneBuffersLength / tempHeight) + return FALSE; + + for (UINT32 y = 0; y < tempHeight >> 1; y++) + { + BYTE* co_dst = context->priv->PlaneBuffers[1] + y * (tempWidth >> 1); + BYTE* cg_dst = context->priv->PlaneBuffers[2] + y * (tempWidth >> 1); + const INT8* co_src0 = (INT8*)context->priv->PlaneBuffers[1] + (y << 1) * tempWidth; + const INT8* co_src1 = co_src0 + tempWidth; + const INT8* cg_src0 = (INT8*)context->priv->PlaneBuffers[2] + (y << 1) * tempWidth; + const INT8* cg_src1 = cg_src0 + tempWidth; + + for (UINT32 x = 0; x < tempWidth >> 1; x++) + { + *co_dst++ = (BYTE)(((INT16)*co_src0 + (INT16) * (co_src0 + 1) + (INT16)*co_src1 + + (INT16) * (co_src1 + 1)) >> + 2); + *cg_dst++ = (BYTE)(((INT16)*cg_src0 + (INT16) * (cg_src0 + 1) + (INT16)*cg_src1 + + (INT16) * (cg_src1 + 1)) >> + 2); + co_src0 += 2; + co_src1 += 2; + cg_src0 += 2; + cg_src1 += 2; + } + } + + return TRUE; +} + +BOOL nsc_encode(NSC_CONTEXT* context, const BYTE* bmpdata, UINT32 rowstride) +{ + if (!context || !bmpdata || (rowstride == 0)) + return FALSE; + + if (!nsc_encode_argb_to_aycocg(context, bmpdata, rowstride)) + return FALSE; + + if (context->ChromaSubsamplingLevel) + { + if (!nsc_encode_subsampling(context)) + return FALSE; + } + + return TRUE; +} + +static UINT32 nsc_rle_encode(const BYTE* in, BYTE* out, UINT32 originalSize) +{ + UINT32 left = 0; + UINT32 runlength = 1; + UINT32 planeSize = 0; + left = originalSize; + + /** + * We quit the loop if the running compressed size is larger than the original. + * In such cases data will be sent uncompressed. + */ + while (left > 4 && planeSize < originalSize - 4) + { + if (left > 5 && *in == *(in + 1)) + { + runlength++; + } + else if (runlength == 1) + { + *out++ = *in; + planeSize++; + } + else if (runlength < 256) + { + *out++ = *in; + *out++ = *in; + *out++ = runlength - 2; + runlength = 1; + planeSize += 3; + } + else + { + *out++ = *in; + *out++ = *in; + *out++ = 0xFF; + *out++ = (runlength & 0x000000FF); + *out++ = (runlength & 0x0000FF00) >> 8; + *out++ = (runlength & 0x00FF0000) >> 16; + *out++ = (runlength & 0xFF000000) >> 24; + runlength = 1; + planeSize += 7; + } + + in++; + left--; + } + + if (planeSize < originalSize - 4) + CopyMemory(out, in, 4); + + planeSize += 4; + return planeSize; +} + +static void nsc_rle_compress_data(NSC_CONTEXT* context) +{ + UINT32 planeSize = 0; + UINT32 originalSize = 0; + + for (UINT16 i = 0; i < 4; i++) + { + originalSize = context->OrgByteCount[i]; + + if (originalSize == 0) + { + planeSize = 0; + } + else + { + planeSize = nsc_rle_encode(context->priv->PlaneBuffers[i], + context->priv->PlaneBuffers[4], originalSize); + + if (planeSize < originalSize) + CopyMemory(context->priv->PlaneBuffers[i], context->priv->PlaneBuffers[4], + planeSize); + else + planeSize = originalSize; + } + + context->PlaneByteCount[i] = planeSize; + } +} + +static UINT32 nsc_compute_byte_count(NSC_CONTEXT* context, UINT32* ByteCount, UINT32 width, + UINT32 height) +{ + UINT32 tempWidth = 0; + UINT32 tempHeight = 0; + UINT32 maxPlaneSize = 0; + tempWidth = ROUND_UP_TO(width, 8); + tempHeight = ROUND_UP_TO(height, 2); + maxPlaneSize = tempWidth * tempHeight + 16; + + if (context->ChromaSubsamplingLevel) + { + ByteCount[0] = tempWidth * height; + ByteCount[1] = tempWidth * tempHeight / 4; + ByteCount[2] = tempWidth * tempHeight / 4; + ByteCount[3] = width * height; + } + else + { + ByteCount[0] = width * height; + ByteCount[1] = width * height; + ByteCount[2] = width * height; + ByteCount[3] = width * height; + } + + return maxPlaneSize; +} + +BOOL nsc_write_message(NSC_CONTEXT* context, wStream* s, const NSC_MESSAGE* message) +{ + UINT32 totalPlaneByteCount = 0; + totalPlaneByteCount = message->LumaPlaneByteCount + message->OrangeChromaPlaneByteCount + + message->GreenChromaPlaneByteCount + message->AlphaPlaneByteCount; + + if (!Stream_EnsureRemainingCapacity(s, 20 + totalPlaneByteCount)) + return FALSE; + + Stream_Write_UINT32(s, message->LumaPlaneByteCount); /* LumaPlaneByteCount (4 bytes) */ + Stream_Write_UINT32( + s, message->OrangeChromaPlaneByteCount); /* OrangeChromaPlaneByteCount (4 bytes) */ + Stream_Write_UINT32( + s, message->GreenChromaPlaneByteCount); /* GreenChromaPlaneByteCount (4 bytes) */ + Stream_Write_UINT32(s, message->AlphaPlaneByteCount); /* AlphaPlaneByteCount (4 bytes) */ + Stream_Write_UINT8(s, message->ColorLossLevel); /* ColorLossLevel (1 byte) */ + Stream_Write_UINT8(s, message->ChromaSubsamplingLevel); /* ChromaSubsamplingLevel (1 byte) */ + Stream_Write_UINT16(s, 0); /* Reserved (2 bytes) */ + + if (message->LumaPlaneByteCount) + Stream_Write(s, message->PlaneBuffers[0], message->LumaPlaneByteCount); /* LumaPlane */ + + if (message->OrangeChromaPlaneByteCount) + Stream_Write(s, message->PlaneBuffers[1], + message->OrangeChromaPlaneByteCount); /* OrangeChromaPlane */ + + if (message->GreenChromaPlaneByteCount) + Stream_Write(s, message->PlaneBuffers[2], + message->GreenChromaPlaneByteCount); /* GreenChromaPlane */ + + if (message->AlphaPlaneByteCount) + Stream_Write(s, message->PlaneBuffers[3], message->AlphaPlaneByteCount); /* AlphaPlane */ + + return TRUE; +} + +BOOL nsc_compose_message(NSC_CONTEXT* context, wStream* s, const BYTE* data, UINT32 width, + UINT32 height, UINT32 scanline) +{ + BOOL rc = 0; + NSC_MESSAGE message = { 0 }; + + if (!context || !s || !data) + return FALSE; + + context->width = width; + context->height = height; + + if (!nsc_context_initialize_encode(context)) + return FALSE; + + /* ARGB to AYCoCg conversion, chroma subsampling and colorloss reduction */ + PROFILER_ENTER(context->priv->prof_nsc_encode) + rc = context->encode(context, data, scanline); + PROFILER_EXIT(context->priv->prof_nsc_encode) + if (!rc) + return FALSE; + + /* RLE encode */ + PROFILER_ENTER(context->priv->prof_nsc_rle_compress_data) + nsc_rle_compress_data(context); + PROFILER_EXIT(context->priv->prof_nsc_rle_compress_data) + message.PlaneBuffers[0] = context->priv->PlaneBuffers[0]; + message.PlaneBuffers[1] = context->priv->PlaneBuffers[1]; + message.PlaneBuffers[2] = context->priv->PlaneBuffers[2]; + message.PlaneBuffers[3] = context->priv->PlaneBuffers[3]; + message.LumaPlaneByteCount = context->PlaneByteCount[0]; + message.OrangeChromaPlaneByteCount = context->PlaneByteCount[1]; + message.GreenChromaPlaneByteCount = context->PlaneByteCount[2]; + message.AlphaPlaneByteCount = context->PlaneByteCount[3]; + message.ColorLossLevel = context->ColorLossLevel; + message.ChromaSubsamplingLevel = context->ChromaSubsamplingLevel; + return nsc_write_message(context, s, &message); +} + +BOOL nsc_decompose_message(NSC_CONTEXT* context, wStream* s, BYTE* bmpdata, UINT32 x, UINT32 y, + UINT32 width, UINT32 height, UINT32 rowstride, UINT32 format, + UINT32 flip) +{ + size_t size = Stream_GetRemainingLength(s); + + if (size > UINT32_MAX) + return FALSE; + + if (!nsc_process_message(context, (UINT16)FreeRDPGetBitsPerPixel(context->format), width, + height, Stream_Pointer(s), (UINT32)size, bmpdata, format, rowstride, x, + y, width, height, flip)) + return FALSE; + Stream_Seek(s, size); + return TRUE; +} diff --git a/libfreerdp/codec/nsc_encode.h b/libfreerdp/codec/nsc_encode.h new file mode 100644 index 0000000..a84a519 --- /dev/null +++ b/libfreerdp/codec/nsc_encode.h @@ -0,0 +1,29 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * NSCodec Encoder + * + * Copyright 2012 Vic Lee + * Copyright 2016 Armin Novak <armin.novak@thincast.com> + * Copyright 2016 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_LIB_CODEC_NSC_ENCODE_H +#define FREERDP_LIB_CODEC_NSC_ENCODE_H + +#include <freerdp/api.h> + +FREERDP_LOCAL BOOL nsc_encode(NSC_CONTEXT* context, const BYTE* bmpdata, UINT32 rowstride); + +#endif /* FREERDP_LIB_CODEC_NSC_ENCODE_H */ diff --git a/libfreerdp/codec/nsc_sse2.c b/libfreerdp/codec/nsc_sse2.c new file mode 100644 index 0000000..7ef0275 --- /dev/null +++ b/libfreerdp/codec/nsc_sse2.c @@ -0,0 +1,384 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * NSCodec Library - SSE2 Optimizations + * + * Copyright 2012 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <xmmintrin.h> +#include <emmintrin.h> + +#include <freerdp/codec/color.h> +#include <winpr/crt.h> +#include <winpr/sysinfo.h> + +#include "nsc_types.h" +#include "nsc_sse2.h" + +static BOOL nsc_encode_argb_to_aycocg_sse2(NSC_CONTEXT* context, const BYTE* data, UINT32 scanline) +{ + UINT16 y = 0; + UINT16 rw = 0; + BYTE ccl = 0; + const BYTE* src = NULL; + BYTE* yplane = NULL; + BYTE* coplane = NULL; + BYTE* cgplane = NULL; + BYTE* aplane = NULL; + __m128i r_val; + __m128i g_val; + __m128i b_val; + __m128i a_val; + __m128i y_val; + __m128i co_val; + __m128i cg_val; + UINT32 tempWidth = 0; + + if (!context || !data || (scanline == 0)) + return FALSE; + + tempWidth = ROUND_UP_TO(context->width, 8); + rw = (context->ChromaSubsamplingLevel > 0 ? tempWidth : context->width); + ccl = context->ColorLossLevel; + + for (; y < context->height; y++) + { + src = data + (context->height - 1 - y) * scanline; + yplane = context->priv->PlaneBuffers[0] + y * rw; + coplane = context->priv->PlaneBuffers[1] + y * rw; + cgplane = context->priv->PlaneBuffers[2] + y * rw; + aplane = context->priv->PlaneBuffers[3] + y * context->width; + + for (UINT16 x = 0; x < context->width; x += 8) + { + switch (context->format) + { + case PIXEL_FORMAT_BGRX32: + b_val = _mm_set_epi16(*(src + 28), *(src + 24), *(src + 20), *(src + 16), + *(src + 12), *(src + 8), *(src + 4), *src); + g_val = _mm_set_epi16(*(src + 29), *(src + 25), *(src + 21), *(src + 17), + *(src + 13), *(src + 9), *(src + 5), *(src + 1)); + r_val = _mm_set_epi16(*(src + 30), *(src + 26), *(src + 22), *(src + 18), + *(src + 14), *(src + 10), *(src + 6), *(src + 2)); + a_val = _mm_set1_epi16(0xFF); + src += 32; + break; + + case PIXEL_FORMAT_BGRA32: + b_val = _mm_set_epi16(*(src + 28), *(src + 24), *(src + 20), *(src + 16), + *(src + 12), *(src + 8), *(src + 4), *src); + g_val = _mm_set_epi16(*(src + 29), *(src + 25), *(src + 21), *(src + 17), + *(src + 13), *(src + 9), *(src + 5), *(src + 1)); + r_val = _mm_set_epi16(*(src + 30), *(src + 26), *(src + 22), *(src + 18), + *(src + 14), *(src + 10), *(src + 6), *(src + 2)); + a_val = _mm_set_epi16(*(src + 31), *(src + 27), *(src + 23), *(src + 19), + *(src + 15), *(src + 11), *(src + 7), *(src + 3)); + src += 32; + break; + + case PIXEL_FORMAT_RGBX32: + r_val = _mm_set_epi16(*(src + 28), *(src + 24), *(src + 20), *(src + 16), + *(src + 12), *(src + 8), *(src + 4), *src); + g_val = _mm_set_epi16(*(src + 29), *(src + 25), *(src + 21), *(src + 17), + *(src + 13), *(src + 9), *(src + 5), *(src + 1)); + b_val = _mm_set_epi16(*(src + 30), *(src + 26), *(src + 22), *(src + 18), + *(src + 14), *(src + 10), *(src + 6), *(src + 2)); + a_val = _mm_set1_epi16(0xFF); + src += 32; + break; + + case PIXEL_FORMAT_RGBA32: + r_val = _mm_set_epi16(*(src + 28), *(src + 24), *(src + 20), *(src + 16), + *(src + 12), *(src + 8), *(src + 4), *src); + g_val = _mm_set_epi16(*(src + 29), *(src + 25), *(src + 21), *(src + 17), + *(src + 13), *(src + 9), *(src + 5), *(src + 1)); + b_val = _mm_set_epi16(*(src + 30), *(src + 26), *(src + 22), *(src + 18), + *(src + 14), *(src + 10), *(src + 6), *(src + 2)); + a_val = _mm_set_epi16(*(src + 31), *(src + 27), *(src + 23), *(src + 19), + *(src + 15), *(src + 11), *(src + 7), *(src + 3)); + src += 32; + break; + + case PIXEL_FORMAT_BGR24: + b_val = _mm_set_epi16(*(src + 21), *(src + 18), *(src + 15), *(src + 12), + *(src + 9), *(src + 6), *(src + 3), *src); + g_val = _mm_set_epi16(*(src + 22), *(src + 19), *(src + 16), *(src + 13), + *(src + 10), *(src + 7), *(src + 4), *(src + 1)); + r_val = _mm_set_epi16(*(src + 23), *(src + 20), *(src + 17), *(src + 14), + *(src + 11), *(src + 8), *(src + 5), *(src + 2)); + a_val = _mm_set1_epi16(0xFF); + src += 24; + break; + + case PIXEL_FORMAT_RGB24: + r_val = _mm_set_epi16(*(src + 21), *(src + 18), *(src + 15), *(src + 12), + *(src + 9), *(src + 6), *(src + 3), *src); + g_val = _mm_set_epi16(*(src + 22), *(src + 19), *(src + 16), *(src + 13), + *(src + 10), *(src + 7), *(src + 4), *(src + 1)); + b_val = _mm_set_epi16(*(src + 23), *(src + 20), *(src + 17), *(src + 14), + *(src + 11), *(src + 8), *(src + 5), *(src + 2)); + a_val = _mm_set1_epi16(0xFF); + src += 24; + break; + + case PIXEL_FORMAT_BGR16: + b_val = _mm_set_epi16((((*(src + 15)) & 0xF8) | ((*(src + 15)) >> 5)), + (((*(src + 13)) & 0xF8) | ((*(src + 13)) >> 5)), + (((*(src + 11)) & 0xF8) | ((*(src + 11)) >> 5)), + (((*(src + 9)) & 0xF8) | ((*(src + 9)) >> 5)), + (((*(src + 7)) & 0xF8) | ((*(src + 7)) >> 5)), + (((*(src + 5)) & 0xF8) | ((*(src + 5)) >> 5)), + (((*(src + 3)) & 0xF8) | ((*(src + 3)) >> 5)), + (((*(src + 1)) & 0xF8) | ((*(src + 1)) >> 5))); + g_val = _mm_set_epi16( + ((((*(src + 15)) & 0x07) << 5) | (((*(src + 14)) & 0xE0) >> 3)), + ((((*(src + 13)) & 0x07) << 5) | (((*(src + 12)) & 0xE0) >> 3)), + ((((*(src + 11)) & 0x07) << 5) | (((*(src + 10)) & 0xE0) >> 3)), + ((((*(src + 9)) & 0x07) << 5) | (((*(src + 8)) & 0xE0) >> 3)), + ((((*(src + 7)) & 0x07) << 5) | (((*(src + 6)) & 0xE0) >> 3)), + ((((*(src + 5)) & 0x07) << 5) | (((*(src + 4)) & 0xE0) >> 3)), + ((((*(src + 3)) & 0x07) << 5) | (((*(src + 2)) & 0xE0) >> 3)), + ((((*(src + 1)) & 0x07) << 5) | (((*src) & 0xE0) >> 3))); + r_val = _mm_set_epi16( + ((((*(src + 14)) & 0x1F) << 3) | (((*(src + 14)) >> 2) & 0x07)), + ((((*(src + 12)) & 0x1F) << 3) | (((*(src + 12)) >> 2) & 0x07)), + ((((*(src + 10)) & 0x1F) << 3) | (((*(src + 10)) >> 2) & 0x07)), + ((((*(src + 8)) & 0x1F) << 3) | (((*(src + 8)) >> 2) & 0x07)), + ((((*(src + 6)) & 0x1F) << 3) | (((*(src + 6)) >> 2) & 0x07)), + ((((*(src + 4)) & 0x1F) << 3) | (((*(src + 4)) >> 2) & 0x07)), + ((((*(src + 2)) & 0x1F) << 3) | (((*(src + 2)) >> 2) & 0x07)), + ((((*src) & 0x1F) << 3) | (((*src) >> 2) & 0x07))); + a_val = _mm_set1_epi16(0xFF); + src += 16; + break; + + case PIXEL_FORMAT_RGB16: + r_val = _mm_set_epi16((((*(src + 15)) & 0xF8) | ((*(src + 15)) >> 5)), + (((*(src + 13)) & 0xF8) | ((*(src + 13)) >> 5)), + (((*(src + 11)) & 0xF8) | ((*(src + 11)) >> 5)), + (((*(src + 9)) & 0xF8) | ((*(src + 9)) >> 5)), + (((*(src + 7)) & 0xF8) | ((*(src + 7)) >> 5)), + (((*(src + 5)) & 0xF8) | ((*(src + 5)) >> 5)), + (((*(src + 3)) & 0xF8) | ((*(src + 3)) >> 5)), + (((*(src + 1)) & 0xF8) | ((*(src + 1)) >> 5))); + g_val = _mm_set_epi16( + ((((*(src + 15)) & 0x07) << 5) | (((*(src + 14)) & 0xE0) >> 3)), + ((((*(src + 13)) & 0x07) << 5) | (((*(src + 12)) & 0xE0) >> 3)), + ((((*(src + 11)) & 0x07) << 5) | (((*(src + 10)) & 0xE0) >> 3)), + ((((*(src + 9)) & 0x07) << 5) | (((*(src + 8)) & 0xE0) >> 3)), + ((((*(src + 7)) & 0x07) << 5) | (((*(src + 6)) & 0xE0) >> 3)), + ((((*(src + 5)) & 0x07) << 5) | (((*(src + 4)) & 0xE0) >> 3)), + ((((*(src + 3)) & 0x07) << 5) | (((*(src + 2)) & 0xE0) >> 3)), + ((((*(src + 1)) & 0x07) << 5) | (((*src) & 0xE0) >> 3))); + b_val = _mm_set_epi16( + ((((*(src + 14)) & 0x1F) << 3) | (((*(src + 14)) >> 2) & 0x07)), + ((((*(src + 12)) & 0x1F) << 3) | (((*(src + 12)) >> 2) & 0x07)), + ((((*(src + 10)) & 0x1F) << 3) | (((*(src + 10)) >> 2) & 0x07)), + ((((*(src + 8)) & 0x1F) << 3) | (((*(src + 8)) >> 2) & 0x07)), + ((((*(src + 6)) & 0x1F) << 3) | (((*(src + 6)) >> 2) & 0x07)), + ((((*(src + 4)) & 0x1F) << 3) | (((*(src + 4)) >> 2) & 0x07)), + ((((*(src + 2)) & 0x1F) << 3) | (((*(src + 2)) >> 2) & 0x07)), + ((((*src) & 0x1F) << 3) | (((*src) >> 2) & 0x07))); + a_val = _mm_set1_epi16(0xFF); + src += 16; + break; + + case PIXEL_FORMAT_A4: + { + BYTE idx[8] = { 0 }; + + for (int shift = 7; shift >= 0; shift--) + { + idx[shift] = ((*src) >> shift) & 1; + idx[shift] |= (((*(src + 1)) >> shift) & 1) << 1; + idx[shift] |= (((*(src + 2)) >> shift) & 1) << 2; + idx[shift] |= (((*(src + 3)) >> shift) & 1) << 3; + idx[shift] *= 3; + } + + r_val = _mm_set_epi16(context->palette[idx[0]], context->palette[idx[1]], + context->palette[idx[2]], context->palette[idx[3]], + context->palette[idx[4]], context->palette[idx[5]], + context->palette[idx[6]], context->palette[idx[7]]); + g_val = + _mm_set_epi16(context->palette[idx[0] + 1], context->palette[idx[1] + 1], + context->palette[idx[2] + 1], context->palette[idx[3] + 1], + context->palette[idx[4] + 1], context->palette[idx[5] + 1], + context->palette[idx[6] + 1], context->palette[idx[7] + 1]); + b_val = + _mm_set_epi16(context->palette[idx[0] + 2], context->palette[idx[1] + 2], + context->palette[idx[2] + 2], context->palette[idx[3] + 2], + context->palette[idx[4] + 2], context->palette[idx[5] + 2], + context->palette[idx[6] + 2], context->palette[idx[7] + 2]); + src += 4; + } + + a_val = _mm_set1_epi16(0xFF); + break; + + case PIXEL_FORMAT_RGB8: + { + r_val = _mm_set_epi16( + context->palette[(*(src + 7)) * 3], context->palette[(*(src + 6)) * 3], + context->palette[(*(src + 5)) * 3], context->palette[(*(src + 4)) * 3], + context->palette[(*(src + 3)) * 3], context->palette[(*(src + 2)) * 3], + context->palette[(*(src + 1)) * 3], context->palette[(*src) * 3]); + g_val = _mm_set_epi16(context->palette[(*(src + 7)) * 3 + 1], + context->palette[(*(src + 6)) * 3 + 1], + context->palette[(*(src + 5)) * 3 + 1], + context->palette[(*(src + 4)) * 3 + 1], + context->palette[(*(src + 3)) * 3 + 1], + context->palette[(*(src + 2)) * 3 + 1], + context->palette[(*(src + 1)) * 3 + 1], + context->palette[(*src) * 3 + 1]); + b_val = _mm_set_epi16(context->palette[(*(src + 7)) * 3 + 2], + context->palette[(*(src + 6)) * 3 + 2], + context->palette[(*(src + 5)) * 3 + 2], + context->palette[(*(src + 4)) * 3 + 2], + context->palette[(*(src + 3)) * 3 + 2], + context->palette[(*(src + 2)) * 3 + 2], + context->palette[(*(src + 1)) * 3 + 2], + context->palette[(*src) * 3 + 2]); + src += 8; + } + + a_val = _mm_set1_epi16(0xFF); + break; + + default: + r_val = g_val = b_val = a_val = _mm_set1_epi16(0); + break; + } + + y_val = _mm_srai_epi16(r_val, 2); + y_val = _mm_add_epi16(y_val, _mm_srai_epi16(g_val, 1)); + y_val = _mm_add_epi16(y_val, _mm_srai_epi16(b_val, 2)); + co_val = _mm_sub_epi16(r_val, b_val); + co_val = _mm_srai_epi16(co_val, ccl); + cg_val = _mm_sub_epi16(g_val, _mm_srai_epi16(r_val, 1)); + cg_val = _mm_sub_epi16(cg_val, _mm_srai_epi16(b_val, 1)); + cg_val = _mm_srai_epi16(cg_val, ccl); + y_val = _mm_packus_epi16(y_val, y_val); + _mm_storeu_si128((__m128i*)yplane, y_val); + co_val = _mm_packs_epi16(co_val, co_val); + _mm_storeu_si128((__m128i*)coplane, co_val); + cg_val = _mm_packs_epi16(cg_val, cg_val); + _mm_storeu_si128((__m128i*)cgplane, cg_val); + a_val = _mm_packus_epi16(a_val, a_val); + _mm_storeu_si128((__m128i*)aplane, a_val); + yplane += 8; + coplane += 8; + cgplane += 8; + aplane += 8; + } + + if (context->ChromaSubsamplingLevel > 0 && (context->width % 2) == 1) + { + context->priv->PlaneBuffers[0][y * rw + context->width] = + context->priv->PlaneBuffers[0][y * rw + context->width - 1]; + context->priv->PlaneBuffers[1][y * rw + context->width] = + context->priv->PlaneBuffers[1][y * rw + context->width - 1]; + context->priv->PlaneBuffers[2][y * rw + context->width] = + context->priv->PlaneBuffers[2][y * rw + context->width - 1]; + } + } + + if (context->ChromaSubsamplingLevel > 0 && (y % 2) == 1) + { + yplane = context->priv->PlaneBuffers[0] + y * rw; + coplane = context->priv->PlaneBuffers[1] + y * rw; + cgplane = context->priv->PlaneBuffers[2] + y * rw; + CopyMemory(yplane, yplane - rw, rw); + CopyMemory(coplane, coplane - rw, rw); + CopyMemory(cgplane, cgplane - rw, rw); + } + + return TRUE; +} + +static void nsc_encode_subsampling_sse2(NSC_CONTEXT* context) +{ + BYTE* co_dst = NULL; + BYTE* cg_dst = NULL; + INT8* co_src0 = NULL; + INT8* co_src1 = NULL; + INT8* cg_src0 = NULL; + INT8* cg_src1 = NULL; + UINT32 tempWidth = 0; + UINT32 tempHeight = 0; + __m128i t; + __m128i val; + __m128i mask = _mm_set1_epi16(0xFF); + tempWidth = ROUND_UP_TO(context->width, 8); + tempHeight = ROUND_UP_TO(context->height, 2); + + for (UINT32 y = 0; y < tempHeight >> 1; y++) + { + co_dst = context->priv->PlaneBuffers[1] + y * (tempWidth >> 1); + cg_dst = context->priv->PlaneBuffers[2] + y * (tempWidth >> 1); + co_src0 = (INT8*)context->priv->PlaneBuffers[1] + (y << 1) * tempWidth; + co_src1 = co_src0 + tempWidth; + cg_src0 = (INT8*)context->priv->PlaneBuffers[2] + (y << 1) * tempWidth; + cg_src1 = cg_src0 + tempWidth; + + for (UINT32 x = 0; x < tempWidth >> 1; x += 8) + { + t = _mm_loadu_si128((__m128i*)co_src0); + t = _mm_avg_epu8(t, _mm_loadu_si128((__m128i*)co_src1)); + val = _mm_and_si128(_mm_srli_si128(t, 1), mask); + val = _mm_avg_epu16(val, _mm_and_si128(t, mask)); + val = _mm_packus_epi16(val, val); + _mm_storeu_si128((__m128i*)co_dst, val); + co_dst += 8; + co_src0 += 16; + co_src1 += 16; + t = _mm_loadu_si128((__m128i*)cg_src0); + t = _mm_avg_epu8(t, _mm_loadu_si128((__m128i*)cg_src1)); + val = _mm_and_si128(_mm_srli_si128(t, 1), mask); + val = _mm_avg_epu16(val, _mm_and_si128(t, mask)); + val = _mm_packus_epi16(val, val); + _mm_storeu_si128((__m128i*)cg_dst, val); + cg_dst += 8; + cg_src0 += 16; + cg_src1 += 16; + } + } +} + +static BOOL nsc_encode_sse2(NSC_CONTEXT* context, const BYTE* data, UINT32 scanline) +{ + if (!nsc_encode_argb_to_aycocg_sse2(context, data, scanline)) + return FALSE; + + if (context->ChromaSubsamplingLevel > 0) + nsc_encode_subsampling_sse2(context); + + return TRUE; +} + +void nsc_init_sse2(NSC_CONTEXT* context) +{ + if (!IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE)) + return; + + PROFILER_RENAME(context->priv->prof_nsc_encode, "nsc_encode_sse2") + context->encode = nsc_encode_sse2; +} diff --git a/libfreerdp/codec/nsc_sse2.h b/libfreerdp/codec/nsc_sse2.h new file mode 100644 index 0000000..8b795d7 --- /dev/null +++ b/libfreerdp/codec/nsc_sse2.h @@ -0,0 +1,34 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * NSCodec Library - SSE2 Optimizations + * + * Copyright 2012 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_LIB_CODEC_NSC_SSE2_H +#define FREERDP_LIB_CODEC_NSC_SSE2_H + +#include <freerdp/codec/nsc.h> +#include <freerdp/api.h> + +FREERDP_LOCAL void nsc_init_sse2(NSC_CONTEXT* context); + +#ifdef WITH_SSE2 +#ifndef NSC_INIT_SIMD +#define NSC_INIT_SIMD(_context) nsc_init_sse2(_context) +#endif +#endif + +#endif /* FREERDP_LIB_CODEC_NSC_SSE2_H */ diff --git a/libfreerdp/codec/nsc_types.h b/libfreerdp/codec/nsc_types.h new file mode 100644 index 0000000..731a08a --- /dev/null +++ b/libfreerdp/codec/nsc_types.h @@ -0,0 +1,75 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * NSCodec Library + * + * Copyright 2011 Samsung, Author Jiten Pathy + * Copyright 2012 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_LIB_CODEC_NSC_TYPES_H +#define FREERDP_LIB_CODEC_NSC_TYPES_H + +#include <freerdp/config.h> + +#include <winpr/crt.h> +#include <winpr/wlog.h> +#include <winpr/collections.h> + +#include <freerdp/utils/profiler.h> +#include <freerdp/codec/nsc.h> + +#define ROUND_UP_TO(_b, _n) (_b + ((~(_b & (_n - 1)) + 0x1) & (_n - 1))) +#define MINMAX(_v, _l, _h) ((_v) < (_l) ? (_l) : ((_v) > (_h) ? (_h) : (_v))) + +typedef struct +{ + wLog* log; + + BYTE* PlaneBuffers[5]; /* Decompressed Plane Buffers in the respective order */ + UINT32 PlaneBuffersLength; /* Lengths of each plane buffer */ + + /* profilers */ + PROFILER_DEFINE(prof_nsc_rle_decompress_data) + PROFILER_DEFINE(prof_nsc_decode) + PROFILER_DEFINE(prof_nsc_rle_compress_data) + PROFILER_DEFINE(prof_nsc_encode) +} NSC_CONTEXT_PRIV; + +struct S_NSC_CONTEXT +{ + UINT32 OrgByteCount[4]; + UINT32 format; + UINT16 width; + UINT16 height; + BYTE* BitmapData; + UINT32 BitmapDataLength; + + BYTE* Planes; + size_t PlanesSize; + UINT32 PlaneByteCount[4]; + UINT32 ColorLossLevel; + UINT32 ChromaSubsamplingLevel; + BOOL DynamicColorFidelity; + + /* color palette allocated by the application */ + const BYTE* palette; + + BOOL (*decode)(NSC_CONTEXT* context); + BOOL (*encode)(NSC_CONTEXT* context, const BYTE* BitmapData, UINT32 rowstride); + + NSC_CONTEXT_PRIV* priv; +}; + +#endif /* FREERDP_LIB_CODEC_NSC_TYPES_H */ diff --git a/libfreerdp/codec/planar.c b/libfreerdp/codec/planar.c new file mode 100644 index 0000000..0ec0862 --- /dev/null +++ b/libfreerdp/codec/planar.c @@ -0,0 +1,1753 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RDP6 Planar Codec + * + * Copyright 2013 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2016 Armin Novak <armin.novak@thincast.com> + * Copyright 2016 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <winpr/crt.h> +#include <winpr/assert.h> +#include <winpr/print.h> + +#include <freerdp/primitives.h> +#include <freerdp/log.h> +#include <freerdp/codec/bitmap.h> +#include <freerdp/codec/planar.h> + +#define TAG FREERDP_TAG("codec") + +#define PLANAR_ALIGN(val, align) \ + ((val) % (align) == 0) ? (val) : ((val) + (align) - (val) % (align)) + +typedef struct +{ + /** + * controlByte: + * [0-3]: nRunLength + * [4-7]: cRawBytes + */ + BYTE controlByte; + BYTE* rawValues; +} RDP6_RLE_SEGMENT; + +typedef struct +{ + UINT32 cSegments; + RDP6_RLE_SEGMENT* segments; +} RDP6_RLE_SEGMENTS; + +typedef struct +{ + /** + * formatHeader: + * [0-2]: Color Loss Level (CLL) + * [3] : Chroma Subsampling (CS) + * [4] : Run Length Encoding (RLE) + * [5] : No Alpha (NA) + * [6-7]: Reserved + */ + BYTE formatHeader; +} RDP6_BITMAP_STREAM; + +struct S_BITMAP_PLANAR_CONTEXT +{ + UINT32 maxWidth; + UINT32 maxHeight; + UINT32 maxPlaneSize; + + BOOL AllowSkipAlpha; + BOOL AllowRunLengthEncoding; + BOOL AllowColorSubsampling; + BOOL AllowDynamicColorFidelity; + + UINT32 ColorLossLevel; + + BYTE* planes[4]; + BYTE* planesBuffer; + + BYTE* deltaPlanes[4]; + BYTE* deltaPlanesBuffer; + + BYTE* rlePlanes[4]; + BYTE* rlePlanesBuffer; + + BYTE* pTempData; + UINT32 nTempStep; + + BOOL bgr; + BOOL topdown; +}; + +static INLINE UINT32 planar_invert_format(BITMAP_PLANAR_CONTEXT* planar, BOOL alpha, + UINT32 DstFormat) +{ + + if (planar->bgr && alpha) + { + switch (DstFormat) + { + case PIXEL_FORMAT_ARGB32: + DstFormat = PIXEL_FORMAT_ABGR32; + break; + case PIXEL_FORMAT_XRGB32: + DstFormat = PIXEL_FORMAT_XBGR32; + break; + case PIXEL_FORMAT_ABGR32: + DstFormat = PIXEL_FORMAT_ARGB32; + break; + case PIXEL_FORMAT_XBGR32: + DstFormat = PIXEL_FORMAT_XRGB32; + break; + case PIXEL_FORMAT_BGRA32: + DstFormat = PIXEL_FORMAT_RGBA32; + break; + case PIXEL_FORMAT_BGRX32: + DstFormat = PIXEL_FORMAT_RGBX32; + break; + case PIXEL_FORMAT_RGBA32: + DstFormat = PIXEL_FORMAT_BGRA32; + break; + case PIXEL_FORMAT_RGBX32: + DstFormat = PIXEL_FORMAT_BGRX32; + break; + case PIXEL_FORMAT_RGB24: + DstFormat = PIXEL_FORMAT_BGR24; + break; + case PIXEL_FORMAT_BGR24: + DstFormat = PIXEL_FORMAT_RGB24; + break; + case PIXEL_FORMAT_RGB16: + DstFormat = PIXEL_FORMAT_BGR16; + break; + case PIXEL_FORMAT_BGR16: + DstFormat = PIXEL_FORMAT_RGB16; + break; + case PIXEL_FORMAT_ARGB15: + DstFormat = PIXEL_FORMAT_ABGR15; + break; + case PIXEL_FORMAT_RGB15: + DstFormat = PIXEL_FORMAT_BGR15; + break; + case PIXEL_FORMAT_ABGR15: + DstFormat = PIXEL_FORMAT_ARGB15; + break; + case PIXEL_FORMAT_BGR15: + DstFormat = PIXEL_FORMAT_RGB15; + break; + default: + break; + } + } + return DstFormat; +} + +static INLINE BOOL freerdp_bitmap_planar_compress_plane_rle(const BYTE* plane, UINT32 width, + UINT32 height, BYTE* outPlane, + UINT32* dstSize); +static INLINE BYTE* freerdp_bitmap_planar_delta_encode_plane(const BYTE* inPlane, UINT32 width, + UINT32 height, BYTE* outPlane); + +static INLINE INT32 planar_skip_plane_rle(const BYTE* pSrcData, UINT32 SrcSize, UINT32 nWidth, + UINT32 nHeight) +{ + UINT32 used = 0; + BYTE controlByte = 0; + + WINPR_ASSERT(pSrcData); + + for (UINT32 y = 0; y < nHeight; y++) + { + for (UINT32 x = 0; x < nWidth;) + { + int cRawBytes = 0; + int nRunLength = 0; + + if (used >= SrcSize) + { + WLog_ERR(TAG, "planar plane used %" PRIu32 " exceeds SrcSize %" PRIu32, used, + SrcSize); + return -1; + } + + controlByte = pSrcData[used++]; + nRunLength = PLANAR_CONTROL_BYTE_RUN_LENGTH(controlByte); + cRawBytes = PLANAR_CONTROL_BYTE_RAW_BYTES(controlByte); + + if (nRunLength == 1) + { + nRunLength = cRawBytes + 16; + cRawBytes = 0; + } + else if (nRunLength == 2) + { + nRunLength = cRawBytes + 32; + cRawBytes = 0; + } + + used += cRawBytes; + x += cRawBytes; + x += nRunLength; + + if (x > nWidth) + { + WLog_ERR(TAG, "planar plane x %" PRIu32 " exceeds width %" PRIu32, x, nWidth); + return -1; + } + + if (used > SrcSize) + { + WLog_ERR(TAG, "planar plane used %" PRIu32 " exceeds SrcSize %" PRIu32, used, + INT32_MAX); + return -1; + } + } + } + + if (used > INT32_MAX) + { + WLog_ERR(TAG, "planar plane used %" PRIu32 " exceeds SrcSize %" PRIu32, used, SrcSize); + return -1; + } + return (INT32)used; +} + +static INLINE INT32 planar_decompress_plane_rle_only(const BYTE* pSrcData, UINT32 SrcSize, + BYTE* pDstData, UINT32 nWidth, UINT32 nHeight) +{ + UINT32 pixel = 0; + UINT32 cRawBytes = 0; + UINT32 nRunLength = 0; + INT32 deltaValue = 0; + BYTE controlByte = 0; + BYTE* currentScanline = NULL; + BYTE* previousScanline = NULL; + const BYTE* srcp = pSrcData; + + WINPR_ASSERT(nHeight <= INT32_MAX); + WINPR_ASSERT(nWidth <= INT32_MAX); + + previousScanline = NULL; + + for (INT32 y = 0; y < (INT32)nHeight; y++) + { + BYTE* dstp = &pDstData[((y) * (INT32)nWidth)]; + pixel = 0; + currentScanline = dstp; + + for (INT32 x = 0; x < (INT32)nWidth;) + { + controlByte = *srcp; + srcp++; + + if ((srcp - pSrcData) > SrcSize * 1ll) + { + WLog_ERR(TAG, "error reading input buffer"); + return -1; + } + + nRunLength = PLANAR_CONTROL_BYTE_RUN_LENGTH(controlByte); + cRawBytes = PLANAR_CONTROL_BYTE_RAW_BYTES(controlByte); + + if (nRunLength == 1) + { + nRunLength = cRawBytes + 16; + cRawBytes = 0; + } + else if (nRunLength == 2) + { + nRunLength = cRawBytes + 32; + cRawBytes = 0; + } + + if (((dstp + (cRawBytes + nRunLength)) - currentScanline) > nWidth * 1ll) + { + WLog_ERR(TAG, "too many pixels in scanline"); + return -1; + } + + if (!previousScanline) + { + /* first scanline, absolute values */ + while (cRawBytes > 0) + { + pixel = *srcp; + srcp++; + *dstp = pixel; + dstp++; + x++; + cRawBytes--; + } + + while (nRunLength > 0) + { + *dstp = pixel; + dstp++; + x++; + nRunLength--; + } + } + else + { + /* delta values relative to previous scanline */ + while (cRawBytes > 0) + { + deltaValue = *srcp; + srcp++; + + if (deltaValue & 1) + { + deltaValue = deltaValue >> 1; + deltaValue = deltaValue + 1; + pixel = -deltaValue; + } + else + { + deltaValue = deltaValue >> 1; + pixel = deltaValue; + } + + deltaValue = previousScanline[x] + pixel; + *dstp = deltaValue; + dstp++; + x++; + cRawBytes--; + } + + while (nRunLength > 0) + { + deltaValue = previousScanline[x] + pixel; + *dstp = deltaValue; + dstp++; + x++; + nRunLength--; + } + } + } + + previousScanline = currentScanline; + } + + return (INT32)(srcp - pSrcData); +} + +static INLINE INT32 planar_decompress_plane_rle(const BYTE* pSrcData, UINT32 SrcSize, + BYTE* pDstData, INT32 nDstStep, UINT32 nXDst, + UINT32 nYDst, UINT32 nWidth, UINT32 nHeight, + UINT32 nChannel, BOOL vFlip) +{ + UINT32 pixel = 0; + UINT32 cRawBytes = 0; + UINT32 nRunLength = 0; + INT32 deltaValue = 0; + INT32 beg = 0; + INT32 end = 0; + INT32 inc = 0; + BYTE controlByte = 0; + BYTE* currentScanline = NULL; + BYTE* previousScanline = NULL; + const BYTE* srcp = pSrcData; + + WINPR_ASSERT(nHeight <= INT32_MAX); + WINPR_ASSERT(nWidth <= INT32_MAX); + WINPR_ASSERT(nDstStep <= INT32_MAX); + + previousScanline = NULL; + + if (vFlip) + { + beg = (INT32)nHeight - 1; + end = -1; + inc = -1; + } + else + { + beg = 0; + end = (INT32)nHeight; + inc = 1; + } + + for (INT32 y = beg; y != end; y += inc) + { + BYTE* dstp = &pDstData[((nYDst + y) * (INT32)nDstStep) + (nXDst * 4) + nChannel]; + pixel = 0; + currentScanline = dstp; + + for (INT32 x = 0; x < (INT32)nWidth;) + { + controlByte = *srcp; + srcp++; + + if ((srcp - pSrcData) > SrcSize * 1ll) + { + WLog_ERR(TAG, "error reading input buffer"); + return -1; + } + + nRunLength = PLANAR_CONTROL_BYTE_RUN_LENGTH(controlByte); + cRawBytes = PLANAR_CONTROL_BYTE_RAW_BYTES(controlByte); + + if (nRunLength == 1) + { + nRunLength = cRawBytes + 16; + cRawBytes = 0; + } + else if (nRunLength == 2) + { + nRunLength = cRawBytes + 32; + cRawBytes = 0; + } + + if (((dstp + (cRawBytes + nRunLength)) - currentScanline) > nWidth * 4ll) + { + WLog_ERR(TAG, "too many pixels in scanline"); + return -1; + } + + if (!previousScanline) + { + /* first scanline, absolute values */ + while (cRawBytes > 0) + { + pixel = *srcp; + srcp++; + *dstp = pixel; + dstp += 4; + x++; + cRawBytes--; + } + + while (nRunLength > 0) + { + *dstp = pixel; + dstp += 4; + x++; + nRunLength--; + } + } + else + { + /* delta values relative to previous scanline */ + while (cRawBytes > 0) + { + deltaValue = *srcp; + srcp++; + + if (deltaValue & 1) + { + deltaValue = deltaValue >> 1; + deltaValue = deltaValue + 1; + pixel = -deltaValue; + } + else + { + deltaValue = deltaValue >> 1; + pixel = deltaValue; + } + + deltaValue = previousScanline[x * 4] + pixel; + *dstp = deltaValue; + dstp += 4; + x++; + cRawBytes--; + } + + while (nRunLength > 0) + { + deltaValue = previousScanline[x * 4] + pixel; + *dstp = deltaValue; + dstp += 4; + x++; + nRunLength--; + } + } + } + + previousScanline = currentScanline; + } + + return (INT32)(srcp - pSrcData); +} + +static INLINE INT32 planar_set_plane(BYTE bValue, BYTE* pDstData, INT32 nDstStep, UINT32 nXDst, + UINT32 nYDst, UINT32 nWidth, UINT32 nHeight, UINT32 nChannel, + BOOL vFlip) +{ + INT32 beg = 0; + INT32 end = 0; + INT32 inc = 0; + + WINPR_ASSERT(nHeight <= INT32_MAX); + WINPR_ASSERT(nWidth <= INT32_MAX); + WINPR_ASSERT(nDstStep <= INT32_MAX); + + if (vFlip) + { + beg = (INT32)nHeight - 1; + end = -1; + inc = -1; + } + else + { + beg = 0; + end = (INT32)nHeight; + inc = 1; + } + + for (INT32 y = beg; y != end; y += inc) + { + BYTE* dstp = &pDstData[((nYDst + y) * (INT32)nDstStep) + (nXDst * 4) + nChannel]; + + for (INT32 x = 0; x < (INT32)nWidth; ++x) + { + *dstp = bValue; + dstp += 4; + } + } + + return 0; +} + +static INLINE BOOL writeLine(BYTE** ppRgba, UINT32 DstFormat, UINT32 width, const BYTE** ppR, + const BYTE** ppG, const BYTE** ppB, const BYTE** ppA) +{ + WINPR_ASSERT(ppRgba); + WINPR_ASSERT(ppR); + WINPR_ASSERT(ppG); + WINPR_ASSERT(ppB); + + switch (DstFormat) + { + case PIXEL_FORMAT_BGRA32: + for (UINT32 x = 0; x < width; x++) + { + *(*ppRgba)++ = *(*ppB)++; + *(*ppRgba)++ = *(*ppG)++; + *(*ppRgba)++ = *(*ppR)++; + *(*ppRgba)++ = *(*ppA)++; + } + + return TRUE; + + case PIXEL_FORMAT_BGRX32: + for (UINT32 x = 0; x < width; x++) + { + *(*ppRgba)++ = *(*ppB)++; + *(*ppRgba)++ = *(*ppG)++; + *(*ppRgba)++ = *(*ppR)++; + *(*ppRgba)++ = 0xFF; + } + + return TRUE; + + default: + if (ppA) + { + for (UINT32 x = 0; x < width; x++) + { + BYTE alpha = *(*ppA)++; + UINT32 color = + FreeRDPGetColor(DstFormat, *(*ppR)++, *(*ppG)++, *(*ppB)++, alpha); + FreeRDPWriteColor(*ppRgba, DstFormat, color); + *ppRgba += FreeRDPGetBytesPerPixel(DstFormat); + } + } + else + { + const BYTE alpha = 0xFF; + + for (UINT32 x = 0; x < width; x++) + { + UINT32 color = + FreeRDPGetColor(DstFormat, *(*ppR)++, *(*ppG)++, *(*ppB)++, alpha); + FreeRDPWriteColor(*ppRgba, DstFormat, color); + *ppRgba += FreeRDPGetBytesPerPixel(DstFormat); + } + } + + return TRUE; + } +} + +static INLINE BOOL planar_decompress_planes_raw(const BYTE* pSrcData[4], BYTE* pDstData, + UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst, + UINT32 nYDst, UINT32 nWidth, UINT32 nHeight, + BOOL vFlip, UINT32 totalHeight) +{ + INT32 beg = 0; + INT32 end = 0; + INT32 inc = 0; + const BYTE* pR = pSrcData[0]; + const BYTE* pG = pSrcData[1]; + const BYTE* pB = pSrcData[2]; + const BYTE* pA = pSrcData[3]; + const UINT32 bpp = FreeRDPGetBytesPerPixel(DstFormat); + + if (vFlip) + { + beg = nHeight - 1; + end = -1; + inc = -1; + } + else + { + beg = 0; + end = nHeight; + inc = 1; + } + + if (nYDst + nHeight > totalHeight) + { + WLog_ERR(TAG, + "planar plane destination Y %" PRIu32 " + height %" PRIu32 + " exceeds totalHeight %" PRIu32, + nYDst, nHeight, totalHeight); + return FALSE; + } + + if ((nXDst + nWidth) * bpp > nDstStep) + { + WLog_ERR(TAG, + "planar plane destination (X %" PRIu32 " + width %" PRIu32 ") * bpp %" PRIu32 + " exceeds stride %" PRIu32, + nXDst, nWidth, bpp, nDstStep); + return FALSE; + } + + for (INT32 y = beg; y != end; y += inc) + { + BYTE* pRGB = NULL; + + if (y > (INT64)nHeight) + { + WLog_ERR(TAG, "planar plane destination Y %" PRId32 " exceeds height %" PRIu32, y, + nHeight); + return FALSE; + } + + pRGB = &pDstData[((nYDst + y) * nDstStep) + (nXDst * bpp)]; + + if (!writeLine(&pRGB, DstFormat, nWidth, &pR, &pG, &pB, &pA)) + return FALSE; + } + + return TRUE; +} + +static BOOL planar_subsample_expand(const BYTE* plane, size_t planeLength, UINT32 nWidth, + UINT32 nHeight, UINT32 nPlaneWidth, UINT32 nPlaneHeight, + BYTE* deltaPlane) +{ + size_t pos = 0; + WINPR_UNUSED(planeLength); + + WINPR_ASSERT(plane); + WINPR_ASSERT(deltaPlane); + + if (nWidth > nPlaneWidth * 2) + { + WLog_ERR(TAG, "planar subsample width %" PRIu32 " > PlaneWidth %" PRIu32 " * 2", nWidth, + nPlaneWidth); + return FALSE; + } + + if (nHeight > nPlaneHeight * 2) + { + WLog_ERR(TAG, "planar subsample height %" PRIu32 " > PlaneHeight %" PRIu32 " * 2", nHeight, + nPlaneHeight); + return FALSE; + } + + for (UINT32 y = 0; y < nHeight; y++) + { + const BYTE* src = plane + y / 2 * nPlaneWidth; + + for (UINT32 x = 0; x < nWidth; x++) + { + deltaPlane[pos++] = src[x / 2]; + } + } + + return TRUE; +} + +BOOL planar_decompress(BITMAP_PLANAR_CONTEXT* planar, const BYTE* pSrcData, UINT32 SrcSize, + UINT32 nSrcWidth, UINT32 nSrcHeight, BYTE* pDstData, UINT32 DstFormat, + UINT32 nDstStep, UINT32 nXDst, UINT32 nYDst, UINT32 nDstWidth, + UINT32 nDstHeight, BOOL vFlip) +{ + BOOL cs = 0; + BOOL rle = 0; + UINT32 cll = 0; + BOOL alpha = 0; + BOOL useAlpha = FALSE; + INT32 status = 0; + const BYTE* srcp = NULL; + UINT32 subSize = 0; + UINT32 subWidth = 0; + UINT32 subHeight = 0; + UINT32 planeSize = 0; + INT32 rleSizes[4] = { 0, 0, 0, 0 }; + UINT32 rawSizes[4]; + UINT32 rawWidths[4]; + UINT32 rawHeights[4]; + BYTE FormatHeader = 0; + const BYTE* planes[4] = { 0 }; + const UINT32 w = MIN(nSrcWidth, nDstWidth); + const UINT32 h = MIN(nSrcHeight, nDstHeight); + const primitives_t* prims = primitives_get(); + + WINPR_ASSERT(planar); + WINPR_ASSERT(prims); + + if (nDstStep <= 0) + nDstStep = nDstWidth * FreeRDPGetBytesPerPixel(DstFormat); + + srcp = pSrcData; + + if (!pSrcData) + { + WLog_ERR(TAG, "Invalid argument pSrcData=NULL"); + return FALSE; + } + + if (!pDstData) + { + WLog_ERR(TAG, "Invalid argument pDstData=NULL"); + return FALSE; + } + + FormatHeader = *srcp++; + cll = (FormatHeader & PLANAR_FORMAT_HEADER_CLL_MASK); + cs = (FormatHeader & PLANAR_FORMAT_HEADER_CS) ? TRUE : FALSE; + rle = (FormatHeader & PLANAR_FORMAT_HEADER_RLE) ? TRUE : FALSE; + alpha = (FormatHeader & PLANAR_FORMAT_HEADER_NA) ? FALSE : TRUE; + + DstFormat = planar_invert_format(planar, alpha, DstFormat); + + if (alpha) + useAlpha = FreeRDPColorHasAlpha(DstFormat); + + // WLog_INFO(TAG, "CLL: %"PRIu32" CS: %"PRIu8" RLE: %"PRIu8" ALPHA: %"PRIu8"", cll, cs, rle, + // alpha); + + if (!cll && cs) + { + WLog_ERR(TAG, "Chroma subsampling requires YCoCg and does not work with RGB data"); + return FALSE; /* Chroma subsampling requires YCoCg */ + } + + subWidth = (nSrcWidth / 2) + (nSrcWidth % 2); + subHeight = (nSrcHeight / 2) + (nSrcHeight % 2); + planeSize = nSrcWidth * nSrcHeight; + subSize = subWidth * subHeight; + + if (!cs) + { + rawSizes[0] = planeSize; /* LumaOrRedPlane */ + rawWidths[0] = nSrcWidth; + rawHeights[0] = nSrcHeight; + rawSizes[1] = planeSize; /* OrangeChromaOrGreenPlane */ + rawWidths[1] = nSrcWidth; + rawHeights[1] = nSrcHeight; + rawSizes[2] = planeSize; /* GreenChromaOrBluePlane */ + rawWidths[2] = nSrcWidth; + rawHeights[2] = nSrcHeight; + rawSizes[3] = planeSize; /* AlphaPlane */ + rawWidths[3] = nSrcWidth; + rawHeights[3] = nSrcHeight; + } + else /* Chroma Subsampling */ + { + rawSizes[0] = planeSize; /* LumaOrRedPlane */ + rawWidths[0] = nSrcWidth; + rawHeights[0] = nSrcHeight; + rawSizes[1] = subSize; /* OrangeChromaOrGreenPlane */ + rawWidths[1] = subWidth; + rawHeights[1] = subHeight; + rawSizes[2] = subSize; /* GreenChromaOrBluePlane */ + rawWidths[2] = subWidth; + rawHeights[2] = subHeight; + rawSizes[3] = planeSize; /* AlphaPlane */ + rawWidths[3] = nSrcWidth; + rawHeights[3] = nSrcHeight; + } + + if (!rle) /* RAW */ + { + UINT32 base = planeSize * 3; + if (cs) + base = planeSize + planeSize / 2; + + if (alpha) + { + if ((SrcSize - (srcp - pSrcData)) < (planeSize + base)) + { + WLog_ERR(TAG, "Alpha plane size mismatch %" PRIu32 " < %" PRIu32, + SrcSize - (srcp - pSrcData), (planeSize + base)); + return FALSE; + } + + planes[3] = srcp; /* AlphaPlane */ + planes[0] = planes[3] + rawSizes[3]; /* LumaOrRedPlane */ + planes[1] = planes[0] + rawSizes[0]; /* OrangeChromaOrGreenPlane */ + planes[2] = planes[1] + rawSizes[1]; /* GreenChromaOrBluePlane */ + + if ((planes[2] + rawSizes[2]) > &pSrcData[SrcSize]) + { + WLog_ERR(TAG, "plane size mismatch %p + %" PRIu32 " > %p", planes[2], rawSizes[2], + &pSrcData[SrcSize]); + return FALSE; + } + } + else + { + if ((SrcSize - (srcp - pSrcData)) < base) + { + WLog_ERR(TAG, "plane size mismatch %" PRIu32 " < %" PRIu32, + SrcSize - (srcp - pSrcData), base); + return FALSE; + } + + planes[0] = srcp; /* LumaOrRedPlane */ + planes[1] = planes[0] + rawSizes[0]; /* OrangeChromaOrGreenPlane */ + planes[2] = planes[1] + rawSizes[1]; /* GreenChromaOrBluePlane */ + + if ((planes[2] + rawSizes[2]) > &pSrcData[SrcSize]) + { + WLog_ERR(TAG, "plane size mismatch %p + %" PRIu32 " > %p", planes[2], rawSizes[2], + &pSrcData[SrcSize]); + return FALSE; + } + } + } + else /* RLE */ + { + if (alpha) + { + planes[3] = srcp; + rleSizes[3] = planar_skip_plane_rle(planes[3], SrcSize - (planes[3] - pSrcData), + rawWidths[3], rawHeights[3]); /* AlphaPlane */ + + if (rleSizes[3] < 0) + return FALSE; + + planes[0] = planes[3] + rleSizes[3]; + } + else + planes[0] = srcp; + + rleSizes[0] = planar_skip_plane_rle(planes[0], SrcSize - (planes[0] - pSrcData), + rawWidths[0], rawHeights[0]); /* RedPlane */ + + if (rleSizes[0] < 0) + return FALSE; + + planes[1] = planes[0] + rleSizes[0]; + rleSizes[1] = planar_skip_plane_rle(planes[1], SrcSize - (planes[1] - pSrcData), + rawWidths[1], rawHeights[1]); /* GreenPlane */ + + if (rleSizes[1] < 1) + return FALSE; + + planes[2] = planes[1] + rleSizes[1]; + rleSizes[2] = planar_skip_plane_rle(planes[2], SrcSize - (planes[2] - pSrcData), + rawWidths[2], rawHeights[2]); /* BluePlane */ + + if (rleSizes[2] < 1) + return FALSE; + } + + if (!cll) /* RGB */ + { + UINT32 TempFormat = 0; + BYTE* pTempData = pDstData; + UINT32 nTempStep = nDstStep; + UINT32 nTotalHeight = nYDst + nDstHeight; + + if (useAlpha) + TempFormat = PIXEL_FORMAT_BGRA32; + else + TempFormat = PIXEL_FORMAT_BGRX32; + + TempFormat = planar_invert_format(planar, alpha, TempFormat); + + if ((TempFormat != DstFormat) || (nSrcWidth != nDstWidth) || (nSrcHeight != nDstHeight)) + { + pTempData = planar->pTempData; + nTempStep = planar->nTempStep; + nTotalHeight = planar->maxHeight; + } + + if (!rle) /* RAW */ + { + if (!planar_decompress_planes_raw(planes, pTempData, TempFormat, nTempStep, nXDst, + nYDst, nSrcWidth, nSrcHeight, vFlip, nTotalHeight)) + return FALSE; + + if (alpha) + srcp += rawSizes[0] + rawSizes[1] + rawSizes[2] + rawSizes[3]; + else /* NoAlpha */ + srcp += rawSizes[0] + rawSizes[1] + rawSizes[2]; + + if ((SrcSize - (srcp - pSrcData)) == 1) + srcp++; /* pad */ + } + else /* RLE */ + { + status = + planar_decompress_plane_rle(planes[0], rleSizes[0], pTempData, nTempStep, nXDst, + nYDst, nSrcWidth, nSrcHeight, 2, vFlip); /* RedPlane */ + + if (status < 0) + return FALSE; + + status = planar_decompress_plane_rle(planes[1], rleSizes[1], pTempData, nTempStep, + nXDst, nYDst, nSrcWidth, nSrcHeight, 1, + vFlip); /* GreenPlane */ + + if (status < 0) + return FALSE; + + status = + planar_decompress_plane_rle(planes[2], rleSizes[2], pTempData, nTempStep, nXDst, + nYDst, nSrcWidth, nSrcHeight, 0, vFlip); /* BluePlane */ + + if (status < 0) + return FALSE; + + srcp += rleSizes[0] + rleSizes[1] + rleSizes[2]; + + if (useAlpha) + { + status = planar_decompress_plane_rle(planes[3], rleSizes[3], pTempData, nTempStep, + nXDst, nYDst, nSrcWidth, nSrcHeight, 3, + vFlip); /* AlphaPlane */ + } + else + status = planar_set_plane(0xFF, pTempData, nTempStep, nXDst, nYDst, nSrcWidth, + nSrcHeight, 3, vFlip); + + if (status < 0) + return FALSE; + + if (alpha) + srcp += rleSizes[3]; + } + + if (pTempData != pDstData) + { + if (!freerdp_image_copy(pDstData, DstFormat, nDstStep, nXDst, nYDst, w, h, pTempData, + TempFormat, nTempStep, nXDst, nYDst, NULL, FREERDP_FLIP_NONE)) + { + WLog_ERR(TAG, "planar image copy failed"); + return FALSE; + } + } + } + else /* YCoCg */ + { + UINT32 TempFormat = 0; + BYTE* pTempData = planar->pTempData; + UINT32 nTempStep = planar->nTempStep; + UINT32 nTotalHeight = planar->maxHeight; + BYTE* dst = &pDstData[nXDst * FreeRDPGetBytesPerPixel(DstFormat) + nYDst * nDstStep]; + + if (useAlpha) + TempFormat = PIXEL_FORMAT_BGRA32; + else + TempFormat = PIXEL_FORMAT_BGRX32; + + if (!pTempData) + return FALSE; + + if (rle) /* RLE encoded data. Decode and handle it like raw data. */ + { + BYTE* rleBuffer[4] = { 0 }; + + if (!planar->rlePlanesBuffer) + return FALSE; + + rleBuffer[3] = planar->rlePlanesBuffer; /* AlphaPlane */ + rleBuffer[0] = rleBuffer[3] + planeSize; /* LumaOrRedPlane */ + rleBuffer[1] = rleBuffer[0] + planeSize; /* OrangeChromaOrGreenPlane */ + rleBuffer[2] = rleBuffer[1] + planeSize; /* GreenChromaOrBluePlane */ + if (useAlpha) + { + status = + planar_decompress_plane_rle_only(planes[3], rleSizes[3], rleBuffer[3], + rawWidths[3], rawHeights[3]); /* AlphaPlane */ + + if (status < 0) + return FALSE; + } + + if (alpha) + srcp += rleSizes[3]; + + status = planar_decompress_plane_rle_only(planes[0], rleSizes[0], rleBuffer[0], + rawWidths[0], rawHeights[0]); /* LumaPlane */ + + if (status < 0) + return FALSE; + + status = + planar_decompress_plane_rle_only(planes[1], rleSizes[1], rleBuffer[1], rawWidths[1], + rawHeights[1]); /* OrangeChromaPlane */ + + if (status < 0) + return FALSE; + + status = + planar_decompress_plane_rle_only(planes[2], rleSizes[2], rleBuffer[2], rawWidths[2], + rawHeights[2]); /* GreenChromaPlane */ + + if (status < 0) + return FALSE; + + planes[0] = rleBuffer[0]; + planes[1] = rleBuffer[1]; + planes[2] = rleBuffer[2]; + planes[3] = rleBuffer[3]; + } + + /* RAW */ + { + if (cs) + { /* Chroma subsampling for Co and Cg: + * Each pixel contains the value that should be expanded to + * [2x,2y;2x+1,2y;2x+1,2y+1;2x;2y+1] */ + if (!planar_subsample_expand(planes[1], rawSizes[1], nSrcWidth, nSrcHeight, + rawWidths[1], rawHeights[1], planar->deltaPlanes[0])) + return FALSE; + + planes[1] = planar->deltaPlanes[0]; + rawSizes[1] = planeSize; /* OrangeChromaOrGreenPlane */ + rawWidths[1] = nSrcWidth; + rawHeights[1] = nSrcHeight; + + if (!planar_subsample_expand(planes[2], rawSizes[2], nSrcWidth, nSrcHeight, + rawWidths[2], rawHeights[2], planar->deltaPlanes[1])) + return FALSE; + + planes[2] = planar->deltaPlanes[1]; + rawSizes[2] = planeSize; /* GreenChromaOrBluePlane */ + rawWidths[2] = nSrcWidth; + rawHeights[2] = nSrcHeight; + } + + if (!planar_decompress_planes_raw(planes, pTempData, TempFormat, nTempStep, nXDst, + nYDst, nSrcWidth, nSrcHeight, vFlip, nTotalHeight)) + return FALSE; + + if (alpha) + srcp += rawSizes[0] + rawSizes[1] + rawSizes[2] + rawSizes[3]; + else /* NoAlpha */ + srcp += rawSizes[0] + rawSizes[1] + rawSizes[2]; + + if ((SrcSize - (srcp - pSrcData)) == 1) + srcp++; /* pad */ + } + + WINPR_ASSERT(prims->YCoCgToRGB_8u_AC4R); + int rc = prims->YCoCgToRGB_8u_AC4R(pTempData, nTempStep, dst, DstFormat, nDstStep, w, h, + cll, useAlpha); + if (rc != PRIMITIVES_SUCCESS) + { + WLog_ERR(TAG, "YCoCgToRGB_8u_AC4R failed with %d", rc); + return FALSE; + } + } + + WINPR_UNUSED(srcp); + return TRUE; +} + +static INLINE BOOL freerdp_split_color_planes(BITMAP_PLANAR_CONTEXT* planar, const BYTE* data, + UINT32 format, UINT32 width, UINT32 height, + UINT32 scanline, BYTE* planes[4]) +{ + WINPR_ASSERT(planar); + + if ((width > INT32_MAX) || (height > INT32_MAX) || (scanline > INT32_MAX)) + return FALSE; + + if (scanline == 0) + scanline = width * FreeRDPGetBytesPerPixel(format); + + if (planar->topdown) + { + UINT32 k = 0; + for (UINT32 i = 0; i < height; i++) + { + const BYTE* pixel = &data[scanline * (UINT32)i]; + + for (UINT32 j = 0; j < width; j++) + { + const UINT32 color = FreeRDPReadColor(pixel, format); + pixel += FreeRDPGetBytesPerPixel(format); + FreeRDPSplitColor(color, format, &planes[1][k], &planes[2][k], &planes[3][k], + &planes[0][k], NULL); + k++; + } + } + } + else + { + UINT32 k = 0; + + for (INT64 i = (INT64)height - 1; i >= 0; i--) + { + const BYTE* pixel = &data[scanline * (UINT32)i]; + + for (UINT32 j = 0; j < width; j++) + { + const UINT32 color = FreeRDPReadColor(pixel, format); + pixel += FreeRDPGetBytesPerPixel(format); + FreeRDPSplitColor(color, format, &planes[1][k], &planes[2][k], &planes[3][k], + &planes[0][k], NULL); + k++; + } + } + } + return TRUE; +} + +static INLINE UINT32 freerdp_bitmap_planar_write_rle_bytes(const BYTE* pInBuffer, UINT32 cRawBytes, + UINT32 nRunLength, BYTE* pOutBuffer, + UINT32 outBufferSize) +{ + const BYTE* pInput = NULL; + BYTE* pOutput = NULL; + BYTE controlByte = 0; + UINT32 nBytesToWrite = 0; + pInput = pInBuffer; + pOutput = pOutBuffer; + + if (!cRawBytes && !nRunLength) + return 0; + + if (nRunLength < 3) + { + cRawBytes += nRunLength; + nRunLength = 0; + } + + while (cRawBytes) + { + if (cRawBytes < 16) + { + if (nRunLength > 15) + { + if (nRunLength < 18) + { + controlByte = PLANAR_CONTROL_BYTE(13, cRawBytes); + nRunLength -= 13; + cRawBytes = 0; + } + else + { + controlByte = PLANAR_CONTROL_BYTE(15, cRawBytes); + nRunLength -= 15; + cRawBytes = 0; + } + } + else + { + controlByte = PLANAR_CONTROL_BYTE(nRunLength, cRawBytes); + nRunLength = 0; + cRawBytes = 0; + } + } + else + { + controlByte = PLANAR_CONTROL_BYTE(0, 15); + cRawBytes -= 15; + } + + if (outBufferSize < 1) + return 0; + + outBufferSize--; + *pOutput = controlByte; + pOutput++; + nBytesToWrite = (int)(controlByte >> 4); + + if (nBytesToWrite) + { + if (outBufferSize < nBytesToWrite) + return 0; + + outBufferSize -= nBytesToWrite; + CopyMemory(pOutput, pInput, nBytesToWrite); + pOutput += nBytesToWrite; + pInput += nBytesToWrite; + } + } + + while (nRunLength) + { + if (nRunLength > 47) + { + if (nRunLength < 50) + { + controlByte = PLANAR_CONTROL_BYTE(2, 13); + nRunLength -= 45; + } + else + { + controlByte = PLANAR_CONTROL_BYTE(2, 15); + nRunLength -= 47; + } + } + else if (nRunLength > 31) + { + controlByte = PLANAR_CONTROL_BYTE(2, (nRunLength - 32)); + nRunLength = 0; + } + else if (nRunLength > 15) + { + controlByte = PLANAR_CONTROL_BYTE(1, (nRunLength - 16)); + nRunLength = 0; + } + else + { + controlByte = PLANAR_CONTROL_BYTE(nRunLength, 0); + nRunLength = 0; + } + + if (outBufferSize < 1) + return 0; + + --outBufferSize; + *pOutput = controlByte; + pOutput++; + } + + return (pOutput - pOutBuffer); +} + +static INLINE UINT32 freerdp_bitmap_planar_encode_rle_bytes(const BYTE* pInBuffer, + UINT32 inBufferSize, BYTE* pOutBuffer, + UINT32 outBufferSize) +{ + BYTE symbol = 0; + const BYTE* pInput = NULL; + BYTE* pOutput = NULL; + const BYTE* pBytes = NULL; + UINT32 cRawBytes = 0; + UINT32 nRunLength = 0; + UINT32 bSymbolMatch = 0; + UINT32 nBytesWritten = 0; + UINT32 nTotalBytesWritten = 0; + symbol = 0; + cRawBytes = 0; + nRunLength = 0; + pInput = pInBuffer; + pOutput = pOutBuffer; + nTotalBytesWritten = 0; + + if (!outBufferSize) + return 0; + + do + { + if (!inBufferSize) + break; + + bSymbolMatch = (symbol == *pInput) ? TRUE : FALSE; + symbol = *pInput; + pInput++; + inBufferSize--; + + if (nRunLength && !bSymbolMatch) + { + if (nRunLength < 3) + { + cRawBytes += nRunLength; + nRunLength = 0; + } + else + { + pBytes = pInput - (cRawBytes + nRunLength + 1); + nBytesWritten = freerdp_bitmap_planar_write_rle_bytes(pBytes, cRawBytes, nRunLength, + pOutput, outBufferSize); + nRunLength = 0; + + if (!nBytesWritten || (nBytesWritten > outBufferSize)) + return nRunLength; + + nTotalBytesWritten += nBytesWritten; + outBufferSize -= nBytesWritten; + pOutput += nBytesWritten; + cRawBytes = 0; + } + } + + nRunLength += bSymbolMatch; + cRawBytes += (!bSymbolMatch) ? TRUE : FALSE; + } while (outBufferSize); + + if (cRawBytes || nRunLength) + { + pBytes = pInput - (cRawBytes + nRunLength); + nBytesWritten = freerdp_bitmap_planar_write_rle_bytes(pBytes, cRawBytes, nRunLength, + pOutput, outBufferSize); + + if (!nBytesWritten) + return 0; + + nTotalBytesWritten += nBytesWritten; + } + + if (inBufferSize) + return 0; + + return nTotalBytesWritten; +} + +BOOL freerdp_bitmap_planar_compress_plane_rle(const BYTE* inPlane, UINT32 width, UINT32 height, + BYTE* outPlane, UINT32* dstSize) +{ + UINT32 index = 0; + const BYTE* pInput = NULL; + BYTE* pOutput = NULL; + UINT32 outBufferSize = 0; + UINT32 nBytesWritten = 0; + UINT32 nTotalBytesWritten = 0; + + if (!outPlane) + return FALSE; + + index = 0; + pInput = inPlane; + pOutput = outPlane; + outBufferSize = *dstSize; + nTotalBytesWritten = 0; + + while (outBufferSize) + { + nBytesWritten = + freerdp_bitmap_planar_encode_rle_bytes(pInput, width, pOutput, outBufferSize); + + if ((!nBytesWritten) || (nBytesWritten > outBufferSize)) + return FALSE; + + outBufferSize -= nBytesWritten; + nTotalBytesWritten += nBytesWritten; + pOutput += nBytesWritten; + pInput += width; + index++; + + if (index >= height) + break; + } + + *dstSize = nTotalBytesWritten; + return TRUE; +} + +static INLINE BOOL freerdp_bitmap_planar_compress_planes_rle(BYTE* inPlanes[4], UINT32 width, + UINT32 height, BYTE* outPlanes, + UINT32* dstSizes, BOOL skipAlpha) +{ + UINT32 outPlanesSize = width * height * 4; + + /* AlphaPlane */ + if (skipAlpha) + { + dstSizes[0] = 0; + } + else + { + dstSizes[0] = outPlanesSize; + + if (!freerdp_bitmap_planar_compress_plane_rle(inPlanes[0], width, height, outPlanes, + &dstSizes[0])) + return FALSE; + + outPlanes += dstSizes[0]; + outPlanesSize -= dstSizes[0]; + } + + /* LumaOrRedPlane */ + dstSizes[1] = outPlanesSize; + + if (!freerdp_bitmap_planar_compress_plane_rle(inPlanes[1], width, height, outPlanes, + &dstSizes[1])) + return FALSE; + + outPlanes += dstSizes[1]; + outPlanesSize -= dstSizes[1]; + /* OrangeChromaOrGreenPlane */ + dstSizes[2] = outPlanesSize; + + if (!freerdp_bitmap_planar_compress_plane_rle(inPlanes[2], width, height, outPlanes, + &dstSizes[2])) + return FALSE; + + outPlanes += dstSizes[2]; + outPlanesSize -= dstSizes[2]; + /* GreenChromeOrBluePlane */ + dstSizes[3] = outPlanesSize; + + if (!freerdp_bitmap_planar_compress_plane_rle(inPlanes[3], width, height, outPlanes, + &dstSizes[3])) + return FALSE; + + return TRUE; +} + +BYTE* freerdp_bitmap_planar_delta_encode_plane(const BYTE* inPlane, UINT32 width, UINT32 height, + BYTE* outPlane) +{ + char s2c = 0; + BYTE* outPtr = NULL; + const BYTE* srcPtr = NULL; + const BYTE* prevLinePtr = NULL; + + if (!outPlane) + { + if (width * height == 0) + return NULL; + + if (!(outPlane = (BYTE*)calloc(height, width))) + return NULL; + } + + // first line is copied as is + CopyMemory(outPlane, inPlane, width); + outPtr = outPlane + width; + srcPtr = inPlane + width; + prevLinePtr = inPlane; + + for (UINT32 y = 1; y < height; y++) + { + for (UINT32 x = 0; x < width; x++, outPtr++, srcPtr++, prevLinePtr++) + { + INT32 delta = *srcPtr - *prevLinePtr; + s2c = (delta >= 0) ? (char)delta : (char)(~((BYTE)(-delta)) + 1); + s2c = (s2c >= 0) ? (char)((UINT32)s2c << 1) + : (char)(((UINT32)(~((BYTE)s2c) + 1) << 1) - 1); + *outPtr = (BYTE)s2c; + } + } + + return outPlane; +} + +static INLINE BOOL freerdp_bitmap_planar_delta_encode_planes(BYTE* inPlanes[4], UINT32 width, + UINT32 height, BYTE* outPlanes[4]) +{ + for (UINT32 i = 0; i < 4; i++) + { + outPlanes[i] = + freerdp_bitmap_planar_delta_encode_plane(inPlanes[i], width, height, outPlanes[i]); + + if (!outPlanes[i]) + return FALSE; + } + + return TRUE; +} + +BYTE* freerdp_bitmap_compress_planar(BITMAP_PLANAR_CONTEXT* context, const BYTE* data, + UINT32 format, UINT32 width, UINT32 height, UINT32 scanline, + BYTE* dstData, UINT32* pDstSize) +{ + UINT32 size = 0; + BYTE* dstp = NULL; + UINT32 planeSize = 0; + UINT32 dstSizes[4] = { 0 }; + BYTE FormatHeader = 0; + + if (!context || !context->rlePlanesBuffer) + return NULL; + + if (context->AllowSkipAlpha) + FormatHeader |= PLANAR_FORMAT_HEADER_NA; + + planeSize = width * height; + + if (!context->AllowSkipAlpha) + format = planar_invert_format(context, TRUE, format); + + if (!freerdp_split_color_planes(context, data, format, width, height, scanline, + context->planes)) + return NULL; + + if (context->AllowRunLengthEncoding) + { + if (!freerdp_bitmap_planar_delta_encode_planes(context->planes, width, height, + context->deltaPlanes)) + return NULL; + + if (!freerdp_bitmap_planar_compress_planes_rle(context->deltaPlanes, width, height, + context->rlePlanesBuffer, dstSizes, + context->AllowSkipAlpha)) + return NULL; + + { + int offset = 0; + FormatHeader |= PLANAR_FORMAT_HEADER_RLE; + context->rlePlanes[0] = &context->rlePlanesBuffer[offset]; + offset += dstSizes[0]; + context->rlePlanes[1] = &context->rlePlanesBuffer[offset]; + offset += dstSizes[1]; + context->rlePlanes[2] = &context->rlePlanesBuffer[offset]; + offset += dstSizes[2]; + context->rlePlanes[3] = &context->rlePlanesBuffer[offset]; + +#if defined(WITH_DEBUG_CODECS) + WLog_DBG(TAG, + "R: [%" PRIu32 "/%" PRIu32 "] G: [%" PRIu32 "/%" PRIu32 "] B: [%" PRIu32 + " / %" PRIu32 "] ", + dstSizes[1], planeSize, dstSizes[2], planeSize, dstSizes[3], planeSize); +#endif + } + } + + if (FormatHeader & PLANAR_FORMAT_HEADER_RLE) + { + if (!context->AllowRunLengthEncoding) + return NULL; + + if (context->rlePlanes[0] == NULL) + return NULL; + + if (context->rlePlanes[1] == NULL) + return NULL; + + if (context->rlePlanes[2] == NULL) + return NULL; + + if (context->rlePlanes[3] == NULL) + return NULL; + } + + if (!dstData) + { + size = 1; + + if (!(FormatHeader & PLANAR_FORMAT_HEADER_NA)) + { + if (FormatHeader & PLANAR_FORMAT_HEADER_RLE) + size += dstSizes[0]; + else + size += planeSize; + } + + if (FormatHeader & PLANAR_FORMAT_HEADER_RLE) + size += (dstSizes[1] + dstSizes[2] + dstSizes[3]); + else + size += (planeSize * 3); + + if (!(FormatHeader & PLANAR_FORMAT_HEADER_RLE)) + size++; + + dstData = malloc(size); + + if (!dstData) + return NULL; + + *pDstSize = size; + } + + dstp = dstData; + *dstp = FormatHeader; /* FormatHeader */ + dstp++; + + /* AlphaPlane */ + + if (!(FormatHeader & PLANAR_FORMAT_HEADER_NA)) + { + if (FormatHeader & PLANAR_FORMAT_HEADER_RLE) + { + CopyMemory(dstp, context->rlePlanes[0], dstSizes[0]); /* Alpha */ + dstp += dstSizes[0]; + } + else + { + CopyMemory(dstp, context->planes[0], planeSize); /* Alpha */ + dstp += planeSize; + } + } + + /* LumaOrRedPlane */ + + if (FormatHeader & PLANAR_FORMAT_HEADER_RLE) + { + CopyMemory(dstp, context->rlePlanes[1], dstSizes[1]); /* Red */ + dstp += dstSizes[1]; + } + else + { + CopyMemory(dstp, context->planes[1], planeSize); /* Red */ + dstp += planeSize; + } + + /* OrangeChromaOrGreenPlane */ + + if (FormatHeader & PLANAR_FORMAT_HEADER_RLE) + { + CopyMemory(dstp, context->rlePlanes[2], dstSizes[2]); /* Green */ + dstp += dstSizes[2]; + } + else + { + CopyMemory(dstp, context->planes[2], planeSize); /* Green */ + dstp += planeSize; + } + + /* GreenChromeOrBluePlane */ + + if (FormatHeader & PLANAR_FORMAT_HEADER_RLE) + { + CopyMemory(dstp, context->rlePlanes[3], dstSizes[3]); /* Blue */ + dstp += dstSizes[3]; + } + else + { + CopyMemory(dstp, context->planes[3], planeSize); /* Blue */ + dstp += planeSize; + } + + /* Pad1 (1 byte) */ + + if (!(FormatHeader & PLANAR_FORMAT_HEADER_RLE)) + { + *dstp = 0; + dstp++; + } + + size = (dstp - dstData); + *pDstSize = size; + return dstData; +} + +BOOL freerdp_bitmap_planar_context_reset(BITMAP_PLANAR_CONTEXT* context, UINT32 width, + UINT32 height) +{ + if (!context) + return FALSE; + + context->bgr = FALSE; + context->maxWidth = PLANAR_ALIGN(width, 4); + context->maxHeight = PLANAR_ALIGN(height, 4); + const UINT64 tmp = (UINT64)context->maxWidth * context->maxHeight; + if (tmp > UINT32_MAX) + return FALSE; + context->maxPlaneSize = tmp; + + if (context->maxWidth > UINT32_MAX / 4) + return FALSE; + context->nTempStep = context->maxWidth * 4; + + memset(context->planes, 0, sizeof(context->planes)); + memset(context->rlePlanes, 0, sizeof(context->rlePlanes)); + memset(context->deltaPlanes, 0, sizeof(context->deltaPlanes)); + + if (context->maxPlaneSize > 0) + { + void* tmp = winpr_aligned_recalloc(context->planesBuffer, context->maxPlaneSize, 4, 32); + if (!tmp) + return FALSE; + context->planesBuffer = tmp; + + tmp = winpr_aligned_recalloc(context->pTempData, context->maxPlaneSize, 6, 32); + if (!tmp) + return FALSE; + context->pTempData = tmp; + + tmp = winpr_aligned_recalloc(context->deltaPlanesBuffer, context->maxPlaneSize, 4, 32); + if (!tmp) + return FALSE; + context->deltaPlanesBuffer = tmp; + + tmp = winpr_aligned_recalloc(context->rlePlanesBuffer, context->maxPlaneSize, 4, 32); + if (!tmp) + return FALSE; + context->rlePlanesBuffer = tmp; + + context->planes[0] = &context->planesBuffer[context->maxPlaneSize * 0]; + context->planes[1] = &context->planesBuffer[context->maxPlaneSize * 1]; + context->planes[2] = &context->planesBuffer[context->maxPlaneSize * 2]; + context->planes[3] = &context->planesBuffer[context->maxPlaneSize * 3]; + context->deltaPlanes[0] = &context->deltaPlanesBuffer[context->maxPlaneSize * 0]; + context->deltaPlanes[1] = &context->deltaPlanesBuffer[context->maxPlaneSize * 1]; + context->deltaPlanes[2] = &context->deltaPlanesBuffer[context->maxPlaneSize * 2]; + context->deltaPlanes[3] = &context->deltaPlanesBuffer[context->maxPlaneSize * 3]; + } + return TRUE; +} + +BITMAP_PLANAR_CONTEXT* freerdp_bitmap_planar_context_new(DWORD flags, UINT32 maxWidth, + UINT32 maxHeight) +{ + BITMAP_PLANAR_CONTEXT* context = + (BITMAP_PLANAR_CONTEXT*)winpr_aligned_calloc(1, sizeof(BITMAP_PLANAR_CONTEXT), 32); + + if (!context) + return NULL; + + if (flags & PLANAR_FORMAT_HEADER_NA) + context->AllowSkipAlpha = TRUE; + + if (flags & PLANAR_FORMAT_HEADER_RLE) + context->AllowRunLengthEncoding = TRUE; + + if (flags & PLANAR_FORMAT_HEADER_CS) + context->AllowColorSubsampling = TRUE; + + context->ColorLossLevel = flags & PLANAR_FORMAT_HEADER_CLL_MASK; + + if (context->ColorLossLevel) + context->AllowDynamicColorFidelity = TRUE; + + if (!freerdp_bitmap_planar_context_reset(context, maxWidth, maxHeight)) + { + freerdp_bitmap_planar_context_free(context); + return NULL; + } + + return context; +} + +void freerdp_bitmap_planar_context_free(BITMAP_PLANAR_CONTEXT* context) +{ + if (!context) + return; + + winpr_aligned_free(context->pTempData); + winpr_aligned_free(context->planesBuffer); + winpr_aligned_free(context->deltaPlanesBuffer); + winpr_aligned_free(context->rlePlanesBuffer); + winpr_aligned_free(context); +} + +void freerdp_planar_switch_bgr(BITMAP_PLANAR_CONTEXT* planar, BOOL bgr) +{ + WINPR_ASSERT(planar); + planar->bgr = bgr; +} + +void freerdp_planar_topdown_image(BITMAP_PLANAR_CONTEXT* planar, BOOL topdown) +{ + WINPR_ASSERT(planar); + planar->topdown = topdown; +} diff --git a/libfreerdp/codec/progressive.c b/libfreerdp/codec/progressive.c new file mode 100644 index 0000000..df98ad3 --- /dev/null +++ b/libfreerdp/codec/progressive.c @@ -0,0 +1,2651 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Progressive Codec Bitmap Compression + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2019 Armin Novak <armin.novak@thincast.com> + * Copyright 2019 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <winpr/assert.h> +#include <winpr/crt.h> +#include <winpr/print.h> +#include <winpr/bitstream.h> + +#include <freerdp/primitives.h> +#include <freerdp/codec/color.h> +#include <freerdp/codec/progressive.h> +#include <freerdp/codec/region.h> +#include <freerdp/log.h> + +#include "rfx_differential.h" +#include "rfx_quantization.h" +#include "rfx_dwt.h" +#include "rfx_rlgr.h" +#include "rfx_constants.h" +#include "rfx_types.h" +#include "progressive.h" + +#define TAG FREERDP_TAG("codec.progressive") + +typedef struct +{ + BOOL nonLL; + wBitStream* srl; + wBitStream* raw; + + /* SRL state */ + + UINT32 kp; + int nz; + BOOL mode; +} RFX_PROGRESSIVE_UPGRADE_STATE; + +static INLINE void progressive_component_codec_quant_read(wStream* s, + RFX_COMPONENT_CODEC_QUANT* quantVal) +{ + BYTE b = 0; + Stream_Read_UINT8(s, b); + quantVal->LL3 = b & 0x0F; + quantVal->HL3 = b >> 4; + Stream_Read_UINT8(s, b); + quantVal->LH3 = b & 0x0F; + quantVal->HH3 = b >> 4; + Stream_Read_UINT8(s, b); + quantVal->HL2 = b & 0x0F; + quantVal->LH2 = b >> 4; + Stream_Read_UINT8(s, b); + quantVal->HH2 = b & 0x0F; + quantVal->HL1 = b >> 4; + Stream_Read_UINT8(s, b); + quantVal->LH1 = b & 0x0F; + quantVal->HH1 = b >> 4; +} + +static INLINE void progressive_rfx_quant_ladd(RFX_COMPONENT_CODEC_QUANT* q, int val) +{ + q->HL1 += val; /* HL1 */ + q->LH1 += val; /* LH1 */ + q->HH1 += val; /* HH1 */ + q->HL2 += val; /* HL2 */ + q->LH2 += val; /* LH2 */ + q->HH2 += val; /* HH2 */ + q->HL3 += val; /* HL3 */ + q->LH3 += val; /* LH3 */ + q->HH3 += val; /* HH3 */ + q->LL3 += val; /* LL3 */ +} + +static INLINE void progressive_rfx_quant_add(const RFX_COMPONENT_CODEC_QUANT* q1, + const RFX_COMPONENT_CODEC_QUANT* q2, + RFX_COMPONENT_CODEC_QUANT* dst) +{ + dst->HL1 = q1->HL1 + q2->HL1; /* HL1 */ + dst->LH1 = q1->LH1 + q2->LH1; /* LH1 */ + dst->HH1 = q1->HH1 + q2->HH1; /* HH1 */ + dst->HL2 = q1->HL2 + q2->HL2; /* HL2 */ + dst->LH2 = q1->LH2 + q2->LH2; /* LH2 */ + dst->HH2 = q1->HH2 + q2->HH2; /* HH2 */ + dst->HL3 = q1->HL3 + q2->HL3; /* HL3 */ + dst->LH3 = q1->LH3 + q2->LH3; /* LH3 */ + dst->HH3 = q1->HH3 + q2->HH3; /* HH3 */ + dst->LL3 = q1->LL3 + q2->LL3; /* LL3 */ +} + +static INLINE void progressive_rfx_quant_lsub(RFX_COMPONENT_CODEC_QUANT* q, int val) +{ + q->HL1 -= val; /* HL1 */ + q->LH1 -= val; /* LH1 */ + q->HH1 -= val; /* HH1 */ + q->HL2 -= val; /* HL2 */ + q->LH2 -= val; /* LH2 */ + q->HH2 -= val; /* HH2 */ + q->HL3 -= val; /* HL3 */ + q->LH3 -= val; /* LH3 */ + q->HH3 -= val; /* HH3 */ + q->LL3 -= val; /* LL3 */ +} + +static INLINE void progressive_rfx_quant_sub(const RFX_COMPONENT_CODEC_QUANT* q1, + const RFX_COMPONENT_CODEC_QUANT* q2, + RFX_COMPONENT_CODEC_QUANT* dst) +{ + dst->HL1 = q1->HL1 - q2->HL1; /* HL1 */ + dst->LH1 = q1->LH1 - q2->LH1; /* LH1 */ + dst->HH1 = q1->HH1 - q2->HH1; /* HH1 */ + dst->HL2 = q1->HL2 - q2->HL2; /* HL2 */ + dst->LH2 = q1->LH2 - q2->LH2; /* LH2 */ + dst->HH2 = q1->HH2 - q2->HH2; /* HH2 */ + dst->HL3 = q1->HL3 - q2->HL3; /* HL3 */ + dst->LH3 = q1->LH3 - q2->LH3; /* LH3 */ + dst->HH3 = q1->HH3 - q2->HH3; /* HH3 */ + dst->LL3 = q1->LL3 - q2->LL3; /* LL3 */ +} + +static INLINE BOOL progressive_rfx_quant_lcmp_less_equal(const RFX_COMPONENT_CODEC_QUANT* q, + int val) +{ + if (q->HL1 > val) + return FALSE; /* HL1 */ + + if (q->LH1 > val) + return FALSE; /* LH1 */ + + if (q->HH1 > val) + return FALSE; /* HH1 */ + + if (q->HL2 > val) + return FALSE; /* HL2 */ + + if (q->LH2 > val) + return FALSE; /* LH2 */ + + if (q->HH2 > val) + return FALSE; /* HH2 */ + + if (q->HL3 > val) + return FALSE; /* HL3 */ + + if (q->LH3 > val) + return FALSE; /* LH3 */ + + if (q->HH3 > val) + return FALSE; /* HH3 */ + + if (q->LL3 > val) + return FALSE; /* LL3 */ + + return TRUE; +} + +static INLINE BOOL progressive_rfx_quant_cmp_less_equal(const RFX_COMPONENT_CODEC_QUANT* q1, + const RFX_COMPONENT_CODEC_QUANT* q2) +{ + if (q1->HL1 > q2->HL1) + return FALSE; /* HL1 */ + + if (q1->LH1 > q2->LH1) + return FALSE; /* LH1 */ + + if (q1->HH1 > q2->HH1) + return FALSE; /* HH1 */ + + if (q1->HL2 > q2->HL2) + return FALSE; /* HL2 */ + + if (q1->LH2 > q2->LH2) + return FALSE; /* LH2 */ + + if (q1->HH2 > q2->HH2) + return FALSE; /* HH2 */ + + if (q1->HL3 > q2->HL3) + return FALSE; /* HL3 */ + + if (q1->LH3 > q2->LH3) + return FALSE; /* LH3 */ + + if (q1->HH3 > q2->HH3) + return FALSE; /* HH3 */ + + if (q1->LL3 > q2->LL3) + return FALSE; /* LL3 */ + + return TRUE; +} + +static INLINE BOOL progressive_rfx_quant_lcmp_greater_equal(const RFX_COMPONENT_CODEC_QUANT* q, + int val) +{ + if (q->HL1 < val) + return FALSE; /* HL1 */ + + if (q->LH1 < val) + return FALSE; /* LH1 */ + + if (q->HH1 < val) + return FALSE; /* HH1 */ + + if (q->HL2 < val) + return FALSE; /* HL2 */ + + if (q->LH2 < val) + return FALSE; /* LH2 */ + + if (q->HH2 < val) + return FALSE; /* HH2 */ + + if (q->HL3 < val) + return FALSE; /* HL3 */ + + if (q->LH3 < val) + return FALSE; /* LH3 */ + + if (q->HH3 < val) + return FALSE; /* HH3 */ + + if (q->LL3 < val) + return FALSE; /* LL3 */ + + return TRUE; +} + +static INLINE BOOL progressive_rfx_quant_cmp_greater_equal(const RFX_COMPONENT_CODEC_QUANT* q1, + const RFX_COMPONENT_CODEC_QUANT* q2) +{ + if (q1->HL1 < q2->HL1) + return FALSE; /* HL1 */ + + if (q1->LH1 < q2->LH1) + return FALSE; /* LH1 */ + + if (q1->HH1 < q2->HH1) + return FALSE; /* HH1 */ + + if (q1->HL2 < q2->HL2) + return FALSE; /* HL2 */ + + if (q1->LH2 < q2->LH2) + return FALSE; /* LH2 */ + + if (q1->HH2 < q2->HH2) + return FALSE; /* HH2 */ + + if (q1->HL3 < q2->HL3) + return FALSE; /* HL3 */ + + if (q1->LH3 < q2->LH3) + return FALSE; /* LH3 */ + + if (q1->HH3 < q2->HH3) + return FALSE; /* HH3 */ + + if (q1->LL3 < q2->LL3) + return FALSE; /* LL3 */ + + return TRUE; +} + +static INLINE BOOL progressive_rfx_quant_cmp_equal(const RFX_COMPONENT_CODEC_QUANT* q1, + const RFX_COMPONENT_CODEC_QUANT* q2) +{ + if (q1->HL1 != q2->HL1) + return FALSE; /* HL1 */ + + if (q1->LH1 != q2->LH1) + return FALSE; /* LH1 */ + + if (q1->HH1 != q2->HH1) + return FALSE; /* HH1 */ + + if (q1->HL2 != q2->HL2) + return FALSE; /* HL2 */ + + if (q1->LH2 != q2->LH2) + return FALSE; /* LH2 */ + + if (q1->HH2 != q2->HH2) + return FALSE; /* HH2 */ + + if (q1->HL3 != q2->HL3) + return FALSE; /* HL3 */ + + if (q1->LH3 != q2->LH3) + return FALSE; /* LH3 */ + + if (q1->HH3 != q2->HH3) + return FALSE; /* HH3 */ + + if (q1->LL3 != q2->LL3) + return FALSE; /* LL3 */ + + return TRUE; +} + +static INLINE BOOL progressive_set_surface_data(PROGRESSIVE_CONTEXT* progressive, UINT16 surfaceId, + void* pData) +{ + ULONG_PTR key = 0; + key = ((ULONG_PTR)surfaceId) + 1; + + if (pData) + return HashTable_Insert(progressive->SurfaceContexts, (void*)key, pData); + + HashTable_Remove(progressive->SurfaceContexts, (void*)key); + return TRUE; +} + +static INLINE PROGRESSIVE_SURFACE_CONTEXT* +progressive_get_surface_data(PROGRESSIVE_CONTEXT* progressive, UINT16 surfaceId) +{ + void* key = (void*)(((ULONG_PTR)surfaceId) + 1); + + if (!progressive) + return NULL; + + return HashTable_GetItemValue(progressive->SurfaceContexts, key); +} + +static void progressive_tile_free(RFX_PROGRESSIVE_TILE* tile) +{ + if (tile) + { + winpr_aligned_free(tile->sign); + winpr_aligned_free(tile->current); + winpr_aligned_free(tile->data); + winpr_aligned_free(tile); + } +} + +static void progressive_surface_context_free(void* ptr) +{ + PROGRESSIVE_SURFACE_CONTEXT* surface = ptr; + + if (!surface) + return; + + if (surface->tiles) + { + for (size_t index = 0; index < surface->tilesSize; index++) + { + RFX_PROGRESSIVE_TILE* tile = surface->tiles[index]; + progressive_tile_free(tile); + } + } + + winpr_aligned_free(surface->tiles); + winpr_aligned_free(surface->updatedTileIndices); + winpr_aligned_free(surface); +} + +static INLINE RFX_PROGRESSIVE_TILE* progressive_tile_new(void) +{ + RFX_PROGRESSIVE_TILE* tile = winpr_aligned_calloc(1, sizeof(RFX_PROGRESSIVE_TILE), 32); + if (!tile) + goto fail; + + tile->width = 64; + tile->height = 64; + tile->stride = 4 * tile->width; + + size_t dataLen = 1ull * tile->stride * tile->height; + tile->data = (BYTE*)winpr_aligned_malloc(dataLen, 16); + if (!tile->data) + goto fail; + memset(tile->data, 0xFF, dataLen); + + size_t signLen = (8192 + 32) * 3; + tile->sign = (BYTE*)winpr_aligned_calloc(signLen, sizeof(BYTE), 16); + if (!tile->sign) + goto fail; + + size_t currentLen = (8192 + 32) * 3; + tile->current = (BYTE*)winpr_aligned_calloc(currentLen, sizeof(BYTE), 16); + if (!tile->current) + goto fail; + + return tile; + +fail: + progressive_tile_free(tile); + return NULL; +} + +static BOOL progressive_allocate_tile_cache(PROGRESSIVE_SURFACE_CONTEXT* surface, size_t min) +{ + size_t oldIndex = 0; + + WINPR_ASSERT(surface); + WINPR_ASSERT(surface->gridSize > 0); + + if (surface->tiles) + { + oldIndex = surface->gridSize; + while (surface->gridSize < min) + surface->gridSize += 1024; + } + + void* tmp = winpr_aligned_recalloc(surface->tiles, surface->gridSize, + sizeof(RFX_PROGRESSIVE_TILE*), 32); + if (!tmp) + return FALSE; + surface->tilesSize = surface->gridSize; + surface->tiles = tmp; + + for (size_t x = oldIndex; x < surface->tilesSize; x++) + { + surface->tiles[x] = progressive_tile_new(); + if (!surface->tiles[x]) + return FALSE; + } + + tmp = + winpr_aligned_recalloc(surface->updatedTileIndices, surface->gridSize, sizeof(UINT32), 32); + if (!tmp) + return FALSE; + + surface->updatedTileIndices = tmp; + + return TRUE; +} + +static PROGRESSIVE_SURFACE_CONTEXT* progressive_surface_context_new(UINT16 surfaceId, UINT32 width, + UINT32 height) +{ + PROGRESSIVE_SURFACE_CONTEXT* surface = (PROGRESSIVE_SURFACE_CONTEXT*)winpr_aligned_calloc( + 1, sizeof(PROGRESSIVE_SURFACE_CONTEXT), 32); + + if (!surface) + return NULL; + + surface->id = surfaceId; + surface->width = width; + surface->height = height; + surface->gridWidth = (width + (64 - width % 64)) / 64; + surface->gridHeight = (height + (64 - height % 64)) / 64; + surface->gridSize = surface->gridWidth * surface->gridHeight; + + if (!progressive_allocate_tile_cache(surface, surface->gridSize)) + { + progressive_surface_context_free(surface); + return NULL; + } + + return surface; +} + +static BOOL progressive_surface_tile_replace(PROGRESSIVE_SURFACE_CONTEXT* surface, + PROGRESSIVE_BLOCK_REGION* region, + const RFX_PROGRESSIVE_TILE* tile, BOOL upgrade) +{ + RFX_PROGRESSIVE_TILE* t = NULL; + + size_t zIdx = 0; + if (!surface || !tile) + return FALSE; + + zIdx = (tile->yIdx * surface->gridWidth) + tile->xIdx; + + if (zIdx >= surface->tilesSize) + { + WLog_ERR(TAG, "Invalid zIndex %" PRIuz, zIdx); + return FALSE; + } + + t = surface->tiles[zIdx]; + + t->blockType = tile->blockType; + t->blockLen = tile->blockLen; + t->quantIdxY = tile->quantIdxY; + t->quantIdxCb = tile->quantIdxCb; + t->quantIdxCr = tile->quantIdxCr; + t->xIdx = tile->xIdx; + t->yIdx = tile->yIdx; + t->flags = tile->flags; + t->quality = tile->quality; + t->x = tile->xIdx * t->width; + t->y = tile->yIdx * t->height; + + if (upgrade) + { + t->ySrlLen = tile->ySrlLen; + t->yRawLen = tile->yRawLen; + t->cbSrlLen = tile->cbSrlLen; + t->cbRawLen = tile->cbRawLen; + t->crSrlLen = tile->crSrlLen; + t->crRawLen = tile->crRawLen; + t->ySrlData = tile->ySrlData; + t->yRawData = tile->yRawData; + t->cbSrlData = tile->cbSrlData; + t->cbRawData = tile->cbRawData; + t->crSrlData = tile->crSrlData; + t->crRawData = tile->crRawData; + } + else + { + t->yLen = tile->yLen; + t->cbLen = tile->cbLen; + t->crLen = tile->crLen; + t->tailLen = tile->tailLen; + t->yData = tile->yData; + t->cbData = tile->cbData; + t->crData = tile->crData; + t->tailData = tile->tailData; + } + + if (region->usedTiles >= region->numTiles) + { + WLog_ERR(TAG, "Invalid tile count, only expected %" PRIu16 ", got %" PRIu16, + region->numTiles, region->usedTiles); + return FALSE; + } + + region->tiles[region->usedTiles++] = t; + if (!t->dirty) + { + if (surface->numUpdatedTiles >= surface->gridSize) + { + if (!progressive_allocate_tile_cache(surface, surface->numUpdatedTiles + 1)) + return FALSE; + } + + surface->updatedTileIndices[surface->numUpdatedTiles++] = (UINT32)zIdx; + } + + t->dirty = TRUE; + return TRUE; +} + +INT32 progressive_create_surface_context(PROGRESSIVE_CONTEXT* progressive, UINT16 surfaceId, + UINT32 width, UINT32 height) +{ + PROGRESSIVE_SURFACE_CONTEXT* surface = progressive_get_surface_data(progressive, surfaceId); + + if (!surface) + { + surface = progressive_surface_context_new(surfaceId, width, height); + + if (!surface) + return -1; + + if (!progressive_set_surface_data(progressive, surfaceId, (void*)surface)) + { + progressive_surface_context_free(surface); + return -1; + } + } + + return 1; +} + +int progressive_delete_surface_context(PROGRESSIVE_CONTEXT* progressive, UINT16 surfaceId) +{ + progressive_set_surface_data(progressive, surfaceId, NULL); + + return 1; +} + +/* + * Band Offset Dimensions Size + * + * HL1 0 31x33 1023 + * LH1 1023 33x31 1023 + * HH1 2046 31x31 961 + * + * HL2 3007 16x17 272 + * LH2 3279 17x16 272 + * HH2 3551 16x16 256 + * + * HL3 3807 8x9 72 + * LH3 3879 9x8 72 + * HH3 3951 8x8 64 + * + * LL3 4015 9x9 81 + */ + +static INLINE void progressive_rfx_idwt_x(const INT16* pLowBand, size_t nLowStep, + const INT16* pHighBand, size_t nHighStep, INT16* pDstBand, + size_t nDstStep, size_t nLowCount, size_t nHighCount, + size_t nDstCount) +{ + INT16 L0 = 0; + INT16 H0 = 0; + INT16 H1 = 0; + INT16 X0 = 0; + INT16 X1 = 0; + INT16 X2 = 0; + + for (size_t i = 0; i < nDstCount; i++) + { + const INT16* pL = pLowBand; + const INT16* pH = pHighBand; + INT16* pX = pDstBand; + H0 = *pH++; + L0 = *pL++; + X0 = L0 - H0; + X2 = L0 - H0; + + for (size_t j = 0; j < (nHighCount - 1); j++) + { + H1 = *pH; + pH++; + L0 = *pL; + pL++; + X2 = L0 - ((H0 + H1) / 2); + X1 = ((X0 + X2) / 2) + (2 * H0); + pX[0] = X0; + pX[1] = X1; + pX += 2; + X0 = X2; + H0 = H1; + } + + if (nLowCount <= (nHighCount + 1)) + { + if (nLowCount <= nHighCount) + { + pX[0] = X2; + pX[1] = X2 + (2 * H0); + } + else + { + L0 = *pL; + pL++; + X0 = L0 - H0; + pX[0] = X2; + pX[1] = ((X0 + X2) / 2) + (2 * H0); + pX[2] = X0; + } + } + else + { + L0 = *pL; + pL++; + X0 = L0 - (H0 / 2); + pX[0] = X2; + pX[1] = ((X0 + X2) / 2) + (2 * H0); + pX[2] = X0; + L0 = *pL; + pL++; + pX[3] = (X0 + L0) / 2; + } + + pLowBand += nLowStep; + pHighBand += nHighStep; + pDstBand += nDstStep; + } +} + +static INLINE void progressive_rfx_idwt_y(const INT16* pLowBand, size_t nLowStep, + const INT16* pHighBand, size_t nHighStep, INT16* pDstBand, + size_t nDstStep, size_t nLowCount, size_t nHighCount, + size_t nDstCount) +{ + INT16 L0 = 0; + INT16 H0 = 0; + INT16 H1 = 0; + INT16 X0 = 0; + INT16 X1 = 0; + INT16 X2 = 0; + + for (size_t i = 0; i < nDstCount; i++) + { + const INT16* pL = pLowBand; + const INT16* pH = pHighBand; + INT16* pX = pDstBand; + H0 = *pH; + pH += nHighStep; + L0 = *pL; + pL += nLowStep; + X0 = L0 - H0; + X2 = L0 - H0; + + for (size_t j = 0; j < (nHighCount - 1); j++) + { + H1 = *pH; + pH += nHighStep; + L0 = *pL; + pL += nLowStep; + X2 = L0 - ((H0 + H1) / 2); + X1 = ((X0 + X2) / 2) + (2 * H0); + *pX = X0; + pX += nDstStep; + *pX = X1; + pX += nDstStep; + X0 = X2; + H0 = H1; + } + + if (nLowCount <= (nHighCount + 1)) + { + if (nLowCount <= nHighCount) + { + *pX = X2; + pX += nDstStep; + *pX = X2 + (2 * H0); + } + else + { + L0 = *pL; + X0 = L0 - H0; + *pX = X2; + pX += nDstStep; + *pX = ((X0 + X2) / 2) + (2 * H0); + pX += nDstStep; + *pX = X0; + } + } + else + { + L0 = *pL; + pL += nLowStep; + X0 = L0 - (H0 / 2); + *pX = X2; + pX += nDstStep; + *pX = ((X0 + X2) / 2) + (2 * H0); + pX += nDstStep; + *pX = X0; + pX += nDstStep; + L0 = *pL; + *pX = (X0 + L0) / 2; + } + + pLowBand++; + pHighBand++; + pDstBand++; + } +} + +static INLINE size_t progressive_rfx_get_band_l_count(size_t level) +{ + return (64 >> level) + 1; +} + +static INLINE size_t progressive_rfx_get_band_h_count(size_t level) +{ + if (level == 1) + return (64 >> 1) - 1; + else + return (64 + (1 << (level - 1))) >> level; +} + +static INLINE void progressive_rfx_dwt_2d_decode_block(INT16* buffer, INT16* temp, size_t level) +{ + size_t nDstStepX = 0; + size_t nDstStepY = 0; + INT16* HL = NULL; + INT16* LH = NULL; + INT16* HH = NULL; + INT16* LL = NULL; + INT16* L = NULL; + INT16* H = NULL; + INT16* LLx = NULL; + + const size_t nBandL = progressive_rfx_get_band_l_count(level); + const size_t nBandH = progressive_rfx_get_band_h_count(level); + size_t offset = 0; + + HL = &buffer[offset]; + offset += (nBandH * nBandL); + LH = &buffer[offset]; + offset += (nBandL * nBandH); + HH = &buffer[offset]; + offset += (nBandH * nBandH); + LL = &buffer[offset]; + nDstStepX = (nBandL + nBandH); + nDstStepY = (nBandL + nBandH); + offset = 0; + L = &temp[offset]; + offset += (nBandL * nDstStepX); + H = &temp[offset]; + LLx = &buffer[0]; + + /* horizontal (LL + HL -> L) */ + progressive_rfx_idwt_x(LL, nBandL, HL, nBandH, L, nDstStepX, nBandL, nBandH, nBandL); + + /* horizontal (LH + HH -> H) */ + progressive_rfx_idwt_x(LH, nBandL, HH, nBandH, H, nDstStepX, nBandL, nBandH, nBandH); + + /* vertical (L + H -> LL) */ + progressive_rfx_idwt_y(L, nDstStepX, H, nDstStepX, LLx, nDstStepY, nBandL, nBandH, + nBandL + nBandH); +} + +void rfx_dwt_2d_extrapolate_decode(INT16* buffer, INT16* temp) +{ + WINPR_ASSERT(buffer); + WINPR_ASSERT(temp); + progressive_rfx_dwt_2d_decode_block(&buffer[3807], temp, 3); + progressive_rfx_dwt_2d_decode_block(&buffer[3007], temp, 2); + progressive_rfx_dwt_2d_decode_block(&buffer[0], temp, 1); +} + +static INLINE int progressive_rfx_dwt_2d_decode(PROGRESSIVE_CONTEXT* progressive, INT16* buffer, + INT16* current, BOOL coeffDiff, BOOL extrapolate, + BOOL reverse) +{ + const primitives_t* prims = primitives_get(); + + if (!progressive || !buffer || !current) + return -1; + + INT16 dst[4096] = { 0 }; + if (reverse) + memcpy(buffer, current, sizeof(dst)); + else + { + if (coeffDiff) + { + prims->add_16s(buffer, current, dst, ARRAYSIZE(dst)); + memcpy(current, dst, sizeof(dst)); + memcpy(buffer, dst, sizeof(dst)); + } + else + memcpy(current, buffer, sizeof(dst)); + } + + INT16* temp = (INT16*)BufferPool_Take(progressive->bufferPool, -1); /* DWT buffer */ + + if (!temp) + return -2; + + if (!extrapolate) + { + progressive->rfx_context->dwt_2d_decode(buffer, temp); + } + else + { + WINPR_ASSERT(progressive->rfx_context->dwt_2d_extrapolate_decode); + progressive->rfx_context->dwt_2d_extrapolate_decode(buffer, temp); + } + BufferPool_Return(progressive->bufferPool, temp); + return 1; +} + +static INLINE void progressive_rfx_decode_block(const primitives_t* prims, INT16* buffer, + UINT32 length, UINT32 shift) +{ + if (!shift) + return; + + prims->lShiftC_16s(buffer, shift, buffer, length); +} + +static INLINE int progressive_rfx_decode_component(PROGRESSIVE_CONTEXT* progressive, + const RFX_COMPONENT_CODEC_QUANT* shift, + const BYTE* data, UINT32 length, INT16* buffer, + INT16* current, INT16* sign, BOOL coeffDiff, + BOOL subbandDiff, BOOL extrapolate) +{ + int status = 0; + const primitives_t* prims = primitives_get(); + + status = progressive->rfx_context->rlgr_decode(RLGR1, data, length, buffer, 4096); + + if (status < 0) + return status; + + CopyMemory(sign, buffer, 4096 * 2); + if (!extrapolate) + { + rfx_differential_decode(buffer + 4032, 64); + progressive_rfx_decode_block(prims, &buffer[0], 1024, shift->HL1); /* HL1 */ + progressive_rfx_decode_block(prims, &buffer[1024], 1024, shift->LH1); /* LH1 */ + progressive_rfx_decode_block(prims, &buffer[2048], 1024, shift->HH1); /* HH1 */ + progressive_rfx_decode_block(prims, &buffer[3072], 256, shift->HL2); /* HL2 */ + progressive_rfx_decode_block(prims, &buffer[3328], 256, shift->LH2); /* LH2 */ + progressive_rfx_decode_block(prims, &buffer[3584], 256, shift->HH2); /* HH2 */ + progressive_rfx_decode_block(prims, &buffer[3840], 64, shift->HL3); /* HL3 */ + progressive_rfx_decode_block(prims, &buffer[3904], 64, shift->LH3); /* LH3 */ + progressive_rfx_decode_block(prims, &buffer[3968], 64, shift->HH3); /* HH3 */ + progressive_rfx_decode_block(prims, &buffer[4032], 64, shift->LL3); /* LL3 */ + } + else + { + progressive_rfx_decode_block(prims, &buffer[0], 1023, shift->HL1); /* HL1 */ + progressive_rfx_decode_block(prims, &buffer[1023], 1023, shift->LH1); /* LH1 */ + progressive_rfx_decode_block(prims, &buffer[2046], 961, shift->HH1); /* HH1 */ + progressive_rfx_decode_block(prims, &buffer[3007], 272, shift->HL2); /* HL2 */ + progressive_rfx_decode_block(prims, &buffer[3279], 272, shift->LH2); /* LH2 */ + progressive_rfx_decode_block(prims, &buffer[3551], 256, shift->HH2); /* HH2 */ + progressive_rfx_decode_block(prims, &buffer[3807], 72, shift->HL3); /* HL3 */ + progressive_rfx_decode_block(prims, &buffer[3879], 72, shift->LH3); /* LH3 */ + progressive_rfx_decode_block(prims, &buffer[3951], 64, shift->HH3); /* HH3 */ + rfx_differential_decode(&buffer[4015], 81); /* LL3 */ + progressive_rfx_decode_block(prims, &buffer[4015], 81, shift->LL3); /* LL3 */ + } + return progressive_rfx_dwt_2d_decode(progressive, buffer, current, coeffDiff, extrapolate, + FALSE); +} + +static INLINE int progressive_decompress_tile_first(PROGRESSIVE_CONTEXT* progressive, + RFX_PROGRESSIVE_TILE* tile, + PROGRESSIVE_BLOCK_REGION* region, + const PROGRESSIVE_BLOCK_CONTEXT* context) +{ + int rc = 0; + BOOL diff = 0; + BOOL sub = 0; + BOOL extrapolate = 0; + BYTE* pBuffer = NULL; + INT16* pSign[3]; + INT16* pSrcDst[3]; + INT16* pCurrent[3]; + RFX_COMPONENT_CODEC_QUANT shiftY = { 0 }; + RFX_COMPONENT_CODEC_QUANT shiftCb = { 0 }; + RFX_COMPONENT_CODEC_QUANT shiftCr = { 0 }; + RFX_COMPONENT_CODEC_QUANT* quantY = NULL; + RFX_COMPONENT_CODEC_QUANT* quantCb = NULL; + RFX_COMPONENT_CODEC_QUANT* quantCr = NULL; + RFX_COMPONENT_CODEC_QUANT* quantProgY = NULL; + RFX_COMPONENT_CODEC_QUANT* quantProgCb = NULL; + RFX_COMPONENT_CODEC_QUANT* quantProgCr = NULL; + RFX_PROGRESSIVE_CODEC_QUANT* quantProgVal = NULL; + static const prim_size_t roi_64x64 = { 64, 64 }; + const primitives_t* prims = primitives_get(); + + tile->pass = 1; + diff = tile->flags & RFX_TILE_DIFFERENCE; + sub = context->flags & RFX_SUBBAND_DIFFING; + extrapolate = region->flags & RFX_DWT_REDUCE_EXTRAPOLATE; + +#if defined(WITH_DEBUG_CODECS) + WLog_Print(progressive->log, WLOG_DEBUG, + "ProgressiveTile%s: quantIdx Y: %" PRIu8 " Cb: %" PRIu8 " Cr: %" PRIu8 + " xIdx: %" PRIu16 " yIdx: %" PRIu16 " flags: 0x%02" PRIX8 " quality: %" PRIu8 + " yLen: %" PRIu16 " cbLen: %" PRIu16 " crLen: %" PRIu16 " tailLen: %" PRIu16 "", + (tile->blockType == PROGRESSIVE_WBT_TILE_FIRST) ? "First" : "Simple", + tile->quantIdxY, tile->quantIdxCb, tile->quantIdxCr, tile->xIdx, tile->yIdx, + tile->flags, tile->quality, tile->yLen, tile->cbLen, tile->crLen, tile->tailLen); +#endif + + if (tile->quantIdxY >= region->numQuant) + { + WLog_ERR(TAG, "quantIdxY %" PRIu8 " > numQuant %" PRIu8, tile->quantIdxY, region->numQuant); + return -1; + } + + quantY = &(region->quantVals[tile->quantIdxY]); + + if (tile->quantIdxCb >= region->numQuant) + { + WLog_ERR(TAG, "quantIdxCb %" PRIu8 " > numQuant %" PRIu8, tile->quantIdxCb, + region->numQuant); + return -1; + } + + quantCb = &(region->quantVals[tile->quantIdxCb]); + + if (tile->quantIdxCr >= region->numQuant) + { + WLog_ERR(TAG, "quantIdxCr %" PRIu8 " > numQuant %" PRIu8, tile->quantIdxCr, + region->numQuant); + return -1; + } + + quantCr = &(region->quantVals[tile->quantIdxCr]); + + if (tile->quality == 0xFF) + { + quantProgVal = &(progressive->quantProgValFull); + } + else + { + if (tile->quality >= region->numProgQuant) + { + WLog_ERR(TAG, "quality %" PRIu8 " > numProgQuant %" PRIu8, tile->quality, + region->numProgQuant); + return -1; + } + + quantProgVal = &(region->quantProgVals[tile->quality]); + } + + quantProgY = &(quantProgVal->yQuantValues); + quantProgCb = &(quantProgVal->cbQuantValues); + quantProgCr = &(quantProgVal->crQuantValues); + + tile->yQuant = *quantY; + tile->cbQuant = *quantCb; + tile->crQuant = *quantCr; + tile->yProgQuant = *quantProgY; + tile->cbProgQuant = *quantProgCb; + tile->crProgQuant = *quantProgCr; + + progressive_rfx_quant_add(quantY, quantProgY, &(tile->yBitPos)); + progressive_rfx_quant_add(quantCb, quantProgCb, &(tile->cbBitPos)); + progressive_rfx_quant_add(quantCr, quantProgCr, &(tile->crBitPos)); + progressive_rfx_quant_add(quantY, quantProgY, &shiftY); + progressive_rfx_quant_lsub(&shiftY, 1); /* -6 + 5 = -1 */ + progressive_rfx_quant_add(quantCb, quantProgCb, &shiftCb); + progressive_rfx_quant_lsub(&shiftCb, 1); /* -6 + 5 = -1 */ + progressive_rfx_quant_add(quantCr, quantProgCr, &shiftCr); + progressive_rfx_quant_lsub(&shiftCr, 1); /* -6 + 5 = -1 */ + + pSign[0] = (INT16*)((BYTE*)(&tile->sign[((8192 + 32) * 0) + 16])); /* Y/R buffer */ + pSign[1] = (INT16*)((BYTE*)(&tile->sign[((8192 + 32) * 1) + 16])); /* Cb/G buffer */ + pSign[2] = (INT16*)((BYTE*)(&tile->sign[((8192 + 32) * 2) + 16])); /* Cr/B buffer */ + + pCurrent[0] = (INT16*)((BYTE*)(&tile->current[((8192 + 32) * 0) + 16])); /* Y/R buffer */ + pCurrent[1] = (INT16*)((BYTE*)(&tile->current[((8192 + 32) * 1) + 16])); /* Cb/G buffer */ + pCurrent[2] = (INT16*)((BYTE*)(&tile->current[((8192 + 32) * 2) + 16])); /* Cr/B buffer */ + + pBuffer = (BYTE*)BufferPool_Take(progressive->bufferPool, -1); + pSrcDst[0] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 0) + 16])); /* Y/R buffer */ + pSrcDst[1] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 1) + 16])); /* Cb/G buffer */ + pSrcDst[2] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 2) + 16])); /* Cr/B buffer */ + + rc = progressive_rfx_decode_component(progressive, &shiftY, tile->yData, tile->yLen, pSrcDst[0], + pCurrent[0], pSign[0], diff, sub, extrapolate); /* Y */ + if (rc < 0) + goto fail; + rc = progressive_rfx_decode_component(progressive, &shiftCb, tile->cbData, tile->cbLen, + pSrcDst[1], pCurrent[1], pSign[1], diff, sub, + extrapolate); /* Cb */ + if (rc < 0) + goto fail; + rc = progressive_rfx_decode_component(progressive, &shiftCr, tile->crData, tile->crLen, + pSrcDst[2], pCurrent[2], pSign[2], diff, sub, + extrapolate); /* Cr */ + if (rc < 0) + goto fail; + + rc = prims->yCbCrToRGB_16s8u_P3AC4R((const INT16* const*)pSrcDst, 64 * 2, tile->data, + tile->stride, progressive->format, &roi_64x64); +fail: + BufferPool_Return(progressive->bufferPool, pBuffer); + return rc; +} + +static INLINE INT16 progressive_rfx_srl_read(RFX_PROGRESSIVE_UPGRADE_STATE* state, UINT32 numBits) +{ + UINT32 k = 0; + UINT32 bit = 0; + UINT32 max = 0; + UINT32 mag = 0; + UINT32 sign = 0; + wBitStream* bs = state->srl; + + if (state->nz) + { + state->nz--; + return 0; + } + + k = state->kp / 8; + + if (!state->mode) + { + /* zero encoding */ + bit = (bs->accumulator & 0x80000000) ? 1 : 0; + BitStream_Shift(bs, 1); + + if (!bit) + { + /* '0' bit, nz >= (1 << k), nz = (1 << k) */ + state->nz = (1 << k); + state->kp += 4; + + if (state->kp > 80) + state->kp = 80; + + state->nz--; + return 0; + } + else + { + /* '1' bit, nz < (1 << k), nz = next k bits */ + state->nz = 0; + state->mode = 1; /* unary encoding is next */ + + if (k) + { + bs->mask = ((1 << k) - 1); + state->nz = ((bs->accumulator >> (32u - k)) & bs->mask); + BitStream_Shift(bs, k); + } + + if (state->nz) + { + state->nz--; + return 0; + } + } + } + + state->mode = 0; /* zero encoding is next */ + /* unary encoding */ + /* read sign bit */ + sign = (bs->accumulator & 0x80000000) ? 1 : 0; + BitStream_Shift(bs, 1); + + if (state->kp < 6) + state->kp = 0; + else + state->kp -= 6; + + if (numBits == 1) + return sign ? -1 : 1; + + mag = 1; + max = (1 << numBits) - 1; + + while (mag < max) + { + bit = (bs->accumulator & 0x80000000) ? 1 : 0; + BitStream_Shift(bs, 1); + + if (bit) + break; + + mag++; + } + + return sign ? -1 * mag : mag; +} + +static INLINE int progressive_rfx_upgrade_state_finish(RFX_PROGRESSIVE_UPGRADE_STATE* state) +{ + UINT32 pad = 0; + wBitStream* srl = NULL; + wBitStream* raw = NULL; + if (!state) + return -1; + + srl = state->srl; + raw = state->raw; + /* Read trailing bits from RAW/SRL bit streams */ + pad = (raw->position % 8) ? (8 - (raw->position % 8)) : 0; + + if (pad) + BitStream_Shift(raw, pad); + + pad = (srl->position % 8) ? (8 - (srl->position % 8)) : 0; + + if (pad) + BitStream_Shift(srl, pad); + + if (BitStream_GetRemainingLength(srl) == 8) + BitStream_Shift(srl, 8); + + return 1; +} + +static INLINE int progressive_rfx_upgrade_block(RFX_PROGRESSIVE_UPGRADE_STATE* state, INT16* buffer, + INT16* sign, UINT32 length, UINT32 shift, + UINT32 bitPos, UINT32 numBits) +{ + INT16 input = 0; + wBitStream* raw = NULL; + + if (!numBits) + return 1; + + raw = state->raw; + + if (!state->nonLL) + { + for (UINT32 index = 0; index < length; index++) + { + raw->mask = ((1 << numBits) - 1); + input = (INT16)((raw->accumulator >> (32 - numBits)) & raw->mask); + BitStream_Shift(raw, numBits); + buffer[index] += (input << shift); + } + + return 1; + } + + for (UINT32 index = 0; index < length; index++) + { + if (sign[index] > 0) + { + /* sign > 0, read from raw */ + raw->mask = ((1 << numBits) - 1); + input = (INT16)((raw->accumulator >> (32 - numBits)) & raw->mask); + BitStream_Shift(raw, numBits); + } + else if (sign[index] < 0) + { + /* sign < 0, read from raw */ + raw->mask = ((1 << numBits) - 1); + input = (INT16)((raw->accumulator >> (32 - numBits)) & raw->mask); + BitStream_Shift(raw, numBits); + input *= -1; + } + else + { + /* sign == 0, read from srl */ + input = progressive_rfx_srl_read(state, numBits); + sign[index] = input; + } + + buffer[index] += (INT16)((UINT32)input << shift); + } + + return 1; +} + +static INLINE int progressive_rfx_upgrade_component( + PROGRESSIVE_CONTEXT* progressive, const RFX_COMPONENT_CODEC_QUANT* shift, + const RFX_COMPONENT_CODEC_QUANT* bitPos, const RFX_COMPONENT_CODEC_QUANT* numBits, + INT16* buffer, INT16* current, INT16* sign, const BYTE* srlData, UINT32 srlLen, + const BYTE* rawData, UINT32 rawLen, BOOL coeffDiff, BOOL subbandDiff, BOOL extrapolate) +{ + int rc = 0; + UINT32 aRawLen = 0; + UINT32 aSrlLen = 0; + wBitStream s_srl = { 0 }; + wBitStream s_raw = { 0 }; + RFX_PROGRESSIVE_UPGRADE_STATE state = { 0 }; + + state.kp = 8; + state.mode = 0; + state.srl = &s_srl; + state.raw = &s_raw; + BitStream_Attach(state.srl, srlData, srlLen); + BitStream_Fetch(state.srl); + BitStream_Attach(state.raw, rawData, rawLen); + BitStream_Fetch(state.raw); + + state.nonLL = TRUE; + rc = progressive_rfx_upgrade_block(&state, ¤t[0], &sign[0], 1023, shift->HL1, bitPos->HL1, + numBits->HL1); /* HL1 */ + if (rc < 0) + return rc; + rc = progressive_rfx_upgrade_block(&state, ¤t[1023], &sign[1023], 1023, shift->LH1, + bitPos->LH1, numBits->LH1); /* LH1 */ + if (rc < 0) + return rc; + rc = progressive_rfx_upgrade_block(&state, ¤t[2046], &sign[2046], 961, shift->HH1, + bitPos->HH1, numBits->HH1); /* HH1 */ + if (rc < 0) + return rc; + rc = progressive_rfx_upgrade_block(&state, ¤t[3007], &sign[3007], 272, shift->HL2, + bitPos->HL2, numBits->HL2); /* HL2 */ + if (rc < 0) + return rc; + rc = progressive_rfx_upgrade_block(&state, ¤t[3279], &sign[3279], 272, shift->LH2, + bitPos->LH2, numBits->LH2); /* LH2 */ + if (rc < 0) + return rc; + rc = progressive_rfx_upgrade_block(&state, ¤t[3551], &sign[3551], 256, shift->HH2, + bitPos->HH2, numBits->HH2); /* HH2 */ + if (rc < 0) + return rc; + rc = progressive_rfx_upgrade_block(&state, ¤t[3807], &sign[3807], 72, shift->HL3, + bitPos->HL3, numBits->HL3); /* HL3 */ + if (rc < 0) + return rc; + rc = progressive_rfx_upgrade_block(&state, ¤t[3879], &sign[3879], 72, shift->LH3, + bitPos->LH3, numBits->LH3); /* LH3 */ + if (rc < 0) + return rc; + rc = progressive_rfx_upgrade_block(&state, ¤t[3951], &sign[3951], 64, shift->HH3, + bitPos->HH3, numBits->HH3); /* HH3 */ + if (rc < 0) + return rc; + + state.nonLL = FALSE; + rc = progressive_rfx_upgrade_block(&state, ¤t[4015], &sign[4015], 81, shift->LL3, + bitPos->LL3, numBits->LL3); /* LL3 */ + if (rc < 0) + return rc; + rc = progressive_rfx_upgrade_state_finish(&state); + if (rc < 0) + return rc; + aRawLen = (state.raw->position + 7) / 8; + aSrlLen = (state.srl->position + 7) / 8; + + if ((aRawLen != rawLen) || (aSrlLen != srlLen)) + { + int pRawLen = 0; + int pSrlLen = 0; + + if (rawLen) + pRawLen = (int)((((float)aRawLen) / ((float)rawLen)) * 100.0f); + + if (srlLen) + pSrlLen = (int)((((float)aSrlLen) / ((float)srlLen)) * 100.0f); + + WLog_Print(progressive->log, WLOG_WARN, + "RAW: %" PRIu32 "/%" PRIu32 " %d%% (%" PRIu32 "/%" PRIu32 ":%" PRIu32 + ")\tSRL: %" PRIu32 "/%" PRIu32 " %d%% (%" PRIu32 "/%" PRIu32 ":%" PRIu32 ")", + aRawLen, rawLen, pRawLen, state.raw->position, rawLen * 8, + (rawLen * 8) - state.raw->position, aSrlLen, srlLen, pSrlLen, + state.srl->position, srlLen * 8, (srlLen * 8) - state.srl->position); + return -1; + } + + return progressive_rfx_dwt_2d_decode(progressive, buffer, current, coeffDiff, extrapolate, + TRUE); +} + +static INLINE int progressive_decompress_tile_upgrade(PROGRESSIVE_CONTEXT* progressive, + RFX_PROGRESSIVE_TILE* tile, + PROGRESSIVE_BLOCK_REGION* region, + const PROGRESSIVE_BLOCK_CONTEXT* context) +{ + int status = 0; + BOOL coeffDiff = 0; + BOOL sub = 0; + BOOL extrapolate = 0; + BYTE* pBuffer = NULL; + INT16* pSign[3] = { 0 }; + INT16* pSrcDst[3] = { 0 }; + INT16* pCurrent[3] = { 0 }; + RFX_COMPONENT_CODEC_QUANT shiftY = { 0 }; + RFX_COMPONENT_CODEC_QUANT shiftCb = { 0 }; + RFX_COMPONENT_CODEC_QUANT shiftCr = { 0 }; + RFX_COMPONENT_CODEC_QUANT yBitPos = { 0 }; + RFX_COMPONENT_CODEC_QUANT cbBitPos = { 0 }; + RFX_COMPONENT_CODEC_QUANT crBitPos = { 0 }; + RFX_COMPONENT_CODEC_QUANT yNumBits = { 0 }; + RFX_COMPONENT_CODEC_QUANT cbNumBits = { 0 }; + RFX_COMPONENT_CODEC_QUANT crNumBits = { 0 }; + RFX_COMPONENT_CODEC_QUANT* quantY = NULL; + RFX_COMPONENT_CODEC_QUANT* quantCb = NULL; + RFX_COMPONENT_CODEC_QUANT* quantCr = NULL; + RFX_COMPONENT_CODEC_QUANT* quantProgY = NULL; + RFX_COMPONENT_CODEC_QUANT* quantProgCb = NULL; + RFX_COMPONENT_CODEC_QUANT* quantProgCr = NULL; + RFX_PROGRESSIVE_CODEC_QUANT* quantProg = NULL; + static const prim_size_t roi_64x64 = { 64, 64 }; + const primitives_t* prims = primitives_get(); + + coeffDiff = tile->flags & RFX_TILE_DIFFERENCE; + sub = context->flags & RFX_SUBBAND_DIFFING; + extrapolate = region->flags & RFX_DWT_REDUCE_EXTRAPOLATE; + + tile->pass++; + +#if defined(WITH_DEBUG_CODECS) + WLog_Print(progressive->log, WLOG_DEBUG, + "ProgressiveTileUpgrade: pass: %" PRIu16 " quantIdx Y: %" PRIu8 " Cb: %" PRIu8 + " Cr: %" PRIu8 " xIdx: %" PRIu16 " yIdx: %" PRIu16 " quality: %" PRIu8 + " ySrlLen: %" PRIu16 " yRawLen: %" PRIu16 " cbSrlLen: %" PRIu16 " cbRawLen: %" PRIu16 + " crSrlLen: %" PRIu16 " crRawLen: %" PRIu16 "", + tile->pass, tile->quantIdxY, tile->quantIdxCb, tile->quantIdxCr, tile->xIdx, + tile->yIdx, tile->quality, tile->ySrlLen, tile->yRawLen, tile->cbSrlLen, + tile->cbRawLen, tile->crSrlLen, tile->crRawLen); +#endif + + if (tile->quantIdxY >= region->numQuant) + { + WLog_ERR(TAG, "quantIdxY %" PRIu8 " > numQuant %" PRIu8, tile->quantIdxY, region->numQuant); + return -1; + } + + quantY = &(region->quantVals[tile->quantIdxY]); + + if (tile->quantIdxCb >= region->numQuant) + { + WLog_ERR(TAG, "quantIdxCb %" PRIu8 " > numQuant %" PRIu8, tile->quantIdxCb, + region->numQuant); + return -1; + } + + quantCb = &(region->quantVals[tile->quantIdxCb]); + + if (tile->quantIdxCr >= region->numQuant) + { + WLog_ERR(TAG, "quantIdxCr %" PRIu8 " > numQuant %" PRIu8, tile->quantIdxCr, + region->numQuant); + return -1; + } + + quantCr = &(region->quantVals[tile->quantIdxCr]); + + if (tile->quality == 0xFF) + { + quantProg = &(progressive->quantProgValFull); + } + else + { + if (tile->quality >= region->numProgQuant) + { + WLog_ERR(TAG, "quality %" PRIu8 " > numProgQuant %" PRIu8, tile->quality, + region->numProgQuant); + return -1; + } + + quantProg = &(region->quantProgVals[tile->quality]); + } + + quantProgY = &(quantProg->yQuantValues); + quantProgCb = &(quantProg->cbQuantValues); + quantProgCr = &(quantProg->crQuantValues); + + if (!progressive_rfx_quant_cmp_equal(quantY, &(tile->yQuant))) + WLog_Print(progressive->log, WLOG_WARN, "non-progressive quantY has changed!"); + + if (!progressive_rfx_quant_cmp_equal(quantCb, &(tile->cbQuant))) + WLog_Print(progressive->log, WLOG_WARN, "non-progressive quantCb has changed!"); + + if (!progressive_rfx_quant_cmp_equal(quantCr, &(tile->crQuant))) + WLog_Print(progressive->log, WLOG_WARN, "non-progressive quantCr has changed!"); + + if (!(context->flags & RFX_SUBBAND_DIFFING)) + WLog_WARN(TAG, "PROGRESSIVE_BLOCK_CONTEXT::flags & RFX_SUBBAND_DIFFING not set"); + + progressive_rfx_quant_add(quantY, quantProgY, &yBitPos); + progressive_rfx_quant_add(quantCb, quantProgCb, &cbBitPos); + progressive_rfx_quant_add(quantCr, quantProgCr, &crBitPos); + progressive_rfx_quant_sub(&(tile->yBitPos), &yBitPos, &yNumBits); + progressive_rfx_quant_sub(&(tile->cbBitPos), &cbBitPos, &cbNumBits); + progressive_rfx_quant_sub(&(tile->crBitPos), &crBitPos, &crNumBits); + progressive_rfx_quant_add(quantY, quantProgY, &shiftY); + progressive_rfx_quant_lsub(&shiftY, 1); /* -6 + 5 = -1 */ + progressive_rfx_quant_add(quantCb, quantProgCb, &shiftCb); + progressive_rfx_quant_lsub(&shiftCb, 1); /* -6 + 5 = -1 */ + progressive_rfx_quant_add(quantCr, quantProgCr, &shiftCr); + progressive_rfx_quant_lsub(&shiftCr, 1); /* -6 + 5 = -1 */ + + tile->yBitPos = yBitPos; + tile->cbBitPos = cbBitPos; + tile->crBitPos = crBitPos; + tile->yQuant = *quantY; + tile->cbQuant = *quantCb; + tile->crQuant = *quantCr; + tile->yProgQuant = *quantProgY; + tile->cbProgQuant = *quantProgCb; + tile->crProgQuant = *quantProgCr; + + pSign[0] = (INT16*)((BYTE*)(&tile->sign[((8192 + 32) * 0) + 16])); /* Y/R buffer */ + pSign[1] = (INT16*)((BYTE*)(&tile->sign[((8192 + 32) * 1) + 16])); /* Cb/G buffer */ + pSign[2] = (INT16*)((BYTE*)(&tile->sign[((8192 + 32) * 2) + 16])); /* Cr/B buffer */ + + pCurrent[0] = (INT16*)((BYTE*)(&tile->current[((8192 + 32) * 0) + 16])); /* Y/R buffer */ + pCurrent[1] = (INT16*)((BYTE*)(&tile->current[((8192 + 32) * 1) + 16])); /* Cb/G buffer */ + pCurrent[2] = (INT16*)((BYTE*)(&tile->current[((8192 + 32) * 2) + 16])); /* Cr/B buffer */ + + pBuffer = (BYTE*)BufferPool_Take(progressive->bufferPool, -1); + pSrcDst[0] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 0) + 16])); /* Y/R buffer */ + pSrcDst[1] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 1) + 16])); /* Cb/G buffer */ + pSrcDst[2] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 2) + 16])); /* Cr/B buffer */ + + status = progressive_rfx_upgrade_component(progressive, &shiftY, quantProgY, &yNumBits, + pSrcDst[0], pCurrent[0], pSign[0], tile->ySrlData, + tile->ySrlLen, tile->yRawData, tile->yRawLen, + coeffDiff, sub, extrapolate); /* Y */ + + if (status < 0) + goto fail; + + status = progressive_rfx_upgrade_component(progressive, &shiftCb, quantProgCb, &cbNumBits, + pSrcDst[1], pCurrent[1], pSign[1], tile->cbSrlData, + tile->cbSrlLen, tile->cbRawData, tile->cbRawLen, + coeffDiff, sub, extrapolate); /* Cb */ + + if (status < 0) + goto fail; + + status = progressive_rfx_upgrade_component(progressive, &shiftCr, quantProgCr, &crNumBits, + pSrcDst[2], pCurrent[2], pSign[2], tile->crSrlData, + tile->crSrlLen, tile->crRawData, tile->crRawLen, + coeffDiff, sub, extrapolate); /* Cr */ + + if (status < 0) + goto fail; + + status = prims->yCbCrToRGB_16s8u_P3AC4R((const INT16* const*)pSrcDst, 64 * 2, tile->data, + tile->stride, progressive->format, &roi_64x64); +fail: + BufferPool_Return(progressive->bufferPool, pBuffer); + return status; +} + +static INLINE BOOL progressive_tile_read_upgrade(PROGRESSIVE_CONTEXT* progressive, wStream* s, + UINT16 blockType, UINT32 blockLen, + PROGRESSIVE_SURFACE_CONTEXT* surface, + PROGRESSIVE_BLOCK_REGION* region, + const PROGRESSIVE_BLOCK_CONTEXT* context) +{ + RFX_PROGRESSIVE_TILE tile = { 0 }; + const size_t expect = 20; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, expect)) + return FALSE; + + tile.blockType = blockType; + tile.blockLen = blockLen; + tile.flags = 0; + + Stream_Read_UINT8(s, tile.quantIdxY); + Stream_Read_UINT8(s, tile.quantIdxCb); + Stream_Read_UINT8(s, tile.quantIdxCr); + Stream_Read_UINT16(s, tile.xIdx); + Stream_Read_UINT16(s, tile.yIdx); + Stream_Read_UINT8(s, tile.quality); + Stream_Read_UINT16(s, tile.ySrlLen); + Stream_Read_UINT16(s, tile.yRawLen); + Stream_Read_UINT16(s, tile.cbSrlLen); + Stream_Read_UINT16(s, tile.cbRawLen); + Stream_Read_UINT16(s, tile.crSrlLen); + Stream_Read_UINT16(s, tile.crRawLen); + + tile.ySrlData = Stream_Pointer(s); + if (!Stream_SafeSeek(s, tile.ySrlLen)) + { + WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", tile.ySrlLen); + return FALSE; + } + + tile.yRawData = Stream_Pointer(s); + if (!Stream_SafeSeek(s, tile.yRawLen)) + { + WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", tile.yRawLen); + return FALSE; + } + + tile.cbSrlData = Stream_Pointer(s); + if (!Stream_SafeSeek(s, tile.cbSrlLen)) + { + WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", + tile.cbSrlLen); + return FALSE; + } + + tile.cbRawData = Stream_Pointer(s); + if (!Stream_SafeSeek(s, tile.cbRawLen)) + { + WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", + tile.cbRawLen); + return FALSE; + } + + tile.crSrlData = Stream_Pointer(s); + if (!Stream_SafeSeek(s, tile.crSrlLen)) + { + WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", + tile.crSrlLen); + return FALSE; + } + + tile.crRawData = Stream_Pointer(s); + if (!Stream_SafeSeek(s, tile.crRawLen)) + { + WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", + tile.crRawLen); + return FALSE; + } + + return progressive_surface_tile_replace(surface, region, &tile, TRUE); +} + +static INLINE BOOL progressive_tile_read(PROGRESSIVE_CONTEXT* progressive, BOOL simple, wStream* s, + UINT16 blockType, UINT32 blockLen, + PROGRESSIVE_SURFACE_CONTEXT* surface, + PROGRESSIVE_BLOCK_REGION* region, + const PROGRESSIVE_BLOCK_CONTEXT* context) +{ + RFX_PROGRESSIVE_TILE tile = { 0 }; + size_t expect = simple ? 16 : 17; + + if (!Stream_CheckAndLogRequiredLength(TAG, s, expect)) + return FALSE; + + tile.blockType = blockType; + tile.blockLen = blockLen; + + Stream_Read_UINT8(s, tile.quantIdxY); + Stream_Read_UINT8(s, tile.quantIdxCb); + Stream_Read_UINT8(s, tile.quantIdxCr); + Stream_Read_UINT16(s, tile.xIdx); + Stream_Read_UINT16(s, tile.yIdx); + Stream_Read_UINT8(s, tile.flags); + + if (!simple) + Stream_Read_UINT8(s, tile.quality); + else + tile.quality = 0xFF; + Stream_Read_UINT16(s, tile.yLen); + Stream_Read_UINT16(s, tile.cbLen); + Stream_Read_UINT16(s, tile.crLen); + Stream_Read_UINT16(s, tile.tailLen); + + tile.yData = Stream_Pointer(s); + if (!Stream_SafeSeek(s, tile.yLen)) + { + WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", tile.yLen); + return FALSE; + } + + tile.cbData = Stream_Pointer(s); + if (!Stream_SafeSeek(s, tile.cbLen)) + { + WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", tile.cbLen); + return FALSE; + } + + tile.crData = Stream_Pointer(s); + if (!Stream_SafeSeek(s, tile.crLen)) + { + WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", tile.crLen); + return FALSE; + } + + tile.tailData = Stream_Pointer(s); + if (!Stream_SafeSeek(s, tile.tailLen)) + { + WLog_Print(progressive->log, WLOG_ERROR, " Failed to seek %" PRIu32 " bytes", tile.tailLen); + return FALSE; + } + + return progressive_surface_tile_replace(surface, region, &tile, FALSE); +} + +typedef struct +{ + PROGRESSIVE_CONTEXT* progressive; + PROGRESSIVE_BLOCK_REGION* region; + const PROGRESSIVE_BLOCK_CONTEXT* context; + RFX_PROGRESSIVE_TILE* tile; +} PROGRESSIVE_TILE_PROCESS_WORK_PARAM; + +static void CALLBACK progressive_process_tiles_tile_work_callback(PTP_CALLBACK_INSTANCE instance, + void* context, PTP_WORK work) +{ + PROGRESSIVE_TILE_PROCESS_WORK_PARAM* param = (PROGRESSIVE_TILE_PROCESS_WORK_PARAM*)context; + + WINPR_UNUSED(instance); + WINPR_UNUSED(work); + + switch (param->tile->blockType) + { + case PROGRESSIVE_WBT_TILE_SIMPLE: + case PROGRESSIVE_WBT_TILE_FIRST: + progressive_decompress_tile_first(param->progressive, param->tile, param->region, + param->context); + break; + + case PROGRESSIVE_WBT_TILE_UPGRADE: + progressive_decompress_tile_upgrade(param->progressive, param->tile, param->region, + param->context); + break; + default: + WLog_Print(param->progressive->log, WLOG_ERROR, "Invalid block type %04" PRIx16 " (%s)", + param->tile->blockType, + rfx_get_progressive_block_type_string(param->tile->blockType)); + break; + } +} + +static INLINE SSIZE_T progressive_process_tiles(PROGRESSIVE_CONTEXT* progressive, wStream* s, + PROGRESSIVE_BLOCK_REGION* region, + PROGRESSIVE_SURFACE_CONTEXT* surface, + const PROGRESSIVE_BLOCK_CONTEXT* context) +{ + int status = 0; + size_t end = 0; + const size_t start = Stream_GetPosition(s); + UINT16 index = 0; + UINT16 blockType = 0; + UINT32 blockLen = 0; + UINT32 count = 0; + PTP_WORK* work_objects = NULL; + PROGRESSIVE_TILE_PROCESS_WORK_PARAM* params = NULL; + UINT16 close_cnt = 0; + + WINPR_ASSERT(progressive); + WINPR_ASSERT(region); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, region->tileDataSize)) + return -1; + + while ((Stream_GetRemainingLength(s) >= 6) && + (region->tileDataSize > (Stream_GetPosition(s) - start))) + { + const size_t pos = Stream_GetPosition(s); + + Stream_Read_UINT16(s, blockType); + Stream_Read_UINT32(s, blockLen); + +#if defined(WITH_DEBUG_CODECS) + WLog_Print(progressive->log, WLOG_DEBUG, "%s", + rfx_get_progressive_block_type_string(blockType)); +#endif + + if (blockLen < 6) + { + WLog_Print(progressive->log, WLOG_ERROR, "Expected >= %" PRIu32 " remaining %" PRIuz, 6, + blockLen); + return -1003; + } + if (!Stream_CheckAndLogRequiredLength(TAG, s, blockLen - 6)) + return -1003; + + switch (blockType) + { + case PROGRESSIVE_WBT_TILE_SIMPLE: + if (!progressive_tile_read(progressive, TRUE, s, blockType, blockLen, surface, + region, context)) + return -1022; + break; + + case PROGRESSIVE_WBT_TILE_FIRST: + if (!progressive_tile_read(progressive, FALSE, s, blockType, blockLen, surface, + region, context)) + return -1027; + break; + + case PROGRESSIVE_WBT_TILE_UPGRADE: + if (!progressive_tile_read_upgrade(progressive, s, blockType, blockLen, surface, + region, context)) + return -1032; + break; + default: + WLog_ERR(TAG, "Invalid block type %04" PRIx16 " (%s)", blockType, + rfx_get_progressive_block_type_string(blockType)); + return -1039; + } + + size_t rem = Stream_GetPosition(s); + if ((rem - pos) != blockLen) + { + WLog_Print(progressive->log, WLOG_ERROR, + "Actual block read %" PRIuz " but expected %" PRIu32, rem - pos, blockLen); + return -1040; + } + count++; + } + + end = Stream_GetPosition(s); + if ((end - start) != region->tileDataSize) + { + WLog_Print(progressive->log, WLOG_ERROR, + "Actual total blocks read %" PRIuz " but expected %" PRIu32, end - start, + region->tileDataSize); + return -1041; + } + + if (count != region->numTiles) + { + WLog_Print(progressive->log, WLOG_WARN, + "numTiles inconsistency: actual: %" PRIu32 ", expected: %" PRIu16 "\n", count, + region->numTiles); + return -1044; + } + + { + size_t tcount = 1; + if (progressive->rfx_context->priv->UseThreads) + tcount = region->numTiles; + + work_objects = (PTP_WORK*)winpr_aligned_calloc(tcount, sizeof(PTP_WORK), 32); + if (!work_objects) + return -1; + } + + params = (PROGRESSIVE_TILE_PROCESS_WORK_PARAM*)winpr_aligned_calloc( + region->numTiles, sizeof(PROGRESSIVE_TILE_PROCESS_WORK_PARAM), 32); + if (!params) + { + winpr_aligned_free(work_objects); + return -1; + } + + for (UINT32 index = 0; index < region->numTiles; index++) + { + RFX_PROGRESSIVE_TILE* tile = region->tiles[index]; + PROGRESSIVE_TILE_PROCESS_WORK_PARAM* param = ¶ms[index]; + param->progressive = progressive; + param->region = region; + param->context = context; + param->tile = tile; + + if (progressive->rfx_context->priv->UseThreads) + { + if (!(work_objects[index] = CreateThreadpoolWork( + progressive_process_tiles_tile_work_callback, (void*)¶ms[index], + &progressive->rfx_context->priv->ThreadPoolEnv))) + { + WLog_ERR(TAG, "CreateThreadpoolWork failed."); + status = -1; + break; + } + + SubmitThreadpoolWork(work_objects[index]); + close_cnt = index + 1; + } + else + { + progressive_process_tiles_tile_work_callback(0, ¶ms[index], 0); + } + + if (status < 0) + { + WLog_Print(progressive->log, WLOG_ERROR, "Failed to decompress %s at %" PRIu16, + rfx_get_progressive_block_type_string(tile->blockType), index); + goto fail; + } + } + + if (status < 0) + WLog_Print(progressive->log, WLOG_ERROR, + "Failed to create ThreadpoolWork for tile %" PRIu16, index); + + if (progressive->rfx_context->priv->UseThreads) + { + for (UINT32 index = 0; index < close_cnt; index++) + { + WaitForThreadpoolWorkCallbacks(work_objects[index], FALSE); + CloseThreadpoolWork(work_objects[index]); + } + } + +fail: + winpr_aligned_free(work_objects); + winpr_aligned_free(params); + + if (status < 0) + return -1; + + return (SSIZE_T)(end - start); +} + +static INLINE SSIZE_T progressive_wb_sync(PROGRESSIVE_CONTEXT* progressive, wStream* s, + UINT16 blockType, UINT32 blockLen) +{ + const UINT32 magic = 0xCACCACCA; + const UINT16 version = 0x0100; + PROGRESSIVE_BLOCK_SYNC sync; + + sync.blockType = blockType; + sync.blockLen = blockLen; + + if (sync.blockLen != 12) + { + WLog_Print(progressive->log, WLOG_ERROR, + "PROGRESSIVE_BLOCK_SYNC::blockLen = 0x%08" PRIx32 " != 0x%08" PRIx32, + sync.blockLen, 12); + return -1005; + } + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 6)) + return -1004; + +#if defined(WITH_DEBUG_CODECS) + WLog_Print(progressive->log, WLOG_DEBUG, "ProgressiveSync"); +#endif + + Stream_Read_UINT32(s, sync.magic); + Stream_Read_UINT16(s, sync.version); + + if (sync.magic != magic) + { + WLog_Print(progressive->log, WLOG_ERROR, + "PROGRESSIVE_BLOCK_SYNC::magic = 0x%08" PRIx32 " != 0x%08" PRIx32, sync.magic, + magic); + return -1005; + } + + if (sync.version != 0x0100) + { + WLog_Print(progressive->log, WLOG_ERROR, + "PROGRESSIVE_BLOCK_SYNC::version = 0x%04" PRIx16 " != 0x%04" PRIu16, + sync.version, version); + return -1006; + } + + if ((progressive->state & FLAG_WBT_SYNC) != 0) + WLog_WARN(TAG, "Duplicate PROGRESSIVE_BLOCK_SYNC, ignoring"); + + progressive->state |= FLAG_WBT_SYNC; + return 0; +} + +static INLINE SSIZE_T progressive_wb_frame_begin(PROGRESSIVE_CONTEXT* progressive, wStream* s, + UINT16 blockType, UINT32 blockLen) +{ + PROGRESSIVE_BLOCK_FRAME_BEGIN frameBegin; + + frameBegin.blockType = blockType; + frameBegin.blockLen = blockLen; + + if (frameBegin.blockLen != 12) + { + WLog_Print(progressive->log, WLOG_ERROR, + " RFX_PROGRESSIVE_FRAME_BEGIN::blockLen = 0x%08" PRIx32 " != 0x%08" PRIx32, + frameBegin.blockLen, 12); + return -1005; + } + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 6)) + return -1007; + + Stream_Read_UINT32(s, frameBegin.frameIndex); + Stream_Read_UINT16(s, frameBegin.regionCount); + +#if defined(WITH_DEBUG_CODECS) + WLog_Print(progressive->log, WLOG_DEBUG, + "ProgressiveFrameBegin: frameIndex: %" PRIu32 " regionCount: %" PRIu16 "", + frameBegin.frameIndex, frameBegin.regionCount); +#endif + + /** + * If the number of elements specified by the regionCount field is + * larger than the actual number of elements in the regions field, + * the decoder SHOULD ignore this inconsistency. + */ + + if ((progressive->state & FLAG_WBT_FRAME_BEGIN) != 0) + { + WLog_ERR(TAG, "Duplicate RFX_PROGRESSIVE_FRAME_BEGIN in stream, this is not allowed!"); + return -1008; + } + + if ((progressive->state & FLAG_WBT_FRAME_END) != 0) + { + WLog_ERR(TAG, "RFX_PROGRESSIVE_FRAME_BEGIN after RFX_PROGRESSIVE_FRAME_END in stream, this " + "is not allowed!"); + return -1008; + } + + progressive->state |= FLAG_WBT_FRAME_BEGIN; + return 0; +} + +static INLINE SSIZE_T progressive_wb_frame_end(PROGRESSIVE_CONTEXT* progressive, wStream* s, + UINT16 blockType, UINT32 blockLen) +{ + PROGRESSIVE_BLOCK_FRAME_END frameEnd; + + frameEnd.blockType = blockType; + frameEnd.blockLen = blockLen; + + if (frameEnd.blockLen != 6) + { + WLog_Print(progressive->log, WLOG_ERROR, + " RFX_PROGRESSIVE_FRAME_END::blockLen = 0x%08" PRIx32 " != 0x%08" PRIx32, + frameEnd.blockLen, 6); + return -1005; + } + + if (Stream_GetRemainingLength(s) != 0) + { + WLog_Print(progressive->log, WLOG_ERROR, + "ProgressiveFrameEnd short %" PRIuz ", expected %" PRIuz, + Stream_GetRemainingLength(s), 0); + return -1008; + } + +#if defined(WITH_DEBUG_CODECS) + WLog_Print(progressive->log, WLOG_DEBUG, "ProgressiveFrameEnd"); +#endif + + if ((progressive->state & FLAG_WBT_FRAME_BEGIN) == 0) + WLog_WARN(TAG, "RFX_PROGRESSIVE_FRAME_END before RFX_PROGRESSIVE_FRAME_BEGIN, ignoring"); + if ((progressive->state & FLAG_WBT_FRAME_END) != 0) + WLog_WARN(TAG, "Duplicate RFX_PROGRESSIVE_FRAME_END, ignoring"); + + progressive->state |= FLAG_WBT_FRAME_END; + return 0; +} + +static INLINE SSIZE_T progressive_wb_context(PROGRESSIVE_CONTEXT* progressive, wStream* s, + UINT16 blockType, UINT32 blockLen) +{ + PROGRESSIVE_BLOCK_CONTEXT* context = &progressive->context; + context->blockType = blockType; + context->blockLen = blockLen; + + if (context->blockLen != 10) + { + WLog_Print(progressive->log, WLOG_ERROR, + "RFX_PROGRESSIVE_CONTEXT::blockLen = 0x%08" PRIx32 " != 0x%08" PRIx32, + context->blockLen, 10); + return -1005; + } + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 4)) + return -1009; + + Stream_Read_UINT8(s, context->ctxId); + Stream_Read_UINT16(s, context->tileSize); + Stream_Read_UINT8(s, context->flags); + + if (context->ctxId != 0x00) + WLog_WARN(TAG, "RFX_PROGRESSIVE_CONTEXT::ctxId != 0x00: %" PRIu8, context->ctxId); + + if (context->tileSize != 64) + { + WLog_ERR(TAG, "RFX_PROGRESSIVE_CONTEXT::tileSize != 0x40: %" PRIu16, context->tileSize); + return -1010; + } + + if ((progressive->state & FLAG_WBT_FRAME_BEGIN) != 0) + WLog_WARN(TAG, "RFX_PROGRESSIVE_CONTEXT received after RFX_PROGRESSIVE_FRAME_BEGIN"); + if ((progressive->state & FLAG_WBT_FRAME_END) != 0) + WLog_WARN(TAG, "RFX_PROGRESSIVE_CONTEXT received after RFX_PROGRESSIVE_FRAME_END"); + if ((progressive->state & FLAG_WBT_CONTEXT) != 0) + WLog_WARN(TAG, "Duplicate RFX_PROGRESSIVE_CONTEXT received, ignoring."); + +#if defined(WITH_DEBUG_CODECS) + WLog_Print(progressive->log, WLOG_DEBUG, "ProgressiveContext: flags: 0x%02" PRIX8 "", + context->flags); +#endif + + progressive->state |= FLAG_WBT_CONTEXT; + return 0; +} + +static INLINE SSIZE_T progressive_wb_read_region_header(PROGRESSIVE_CONTEXT* progressive, + wStream* s, UINT16 blockType, + UINT32 blockLen, + PROGRESSIVE_BLOCK_REGION* region) +{ + SSIZE_T len = 0; + + memset(region, 0, sizeof(PROGRESSIVE_BLOCK_REGION)); + if (!Stream_CheckAndLogRequiredLength(TAG, s, 12)) + return -1011; + + region->blockType = blockType; + region->blockLen = blockLen; + Stream_Read_UINT8(s, region->tileSize); + Stream_Read_UINT16(s, region->numRects); + Stream_Read_UINT8(s, region->numQuant); + Stream_Read_UINT8(s, region->numProgQuant); + Stream_Read_UINT8(s, region->flags); + Stream_Read_UINT16(s, region->numTiles); + Stream_Read_UINT32(s, region->tileDataSize); + + if (region->tileSize != 64) + { + WLog_Print(progressive->log, WLOG_ERROR, + "ProgressiveRegion tile size %" PRIu8 ", expected %" PRIuz, region->tileSize, + 64); + return -1012; + } + + if (region->numRects < 1) + { + WLog_Print(progressive->log, WLOG_ERROR, "ProgressiveRegion missing rect count %" PRIu16, + region->numRects); + return -1013; + } + + if (region->numQuant > 7) + { + WLog_Print(progressive->log, WLOG_ERROR, + "ProgressiveRegion quant count too high %" PRIu8 ", expected < %" PRIuz, + region->numQuant, 7); + return -1014; + } + + len = Stream_GetRemainingLength(s); + if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, s, region->numRects, 8ull)) + { + WLog_Print(progressive->log, WLOG_ERROR, "ProgressiveRegion data short for region->rects"); + return -1015; + } + len -= region->numRects * 8ULL; + + if (len / 5 < region->numQuant) + { + WLog_Print(progressive->log, WLOG_ERROR, "ProgressiveRegion data short for region->cQuant"); + return -1018; + } + len -= region->numQuant * 5ULL; + + if (len / 16 < region->numProgQuant) + { + WLog_Print(progressive->log, WLOG_ERROR, + "ProgressiveRegion data short for region->cProgQuant"); + return -1021; + } + len -= region->numProgQuant * 16ULL; + + if (len < region->tileDataSize * 1ll) + { + WLog_Print(progressive->log, WLOG_ERROR, "ProgressiveRegion data short for region->tiles"); + return -1024; + } + len -= region->tileDataSize; + if (len > 0) + WLog_Print(progressive->log, WLOG_WARN, + "Unused bytes detected, %" PRIuz " bytes not processed", len); + return len; +} + +static INLINE SSIZE_T progressive_wb_skip_region(PROGRESSIVE_CONTEXT* progressive, wStream* s, + UINT16 blockType, UINT32 blockLen) +{ + SSIZE_T rc = 0; + size_t total = 0; + PROGRESSIVE_BLOCK_REGION* region = &progressive->region; + + rc = progressive_wb_read_region_header(progressive, s, blockType, blockLen, region); + if (rc < 0) + return rc; + + total = (region->numRects * 8); + total += (region->numQuant * 5); + total += (region->numProgQuant * 16); + total += region->tileDataSize; + if (!Stream_SafeSeek(s, total)) + return -1111; + + return rc; +} + +static INLINE SSIZE_T progressive_wb_region(PROGRESSIVE_CONTEXT* progressive, wStream* s, + UINT16 blockType, UINT32 blockLen, + PROGRESSIVE_SURFACE_CONTEXT* surface, + PROGRESSIVE_BLOCK_REGION* region) +{ + SSIZE_T rc = -1; + UINT16 boxLeft = 0; + UINT16 boxTop = 0; + UINT16 boxRight = 0; + UINT16 boxBottom = 0; + UINT16 idxLeft = 0; + UINT16 idxTop = 0; + UINT16 idxRight = 0; + UINT16 idxBottom = 0; + const PROGRESSIVE_BLOCK_CONTEXT* context = &progressive->context; + + if ((progressive->state & FLAG_WBT_FRAME_BEGIN) == 0) + { + WLog_WARN(TAG, "RFX_PROGRESSIVE_REGION before RFX_PROGRESSIVE_FRAME_BEGIN, ignoring"); + return progressive_wb_skip_region(progressive, s, blockType, blockLen); + } + if ((progressive->state & FLAG_WBT_FRAME_END) != 0) + { + WLog_WARN(TAG, "RFX_PROGRESSIVE_REGION after RFX_PROGRESSIVE_FRAME_END, ignoring"); + return progressive_wb_skip_region(progressive, s, blockType, blockLen); + } + + progressive->state |= FLAG_WBT_REGION; + + rc = progressive_wb_read_region_header(progressive, s, blockType, blockLen, region); + if (rc < 0) + return rc; + + for (UINT16 index = 0; index < region->numRects; index++) + { + RFX_RECT* rect = &(region->rects[index]); + Stream_Read_UINT16(s, rect->x); + Stream_Read_UINT16(s, rect->y); + Stream_Read_UINT16(s, rect->width); + Stream_Read_UINT16(s, rect->height); + } + + for (BYTE index = 0; index < region->numQuant; index++) + { + RFX_COMPONENT_CODEC_QUANT* quantVal = &(region->quantVals[index]); + progressive_component_codec_quant_read(s, quantVal); + + if (!progressive_rfx_quant_lcmp_greater_equal(quantVal, 6)) + { + WLog_Print(progressive->log, WLOG_ERROR, + "ProgressiveRegion region->cQuant[%" PRIu32 "] < 6", index); + return -1; + } + + if (!progressive_rfx_quant_lcmp_less_equal(quantVal, 15)) + { + WLog_Print(progressive->log, WLOG_ERROR, + "ProgressiveRegion region->cQuant[%" PRIu32 "] > 15", index); + return -1; + } + } + + for (BYTE index = 0; index < region->numProgQuant; index++) + { + RFX_PROGRESSIVE_CODEC_QUANT* quantProgVal = &(region->quantProgVals[index]); + + Stream_Read_UINT8(s, quantProgVal->quality); + + progressive_component_codec_quant_read(s, &(quantProgVal->yQuantValues)); + progressive_component_codec_quant_read(s, &(quantProgVal->cbQuantValues)); + progressive_component_codec_quant_read(s, &(quantProgVal->crQuantValues)); + } + +#if defined(WITH_DEBUG_CODECS) + WLog_Print(progressive->log, WLOG_DEBUG, + "ProgressiveRegion: numRects: %" PRIu16 " numTiles: %" PRIu16 + " tileDataSize: %" PRIu32 " flags: 0x%02" PRIX8 " numQuant: %" PRIu8 + " numProgQuant: %" PRIu8 "", + region->numRects, region->numTiles, region->tileDataSize, region->flags, + region->numQuant, region->numProgQuant); +#endif + + boxLeft = surface->gridWidth; + boxTop = surface->gridHeight; + boxRight = 0; + boxBottom = 0; + + for (UINT16 index = 0; index < region->numRects; index++) + { + RFX_RECT* rect = &(region->rects[index]); + idxLeft = rect->x / 64; + idxTop = rect->y / 64; + idxRight = (rect->x + rect->width + 63) / 64; + idxBottom = (rect->y + rect->height + 63) / 64; + + if (idxLeft < boxLeft) + boxLeft = idxLeft; + + if (idxTop < boxTop) + boxTop = idxTop; + + if (idxRight > boxRight) + boxRight = idxRight; + + if (idxBottom > boxBottom) + boxBottom = idxBottom; + +#if defined(WITH_DEBUG_CODECS) + WLog_Print(progressive->log, WLOG_DEBUG, + "rect[%" PRIu16 "]: x: %" PRIu16 " y: %" PRIu16 " w: %" PRIu16 " h: %" PRIu16 "", + index, rect->x, rect->y, rect->width, rect->height); +#endif + } + + const SSIZE_T res = progressive_process_tiles(progressive, s, region, surface, context); + if (res < 0) + return -1; + return (size_t)rc; +} + +static SSIZE_T progressive_parse_block(PROGRESSIVE_CONTEXT* progressive, wStream* s, + PROGRESSIVE_SURFACE_CONTEXT* surface, + PROGRESSIVE_BLOCK_REGION* region) +{ + UINT16 blockType = 0; + UINT32 blockLen = 0; + SSIZE_T rc = -1; + wStream sub = { 0 }; + + WINPR_ASSERT(progressive); + + if (!Stream_CheckAndLogRequiredLength(TAG, s, 6)) + return -1; + + Stream_Read_UINT16(s, blockType); + Stream_Read_UINT32(s, blockLen); + + if (blockLen < 6) + { + WLog_WARN(TAG, "Invalid blockLen %" PRIu32 ", expected >= 6", blockLen); + return -1; + } + if (!Stream_CheckAndLogRequiredLength(TAG, s, blockLen - 6)) + return -1; + Stream_StaticConstInit(&sub, Stream_Pointer(s), blockLen - 6); + Stream_Seek(s, blockLen - 6); + + switch (blockType) + { + case PROGRESSIVE_WBT_SYNC: + rc = progressive_wb_sync(progressive, &sub, blockType, blockLen); + break; + + case PROGRESSIVE_WBT_FRAME_BEGIN: + rc = progressive_wb_frame_begin(progressive, &sub, blockType, blockLen); + break; + + case PROGRESSIVE_WBT_FRAME_END: + rc = progressive_wb_frame_end(progressive, &sub, blockType, blockLen); + break; + + case PROGRESSIVE_WBT_CONTEXT: + rc = progressive_wb_context(progressive, &sub, blockType, blockLen); + break; + + case PROGRESSIVE_WBT_REGION: + rc = progressive_wb_region(progressive, &sub, blockType, blockLen, surface, region); + break; + + default: + WLog_Print(progressive->log, WLOG_ERROR, "Invalid block type %04" PRIx16, blockType); + return -1; + } + + if (rc < 0) + return -1; + + if (Stream_GetRemainingLength(&sub) > 0) + { + WLog_Print(progressive->log, WLOG_ERROR, + "block len %" PRIu32 " does not match read data %" PRIuz, blockLen, + blockLen - Stream_GetRemainingLength(&sub)); + return -1; + } + + return rc; +} + +INT32 progressive_decompress(PROGRESSIVE_CONTEXT* progressive, const BYTE* pSrcData, UINT32 SrcSize, + BYTE* pDstData, UINT32 DstFormat, UINT32 nDstStep, UINT32 nXDst, + UINT32 nYDst, REGION16* invalidRegion, UINT16 surfaceId, + UINT32 frameId) +{ + INT32 rc = 1; + + WINPR_ASSERT(progressive); + PROGRESSIVE_SURFACE_CONTEXT* surface = progressive_get_surface_data(progressive, surfaceId); + + if (!surface) + { + WLog_Print(progressive->log, WLOG_ERROR, "ProgressiveRegion no surface for %" PRIu16, + surfaceId); + return -1001; + } + + PROGRESSIVE_BLOCK_REGION* region = &progressive->region; + WINPR_ASSERT(region); + + if (surface->frameId != frameId) + { + surface->frameId = frameId; + surface->numUpdatedTiles = 0; + } + + wStream ss = { 0 }; + wStream* s = Stream_StaticConstInit(&ss, pSrcData, SrcSize); + WINPR_ASSERT(s); + + switch (DstFormat) + { + case PIXEL_FORMAT_RGBA32: + case PIXEL_FORMAT_RGBX32: + case PIXEL_FORMAT_BGRA32: + case PIXEL_FORMAT_BGRX32: + progressive->format = DstFormat; + break; + default: + progressive->format = PIXEL_FORMAT_XRGB32; + break; + } + + const size_t start = Stream_GetPosition(s); + progressive->state = 0; /* Set state to not initialized */ + while (Stream_GetRemainingLength(s) > 0) + { + if (progressive_parse_block(progressive, s, surface, region) < 0) + goto fail; + } + + const size_t end = Stream_GetPosition(s); + if ((end - start) != SrcSize) + { + WLog_Print(progressive->log, WLOG_ERROR, + "total block len %" PRIuz " does not match read data %" PRIu32, end - start, + SrcSize); + rc = -1041; + goto fail; + } + + REGION16 clippingRects = { 0 }; + region16_init(&clippingRects); + + for (UINT32 i = 0; i < region->numRects; i++) + { + RECTANGLE_16 clippingRect = { 0 }; + const RFX_RECT* rect = &(region->rects[i]); + + clippingRect.left = (UINT16)nXDst + rect->x; + clippingRect.top = (UINT16)nYDst + rect->y; + clippingRect.right = clippingRect.left + rect->width; + clippingRect.bottom = clippingRect.top + rect->height; + region16_union_rect(&clippingRects, &clippingRects, &clippingRect); + } + + for (UINT32 i = 0; i < surface->numUpdatedTiles; i++) + { + UINT32 nbUpdateRects = 0; + const RECTANGLE_16* updateRects = NULL; + RECTANGLE_16 updateRect = { 0 }; + + WINPR_ASSERT(surface->updatedTileIndices); + const UINT32 index = surface->updatedTileIndices[i]; + + WINPR_ASSERT(index < surface->tilesSize); + RFX_PROGRESSIVE_TILE* tile = surface->tiles[index]; + WINPR_ASSERT(tile); + + updateRect.left = nXDst + tile->x; + updateRect.top = nYDst + tile->y; + updateRect.right = updateRect.left + 64; + updateRect.bottom = updateRect.top + 64; + + REGION16 updateRegion = { 0 }; + region16_init(&updateRegion); + region16_intersect_rect(&updateRegion, &clippingRects, &updateRect); + updateRects = region16_rects(&updateRegion, &nbUpdateRects); + + for (UINT32 j = 0; j < nbUpdateRects; j++) + { + const RECTANGLE_16* rect = &updateRects[j]; + if (rect->left < updateRect.left) + goto fail; + const UINT32 nXSrc = rect->left - updateRect.left; + const UINT32 nYSrc = rect->top - updateRect.top; + const UINT32 width = rect->right - rect->left; + const UINT32 height = rect->bottom - rect->top; + + if (rect->left + width > surface->width) + goto fail; + if (rect->top + height > surface->height) + goto fail; + if (!freerdp_image_copy(pDstData, DstFormat, nDstStep, rect->left, rect->top, width, + height, tile->data, progressive->format, tile->stride, nXSrc, + nYSrc, NULL, FREERDP_KEEP_DST_ALPHA)) + { + rc = -42; + break; + } + + if (invalidRegion) + region16_union_rect(invalidRegion, invalidRegion, rect); + } + + region16_uninit(&updateRegion); + tile->dirty = FALSE; + } + + region16_uninit(&clippingRects); + surface->numUpdatedTiles = 0; +fail: + return rc; +} + +BOOL progressive_rfx_write_message_progressive_simple(PROGRESSIVE_CONTEXT* progressive, wStream* s, + const RFX_MESSAGE* msg) +{ + RFX_CONTEXT* context = NULL; + + WINPR_ASSERT(progressive); + WINPR_ASSERT(s); + WINPR_ASSERT(msg); + context = progressive->rfx_context; + return rfx_write_message_progressive_simple(context, s, msg); +} + +int progressive_compress(PROGRESSIVE_CONTEXT* progressive, const BYTE* pSrcData, UINT32 SrcSize, + UINT32 SrcFormat, UINT32 Width, UINT32 Height, UINT32 ScanLine, + const REGION16* invalidRegion, BYTE** ppDstData, UINT32* pDstSize) +{ + BOOL rc = FALSE; + int res = -6; + wStream* s = NULL; + UINT32 numRects = 0; + UINT32 x = 0; + UINT32 y = 0; + RFX_RECT* rects = NULL; + RFX_MESSAGE* message = NULL; + + if (!progressive || !pSrcData || !ppDstData || !pDstSize) + { + return -1; + } + + if (ScanLine == 0) + { + switch (SrcFormat) + { + case PIXEL_FORMAT_ABGR32: + case PIXEL_FORMAT_ARGB32: + case PIXEL_FORMAT_XBGR32: + case PIXEL_FORMAT_XRGB32: + case PIXEL_FORMAT_BGRA32: + case PIXEL_FORMAT_BGRX32: + case PIXEL_FORMAT_RGBA32: + case PIXEL_FORMAT_RGBX32: + ScanLine = Width * 4; + break; + default: + return -2; + } + } + + if (SrcSize < Height * ScanLine) + return -4; + + if (!invalidRegion) + { + numRects = (Width + 63) / 64; + numRects *= (Height + 63) / 64; + } + else + numRects = region16_n_rects(invalidRegion); + + if (numRects == 0) + return 0; + + if (!Stream_EnsureCapacity(progressive->rects, numRects * sizeof(RFX_RECT))) + return -5; + rects = (RFX_RECT*)Stream_Buffer(progressive->rects); + if (invalidRegion) + { + const RECTANGLE_16* region_rects = region16_rects(invalidRegion, NULL); + for (UINT32 x = 0; x < numRects; x++) + { + const RECTANGLE_16* r = ®ion_rects[x]; + RFX_RECT* rect = &rects[x]; + + rect->x = r->left; + rect->y = r->top; + rect->width = r->right - r->left; + rect->height = r->bottom - r->top; + } + } + else + { + x = 0; + y = 0; + for (UINT32 i = 0; i < numRects; i++) + { + RFX_RECT* r = &rects[i]; + r->x = x; + r->y = y; + r->width = MIN(64, Width - x); + r->height = MIN(64, Height - y); + + if (x + 64 >= Width) + { + y += 64; + x = 0; + } + else + x += 64; + + WINPR_ASSERT(r->x % 64 == 0); + WINPR_ASSERT(r->y % 64 == 0); + WINPR_ASSERT(r->width <= 64); + WINPR_ASSERT(r->height <= 64); + } + } + s = progressive->buffer; + Stream_SetPosition(s, 0); + + progressive->rfx_context->mode = RLGR1; + progressive->rfx_context->width = Width; + progressive->rfx_context->height = Height; + rfx_context_set_pixel_format(progressive->rfx_context, SrcFormat); + message = rfx_encode_message(progressive->rfx_context, rects, numRects, pSrcData, Width, Height, + ScanLine); + if (!message) + { + WLog_ERR(TAG, "failed to encode rfx message"); + goto fail; + } + + rc = progressive_rfx_write_message_progressive_simple(progressive, s, message); + rfx_message_free(progressive->rfx_context, message); + if (!rc) + goto fail; + + const size_t pos = Stream_GetPosition(s); + WINPR_ASSERT(pos <= UINT32_MAX); + *pDstSize = (UINT32)pos; + *ppDstData = Stream_Buffer(s); + res = 1; +fail: + return res; +} + +BOOL progressive_context_reset(PROGRESSIVE_CONTEXT* progressive) +{ + if (!progressive) + return FALSE; + + return TRUE; +} + +PROGRESSIVE_CONTEXT* progressive_context_new(BOOL Compressor) +{ + return progressive_context_new_ex(Compressor, 0); +} + +PROGRESSIVE_CONTEXT* progressive_context_new_ex(BOOL Compressor, UINT32 ThreadingFlags) +{ + PROGRESSIVE_CONTEXT* progressive = + (PROGRESSIVE_CONTEXT*)winpr_aligned_calloc(1, sizeof(PROGRESSIVE_CONTEXT), 32); + + if (!progressive) + return NULL; + + progressive->Compressor = Compressor; + progressive->quantProgValFull.quality = 100; + progressive->log = WLog_Get(TAG); + if (!progressive->log) + goto fail; + progressive->rfx_context = rfx_context_new_ex(Compressor, ThreadingFlags); + if (!progressive->rfx_context) + goto fail; + progressive->buffer = Stream_New(NULL, 1024); + if (!progressive->buffer) + goto fail; + progressive->rects = Stream_New(NULL, 1024); + if (!progressive->rects) + goto fail; + progressive->bufferPool = BufferPool_New(TRUE, (8192 + 32) * 3, 16); + if (!progressive->bufferPool) + goto fail; + progressive->SurfaceContexts = HashTable_New(TRUE); + if (!progressive->SurfaceContexts) + goto fail; + + { + wObject* obj = HashTable_ValueObject(progressive->SurfaceContexts); + WINPR_ASSERT(obj); + obj->fnObjectFree = progressive_surface_context_free; + } + return progressive; +fail: + progressive_context_free(progressive); + return NULL; +} + +void progressive_context_free(PROGRESSIVE_CONTEXT* progressive) +{ + if (!progressive) + return; + + Stream_Free(progressive->buffer, TRUE); + Stream_Free(progressive->rects, TRUE); + rfx_context_free(progressive->rfx_context); + + BufferPool_Free(progressive->bufferPool); + HashTable_Free(progressive->SurfaceContexts); + + winpr_aligned_free(progressive); +} diff --git a/libfreerdp/codec/progressive.h b/libfreerdp/codec/progressive.h new file mode 100644 index 0000000..5c5f2a6 --- /dev/null +++ b/libfreerdp/codec/progressive.h @@ -0,0 +1,221 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * Progressive Codec Bitmap Compression + * + * Copyright 2017 Armin Novak <anovak@thincast.com> + * Copyright 2017 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef INTERNAL_CODEC_PROGRESSIVE_H +#define INTERNAL_CODEC_PROGRESSIVE_H + +#include <winpr/wlog.h> +#include <winpr/collections.h> + +#include <freerdp/codec/rfx.h> + +#define RFX_SUBBAND_DIFFING 0x01 + +#define RFX_TILE_DIFFERENCE 0x01 + +#define RFX_DWT_REDUCE_EXTRAPOLATE 0x01 + +typedef struct +{ + BYTE LL3; + BYTE HL3; + BYTE LH3; + BYTE HH3; + BYTE HL2; + BYTE LH2; + BYTE HH2; + BYTE HL1; + BYTE LH1; + BYTE HH1; +} RFX_COMPONENT_CODEC_QUANT; + +typedef struct +{ + BYTE quality; + RFX_COMPONENT_CODEC_QUANT yQuantValues; + RFX_COMPONENT_CODEC_QUANT cbQuantValues; + RFX_COMPONENT_CODEC_QUANT crQuantValues; +} RFX_PROGRESSIVE_CODEC_QUANT; + +typedef struct +{ + UINT16 blockType; + UINT32 blockLen; +} PROGRESSIVE_BLOCK; + +typedef struct +{ + UINT16 blockType; + UINT32 blockLen; + + UINT32 magic; + UINT16 version; +} PROGRESSIVE_BLOCK_SYNC; + +typedef struct +{ + UINT16 blockType; + UINT32 blockLen; + + BYTE ctxId; + UINT16 tileSize; + BYTE flags; +} PROGRESSIVE_BLOCK_CONTEXT; + +typedef struct +{ + UINT16 blockType; + UINT32 blockLen; + + BYTE quantIdxY; + BYTE quantIdxCb; + BYTE quantIdxCr; + UINT16 xIdx; + UINT16 yIdx; + + BYTE flags; + BYTE quality; + BOOL dirty; + + UINT16 yLen; + UINT16 cbLen; + UINT16 crLen; + UINT16 tailLen; + const BYTE* yData; + const BYTE* cbData; + const BYTE* crData; + const BYTE* tailData; + + UINT16 ySrlLen; + UINT16 yRawLen; + UINT16 cbSrlLen; + UINT16 cbRawLen; + UINT16 crSrlLen; + UINT16 crRawLen; + const BYTE* ySrlData; + const BYTE* yRawData; + const BYTE* cbSrlData; + const BYTE* cbRawData; + const BYTE* crSrlData; + const BYTE* crRawData; + + UINT32 x; + UINT32 y; + UINT32 width; + UINT32 height; + UINT32 stride; + + BYTE* data; + BYTE* current; + + UINT16 pass; + BYTE* sign; + + RFX_COMPONENT_CODEC_QUANT yBitPos; + RFX_COMPONENT_CODEC_QUANT cbBitPos; + RFX_COMPONENT_CODEC_QUANT crBitPos; + RFX_COMPONENT_CODEC_QUANT yQuant; + RFX_COMPONENT_CODEC_QUANT cbQuant; + RFX_COMPONENT_CODEC_QUANT crQuant; + RFX_COMPONENT_CODEC_QUANT yProgQuant; + RFX_COMPONENT_CODEC_QUANT cbProgQuant; + RFX_COMPONENT_CODEC_QUANT crProgQuant; +} RFX_PROGRESSIVE_TILE; + +typedef struct +{ + UINT16 blockType; + UINT32 blockLen; + + BYTE tileSize; + UINT16 numRects; + BYTE numQuant; + BYTE numProgQuant; + BYTE flags; + UINT16 numTiles; + UINT16 usedTiles; + UINT32 tileDataSize; + RFX_RECT rects[0x10000]; + RFX_COMPONENT_CODEC_QUANT quantVals[0x100]; + RFX_PROGRESSIVE_CODEC_QUANT quantProgVals[0x100]; + RFX_PROGRESSIVE_TILE* tiles[0x10000]; +} PROGRESSIVE_BLOCK_REGION; + +typedef struct +{ + UINT16 blockType; + UINT32 blockLen; + + UINT32 frameIndex; + UINT16 regionCount; + PROGRESSIVE_BLOCK_REGION* regions; +} PROGRESSIVE_BLOCK_FRAME_BEGIN; + +typedef struct +{ + UINT16 blockType; + UINT32 blockLen; +} PROGRESSIVE_BLOCK_FRAME_END; + +typedef struct +{ + UINT16 id; + UINT32 width; + UINT32 height; + UINT32 gridWidth; + UINT32 gridHeight; + UINT32 gridSize; + RFX_PROGRESSIVE_TILE** tiles; + size_t tilesSize; + UINT32 frameId; + UINT32 numUpdatedTiles; + UINT32* updatedTileIndices; +} PROGRESSIVE_SURFACE_CONTEXT; + +typedef enum +{ + FLAG_WBT_SYNC = 0x01, + FLAG_WBT_FRAME_BEGIN = 0x02, + FLAG_WBT_FRAME_END = 0x04, + FLAG_WBT_CONTEXT = 0x08, + FLAG_WBT_REGION = 0x10 +} WBT_STATE_FLAG; + +struct S_PROGRESSIVE_CONTEXT +{ + BOOL Compressor; + + wBufferPool* bufferPool; + + UINT32 format; + UINT32 state; + + PROGRESSIVE_BLOCK_CONTEXT context; + PROGRESSIVE_BLOCK_REGION region; + RFX_PROGRESSIVE_CODEC_QUANT quantProgValFull; + + wHashTable* SurfaceContexts; + wLog* log; + wStream* buffer; + wStream* rects; + RFX_CONTEXT* rfx_context; +}; + +#endif /* INTERNAL_CODEC_PROGRESSIVE_H */ diff --git a/libfreerdp/codec/region.c b/libfreerdp/codec/region.c new file mode 100644 index 0000000..ba7f3d2 --- /dev/null +++ b/libfreerdp/codec/region.c @@ -0,0 +1,820 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Thincast Technologies GmbH + * Copyright 2014 Hardening <contact@hardening-consulting.com> + * Copyright 2017 Armin Novak <armin.novak@thincast.com> + * Copyright 2017 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/assert.h> +#include <winpr/memory.h> +#include <freerdp/log.h> +#include <freerdp/codec/region.h> + +#define TAG FREERDP_TAG("codec") + +/* + * The functions in this file implement the Region abstraction largely inspired from + * pixman library. The following comment is taken from the pixman code. + * + * A Region is simply a set of disjoint(non-overlapping) rectangles, plus an + * "extent" rectangle which is the smallest single rectangle that contains all + * the non-overlapping rectangles. + * + * A Region is implemented as a "y-x-banded" array of rectangles. This array + * imposes two degrees of order. First, all rectangles are sorted by top side + * y coordinate first (y1), and then by left side x coordinate (x1). + * + * Furthermore, the rectangles are grouped into "bands". Each rectangle in a + * band has the same top y coordinate (y1), and each has the same bottom y + * coordinate (y2). Thus all rectangles in a band differ only in their left + * and right side (x1 and x2). Bands are implicit in the array of rectangles: + * there is no separate list of band start pointers. + * + * The y-x band representation does not minimize rectangles. In particular, + * if a rectangle vertically crosses a band (the rectangle has scanlines in + * the y1 to y2 area spanned by the band), then the rectangle may be broken + * down into two or more smaller rectangles stacked one atop the other. + * + * ----------- ----------- + * | | | | band 0 + * | | -------- ----------- -------- + * | | | | in y-x banded | | | | band 1 + * | | | | form is | | | | + * ----------- | | ----------- -------- + * | | | | band 2 + * -------- -------- + * + * An added constraint on the rectangles is that they must cover as much + * horizontal area as possible: no two rectangles within a band are allowed + * to touch. + * + * Whenever possible, bands will be merged together to cover a greater vertical + * distance (and thus reduce the number of rectangles). Two bands can be merged + * only if the bottom of one touches the top of the other and they have + * rectangles in the same places (of the same width, of course). + */ + +struct S_REGION16_DATA +{ + long size; + long nbRects; +}; + +static REGION16_DATA empty_region = { 0, 0 }; + +void region16_init(REGION16* region) +{ + WINPR_ASSERT(region); + ZeroMemory(region, sizeof(REGION16)); + region->data = &empty_region; +} + +int region16_n_rects(const REGION16* region) +{ + WINPR_ASSERT(region); + WINPR_ASSERT(region->data); + return region->data->nbRects; +} + +const RECTANGLE_16* region16_rects(const REGION16* region, UINT32* nbRects) +{ + REGION16_DATA* data = NULL; + + if (nbRects) + *nbRects = 0; + + if (!region) + return NULL; + + data = region->data; + + if (!data) + return NULL; + + if (nbRects) + *nbRects = data->nbRects; + + return (RECTANGLE_16*)(data + 1); +} + +static INLINE RECTANGLE_16* region16_rects_noconst(REGION16* region) +{ + REGION16_DATA* data = NULL; + data = region->data; + + if (!data) + return NULL; + + return (RECTANGLE_16*)(&data[1]); +} + +const RECTANGLE_16* region16_extents(const REGION16* region) +{ + if (!region) + return NULL; + + return ®ion->extents; +} + +static RECTANGLE_16* region16_extents_noconst(REGION16* region) +{ + if (!region) + return NULL; + + return ®ion->extents; +} + +BOOL rectangle_is_empty(const RECTANGLE_16* rect) +{ + /* A rectangle with width <= 0 or height <= 0 should be regarded + * as empty. + */ + return ((rect->left >= rect->right) || (rect->top >= rect->bottom)) ? TRUE : FALSE; +} + +BOOL region16_is_empty(const REGION16* region) +{ + WINPR_ASSERT(region); + WINPR_ASSERT(region->data); + return (region->data->nbRects == 0); +} + +BOOL rectangles_equal(const RECTANGLE_16* r1, const RECTANGLE_16* r2) +{ + return ((r1->left == r2->left) && (r1->top == r2->top) && (r1->right == r2->right) && + (r1->bottom == r2->bottom)) + ? TRUE + : FALSE; +} + +BOOL rectangles_intersects(const RECTANGLE_16* r1, const RECTANGLE_16* r2) +{ + RECTANGLE_16 tmp = { 0 }; + return rectangles_intersection(r1, r2, &tmp); +} + +BOOL rectangles_intersection(const RECTANGLE_16* r1, const RECTANGLE_16* r2, RECTANGLE_16* dst) +{ + dst->left = MAX(r1->left, r2->left); + dst->right = MIN(r1->right, r2->right); + dst->top = MAX(r1->top, r2->top); + dst->bottom = MIN(r1->bottom, r2->bottom); + return (dst->left < dst->right) && (dst->top < dst->bottom); +} + +void region16_clear(REGION16* region) +{ + WINPR_ASSERT(region); + WINPR_ASSERT(region->data); + + if ((region->data->size > 0) && (region->data != &empty_region)) + free(region->data); + + region->data = &empty_region; + ZeroMemory(®ion->extents, sizeof(region->extents)); +} + +static INLINE REGION16_DATA* allocateRegion(long nbItems) +{ + long allocSize = sizeof(REGION16_DATA) + (nbItems * sizeof(RECTANGLE_16)); + REGION16_DATA* ret = (REGION16_DATA*)malloc(allocSize); + + if (!ret) + return ret; + + ret->size = allocSize; + ret->nbRects = nbItems; + return ret; +} + +BOOL region16_copy(REGION16* dst, const REGION16* src) +{ + WINPR_ASSERT(dst); + WINPR_ASSERT(dst->data); + WINPR_ASSERT(src); + WINPR_ASSERT(src->data); + + if (dst == src) + return TRUE; + + dst->extents = src->extents; + + if ((dst->data->size > 0) && (dst->data != &empty_region)) + free(dst->data); + + if (src->data->size == 0) + dst->data = &empty_region; + else + { + dst->data = allocateRegion(src->data->nbRects); + + if (!dst->data) + return FALSE; + + CopyMemory(dst->data, src->data, src->data->size); + } + + return TRUE; +} + +void region16_print(const REGION16* region) +{ + const RECTANGLE_16* rects = NULL; + UINT32 nbRects = 0; + int currentBandY = -1; + rects = region16_rects(region, &nbRects); + WLog_DBG(TAG, "nrects=%" PRIu32 "", nbRects); + + for (UINT32 i = 0; i < nbRects; i++, rects++) + { + if (rects->top != currentBandY) + { + currentBandY = rects->top; + WLog_DBG(TAG, "band %d: ", currentBandY); + } + + WLog_DBG(TAG, "(%" PRIu16 ",%" PRIu16 "-%" PRIu16 ",%" PRIu16 ")", rects->left, rects->top, + rects->right, rects->bottom); + } +} + +static void region16_copy_band_with_union(RECTANGLE_16* dst, const RECTANGLE_16* src, + const RECTANGLE_16* end, UINT16 newTop, UINT16 newBottom, + const RECTANGLE_16* unionRect, UINT32* dstCounter, + const RECTANGLE_16** srcPtr, RECTANGLE_16** dstPtr) +{ + UINT16 refY = src->top; + const RECTANGLE_16* startOverlap = NULL; + const RECTANGLE_16* endOverlap = NULL; + + /* merges a band with the given rect + * Input: + * unionRect + * | | + * | | + * ==============+===============+================================ + * |Item1| |Item2| |Item3| |Item4| |Item5| Band + * ==============+===============+================================ + * before | overlap | after + * + * Resulting band: + * +-----+ +----------------------+ +-----+ + * |Item1| | Item2 | |Item3| + * +-----+ +----------------------+ +-----+ + * + * We first copy as-is items that are before Item2, the first overlapping + * item. + * Then we find the last one that overlap unionRect to agregate Item2, Item3 + * and Item4 to create Item2. + * Finally Item5 is copied as Item3. + * + * When no unionRect is provided, we skip the two first steps to just copy items + */ + + if (unionRect) + { + /* items before unionRect */ + while ((src < end) && (src->top == refY) && (src->right < unionRect->left)) + { + dst->top = newTop; + dst->bottom = newBottom; + dst->right = src->right; + dst->left = src->left; + src++; + dst++; + *dstCounter += 1; + } + + /* treat items overlapping with unionRect */ + startOverlap = unionRect; + endOverlap = unionRect; + + if ((src < end) && (src->top == refY) && (src->left < unionRect->left)) + startOverlap = src; + + while ((src < end) && (src->top == refY) && (src->right < unionRect->right)) + { + src++; + } + + if ((src < end) && (src->top == refY) && (src->left < unionRect->right)) + { + endOverlap = src; + src++; + } + + dst->bottom = newBottom; + dst->top = newTop; + dst->left = startOverlap->left; + dst->right = endOverlap->right; + dst++; + *dstCounter += 1; + } + + /* treat remaining items on the same band */ + while ((src < end) && (src->top == refY)) + { + dst->top = newTop; + dst->bottom = newBottom; + dst->right = src->right; + dst->left = src->left; + src++; + dst++; + *dstCounter += 1; + } + + if (srcPtr) + *srcPtr = src; + + *dstPtr = dst; +} + +static RECTANGLE_16* next_band(RECTANGLE_16* band1, RECTANGLE_16* endPtr, int* nbItems) +{ + UINT16 refY = band1->top; + *nbItems = 0; + + while ((band1 < endPtr) && (band1->top == refY)) + { + band1++; + *nbItems += 1; + } + + return band1; +} + +static BOOL band_match(const RECTANGLE_16* band1, const RECTANGLE_16* band2, + const RECTANGLE_16* endPtr) +{ + int refBand2 = band2->top; + const RECTANGLE_16* band2Start = band2; + + while ((band1 < band2Start) && (band2 < endPtr) && (band2->top == refBand2)) + { + if ((band1->left != band2->left) || (band1->right != band2->right)) + return FALSE; + + band1++; + band2++; + } + + if (band1 != band2Start) + return FALSE; + + return (band2 == endPtr) || (band2->top != refBand2); +} + +/** compute if the rectangle is fully included in the band + * @param band a pointer on the beginning of the band + * @param endPtr end of the region + * @param rect the rectangle to test + * @return if rect is fully included in an item of the band + */ +static BOOL rectangle_contained_in_band(const RECTANGLE_16* band, const RECTANGLE_16* endPtr, + const RECTANGLE_16* rect) +{ + UINT16 refY = band->top; + + if ((band->top > rect->top) || (rect->bottom > band->bottom)) + return FALSE; + + /* note: as the band is sorted from left to right, once we've seen an item + * that is after rect->left we're sure that the result is False. + */ + while ((band < endPtr) && (band->top == refY) && (band->left <= rect->left)) + { + if (rect->right <= band->right) + return TRUE; + + band++; + } + + return FALSE; +} + +static BOOL region16_simplify_bands(REGION16* region) +{ + /** Simplify consecutive bands that touch and have the same items + * + * ==================== ==================== + * | 1 | | 2 | | | | | + * ==================== | | | | + * | 1 | | 2 | ====> | 1 | | 2 | + * ==================== | | | | + * | 1 | | 2 | | | | | + * ==================== ==================== + * + */ + RECTANGLE_16* endBand = NULL; + int nbRects = 0; + int finalNbRects = 0; + int bandItems = 0; + int toMove = 0; + finalNbRects = nbRects = region16_n_rects(region); + + if (nbRects < 2) + return TRUE; + + RECTANGLE_16* band1 = region16_rects_noconst(region); + RECTANGLE_16* endPtr = band1 + nbRects; + + do + { + RECTANGLE_16* band2 = next_band(band1, endPtr, &bandItems); + + if (band2 == endPtr) + break; + + if ((band1->bottom == band2->top) && band_match(band1, band2, endPtr)) + { + /* adjust the bottom of band1 items */ + RECTANGLE_16* tmp = band1; + + while (tmp < band2) + { + tmp->bottom = band2->bottom; + tmp++; + } + + /* override band2, we don't move band1 pointer as the band after band2 + * may be merged too */ + endBand = band2 + bandItems; + toMove = (endPtr - endBand) * sizeof(RECTANGLE_16); + + if (toMove) + MoveMemory(band2, endBand, toMove); + + finalNbRects -= bandItems; + endPtr -= bandItems; + } + else + { + band1 = band2; + } + } while (TRUE); + + if (finalNbRects != nbRects) + { + size_t allocSize = sizeof(REGION16_DATA) + (finalNbRects * sizeof(RECTANGLE_16)); + REGION16_DATA* data = realloc(region->data, allocSize); + if (!data) + free(region->data); + region->data = data; + + if (!region->data) + { + region->data = &empty_region; + return FALSE; + } + + region->data->nbRects = finalNbRects; + region->data->size = allocSize; + } + + return TRUE; +} + +BOOL region16_union_rect(REGION16* dst, const REGION16* src, const RECTANGLE_16* rect) +{ + const RECTANGLE_16* srcExtents = NULL; + RECTANGLE_16* dstExtents = NULL; + const RECTANGLE_16* currentBand = NULL; + const RECTANGLE_16* endSrcRect = NULL; + const RECTANGLE_16* nextBand = NULL; + REGION16_DATA* newItems = NULL; + REGION16_DATA* tmpItems = NULL; + RECTANGLE_16* dstRect = NULL; + UINT32 usedRects = 0; + UINT32 srcNbRects = 0; + UINT16 topInterBand = 0; + WINPR_ASSERT(src); + WINPR_ASSERT(dst); + srcExtents = region16_extents(src); + dstExtents = region16_extents_noconst(dst); + + if (!region16_n_rects(src)) + { + /* source is empty, so the union is rect */ + dst->extents = *rect; + dst->data = allocateRegion(1); + + if (!dst->data) + return FALSE; + + dstRect = region16_rects_noconst(dst); + dstRect->top = rect->top; + dstRect->left = rect->left; + dstRect->right = rect->right; + dstRect->bottom = rect->bottom; + return TRUE; + } + + newItems = allocateRegion((1 + region16_n_rects(src)) * 4); + + if (!newItems) + return FALSE; + + dstRect = (RECTANGLE_16*)(&newItems[1]); + usedRects = 0; + + /* adds the piece of rect that is on the top of src */ + if (rect->top < srcExtents->top) + { + dstRect->top = rect->top; + dstRect->left = rect->left; + dstRect->right = rect->right; + dstRect->bottom = MIN(srcExtents->top, rect->bottom); + usedRects++; + dstRect++; + } + + /* treat possibly overlapping region */ + currentBand = region16_rects(src, &srcNbRects); + endSrcRect = currentBand + srcNbRects; + + while (currentBand < endSrcRect) + { + if ((currentBand->bottom <= rect->top) || (rect->bottom <= currentBand->top) || + rectangle_contained_in_band(currentBand, endSrcRect, rect)) + { + /* no overlap between rect and the band, rect is totally below or totally above + * the current band, or rect is already covered by an item of the band. + * let's copy all the rectangles from this band + +----+ + | | rect (case 1) + +----+ + + ================= + band of srcRect + ================= + +----+ + | | rect (case 2) + +----+ + */ + region16_copy_band_with_union(dstRect, currentBand, endSrcRect, currentBand->top, + currentBand->bottom, NULL, &usedRects, &nextBand, + &dstRect); + topInterBand = rect->top; + } + else + { + /* rect overlaps the band: + | | | | + ====^=================| |==| |=========================== band + | top split | | | | + v | 1 | | 2 | + ^ | | | | +----+ +----+ + | merge zone | | | | | | | 4 | + v +----+ | | | | +----+ + ^ | | | 3 | + | bottom split | | | | + ====v=========================| |==| |=================== + | | | | + + possible cases: + 1) no top split, merge zone then a bottom split. The band will be splitted + in two + 2) not band split, only the merge zone, band merged with rect but not splitted + 3) a top split, the merge zone and no bottom split. The band will be split + in two + 4) a top split, the merge zone and also a bottom split. The band will be + splitted in 3, but the coalesce algorithm may merge the created bands + */ + UINT16 mergeTop = currentBand->top; + UINT16 mergeBottom = currentBand->bottom; + + /* test if we need a top split, case 3 and 4 */ + if (rect->top > currentBand->top) + { + region16_copy_band_with_union(dstRect, currentBand, endSrcRect, currentBand->top, + rect->top, NULL, &usedRects, &nextBand, &dstRect); + mergeTop = rect->top; + } + + /* do the merge zone (all cases) */ + if (rect->bottom < currentBand->bottom) + mergeBottom = rect->bottom; + + region16_copy_band_with_union(dstRect, currentBand, endSrcRect, mergeTop, mergeBottom, + rect, &usedRects, &nextBand, &dstRect); + + /* test if we need a bottom split, case 1 and 4 */ + if (rect->bottom < currentBand->bottom) + { + region16_copy_band_with_union(dstRect, currentBand, endSrcRect, mergeBottom, + currentBand->bottom, NULL, &usedRects, &nextBand, + &dstRect); + } + + topInterBand = currentBand->bottom; + } + + /* test if a piece of rect should be inserted as a new band between + * the current band and the next one. band n and n+1 shouldn't touch. + * + * ============================================================== + * band n + * +------+ +------+ + * ===========| rect |====================| |=============== + * | | +------+ | | + * +------+ | rect | | rect | + * +------+ | | + * =======================================| |================ + * +------+ band n+1 + * =============================================================== + * + */ + if ((nextBand < endSrcRect) && (nextBand->top != currentBand->bottom) && + (rect->bottom > currentBand->bottom) && (rect->top < nextBand->top)) + { + dstRect->right = rect->right; + dstRect->left = rect->left; + dstRect->top = topInterBand; + dstRect->bottom = MIN(nextBand->top, rect->bottom); + dstRect++; + usedRects++; + } + + currentBand = nextBand; + } + + /* adds the piece of rect that is below src */ + if (srcExtents->bottom < rect->bottom) + { + dstRect->top = MAX(srcExtents->bottom, rect->top); + dstRect->left = rect->left; + dstRect->right = rect->right; + dstRect->bottom = rect->bottom; + usedRects++; + dstRect++; + } + + if ((src == dst) && (dst->data != &empty_region)) + free(dst->data); + + dstExtents->top = MIN(rect->top, srcExtents->top); + dstExtents->left = MIN(rect->left, srcExtents->left); + dstExtents->bottom = MAX(rect->bottom, srcExtents->bottom); + dstExtents->right = MAX(rect->right, srcExtents->right); + newItems->size = sizeof(REGION16_DATA) + (usedRects * sizeof(RECTANGLE_16)); + tmpItems = realloc(newItems, newItems->size); + if (!tmpItems) + free(newItems); + newItems = tmpItems; + dst->data = newItems; + + if (!dst->data) + return FALSE; + + dst->data->nbRects = usedRects; + return region16_simplify_bands(dst); +} + +BOOL region16_intersects_rect(const REGION16* src, const RECTANGLE_16* arg2) +{ + const RECTANGLE_16* rect = NULL; + const RECTANGLE_16* endPtr = NULL; + const RECTANGLE_16* srcExtents = NULL; + UINT32 nbRects = 0; + + if (!src || !src->data || !arg2) + return FALSE; + + rect = region16_rects(src, &nbRects); + + if (!nbRects) + return FALSE; + + srcExtents = region16_extents(src); + + if (nbRects == 1) + return rectangles_intersects(srcExtents, arg2); + + if (!rectangles_intersects(srcExtents, arg2)) + return FALSE; + + for (endPtr = rect + nbRects; (rect < endPtr) && (arg2->bottom > rect->top); rect++) + { + if (rectangles_intersects(rect, arg2)) + return TRUE; + } + + return FALSE; +} + +BOOL region16_intersect_rect(REGION16* dst, const REGION16* src, const RECTANGLE_16* rect) +{ + REGION16_DATA* newItems = NULL; + const RECTANGLE_16* srcPtr = NULL; + const RECTANGLE_16* endPtr = NULL; + const RECTANGLE_16* srcExtents = NULL; + RECTANGLE_16* dstPtr = NULL; + UINT32 nbRects = 0; + UINT32 usedRects = 0; + RECTANGLE_16 common; + RECTANGLE_16 newExtents; + WINPR_ASSERT(src); + WINPR_ASSERT(src->data); + srcPtr = region16_rects(src, &nbRects); + + if (!nbRects) + { + region16_clear(dst); + return TRUE; + } + + srcExtents = region16_extents(src); + + if (nbRects == 1) + { + BOOL intersects = rectangles_intersection(srcExtents, rect, &common); + region16_clear(dst); + + if (intersects) + return region16_union_rect(dst, dst, &common); + + return TRUE; + } + + newItems = allocateRegion(nbRects); + + if (!newItems) + return FALSE; + + dstPtr = (RECTANGLE_16*)(&newItems[1]); + usedRects = 0; + ZeroMemory(&newExtents, sizeof(newExtents)); + + /* accumulate intersecting rectangles, the final region16_simplify_bands() will + * do all the bad job to recreate correct rectangles + */ + for (endPtr = srcPtr + nbRects; (srcPtr < endPtr) && (rect->bottom > srcPtr->top); srcPtr++) + { + if (rectangles_intersection(srcPtr, rect, &common)) + { + *dstPtr = common; + usedRects++; + dstPtr++; + + if (rectangle_is_empty(&newExtents)) + { + /* Check if the existing newExtents is empty. If it is empty, use + * new common directly. We do not need to check common rectangle + * because the rectangles_intersection() ensures that it is not empty. + */ + newExtents = common; + } + else + { + newExtents.top = MIN(common.top, newExtents.top); + newExtents.left = MIN(common.left, newExtents.left); + newExtents.bottom = MAX(common.bottom, newExtents.bottom); + newExtents.right = MAX(common.right, newExtents.right); + } + } + } + + newItems->nbRects = usedRects; + newItems->size = sizeof(REGION16_DATA) + (usedRects * sizeof(RECTANGLE_16)); + + if ((dst->data->size > 0) && (dst->data != &empty_region)) + free(dst->data); + + dst->data = realloc(newItems, newItems->size); + + if (!dst->data) + { + free(newItems); + return FALSE; + } + + dst->extents = newExtents; + return region16_simplify_bands(dst); +} + +void region16_uninit(REGION16* region) +{ + WINPR_ASSERT(region); + + if (region->data) + { + if ((region->data->size > 0) && (region->data != &empty_region)) + free(region->data); + + region->data = NULL; + } +} diff --git a/libfreerdp/codec/rfx.c b/libfreerdp/codec/rfx.c new file mode 100644 index 0000000..c83cfd5 --- /dev/null +++ b/libfreerdp/codec/rfx.c @@ -0,0 +1,2411 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX Codec Library + * + * Copyright 2011 Vic Lee + * Copyright 2015 Thincast Technologies GmbH + * Copyright 2015 Norbert Federa <norbert.federa@thincast.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <winpr/assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <winpr/crt.h> +#include <winpr/tchar.h> +#include <winpr/sysinfo.h> +#include <winpr/registry.h> + +#include <freerdp/log.h> +#include <freerdp/settings.h> +#include <freerdp/codec/rfx.h> +#include <freerdp/constants.h> +#include <freerdp/primitives.h> +#include <freerdp/codec/region.h> +#include <freerdp/build-config.h> + +#include "rfx_constants.h" +#include "rfx_types.h" +#include "rfx_decode.h" +#include "rfx_encode.h" +#include "rfx_quantization.h" +#include "rfx_dwt.h" +#include "rfx_rlgr.h" + +#include "rfx_sse2.h" +#include "rfx_neon.h" + +#define TAG FREERDP_TAG("codec") + +#ifndef RFX_INIT_SIMD +#define RFX_INIT_SIMD(_rfx_context) \ + do \ + { \ + } while (0) +#endif + +#define RFX_KEY "Software\\" FREERDP_VENDOR_STRING "\\" FREERDP_PRODUCT_STRING "\\RemoteFX" + +/** + * The quantization values control the compression rate and quality. The value + * range is between 6 and 15. The higher value, the higher compression rate + * and lower quality. + * + * This is the default values being use by the MS RDP server, and we will also + * use it as our default values for the encoder. It can be overrided by setting + * the context->num_quants and context->quants member. + * + * The order of the values are: + * LL3, LH3, HL3, HH3, LH2, HL2, HH2, LH1, HL1, HH1 + */ +static const UINT32 rfx_default_quantization_values[] = { 6, 6, 6, 6, 7, 7, 8, 8, 8, 9 }; + +static INLINE BOOL rfx_write_progressive_tile_simple(RFX_CONTEXT* rfx, wStream* s, + const RFX_TILE* tile); + +static void rfx_profiler_create(RFX_CONTEXT* context) +{ + if (!context || !context->priv) + return; + PROFILER_CREATE(context->priv->prof_rfx_decode_rgb, "rfx_decode_rgb") + PROFILER_CREATE(context->priv->prof_rfx_decode_component, "rfx_decode_component") + PROFILER_CREATE(context->priv->prof_rfx_rlgr_decode, "rfx_rlgr_decode") + PROFILER_CREATE(context->priv->prof_rfx_differential_decode, "rfx_differential_decode") + PROFILER_CREATE(context->priv->prof_rfx_quantization_decode, "rfx_quantization_decode") + PROFILER_CREATE(context->priv->prof_rfx_dwt_2d_decode, "rfx_dwt_2d_decode") + PROFILER_CREATE(context->priv->prof_rfx_ycbcr_to_rgb, "prims->yCbCrToRGB") + PROFILER_CREATE(context->priv->prof_rfx_encode_rgb, "rfx_encode_rgb") + PROFILER_CREATE(context->priv->prof_rfx_encode_component, "rfx_encode_component") + PROFILER_CREATE(context->priv->prof_rfx_rlgr_encode, "rfx_rlgr_encode") + PROFILER_CREATE(context->priv->prof_rfx_differential_encode, "rfx_differential_encode") + PROFILER_CREATE(context->priv->prof_rfx_quantization_encode, "rfx_quantization_encode") + PROFILER_CREATE(context->priv->prof_rfx_dwt_2d_encode, "rfx_dwt_2d_encode") + PROFILER_CREATE(context->priv->prof_rfx_rgb_to_ycbcr, "prims->RGBToYCbCr") + PROFILER_CREATE(context->priv->prof_rfx_encode_format_rgb, "rfx_encode_format_rgb") +} + +static void rfx_profiler_free(RFX_CONTEXT* context) +{ + if (!context || !context->priv) + return; + PROFILER_FREE(context->priv->prof_rfx_decode_rgb) + PROFILER_FREE(context->priv->prof_rfx_decode_component) + PROFILER_FREE(context->priv->prof_rfx_rlgr_decode) + PROFILER_FREE(context->priv->prof_rfx_differential_decode) + PROFILER_FREE(context->priv->prof_rfx_quantization_decode) + PROFILER_FREE(context->priv->prof_rfx_dwt_2d_decode) + PROFILER_FREE(context->priv->prof_rfx_ycbcr_to_rgb) + PROFILER_FREE(context->priv->prof_rfx_encode_rgb) + PROFILER_FREE(context->priv->prof_rfx_encode_component) + PROFILER_FREE(context->priv->prof_rfx_rlgr_encode) + PROFILER_FREE(context->priv->prof_rfx_differential_encode) + PROFILER_FREE(context->priv->prof_rfx_quantization_encode) + PROFILER_FREE(context->priv->prof_rfx_dwt_2d_encode) + PROFILER_FREE(context->priv->prof_rfx_rgb_to_ycbcr) + PROFILER_FREE(context->priv->prof_rfx_encode_format_rgb) +} + +static void rfx_profiler_print(RFX_CONTEXT* context) +{ + if (!context || !context->priv) + return; + + PROFILER_PRINT_HEADER + PROFILER_PRINT(context->priv->prof_rfx_decode_rgb) + PROFILER_PRINT(context->priv->prof_rfx_decode_component) + PROFILER_PRINT(context->priv->prof_rfx_rlgr_decode) + PROFILER_PRINT(context->priv->prof_rfx_differential_decode) + PROFILER_PRINT(context->priv->prof_rfx_quantization_decode) + PROFILER_PRINT(context->priv->prof_rfx_dwt_2d_decode) + PROFILER_PRINT(context->priv->prof_rfx_ycbcr_to_rgb) + PROFILER_PRINT(context->priv->prof_rfx_encode_rgb) + PROFILER_PRINT(context->priv->prof_rfx_encode_component) + PROFILER_PRINT(context->priv->prof_rfx_rlgr_encode) + PROFILER_PRINT(context->priv->prof_rfx_differential_encode) + PROFILER_PRINT(context->priv->prof_rfx_quantization_encode) + PROFILER_PRINT(context->priv->prof_rfx_dwt_2d_encode) + PROFILER_PRINT(context->priv->prof_rfx_rgb_to_ycbcr) + PROFILER_PRINT(context->priv->prof_rfx_encode_format_rgb) + PROFILER_PRINT_FOOTER +} + +static void rfx_tile_init(void* obj) +{ + RFX_TILE* tile = (RFX_TILE*)obj; + if (tile) + { + tile->x = 0; + tile->y = 0; + tile->YLen = 0; + tile->YData = NULL; + tile->CbLen = 0; + tile->CbData = NULL; + tile->CrLen = 0; + tile->CrData = NULL; + } +} + +static void* rfx_decoder_tile_new(const void* val) +{ + const size_t size = 4 * 64 * 64; + RFX_TILE* tile = NULL; + WINPR_UNUSED(val); + + if (!(tile = (RFX_TILE*)winpr_aligned_calloc(1, sizeof(RFX_TILE), 32))) + return NULL; + + if (!(tile->data = (BYTE*)winpr_aligned_malloc(size, 16))) + { + winpr_aligned_free(tile); + return NULL; + } + memset(tile->data, 0xff, size); + tile->allocated = TRUE; + return tile; +} + +static void rfx_decoder_tile_free(void* obj) +{ + RFX_TILE* tile = (RFX_TILE*)obj; + + if (tile) + { + if (tile->allocated) + winpr_aligned_free(tile->data); + + winpr_aligned_free(tile); + } +} + +static void* rfx_encoder_tile_new(const void* val) +{ + WINPR_UNUSED(val); + return winpr_aligned_calloc(1, sizeof(RFX_TILE), 32); +} + +static void rfx_encoder_tile_free(void* obj) +{ + winpr_aligned_free(obj); +} + +RFX_CONTEXT* rfx_context_new(BOOL encoder) +{ + return rfx_context_new_ex(encoder, 0); +} + +RFX_CONTEXT* rfx_context_new_ex(BOOL encoder, UINT32 ThreadingFlags) +{ + HKEY hKey = NULL; + LONG status = 0; + DWORD dwType = 0; + DWORD dwSize = 0; + DWORD dwValue = 0; + SYSTEM_INFO sysinfo; + RFX_CONTEXT* context = NULL; + wObject* pool = NULL; + RFX_CONTEXT_PRIV* priv = NULL; + context = (RFX_CONTEXT*)winpr_aligned_calloc(1, sizeof(RFX_CONTEXT), 32); + + if (!context) + return NULL; + + context->encoder = encoder; + context->currentMessage.freeArray = TRUE; + context->priv = priv = (RFX_CONTEXT_PRIV*)winpr_aligned_calloc(1, sizeof(RFX_CONTEXT_PRIV), 32); + + if (!priv) + goto fail; + + priv->log = WLog_Get("com.freerdp.codec.rfx"); + WLog_OpenAppender(priv->log); + priv->TilePool = ObjectPool_New(TRUE); + + if (!priv->TilePool) + goto fail; + + pool = ObjectPool_Object(priv->TilePool); + pool->fnObjectInit = rfx_tile_init; + + if (context->encoder) + { + pool->fnObjectNew = rfx_encoder_tile_new; + pool->fnObjectFree = rfx_encoder_tile_free; + } + else + { + pool->fnObjectNew = rfx_decoder_tile_new; + pool->fnObjectFree = rfx_decoder_tile_free; + } + + /* + * align buffers to 16 byte boundary (needed for SSE/NEON instructions) + * + * y_r_buffer, cb_g_buffer, cr_b_buffer: 64 * 64 * sizeof(INT16) = 8192 (0x2000) + * dwt_buffer: 32 * 32 * 2 * 2 * sizeof(INT16) = 8192, maximum sub-band width is 32 + * + * Additionally we add 32 bytes (16 in front and 16 at the back of the buffer) + * in order to allow optimized functions (SEE, NEON) to read from positions + * that are actually in front/beyond the buffer. Offset calculations are + * performed at the BufferPool_Take function calls in rfx_encode/decode.c. + * + * We then multiply by 3 to use a single, partioned buffer for all 3 channels. + */ + priv->BufferPool = BufferPool_New(TRUE, (8192 + 32) * 3, 16); + + if (!priv->BufferPool) + goto fail; + + if (!(ThreadingFlags & THREADING_FLAGS_DISABLE_THREADS)) + { + priv->UseThreads = TRUE; + + GetNativeSystemInfo(&sysinfo); + priv->MinThreadCount = sysinfo.dwNumberOfProcessors; + priv->MaxThreadCount = 0; + status = RegOpenKeyExA(HKEY_LOCAL_MACHINE, RFX_KEY, 0, KEY_READ | KEY_WOW64_64KEY, &hKey); + + if (status == ERROR_SUCCESS) + { + dwSize = sizeof(dwValue); + + if (RegQueryValueEx(hKey, _T("UseThreads"), NULL, &dwType, (BYTE*)&dwValue, &dwSize) == + ERROR_SUCCESS) + priv->UseThreads = dwValue ? 1 : 0; + + if (RegQueryValueEx(hKey, _T("MinThreadCount"), NULL, &dwType, (BYTE*)&dwValue, + &dwSize) == ERROR_SUCCESS) + priv->MinThreadCount = dwValue; + + if (RegQueryValueEx(hKey, _T("MaxThreadCount"), NULL, &dwType, (BYTE*)&dwValue, + &dwSize) == ERROR_SUCCESS) + priv->MaxThreadCount = dwValue; + + RegCloseKey(hKey); + } + } + else + { + priv->UseThreads = FALSE; + } + + if (priv->UseThreads) + { + /* Call primitives_get here in order to avoid race conditions when using primitives_get */ + /* from multiple threads. This call will initialize all function pointers correctly */ + /* before any decoding threads are started */ + primitives_get(); + priv->ThreadPool = CreateThreadpool(NULL); + + if (!priv->ThreadPool) + goto fail; + + InitializeThreadpoolEnvironment(&priv->ThreadPoolEnv); + SetThreadpoolCallbackPool(&priv->ThreadPoolEnv, priv->ThreadPool); + + if (priv->MinThreadCount) + if (!SetThreadpoolThreadMinimum(priv->ThreadPool, priv->MinThreadCount)) + goto fail; + + if (priv->MaxThreadCount) + SetThreadpoolThreadMaximum(priv->ThreadPool, priv->MaxThreadCount); + } + + /* initialize the default pixel format */ + rfx_context_set_pixel_format(context, PIXEL_FORMAT_BGRX32); + /* create profilers for default decoding routines */ + rfx_profiler_create(context); + /* set up default routines */ + context->quantization_decode = rfx_quantization_decode; + context->quantization_encode = rfx_quantization_encode; + context->dwt_2d_decode = rfx_dwt_2d_decode; + context->dwt_2d_extrapolate_decode = rfx_dwt_2d_extrapolate_decode; + context->dwt_2d_encode = rfx_dwt_2d_encode; + context->rlgr_decode = rfx_rlgr_decode; + context->rlgr_encode = rfx_rlgr_encode; + RFX_INIT_SIMD(context); + context->state = RFX_STATE_SEND_HEADERS; + context->expectedDataBlockType = WBT_FRAME_BEGIN; + return context; +fail: + rfx_context_free(context); + return NULL; +} + +void rfx_context_free(RFX_CONTEXT* context) +{ + RFX_CONTEXT_PRIV* priv = NULL; + + if (!context) + return; + + WINPR_ASSERT(NULL != context); + + priv = context->priv; + WINPR_ASSERT(NULL != priv); + WINPR_ASSERT(NULL != priv->TilePool); + WINPR_ASSERT(NULL != priv->BufferPool); + + /* coverity[address_free] */ + rfx_message_free(context, &context->currentMessage); + winpr_aligned_free(context->quants); + rfx_profiler_print(context); + rfx_profiler_free(context); + + if (priv) + { + ObjectPool_Free(priv->TilePool); + if (priv->UseThreads) + { + if (priv->ThreadPool) + CloseThreadpool(priv->ThreadPool); + DestroyThreadpoolEnvironment(&priv->ThreadPoolEnv); + winpr_aligned_free(priv->workObjects); + winpr_aligned_free(priv->tileWorkParams); +#ifdef WITH_PROFILER + WLog_VRB( + TAG, + "WARNING: Profiling results probably unusable with multithreaded RemoteFX codec!"); +#endif + } + + BufferPool_Free(priv->BufferPool); + winpr_aligned_free(priv); + } + winpr_aligned_free(context); +} + +static RFX_TILE* rfx_message_get_tile(RFX_MESSAGE* message, UINT32 index) +{ + WINPR_ASSERT(message); + WINPR_ASSERT(message->tiles); + WINPR_ASSERT(index < message->numTiles); + return message->tiles[index]; +} + +static const RFX_RECT* rfx_message_get_rect_const(const RFX_MESSAGE* message, UINT32 index) +{ + WINPR_ASSERT(message); + WINPR_ASSERT(message->rects); + WINPR_ASSERT(index < message->numRects); + return &message->rects[index]; +} + +static RFX_RECT* rfx_message_get_rect(RFX_MESSAGE* message, UINT32 index) +{ + WINPR_ASSERT(message); + WINPR_ASSERT(message->rects); + WINPR_ASSERT(index < message->numRects); + return &message->rects[index]; +} + +void rfx_context_set_pixel_format(RFX_CONTEXT* context, UINT32 pixel_format) +{ + WINPR_ASSERT(context); + context->pixel_format = pixel_format; + context->bits_per_pixel = FreeRDPGetBitsPerPixel(pixel_format); +} + +UINT32 rfx_context_get_pixel_format(RFX_CONTEXT* context) +{ + WINPR_ASSERT(context); + return context->pixel_format; +} + +void rfx_context_set_palette(RFX_CONTEXT* context, const BYTE* palette) +{ + WINPR_ASSERT(context); + context->palette = palette; +} + +const BYTE* rfx_context_get_palette(RFX_CONTEXT* context) +{ + WINPR_ASSERT(context); + return context->palette; +} + +BOOL rfx_context_reset(RFX_CONTEXT* context, UINT32 width, UINT32 height) +{ + if (!context) + return FALSE; + + context->width = width; + context->height = height; + context->state = RFX_STATE_SEND_HEADERS; + context->expectedDataBlockType = WBT_FRAME_BEGIN; + context->frameIdx = 0; + return TRUE; +} + +static BOOL rfx_process_message_sync(RFX_CONTEXT* context, wStream* s) +{ + UINT32 magic = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + context->decodedHeaderBlocks &= ~RFX_DECODED_SYNC; + + /* RFX_SYNC */ + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 6)) + return FALSE; + + Stream_Read_UINT32(s, magic); /* magic (4 bytes), 0xCACCACCA */ + if (magic != WF_MAGIC) + { + WLog_Print(context->priv->log, WLOG_ERROR, "invalid magic number 0x%08" PRIX32 "", magic); + return FALSE; + } + + Stream_Read_UINT16(s, context->version); /* version (2 bytes), WF_VERSION_1_0 (0x0100) */ + if (context->version != WF_VERSION_1_0) + { + WLog_Print(context->priv->log, WLOG_ERROR, "invalid version number 0x%08" PRIX32 "", + context->version); + return FALSE; + } + + WLog_Print(context->priv->log, WLOG_DEBUG, "version 0x%08" PRIX32 "", context->version); + context->decodedHeaderBlocks |= RFX_DECODED_SYNC; + return TRUE; +} + +static BOOL rfx_process_message_codec_versions(RFX_CONTEXT* context, wStream* s) +{ + BYTE numCodecs = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + context->decodedHeaderBlocks &= ~RFX_DECODED_VERSIONS; + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return FALSE; + + Stream_Read_UINT8(s, numCodecs); /* numCodecs (1 byte), must be set to 0x01 */ + Stream_Read_UINT8(s, context->codec_id); /* codecId (1 byte), must be set to 0x01 */ + Stream_Read_UINT16( + s, context->codec_version); /* version (2 bytes), must be set to WF_VERSION_1_0 (0x0100) */ + + if (numCodecs != 1) + { + WLog_Print(context->priv->log, WLOG_ERROR, "numCodes is 0x%02" PRIX8 " (must be 0x01)", + numCodecs); + return FALSE; + } + + if (context->codec_id != 0x01) + { + WLog_Print(context->priv->log, WLOG_ERROR, "invalid codec id (0x%02" PRIX32 ")", + context->codec_id); + return FALSE; + } + + if (context->codec_version != WF_VERSION_1_0) + { + WLog_Print(context->priv->log, WLOG_ERROR, "invalid codec version (0x%08" PRIX32 ")", + context->codec_version); + return FALSE; + } + + WLog_Print(context->priv->log, WLOG_DEBUG, "id %" PRIu32 " version 0x%" PRIX32 ".", + context->codec_id, context->codec_version); + context->decodedHeaderBlocks |= RFX_DECODED_VERSIONS; + return TRUE; +} + +static BOOL rfx_process_message_channels(RFX_CONTEXT* context, wStream* s) +{ + BYTE channelId = 0; + BYTE numChannels = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + context->decodedHeaderBlocks &= ~RFX_DECODED_CHANNELS; + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 1)) + return FALSE; + + Stream_Read_UINT8(s, numChannels); /* numChannels (1 byte), must bet set to 0x01 */ + + /* In RDVH sessions, numChannels will represent the number of virtual monitors + * configured and does not always be set to 0x01 as [MS-RDPRFX] said. + */ + if (numChannels < 1) + { + WLog_Print(context->priv->log, WLOG_ERROR, "no channels announced"); + return FALSE; + } + + if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(context->priv->log, s, numChannels, 5ull)) + return FALSE; + + /* RFX_CHANNELT */ + Stream_Read_UINT8(s, channelId); /* channelId (1 byte), must be set to 0x00 */ + + if (channelId != 0x00) + { + WLog_Print(context->priv->log, WLOG_ERROR, "channelId:0x%02" PRIX8 ", expected:0x00", + channelId); + return FALSE; + } + + Stream_Read_UINT16(s, context->width); /* width (2 bytes) */ + Stream_Read_UINT16(s, context->height); /* height (2 bytes) */ + + if (!context->width || !context->height) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "invalid channel with/height: %" PRIu16 "x%" PRIu16 "", context->width, + context->height); + return FALSE; + } + + /* Now, only the first monitor can be used, therefore the other channels will be ignored. */ + Stream_Seek(s, 5 * (numChannels - 1)); + WLog_Print(context->priv->log, WLOG_DEBUG, + "numChannels %" PRIu8 " id %" PRIu8 ", %" PRIu16 "x%" PRIu16 ".", numChannels, + channelId, context->width, context->height); + context->decodedHeaderBlocks |= RFX_DECODED_CHANNELS; + return TRUE; +} + +static BOOL rfx_process_message_context(RFX_CONTEXT* context, wStream* s) +{ + BYTE ctxId = 0; + UINT16 tileSize = 0; + UINT16 properties = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + context->decodedHeaderBlocks &= ~RFX_DECODED_CONTEXT; + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 5)) + return FALSE; + + Stream_Read_UINT8(s, ctxId); /* ctxId (1 byte), must be set to 0x00 */ + Stream_Read_UINT16(s, tileSize); /* tileSize (2 bytes), must be set to CT_TILE_64x64 (0x0040) */ + Stream_Read_UINT16(s, properties); /* properties (2 bytes) */ + WLog_Print(context->priv->log, WLOG_DEBUG, + "ctxId %" PRIu8 " tileSize %" PRIu16 " properties 0x%04" PRIX16 ".", ctxId, tileSize, + properties); + context->properties = properties; + context->flags = (properties & 0x0007); + + if (context->flags == CODEC_MODE) + { + WLog_Print(context->priv->log, WLOG_DEBUG, "codec is in image mode."); + } + else + { + WLog_Print(context->priv->log, WLOG_DEBUG, "codec is in video mode."); + } + + switch ((properties & 0x1E00) >> 9) + { + case CLW_ENTROPY_RLGR1: + context->mode = RLGR1; + WLog_Print(context->priv->log, WLOG_DEBUG, "RLGR1."); + break; + + case CLW_ENTROPY_RLGR3: + context->mode = RLGR3; + WLog_Print(context->priv->log, WLOG_DEBUG, "RLGR3."); + break; + + default: + WLog_Print(context->priv->log, WLOG_ERROR, "unknown RLGR algorithm."); + return FALSE; + } + + context->decodedHeaderBlocks |= RFX_DECODED_CONTEXT; + return TRUE; +} + +static BOOL rfx_process_message_frame_begin(RFX_CONTEXT* context, RFX_MESSAGE* message, wStream* s, + UINT16* pExpectedBlockType) +{ + UINT32 frameIdx = 0; + UINT16 numRegions = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(message); + WINPR_ASSERT(pExpectedBlockType); + + if (*pExpectedBlockType != WBT_FRAME_BEGIN) + { + WLog_Print(context->priv->log, WLOG_ERROR, "message unexpected wants WBT_FRAME_BEGIN"); + return FALSE; + } + + *pExpectedBlockType = WBT_REGION; + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 6)) + return FALSE; + + Stream_Read_UINT32( + s, frameIdx); /* frameIdx (4 bytes), if codec is in video mode, must be ignored */ + Stream_Read_UINT16(s, numRegions); /* numRegions (2 bytes) */ + WLog_Print(context->priv->log, WLOG_DEBUG, + "RFX_FRAME_BEGIN: frameIdx: %" PRIu32 " numRegions: %" PRIu16 "", frameIdx, + numRegions); + return TRUE; +} + +static BOOL rfx_process_message_frame_end(RFX_CONTEXT* context, RFX_MESSAGE* message, wStream* s, + UINT16* pExpectedBlockType) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(message); + WINPR_ASSERT(s); + WINPR_ASSERT(pExpectedBlockType); + + if (*pExpectedBlockType != WBT_FRAME_END) + { + WLog_Print(context->priv->log, WLOG_ERROR, "message unexpected, wants WBT_FRAME_END"); + return FALSE; + } + + *pExpectedBlockType = WBT_FRAME_BEGIN; + WLog_Print(context->priv->log, WLOG_DEBUG, "RFX_FRAME_END"); + return TRUE; +} + +static BOOL rfx_resize_rects(RFX_MESSAGE* message) +{ + WINPR_ASSERT(message); + + RFX_RECT* tmpRects = + winpr_aligned_recalloc(message->rects, message->numRects, sizeof(RFX_RECT), 32); + if (!tmpRects) + return FALSE; + message->rects = tmpRects; + return TRUE; +} + +static BOOL rfx_process_message_region(RFX_CONTEXT* context, RFX_MESSAGE* message, wStream* s, + UINT16* pExpectedBlockType) +{ + UINT16 regionType = 0; + UINT16 numTileSets = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(message); + WINPR_ASSERT(pExpectedBlockType); + + if (*pExpectedBlockType != WBT_REGION) + { + WLog_Print(context->priv->log, WLOG_ERROR, "message unexpected wants WBT_REGION"); + return FALSE; + } + + *pExpectedBlockType = WBT_EXTENSION; + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 3)) + return FALSE; + + Stream_Seek_UINT8(s); /* regionFlags (1 byte) */ + Stream_Read_UINT16(s, message->numRects); /* numRects (2 bytes) */ + + if (message->numRects < 1) + { + /* + If numRects is zero the decoder must generate a rectangle with + coordinates (0, 0, width, height). + See [MS-RDPRFX] (revision >= 17.0) 2.2.2.3.3 TS_RFX_REGION + https://msdn.microsoft.com/en-us/library/ff635233.aspx + */ + message->numRects = 1; + if (!rfx_resize_rects(message)) + return FALSE; + + message->rects->x = 0; + message->rects->y = 0; + message->rects->width = context->width; + message->rects->height = context->height; + return TRUE; + } + + if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(context->priv->log, s, message->numRects, 8ull)) + return FALSE; + + if (!rfx_resize_rects(message)) + return FALSE; + + /* rects */ + for (UINT16 i = 0; i < message->numRects; i++) + { + RFX_RECT* rect = rfx_message_get_rect(message, i); + /* RFX_RECT */ + Stream_Read_UINT16(s, rect->x); /* x (2 bytes) */ + Stream_Read_UINT16(s, rect->y); /* y (2 bytes) */ + Stream_Read_UINT16(s, rect->width); /* width (2 bytes) */ + Stream_Read_UINT16(s, rect->height); /* height (2 bytes) */ + WLog_Print(context->priv->log, WLOG_DEBUG, + "rect %" PRIu16 " (x,y=%" PRIu16 ",%" PRIu16 " w,h=%" PRIu16 " %" PRIu16 ").", i, + rect->x, rect->y, rect->width, rect->height); + } + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 4)) + return FALSE; + + Stream_Read_UINT16(s, regionType); /*regionType (2 bytes): MUST be set to CBT_REGION (0xCAC1)*/ + Stream_Read_UINT16(s, numTileSets); /*numTilesets (2 bytes): MUST be set to 0x0001.*/ + + if (regionType != CBT_REGION) + { + WLog_Print(context->priv->log, WLOG_ERROR, "invalid region type 0x%04" PRIX16 "", + regionType); + return TRUE; + } + + if (numTileSets != 0x0001) + { + WLog_Print(context->priv->log, WLOG_ERROR, "invalid number of tilesets (%" PRIu16 ")", + numTileSets); + return FALSE; + } + + return TRUE; +} + +typedef struct +{ + RFX_TILE* tile; + RFX_CONTEXT* context; +} RFX_TILE_PROCESS_WORK_PARAM; + +static void CALLBACK rfx_process_message_tile_work_callback(PTP_CALLBACK_INSTANCE instance, + void* context, PTP_WORK work) +{ + RFX_TILE_PROCESS_WORK_PARAM* param = (RFX_TILE_PROCESS_WORK_PARAM*)context; + WINPR_ASSERT(param); + rfx_decode_rgb(param->context, param->tile, param->tile->data, 64 * 4); +} + +static BOOL rfx_allocate_tiles(RFX_MESSAGE* message, size_t count, BOOL allocOnly) +{ + WINPR_ASSERT(message); + + RFX_TILE** tmpTiles = winpr_aligned_recalloc(message->tiles, count, sizeof(RFX_TILE*), 32); + if (!tmpTiles && (count != 0)) + return FALSE; + + message->tiles = tmpTiles; + if (!allocOnly) + message->numTiles = count; + else + { + WINPR_ASSERT(message->numTiles <= count); + } + message->allocatedTiles = count; + + return TRUE; +} +static BOOL rfx_process_message_tileset(RFX_CONTEXT* context, RFX_MESSAGE* message, wStream* s, + UINT16* pExpectedBlockType) +{ + BOOL rc = 0; + size_t close_cnt = 0; + BYTE quant = 0; + RFX_TILE* tile = NULL; + UINT32* quants = NULL; + UINT16 subtype = 0; + UINT16 numTiles = 0; + UINT32 blockLen = 0; + UINT32 blockType = 0; + UINT32 tilesDataSize = 0; + PTP_WORK* work_objects = NULL; + RFX_TILE_PROCESS_WORK_PARAM* params = NULL; + void* pmem = NULL; + + WINPR_ASSERT(context); + WINPR_ASSERT(context->priv); + WINPR_ASSERT(message); + WINPR_ASSERT(pExpectedBlockType); + + if (*pExpectedBlockType != WBT_EXTENSION) + { + WLog_Print(context->priv->log, WLOG_ERROR, "message unexpected wants a tileset"); + return FALSE; + } + + *pExpectedBlockType = WBT_FRAME_END; + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 14)) + return FALSE; + + Stream_Read_UINT16(s, subtype); /* subtype (2 bytes) must be set to CBT_TILESET (0xCAC2) */ + if (subtype != CBT_TILESET) + { + WLog_Print(context->priv->log, WLOG_ERROR, "invalid subtype, expected CBT_TILESET."); + return FALSE; + } + + Stream_Seek_UINT16(s); /* idx (2 bytes), must be set to 0x0000 */ + Stream_Seek_UINT16(s); /* properties (2 bytes) */ + Stream_Read_UINT8(s, context->numQuant); /* numQuant (1 byte) */ + Stream_Seek_UINT8(s); /* tileSize (1 byte), must be set to 0x40 */ + + if (context->numQuant < 1) + { + WLog_Print(context->priv->log, WLOG_ERROR, "no quantization value."); + return FALSE; + } + + Stream_Read_UINT16(s, numTiles); /* numTiles (2 bytes) */ + if (numTiles < 1) + { + /* Windows Server 2012 (not R2) can send empty tile sets */ + return TRUE; + } + + Stream_Read_UINT32(s, tilesDataSize); /* tilesDataSize (4 bytes) */ + + if (!(pmem = + winpr_aligned_recalloc(context->quants, context->numQuant, 10 * sizeof(UINT32), 32))) + return FALSE; + + quants = context->quants = (UINT32*)pmem; + + /* quantVals */ + if (!Stream_CheckAndLogRequiredLengthOfSizeWLog(context->priv->log, s, context->numQuant, 5ull)) + return FALSE; + + for (size_t i = 0; i < context->numQuant; i++) + { + /* RFX_CODEC_QUANT */ + Stream_Read_UINT8(s, quant); + *quants++ = (quant & 0x0F); + *quants++ = (quant >> 4); + Stream_Read_UINT8(s, quant); + *quants++ = (quant & 0x0F); + *quants++ = (quant >> 4); + Stream_Read_UINT8(s, quant); + *quants++ = (quant & 0x0F); + *quants++ = (quant >> 4); + Stream_Read_UINT8(s, quant); + *quants++ = (quant & 0x0F); + *quants++ = (quant >> 4); + Stream_Read_UINT8(s, quant); + *quants++ = (quant & 0x0F); + *quants++ = (quant >> 4); + WLog_Print(context->priv->log, WLOG_DEBUG, + "quant %d (%" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32 + " %" PRIu32 " %" PRIu32 " %" PRIu32 " %" PRIu32 ").", + i, context->quants[i * 10], context->quants[i * 10 + 1], + context->quants[i * 10 + 2], context->quants[i * 10 + 3], + context->quants[i * 10 + 4], context->quants[i * 10 + 5], + context->quants[i * 10 + 6], context->quants[i * 10 + 7], + context->quants[i * 10 + 8], context->quants[i * 10 + 9]); + } + + for (size_t i = 0; i < message->numTiles; i++) + { + ObjectPool_Return(context->priv->TilePool, message->tiles[i]); + message->tiles[i] = NULL; + } + + if (!rfx_allocate_tiles(message, numTiles, FALSE)) + return FALSE; + + if (context->priv->UseThreads) + { + work_objects = (PTP_WORK*)winpr_aligned_calloc(message->numTiles, sizeof(PTP_WORK), 32); + params = (RFX_TILE_PROCESS_WORK_PARAM*)winpr_aligned_recalloc( + NULL, message->numTiles, sizeof(RFX_TILE_PROCESS_WORK_PARAM), 32); + + if (!work_objects) + { + winpr_aligned_free(params); + return FALSE; + } + + if (!params) + { + winpr_aligned_free(work_objects); + return FALSE; + } + } + + /* tiles */ + close_cnt = 0; + rc = FALSE; + + if (Stream_GetRemainingLength(s) >= tilesDataSize) + { + rc = TRUE; + for (size_t i = 0; i < message->numTiles; i++) + { + wStream subBuffer; + wStream* sub = NULL; + + if (!(tile = (RFX_TILE*)ObjectPool_Take(context->priv->TilePool))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "RfxMessageTileSet failed to get tile from object pool"); + rc = FALSE; + break; + } + + message->tiles[i] = tile; + + /* RFX_TILE */ + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 6)) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "RfxMessageTileSet packet too small to read tile %d/%" PRIu16 "", i, + message->numTiles); + rc = FALSE; + break; + } + + sub = Stream_StaticInit(&subBuffer, Stream_Pointer(s), Stream_GetRemainingLength(s)); + Stream_Read_UINT16( + sub, blockType); /* blockType (2 bytes), must be set to CBT_TILE (0xCAC3) */ + Stream_Read_UINT32(sub, blockLen); /* blockLen (4 bytes) */ + + if (!Stream_SafeSeek(s, blockLen)) + { + rc = FALSE; + break; + } + if ((blockLen < 6 + 13) || + (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, sub, blockLen - 6))) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "RfxMessageTileSet not enough bytes to read tile %d/%" PRIu16 + " with blocklen=%" PRIu32 "", + i, message->numTiles, blockLen); + rc = FALSE; + break; + } + + if (blockType != CBT_TILE) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "unknown block type 0x%" PRIX32 ", expected CBT_TILE (0xCAC3).", + blockType); + rc = FALSE; + break; + } + + Stream_Read_UINT8(sub, tile->quantIdxY); /* quantIdxY (1 byte) */ + Stream_Read_UINT8(sub, tile->quantIdxCb); /* quantIdxCb (1 byte) */ + Stream_Read_UINT8(sub, tile->quantIdxCr); /* quantIdxCr (1 byte) */ + if (tile->quantIdxY >= context->numQuant) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "quantIdxY %" PRIu8 " >= numQuant %" PRIu8, tile->quantIdxY, + context->numQuant); + rc = FALSE; + break; + } + if (tile->quantIdxCb >= context->numQuant) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "quantIdxCb %" PRIu8 " >= numQuant %" PRIu8, tile->quantIdxCb, + context->numQuant); + rc = FALSE; + break; + } + if (tile->quantIdxCr >= context->numQuant) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "quantIdxCr %" PRIu8 " >= numQuant %" PRIu8, tile->quantIdxCr, + context->numQuant); + rc = FALSE; + break; + } + + Stream_Read_UINT16(sub, tile->xIdx); /* xIdx (2 bytes) */ + Stream_Read_UINT16(sub, tile->yIdx); /* yIdx (2 bytes) */ + Stream_Read_UINT16(sub, tile->YLen); /* YLen (2 bytes) */ + Stream_Read_UINT16(sub, tile->CbLen); /* CbLen (2 bytes) */ + Stream_Read_UINT16(sub, tile->CrLen); /* CrLen (2 bytes) */ + Stream_GetPointer(sub, tile->YData); + if (!Stream_SafeSeek(sub, tile->YLen)) + { + rc = FALSE; + break; + } + Stream_GetPointer(sub, tile->CbData); + if (!Stream_SafeSeek(sub, tile->CbLen)) + { + rc = FALSE; + break; + } + Stream_GetPointer(sub, tile->CrData); + if (!Stream_SafeSeek(sub, tile->CrLen)) + { + rc = FALSE; + break; + } + tile->x = tile->xIdx * 64; + tile->y = tile->yIdx * 64; + + if (context->priv->UseThreads) + { + if (!params) + { + rc = FALSE; + break; + } + + params[i].context = context; + params[i].tile = message->tiles[i]; + + if (!(work_objects[i] = + CreateThreadpoolWork(rfx_process_message_tile_work_callback, + (void*)¶ms[i], &context->priv->ThreadPoolEnv))) + { + WLog_Print(context->priv->log, WLOG_ERROR, "CreateThreadpoolWork failed."); + rc = FALSE; + break; + } + + SubmitThreadpoolWork(work_objects[i]); + close_cnt = i + 1; + } + else + { + rfx_decode_rgb(context, tile, tile->data, 64 * 4); + } + } + } + + if (context->priv->UseThreads) + { + for (size_t i = 0; i < close_cnt; i++) + { + WaitForThreadpoolWorkCallbacks(work_objects[i], FALSE); + CloseThreadpoolWork(work_objects[i]); + } + } + + winpr_aligned_free(work_objects); + winpr_aligned_free(params); + + for (size_t i = 0; i < message->numTiles; i++) + { + if (!(tile = message->tiles[i])) + continue; + + tile->YLen = tile->CbLen = tile->CrLen = 0; + tile->YData = tile->CbData = tile->CrData = NULL; + } + + return rc; +} + +BOOL rfx_process_message(RFX_CONTEXT* context, const BYTE* data, UINT32 length, UINT32 left, + UINT32 top, BYTE* dst, UINT32 dstFormat, UINT32 dstStride, + UINT32 dstHeight, REGION16* invalidRegion) +{ + REGION16 updateRegion = { 0 }; + wStream inStream = { 0 }; + BOOL ok = TRUE; + + if (!context || !data || !length) + return FALSE; + + WINPR_ASSERT(context->priv); + RFX_MESSAGE* message = &context->currentMessage; + + wStream* s = Stream_StaticConstInit(&inStream, data, length); + + while (ok && Stream_GetRemainingLength(s) > 6) + { + wStream subStreamBuffer = { 0 }; + size_t extraBlockLen = 0; + UINT32 blockLen = 0; + UINT32 blockType = 0; + + /* RFX_BLOCKT */ + Stream_Read_UINT16(s, blockType); /* blockType (2 bytes) */ + Stream_Read_UINT32(s, blockLen); /* blockLen (4 bytes) */ + WLog_Print(context->priv->log, WLOG_DEBUG, "blockType 0x%" PRIX32 " blockLen %" PRIu32 "", + blockType, blockLen); + + if (blockLen < 6) + { + WLog_Print(context->priv->log, WLOG_ERROR, "blockLen too small(%" PRIu32 ")", blockLen); + return FALSE; + } + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, blockLen - 6)) + return FALSE; + + if (blockType > WBT_CONTEXT && context->decodedHeaderBlocks != RFX_DECODED_HEADERS) + { + WLog_Print(context->priv->log, WLOG_ERROR, "incomplete header blocks processing"); + return FALSE; + } + + if (blockType >= WBT_CONTEXT && blockType <= WBT_EXTENSION) + { + /* RFX_CODEC_CHANNELT */ + UINT8 codecId = 0; + UINT8 channelId = 0; + + if (!Stream_CheckAndLogRequiredLengthWLog(context->priv->log, s, 2)) + return FALSE; + + extraBlockLen = 2; + Stream_Read_UINT8(s, codecId); /* codecId (1 byte) must be set to 0x01 */ + Stream_Read_UINT8(s, channelId); /* channelId (1 byte) 0xFF or 0x00, see below */ + + if (codecId != 0x01) + { + WLog_Print(context->priv->log, WLOG_ERROR, "invalid codecId 0x%02" PRIX8 "", + codecId); + return FALSE; + } + + if (blockType == WBT_CONTEXT) + { + /* If the blockType is set to WBT_CONTEXT, then channelId MUST be set to 0xFF.*/ + if (channelId != 0xFF) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "invalid channelId 0x%02" PRIX8 " for blockType 0x%08" PRIX32 "", + channelId, blockType); + return FALSE; + } + } + else + { + /* For all other values of blockType, channelId MUST be set to 0x00. */ + if (channelId != 0x00) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "invalid channelId 0x%02" PRIX8 " for blockType WBT_CONTEXT", + channelId); + return FALSE; + } + } + } + + const size_t blockLenNoHeader = blockLen - 6; + if (blockLenNoHeader < extraBlockLen) + { + WLog_Print(context->priv->log, WLOG_ERROR, + "blockLen too small(%" PRIu32 "), must be >= 6 + %" PRIu16, blockLen, + extraBlockLen); + return FALSE; + } + + const size_t subStreamLen = blockLenNoHeader - extraBlockLen; + wStream* subStream = Stream_StaticInit(&subStreamBuffer, Stream_Pointer(s), subStreamLen); + Stream_Seek(s, subStreamLen); + + switch (blockType) + { + /* Header messages: + * The stream MUST start with the header messages and any of these headers can appear + * in the stream at a later stage. The header messages can be repeated. + */ + case WBT_SYNC: + ok = rfx_process_message_sync(context, subStream); + break; + + case WBT_CONTEXT: + ok = rfx_process_message_context(context, subStream); + break; + + case WBT_CODEC_VERSIONS: + ok = rfx_process_message_codec_versions(context, subStream); + break; + + case WBT_CHANNELS: + ok = rfx_process_message_channels(context, subStream); + break; + + /* Data messages: + * The data associated with each encoded frame or image is always bracketed by the + * TS_RFX_FRAME_BEGIN (section 2.2.2.3.1) and TS_RFX_FRAME_END (section 2.2.2.3.2) + * messages. There MUST only be one TS_RFX_REGION (section 2.2.2.3.3) message per + * frame and one TS_RFX_TILESET (section 2.2.2.3.4) message per TS_RFX_REGION. + */ + + case WBT_FRAME_BEGIN: + ok = rfx_process_message_frame_begin(context, message, subStream, + &context->expectedDataBlockType); + break; + + case WBT_REGION: + ok = rfx_process_message_region(context, message, subStream, + &context->expectedDataBlockType); + break; + + case WBT_EXTENSION: + ok = rfx_process_message_tileset(context, message, subStream, + &context->expectedDataBlockType); + break; + + case WBT_FRAME_END: + ok = rfx_process_message_frame_end(context, message, subStream, + &context->expectedDataBlockType); + break; + + default: + WLog_Print(context->priv->log, WLOG_ERROR, "unknown blockType 0x%" PRIX32 "", + blockType); + return FALSE; + } + } + + if (ok) + { + UINT32 nbUpdateRects = 0; + REGION16 clippingRects = { 0 }; + const RECTANGLE_16* updateRects = NULL; + const DWORD formatSize = FreeRDPGetBytesPerPixel(context->pixel_format); + const UINT32 dstWidth = dstStride / FreeRDPGetBytesPerPixel(dstFormat); + region16_init(&clippingRects); + + WINPR_ASSERT(dstWidth <= UINT16_MAX); + WINPR_ASSERT(dstHeight <= UINT16_MAX); + for (UINT32 i = 0; i < message->numRects; i++) + { + RECTANGLE_16 clippingRect = { 0 }; + const RFX_RECT* rect = &(message->rects[i]); + + WINPR_ASSERT(left + rect->x <= UINT16_MAX); + WINPR_ASSERT(top + rect->y <= UINT16_MAX); + WINPR_ASSERT(clippingRect.left + rect->width <= UINT16_MAX); + WINPR_ASSERT(clippingRect.top + rect->height <= UINT16_MAX); + + clippingRect.left = (UINT16)MIN(left + rect->x, dstWidth); + clippingRect.top = (UINT16)MIN(top + rect->y, dstHeight); + clippingRect.right = (UINT16)MIN(clippingRect.left + rect->width, dstWidth); + clippingRect.bottom = (UINT16)MIN(clippingRect.top + rect->height, dstHeight); + region16_union_rect(&clippingRects, &clippingRects, &clippingRect); + } + + for (UINT32 i = 0; i < message->numTiles; i++) + { + RECTANGLE_16 updateRect = { 0 }; + const RFX_TILE* tile = rfx_message_get_tile(message, i); + + WINPR_ASSERT(left + tile->x <= UINT16_MAX); + WINPR_ASSERT(top + tile->y <= UINT16_MAX); + + updateRect.left = (UINT16)left + tile->x; + updateRect.top = (UINT16)top + tile->y; + updateRect.right = updateRect.left + 64; + updateRect.bottom = updateRect.top + 64; + region16_init(&updateRegion); + region16_intersect_rect(&updateRegion, &clippingRects, &updateRect); + updateRects = region16_rects(&updateRegion, &nbUpdateRects); + + for (UINT32 j = 0; j < nbUpdateRects; j++) + { + const UINT32 stride = 64 * formatSize; + const UINT32 nXDst = updateRects[j].left; + const UINT32 nYDst = updateRects[j].top; + const UINT32 nXSrc = nXDst - updateRect.left; + const UINT32 nYSrc = nYDst - updateRect.top; + const UINT32 nWidth = updateRects[j].right - updateRects[j].left; + const UINT32 nHeight = updateRects[j].bottom - updateRects[j].top; + + if (!freerdp_image_copy(dst, dstFormat, dstStride, nXDst, nYDst, nWidth, nHeight, + tile->data, context->pixel_format, stride, nXSrc, nYSrc, + NULL, FREERDP_FLIP_NONE)) + { + region16_uninit(&updateRegion); + WLog_Print(context->priv->log, WLOG_ERROR, + "nbUpdateRectx[%" PRIu32 " (%" PRIu32 ")] freerdp_image_copy failed", + j, nbUpdateRects); + return FALSE; + } + + if (invalidRegion) + region16_union_rect(invalidRegion, invalidRegion, &updateRects[j]); + } + + region16_uninit(&updateRegion); + } + + region16_uninit(&clippingRects); + return TRUE; + } + else + { + rfx_message_free(context, message); + context->currentMessage.freeArray = TRUE; + } + + WLog_Print(context->priv->log, WLOG_ERROR, "failed"); + return FALSE; +} + +const UINT32* rfx_message_get_quants(const RFX_MESSAGE* message, UINT16* numQuantVals) +{ + WINPR_ASSERT(message); + if (numQuantVals) + *numQuantVals = message->numQuant; + return message->quantVals; +} + +const RFX_TILE** rfx_message_get_tiles(const RFX_MESSAGE* message, UINT16* numTiles) +{ + WINPR_ASSERT(message); + if (numTiles) + *numTiles = message->numTiles; + return message->tiles; +} + +UINT16 rfx_message_get_tile_count(const RFX_MESSAGE* message) +{ + WINPR_ASSERT(message); + return message->numTiles; +} + +const RFX_RECT* rfx_message_get_rects(const RFX_MESSAGE* message, UINT16* numRects) +{ + WINPR_ASSERT(message); + if (numRects) + *numRects = message->numRects; + return message->rects; +} + +UINT16 rfx_message_get_rect_count(const RFX_MESSAGE* message) +{ + WINPR_ASSERT(message); + return message->numRects; +} + +void rfx_message_free(RFX_CONTEXT* context, RFX_MESSAGE* message) +{ + if (!message) + return; + + winpr_aligned_free(message->rects); + + if (message->tiles) + { + for (size_t i = 0; i < message->numTiles; i++) + { + RFX_TILE* tile = message->tiles[i]; + if (!tile) + continue; + + if (tile->YCbCrData) + { + BufferPool_Return(context->priv->BufferPool, tile->YCbCrData); + tile->YCbCrData = NULL; + } + + ObjectPool_Return(context->priv->TilePool, (void*)tile); + } + + rfx_allocate_tiles(message, 0, FALSE); + } + + const BOOL freeArray = message->freeArray; + const RFX_MESSAGE empty = { 0 }; + *message = empty; + + if (!freeArray) + winpr_aligned_free(message); +} + +static void rfx_update_context_properties(RFX_CONTEXT* context) +{ + UINT16 properties = 0; + + WINPR_ASSERT(context); + /* properties in tilesets: note that this has different format from the one in TS_RFX_CONTEXT */ + properties = 1; /* lt */ + properties |= (context->flags << 1); /* flags */ + properties |= (COL_CONV_ICT << 4); /* cct */ + properties |= (CLW_XFORM_DWT_53_A << 6); /* xft */ + properties |= ((context->mode == RLGR1 ? CLW_ENTROPY_RLGR1 : CLW_ENTROPY_RLGR3) << 10); /* et */ + properties |= (SCALAR_QUANTIZATION << 14); /* qt */ + context->properties = properties; +} + +static void rfx_write_message_sync(const RFX_CONTEXT* context, wStream* s) +{ + WINPR_ASSERT(context); + + Stream_Write_UINT16(s, WBT_SYNC); /* BlockT.blockType (2 bytes) */ + Stream_Write_UINT32(s, 12); /* BlockT.blockLen (4 bytes) */ + Stream_Write_UINT32(s, WF_MAGIC); /* magic (4 bytes) */ + Stream_Write_UINT16(s, WF_VERSION_1_0); /* version (2 bytes) */ +} + +static void rfx_write_message_codec_versions(const RFX_CONTEXT* context, wStream* s) +{ + WINPR_ASSERT(context); + + Stream_Write_UINT16(s, WBT_CODEC_VERSIONS); /* BlockT.blockType (2 bytes) */ + Stream_Write_UINT32(s, 10); /* BlockT.blockLen (4 bytes) */ + Stream_Write_UINT8(s, 1); /* numCodecs (1 byte) */ + Stream_Write_UINT8(s, 1); /* codecs.codecId (1 byte) */ + Stream_Write_UINT16(s, WF_VERSION_1_0); /* codecs.version (2 bytes) */ +} + +static void rfx_write_message_channels(const RFX_CONTEXT* context, wStream* s) +{ + WINPR_ASSERT(context); + + Stream_Write_UINT16(s, WBT_CHANNELS); /* BlockT.blockType (2 bytes) */ + Stream_Write_UINT32(s, 12); /* BlockT.blockLen (4 bytes) */ + Stream_Write_UINT8(s, 1); /* numChannels (1 byte) */ + Stream_Write_UINT8(s, 0); /* Channel.channelId (1 byte) */ + Stream_Write_UINT16(s, context->width); /* Channel.width (2 bytes) */ + Stream_Write_UINT16(s, context->height); /* Channel.height (2 bytes) */ +} + +static void rfx_write_message_context(RFX_CONTEXT* context, wStream* s) +{ + UINT16 properties = 0; + WINPR_ASSERT(context); + + Stream_Write_UINT16(s, WBT_CONTEXT); /* CodecChannelT.blockType (2 bytes) */ + Stream_Write_UINT32(s, 13); /* CodecChannelT.blockLen (4 bytes) */ + Stream_Write_UINT8(s, 1); /* CodecChannelT.codecId (1 byte) */ + Stream_Write_UINT8(s, 0xFF); /* CodecChannelT.channelId (1 byte) */ + Stream_Write_UINT8(s, 0); /* ctxId (1 byte) */ + Stream_Write_UINT16(s, CT_TILE_64x64); /* tileSize (2 bytes) */ + /* properties */ + properties = context->flags; /* flags */ + properties |= (COL_CONV_ICT << 3); /* cct */ + properties |= (CLW_XFORM_DWT_53_A << 5); /* xft */ + properties |= ((context->mode == RLGR1 ? CLW_ENTROPY_RLGR1 : CLW_ENTROPY_RLGR3) << 9); /* et */ + properties |= (SCALAR_QUANTIZATION << 13); /* qt */ + Stream_Write_UINT16(s, properties); /* properties (2 bytes) */ + rfx_update_context_properties(context); +} + +static BOOL rfx_compose_message_header(RFX_CONTEXT* context, wStream* s) +{ + WINPR_ASSERT(context); + if (!Stream_EnsureRemainingCapacity(s, 12 + 10 + 12 + 13)) + return FALSE; + + rfx_write_message_sync(context, s); + rfx_write_message_context(context, s); + rfx_write_message_codec_versions(context, s); + rfx_write_message_channels(context, s); + return TRUE; +} + +static size_t rfx_tile_length(const RFX_TILE* tile) +{ + WINPR_ASSERT(tile); + return 19ull + tile->YLen + tile->CbLen + tile->CrLen; +} + +static BOOL rfx_write_tile(wStream* s, const RFX_TILE* tile) +{ + const size_t blockLen = rfx_tile_length(tile); + + if (!Stream_EnsureRemainingCapacity(s, blockLen)) + return FALSE; + + Stream_Write_UINT16(s, CBT_TILE); /* BlockT.blockType (2 bytes) */ + Stream_Write_UINT32(s, blockLen); /* BlockT.blockLen (4 bytes) */ + Stream_Write_UINT8(s, tile->quantIdxY); /* quantIdxY (1 byte) */ + Stream_Write_UINT8(s, tile->quantIdxCb); /* quantIdxCb (1 byte) */ + Stream_Write_UINT8(s, tile->quantIdxCr); /* quantIdxCr (1 byte) */ + Stream_Write_UINT16(s, tile->xIdx); /* xIdx (2 bytes) */ + Stream_Write_UINT16(s, tile->yIdx); /* yIdx (2 bytes) */ + Stream_Write_UINT16(s, tile->YLen); /* YLen (2 bytes) */ + Stream_Write_UINT16(s, tile->CbLen); /* CbLen (2 bytes) */ + Stream_Write_UINT16(s, tile->CrLen); /* CrLen (2 bytes) */ + Stream_Write(s, tile->YData, tile->YLen); /* YData */ + Stream_Write(s, tile->CbData, tile->CbLen); /* CbData */ + Stream_Write(s, tile->CrData, tile->CrLen); /* CrData */ + return TRUE; +} + +struct S_RFX_TILE_COMPOSE_WORK_PARAM +{ + RFX_TILE* tile; + RFX_CONTEXT* context; +}; + +static void CALLBACK rfx_compose_message_tile_work_callback(PTP_CALLBACK_INSTANCE instance, + void* context, PTP_WORK work) +{ + RFX_TILE_COMPOSE_WORK_PARAM* param = (RFX_TILE_COMPOSE_WORK_PARAM*)context; + WINPR_ASSERT(param); + rfx_encode_rgb(param->context, param->tile); +} + +static BOOL computeRegion(const RFX_RECT* rects, size_t numRects, REGION16* region, size_t width, + size_t height) +{ + const RECTANGLE_16 mainRect = { 0, 0, width, height }; + + WINPR_ASSERT(rects); + for (size_t i = 0; i < numRects; i++) + { + const RFX_RECT* rect = &rects[i]; + RECTANGLE_16 rect16 = { 0 }; + rect16.left = rect->x; + rect16.top = rect->y; + rect16.right = rect->x + rect->width; + rect16.bottom = rect->y + rect->height; + + if (!region16_union_rect(region, region, &rect16)) + return FALSE; + } + + return region16_intersect_rect(region, region, &mainRect); +} + +#define TILE_NO(v) ((v) / 64) + +static BOOL setupWorkers(RFX_CONTEXT* context, size_t nbTiles) +{ + WINPR_ASSERT(context); + + RFX_CONTEXT_PRIV* priv = context->priv; + WINPR_ASSERT(priv); + + void* pmem = NULL; + + if (!context->priv->UseThreads) + return TRUE; + + if (!(pmem = winpr_aligned_recalloc(priv->workObjects, nbTiles, sizeof(PTP_WORK), 32))) + return FALSE; + + priv->workObjects = (PTP_WORK*)pmem; + + if (!(pmem = winpr_aligned_recalloc(priv->tileWorkParams, nbTiles, + sizeof(RFX_TILE_COMPOSE_WORK_PARAM), 32))) + return FALSE; + + priv->tileWorkParams = (RFX_TILE_COMPOSE_WORK_PARAM*)pmem; + return TRUE; +} + +static BOOL rfx_ensure_tiles(RFX_MESSAGE* message, size_t count) +{ + WINPR_ASSERT(message); + + if (message->numTiles + count <= message->allocatedTiles) + return TRUE; + + const size_t alloc = MAX(message->allocatedTiles + 1024, message->numTiles + count); + return rfx_allocate_tiles(message, alloc, TRUE); +} + +RFX_MESSAGE* rfx_encode_message(RFX_CONTEXT* context, const RFX_RECT* rects, size_t numRects, + const BYTE* data, UINT32 w, UINT32 h, size_t s) +{ + const UINT32 width = (UINT32)w; + const UINT32 height = (UINT32)h; + const UINT32 scanline = (UINT32)s; + RFX_MESSAGE* message = NULL; + PTP_WORK* workObject = NULL; + RFX_TILE_COMPOSE_WORK_PARAM* workParam = NULL; + BOOL success = FALSE; + REGION16 rectsRegion = { 0 }; + REGION16 tilesRegion = { 0 }; + RECTANGLE_16 currentTileRect = { 0 }; + const RECTANGLE_16* regionRect = NULL; + + WINPR_ASSERT(data); + WINPR_ASSERT(rects); + WINPR_ASSERT(numRects > 0); + WINPR_ASSERT(w > 0); + WINPR_ASSERT(h > 0); + WINPR_ASSERT(s > 0); + + if (!(message = (RFX_MESSAGE*)winpr_aligned_calloc(1, sizeof(RFX_MESSAGE), 32))) + return NULL; + + region16_init(&tilesRegion); + region16_init(&rectsRegion); + + if (context->state == RFX_STATE_SEND_HEADERS) + rfx_update_context_properties(context); + + message->frameIdx = context->frameIdx++; + + if (!context->numQuant) + { + WINPR_ASSERT(context->quants == NULL); + if (!(context->quants = + (UINT32*)winpr_aligned_malloc(sizeof(rfx_default_quantization_values), 32))) + goto skip_encoding_loop; + + CopyMemory(context->quants, &rfx_default_quantization_values, + sizeof(rfx_default_quantization_values)); + context->numQuant = 1; + context->quantIdxY = 0; + context->quantIdxCb = 0; + context->quantIdxCr = 0; + } + + message->numQuant = context->numQuant; + message->quantVals = context->quants; + const UINT32 bytesPerPixel = (context->bits_per_pixel / 8); + + if (!computeRegion(rects, numRects, &rectsRegion, width, height)) + goto skip_encoding_loop; + + const RECTANGLE_16* extents = region16_extents(&rectsRegion); + WINPR_ASSERT((INT32)extents->right - extents->left > 0); + WINPR_ASSERT((INT32)extents->bottom - extents->top > 0); + const UINT32 maxTilesX = 1 + TILE_NO(extents->right - 1) - TILE_NO(extents->left); + const UINT32 maxTilesY = 1 + TILE_NO(extents->bottom - 1) - TILE_NO(extents->top); + const UINT32 maxNbTiles = maxTilesX * maxTilesY; + + if (!rfx_ensure_tiles(message, maxNbTiles)) + goto skip_encoding_loop; + + if (!setupWorkers(context, maxNbTiles)) + goto skip_encoding_loop; + + if (context->priv->UseThreads) + { + workObject = context->priv->workObjects; + workParam = context->priv->tileWorkParams; + } + + UINT32 regionNbRects = 0; + regionRect = region16_rects(&rectsRegion, ®ionNbRects); + + if (!(message->rects = winpr_aligned_calloc(regionNbRects, sizeof(RFX_RECT), 32))) + goto skip_encoding_loop; + + message->numRects = regionNbRects; + + for (UINT32 i = 0; i < regionNbRects; i++, regionRect++) + { + RFX_RECT* rfxRect = &message->rects[i]; + UINT32 startTileX = regionRect->left / 64; + UINT32 endTileX = (regionRect->right - 1) / 64; + UINT32 startTileY = regionRect->top / 64; + UINT32 endTileY = (regionRect->bottom - 1) / 64; + rfxRect->x = regionRect->left; + rfxRect->y = regionRect->top; + rfxRect->width = (regionRect->right - regionRect->left); + rfxRect->height = (regionRect->bottom - regionRect->top); + + for (UINT32 yIdx = startTileY, gridRelY = startTileY * 64; yIdx <= endTileY; + yIdx++, gridRelY += 64) + { + UINT32 tileHeight = 64; + + if ((yIdx == endTileY) && (gridRelY + 64 > height)) + tileHeight = height - gridRelY; + + currentTileRect.top = gridRelY; + currentTileRect.bottom = gridRelY + tileHeight; + + for (UINT32 xIdx = startTileX, gridRelX = startTileX * 64; xIdx <= endTileX; + xIdx++, gridRelX += 64) + { + union + { + const BYTE* cpv; + BYTE* pv; + } cnv; + int tileWidth = 64; + + if ((xIdx == endTileX) && (gridRelX + 64 > width)) + tileWidth = width - gridRelX; + + currentTileRect.left = gridRelX; + currentTileRect.right = gridRelX + tileWidth; + + /* checks if this tile is already treated */ + if (region16_intersects_rect(&tilesRegion, ¤tTileRect)) + continue; + + RFX_TILE* tile = (RFX_TILE*)ObjectPool_Take(context->priv->TilePool); + if (!tile) + goto skip_encoding_loop; + + tile->xIdx = xIdx; + tile->yIdx = yIdx; + tile->x = gridRelX; + tile->y = gridRelY; + tile->scanline = scanline; + tile->width = tileWidth; + tile->height = tileHeight; + const UINT32 ax = gridRelX; + const UINT32 ay = gridRelY; + + if (tile->data && tile->allocated) + { + winpr_aligned_free(tile->data); + tile->allocated = FALSE; + } + + /* Cast away const */ + cnv.cpv = &data[(ay * scanline) + (ax * bytesPerPixel)]; + tile->data = cnv.pv; + tile->quantIdxY = context->quantIdxY; + tile->quantIdxCb = context->quantIdxCb; + tile->quantIdxCr = context->quantIdxCr; + tile->YLen = tile->CbLen = tile->CrLen = 0; + + if (!(tile->YCbCrData = (BYTE*)BufferPool_Take(context->priv->BufferPool, -1))) + goto skip_encoding_loop; + + tile->YData = (BYTE*)&(tile->YCbCrData[((8192 + 32) * 0) + 16]); + tile->CbData = (BYTE*)&(tile->YCbCrData[((8192 + 32) * 1) + 16]); + tile->CrData = (BYTE*)&(tile->YCbCrData[((8192 + 32) * 2) + 16]); + + if (!rfx_ensure_tiles(message, 1)) + goto skip_encoding_loop; + message->tiles[message->numTiles++] = tile; + + if (context->priv->UseThreads) + { + workParam->context = context; + workParam->tile = tile; + + if (!(*workObject = CreateThreadpoolWork(rfx_compose_message_tile_work_callback, + (void*)workParam, + &context->priv->ThreadPoolEnv))) + { + goto skip_encoding_loop; + } + + SubmitThreadpoolWork(*workObject); + workObject++; + workParam++; + } + else + { + rfx_encode_rgb(context, tile); + } + + if (!region16_union_rect(&tilesRegion, &tilesRegion, ¤tTileRect)) + goto skip_encoding_loop; + } /* xIdx */ + } /* yIdx */ + } /* rects */ + + success = TRUE; +skip_encoding_loop: + + /* when using threads ensure all computations are done */ + if (success) + { + message->tilesDataSize = 0; + workObject = context->priv->workObjects; + + for (UINT32 i = 0; i < message->numTiles; i++) + { + if (context->priv->UseThreads) + { + if (*workObject) + { + WaitForThreadpoolWorkCallbacks(*workObject, FALSE); + CloseThreadpoolWork(*workObject); + } + + workObject++; + } + + const RFX_TILE* tile = message->tiles[i]; + message->tilesDataSize += rfx_tile_length(tile); + } + + region16_uninit(&tilesRegion); + region16_uninit(&rectsRegion); + + return message; + } + + WLog_Print(context->priv->log, WLOG_ERROR, "failed"); + + rfx_message_free(context, message); + return NULL; +} + +static BOOL rfx_clone_rects(RFX_MESSAGE* dst, const RFX_MESSAGE* src) +{ + WINPR_ASSERT(dst); + WINPR_ASSERT(src); + + WINPR_ASSERT(dst->rects == NULL); + WINPR_ASSERT(dst->numRects == 0); + + if (src->numRects == 0) + return TRUE; + + dst->rects = winpr_aligned_calloc(src->numRects, sizeof(RECTANGLE_16), 32); + if (!dst->rects) + return FALSE; + dst->numRects = src->numRects; + for (size_t x = 0; x < src->numRects; x++) + { + dst->rects[x] = src->rects[x]; + } + return TRUE; +} + +static BOOL rfx_clone_quants(RFX_MESSAGE* dst, const RFX_MESSAGE* src) +{ + WINPR_ASSERT(dst); + WINPR_ASSERT(src); + + WINPR_ASSERT(dst->quantVals == NULL); + WINPR_ASSERT(dst->numQuant == 0); + + if (src->numQuant == 0) + return TRUE; + + /* quantVals are part of context */ + dst->quantVals = src->quantVals; + dst->numQuant = src->numQuant; + + return TRUE; +} + +static RFX_MESSAGE* rfx_split_message(RFX_CONTEXT* context, RFX_MESSAGE* message, + size_t* numMessages, size_t maxDataSize) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(message); + WINPR_ASSERT(numMessages); + + maxDataSize -= 1024; /* reserve enough space for headers */ + *numMessages = ((message->tilesDataSize + maxDataSize) / maxDataSize) * 4ull; + + RFX_MESSAGE* messages = + (RFX_MESSAGE*)winpr_aligned_calloc((*numMessages), sizeof(RFX_MESSAGE), 32); + if (!messages) + return NULL; + + size_t j = 0; + for (size_t i = 0; i < message->numTiles; i++) + { + RFX_TILE* tile = message->tiles[i]; + RFX_MESSAGE* msg = &messages[j]; + + WINPR_ASSERT(tile); + WINPR_ASSERT(msg); + + const size_t tileDataSize = rfx_tile_length(tile); + + if ((msg->tilesDataSize + tileDataSize) > ((UINT32)maxDataSize)) + j++; + + if (msg->numTiles == 0) + { + msg->frameIdx = message->frameIdx + j; + if (!rfx_clone_quants(msg, message)) + goto free_messages; + if (!rfx_clone_rects(msg, message)) + goto free_messages; + msg->freeArray = TRUE; + if (!rfx_allocate_tiles(msg, message->numTiles, TRUE)) + goto free_messages; + } + + msg->tilesDataSize += tileDataSize; + + WINPR_ASSERT(msg->numTiles < msg->allocatedTiles); + msg->tiles[msg->numTiles++] = message->tiles[i]; + message->tiles[i] = NULL; + } + + *numMessages = j + 1; + context->frameIdx += j; + message->numTiles = 0; + return messages; +free_messages: + + for (size_t i = 0; i < j; i++) + rfx_allocate_tiles(&messages[i], 0, FALSE); + + winpr_aligned_free(messages); + return NULL; +} + +const RFX_MESSAGE* rfx_message_list_get(const RFX_MESSAGE_LIST* messages, size_t idx) +{ + WINPR_ASSERT(messages); + if (idx >= messages->count) + return NULL; + WINPR_ASSERT(messages->list); + return &messages->list[idx]; +} + +void rfx_message_list_free(RFX_MESSAGE_LIST* messages) +{ + if (!messages) + return; + for (size_t x = 0; x < messages->count; x++) + rfx_message_free(messages->context, &messages->list[x]); + free(messages); +} + +static RFX_MESSAGE_LIST* rfx_message_list_new(RFX_CONTEXT* context, RFX_MESSAGE* messages, + size_t count) +{ + WINPR_ASSERT(context); + RFX_MESSAGE_LIST* msg = calloc(1, sizeof(RFX_MESSAGE_LIST)); + WINPR_ASSERT(msg); + + msg->context = context; + msg->count = count; + msg->list = messages; + return msg; +} + +RFX_MESSAGE_LIST* rfx_encode_messages(RFX_CONTEXT* context, const RFX_RECT* rects, size_t numRects, + const BYTE* data, UINT32 width, UINT32 height, + UINT32 scanline, size_t* numMessages, size_t maxDataSize) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(numMessages); + + RFX_MESSAGE* message = + rfx_encode_message(context, rects, numRects, data, width, height, scanline); + if (!message) + return NULL; + + RFX_MESSAGE* list = rfx_split_message(context, message, numMessages, maxDataSize); + rfx_message_free(context, message); + if (!list) + return NULL; + + return rfx_message_list_new(context, list, *numMessages); +} + +static BOOL rfx_write_message_tileset(RFX_CONTEXT* context, wStream* s, const RFX_MESSAGE* message) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(message); + + const UINT32 blockLen = 22 + (message->numQuant * 5) + message->tilesDataSize; + + if (!Stream_EnsureRemainingCapacity(s, blockLen)) + return FALSE; + + Stream_Write_UINT16(s, WBT_EXTENSION); /* CodecChannelT.blockType (2 bytes) */ + Stream_Write_UINT32(s, blockLen); /* set CodecChannelT.blockLen (4 bytes) */ + Stream_Write_UINT8(s, 1); /* CodecChannelT.codecId (1 byte) */ + Stream_Write_UINT8(s, 0); /* CodecChannelT.channelId (1 byte) */ + Stream_Write_UINT16(s, CBT_TILESET); /* subtype (2 bytes) */ + Stream_Write_UINT16(s, 0); /* idx (2 bytes) */ + Stream_Write_UINT16(s, context->properties); /* properties (2 bytes) */ + Stream_Write_UINT8(s, message->numQuant); /* numQuant (1 byte) */ + Stream_Write_UINT8(s, 0x40); /* tileSize (1 byte) */ + Stream_Write_UINT16(s, message->numTiles); /* numTiles (2 bytes) */ + Stream_Write_UINT32(s, message->tilesDataSize); /* tilesDataSize (4 bytes) */ + + UINT32* quantVals = message->quantVals; + for (size_t i = 0; i < message->numQuant * 5ul; i++) + { + WINPR_ASSERT(quantVals); + Stream_Write_UINT8(s, quantVals[0] + (quantVals[1] << 4)); + quantVals += 2; + } + + for (size_t i = 0; i < message->numTiles; i++) + { + RFX_TILE* tile = message->tiles[i]; + if (!tile) + return FALSE; + + if (!rfx_write_tile(s, tile)) + return FALSE; + } + +#ifdef WITH_DEBUG_RFX + WLog_Print(context->priv->log, WLOG_DEBUG, + "numQuant: %" PRIu16 " numTiles: %" PRIu16 " tilesDataSize: %" PRIu32 "", + message->numQuant, message->numTiles, message->tilesDataSize); +#endif + return TRUE; +} + +static BOOL rfx_write_message_frame_begin(RFX_CONTEXT* context, wStream* s, + const RFX_MESSAGE* message) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(message); + + if (!Stream_EnsureRemainingCapacity(s, 14)) + return FALSE; + + Stream_Write_UINT16(s, WBT_FRAME_BEGIN); /* CodecChannelT.blockType */ + Stream_Write_UINT32(s, 14); /* CodecChannelT.blockLen */ + Stream_Write_UINT8(s, 1); /* CodecChannelT.codecId */ + Stream_Write_UINT8(s, 0); /* CodecChannelT.channelId */ + Stream_Write_UINT32(s, message->frameIdx); /* frameIdx */ + Stream_Write_UINT16(s, 1); /* numRegions */ + return TRUE; +} + +static BOOL rfx_write_message_region(RFX_CONTEXT* context, wStream* s, const RFX_MESSAGE* message) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(message); + + const size_t blockLen = 15 + (message->numRects * 8); + + if (!Stream_EnsureRemainingCapacity(s, blockLen)) + return FALSE; + + Stream_Write_UINT16(s, WBT_REGION); /* CodecChannelT.blockType (2 bytes) */ + Stream_Write_UINT32(s, blockLen); /* set CodecChannelT.blockLen (4 bytes) */ + Stream_Write_UINT8(s, 1); /* CodecChannelT.codecId (1 byte) */ + Stream_Write_UINT8(s, 0); /* CodecChannelT.channelId (1 byte) */ + Stream_Write_UINT8(s, 1); /* regionFlags (1 byte) */ + Stream_Write_UINT16(s, message->numRects); /* numRects (2 bytes) */ + + for (size_t i = 0; i < message->numRects; i++) + { + const RFX_RECT* rect = rfx_message_get_rect_const(message, i); + WINPR_ASSERT(rect); + + /* Clipping rectangles are relative to destLeft, destTop */ + Stream_Write_UINT16(s, rect->x); /* x (2 bytes) */ + Stream_Write_UINT16(s, rect->y); /* y (2 bytes) */ + Stream_Write_UINT16(s, rect->width); /* width (2 bytes) */ + Stream_Write_UINT16(s, rect->height); /* height (2 bytes) */ + } + + Stream_Write_UINT16(s, CBT_REGION); /* regionType (2 bytes) */ + Stream_Write_UINT16(s, 1); /* numTilesets (2 bytes) */ + return TRUE; +} + +static BOOL rfx_write_message_frame_end(RFX_CONTEXT* context, wStream* s, + const RFX_MESSAGE* message) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(message); + + if (!Stream_EnsureRemainingCapacity(s, 8)) + return FALSE; + + Stream_Write_UINT16(s, WBT_FRAME_END); /* CodecChannelT.blockType */ + Stream_Write_UINT32(s, 8); /* CodecChannelT.blockLen */ + Stream_Write_UINT8(s, 1); /* CodecChannelT.codecId */ + Stream_Write_UINT8(s, 0); /* CodecChannelT.channelId */ + return TRUE; +} + +BOOL rfx_write_message(RFX_CONTEXT* context, wStream* s, const RFX_MESSAGE* message) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(message); + + if (context->state == RFX_STATE_SEND_HEADERS) + { + if (!rfx_compose_message_header(context, s)) + return FALSE; + + context->state = RFX_STATE_SEND_FRAME_DATA; + } + + if (!rfx_write_message_frame_begin(context, s, message) || + !rfx_write_message_region(context, s, message) || + !rfx_write_message_tileset(context, s, message) || + !rfx_write_message_frame_end(context, s, message)) + { + return FALSE; + } + + return TRUE; +} + +BOOL rfx_compose_message(RFX_CONTEXT* context, wStream* s, const RFX_RECT* rects, size_t numRects, + const BYTE* data, UINT32 width, UINT32 height, UINT32 scanline) +{ + WINPR_ASSERT(context); + RFX_MESSAGE* message = + rfx_encode_message(context, rects, numRects, data, width, height, scanline); + if (!message) + return FALSE; + + const BOOL ret = rfx_write_message(context, s, message); + rfx_message_free(context, message); + return ret; +} + +BOOL rfx_context_set_mode(RFX_CONTEXT* context, RLGR_MODE mode) +{ + WINPR_ASSERT(context); + context->mode = mode; + return TRUE; +} + +RLGR_MODE rfx_context_get_mode(RFX_CONTEXT* context) +{ + WINPR_ASSERT(context); + return context->mode; +} + +UINT32 rfx_context_get_frame_idx(const RFX_CONTEXT* context) +{ + WINPR_ASSERT(context); + return context->frameIdx; +} + +UINT32 rfx_message_get_frame_idx(const RFX_MESSAGE* message) +{ + WINPR_ASSERT(message); + return message->frameIdx; +} + +static INLINE BOOL rfx_write_progressive_wb_sync(RFX_CONTEXT* rfx, wStream* s) +{ + const UINT32 blockLen = 12; + WINPR_ASSERT(rfx); + WINPR_ASSERT(s); + + if (!Stream_EnsureRemainingCapacity(s, blockLen)) + return FALSE; + + Stream_Write_UINT16(s, PROGRESSIVE_WBT_SYNC); /* blockType (2 bytes) */ + Stream_Write_UINT32(s, blockLen); /* blockLen (4 bytes) */ + Stream_Write_UINT32(s, 0xCACCACCA); /* magic (4 bytes) */ + Stream_Write_UINT16(s, 0x0100); /* version (2 bytes) */ + return TRUE; +} + +static INLINE BOOL rfx_write_progressive_wb_context(RFX_CONTEXT* rfx, wStream* s) +{ + const UINT32 blockLen = 10; + WINPR_ASSERT(rfx); + WINPR_ASSERT(s); + + if (!Stream_EnsureRemainingCapacity(s, blockLen)) + return FALSE; + + Stream_Write_UINT16(s, PROGRESSIVE_WBT_CONTEXT); /* blockType (2 bytes) */ + Stream_Write_UINT32(s, blockLen); /* blockLen (4 bytes) */ + Stream_Write_UINT8(s, 0); /* ctxId (1 byte) */ + Stream_Write_UINT16(s, 64); /* tileSize (2 bytes) */ + Stream_Write_UINT8(s, 0); /* flags (1 byte) */ + return TRUE; +} + +static INLINE BOOL rfx_write_progressive_region(RFX_CONTEXT* rfx, wStream* s, + const RFX_MESSAGE* msg) +{ + /* RFX_REGION */ + UINT32 blockLen = 18; + UINT32 tilesDataSize = 0; + const size_t start = Stream_GetPosition(s); + + WINPR_ASSERT(rfx); + WINPR_ASSERT(s); + WINPR_ASSERT(msg); + + blockLen += msg->numRects * 8; + blockLen += msg->numQuant * 5; + tilesDataSize = msg->numTiles * 22UL; + for (UINT16 i = 0; i < msg->numTiles; i++) + { + const RFX_TILE* tile = msg->tiles[i]; + WINPR_ASSERT(tile); + tilesDataSize += tile->YLen + tile->CbLen + tile->CrLen; + } + blockLen += tilesDataSize; + + if (!Stream_EnsureRemainingCapacity(s, blockLen)) + return FALSE; + + Stream_Write_UINT16(s, PROGRESSIVE_WBT_REGION); /* blockType (2 bytes) */ + Stream_Write_UINT32(s, blockLen); /* blockLen (4 bytes) */ + Stream_Write_UINT8(s, 64); /* tileSize (1 byte) */ + Stream_Write_UINT16(s, msg->numRects); /* numRects (2 bytes) */ + WINPR_ASSERT(msg->numQuant <= UINT8_MAX); + Stream_Write_UINT8(s, (UINT8)msg->numQuant); /* numQuant (1 byte) */ + Stream_Write_UINT8(s, 0); /* numProgQuant (1 byte) */ + Stream_Write_UINT8(s, 0); /* flags (1 byte) */ + Stream_Write_UINT16(s, msg->numTiles); /* numTiles (2 bytes) */ + Stream_Write_UINT32(s, tilesDataSize); /* tilesDataSize (4 bytes) */ + + for (UINT16 i = 0; i < msg->numRects; i++) + { + /* TS_RFX_RECT */ + const RFX_RECT* r = &msg->rects[i]; + Stream_Write_UINT16(s, r->x); /* x (2 bytes) */ + Stream_Write_UINT16(s, r->y); /* y (2 bytes) */ + Stream_Write_UINT16(s, r->width); /* width (2 bytes) */ + Stream_Write_UINT16(s, r->height); /* height (2 bytes) */ + } + + /** + * Note: The RFX_COMPONENT_CODEC_QUANT structure differs from the + * TS_RFX_CODEC_QUANT ([MS-RDPRFX] section 2.2.2.1.5) structure with respect + * to the order of the bands. + * 0 1 2 3 4 5 6 7 8 9 + * RDPRFX: LL3, LH3, HL3, HH3, LH2, HL2, HH2, LH1, HL1, HH1 + * RDPEGFX: LL3, HL3, LH3, HH3, HL2, LH2, HH2, HL1, LH1, HH1 + */ + for (UINT16 i = 0; i < msg->numQuant; i++) + { + const UINT32* qv = &msg->quantVals[i * 10]; + /* RFX_COMPONENT_CODEC_QUANT */ + Stream_Write_UINT8(s, (UINT8)(qv[0] + (qv[2] << 4))); /* LL3 (4-bit), HL3 (4-bit) */ + Stream_Write_UINT8(s, (UINT8)(qv[1] + (qv[3] << 4))); /* LH3 (4-bit), HH3 (4-bit) */ + Stream_Write_UINT8(s, (UINT8)(qv[5] + (qv[4] << 4))); /* HL2 (4-bit), LH2 (4-bit) */ + Stream_Write_UINT8(s, (UINT8)(qv[6] + (qv[8] << 4))); /* HH2 (4-bit), HL1 (4-bit) */ + Stream_Write_UINT8(s, (UINT8)(qv[7] + (qv[9] << 4))); /* LH1 (4-bit), HH1 (4-bit) */ + } + + for (UINT16 i = 0; i < msg->numTiles; i++) + { + const RFX_TILE* tile = msg->tiles[i]; + if (!rfx_write_progressive_tile_simple(rfx, s, tile)) + return FALSE; + } + + const size_t end = Stream_GetPosition(s); + const size_t used = end - start; + return (used == blockLen); +} + +static INLINE BOOL rfx_write_progressive_frame_begin(RFX_CONTEXT* rfx, wStream* s, + const RFX_MESSAGE* msg) +{ + const UINT32 blockLen = 12; + WINPR_ASSERT(rfx); + WINPR_ASSERT(s); + WINPR_ASSERT(msg); + + if (!Stream_EnsureRemainingCapacity(s, blockLen)) + return FALSE; + + Stream_Write_UINT16(s, PROGRESSIVE_WBT_FRAME_BEGIN); /* blockType (2 bytes) */ + Stream_Write_UINT32(s, blockLen); /* blockLen (4 bytes) */ + Stream_Write_UINT32(s, msg->frameIdx); /* frameIndex (4 bytes) */ + Stream_Write_UINT16(s, 1); /* regionCount (2 bytes) */ + + return TRUE; +} + +static INLINE BOOL rfx_write_progressive_frame_end(RFX_CONTEXT* rfx, wStream* s) +{ + const UINT32 blockLen = 6; + WINPR_ASSERT(rfx); + WINPR_ASSERT(s); + + if (!Stream_EnsureRemainingCapacity(s, blockLen)) + return FALSE; + + Stream_Write_UINT16(s, PROGRESSIVE_WBT_FRAME_END); /* blockType (2 bytes) */ + Stream_Write_UINT32(s, blockLen); /* blockLen (4 bytes) */ + + return TRUE; +} + +static INLINE BOOL rfx_write_progressive_tile_simple(RFX_CONTEXT* rfx, wStream* s, + const RFX_TILE* tile) +{ + UINT32 blockLen = 0; + WINPR_ASSERT(rfx); + WINPR_ASSERT(s); + WINPR_ASSERT(tile); + + blockLen = 22 + tile->YLen + tile->CbLen + tile->CrLen; + if (!Stream_EnsureRemainingCapacity(s, blockLen)) + return FALSE; + + Stream_Write_UINT16(s, PROGRESSIVE_WBT_TILE_SIMPLE); /* blockType (2 bytes) */ + Stream_Write_UINT32(s, blockLen); /* blockLen (4 bytes) */ + Stream_Write_UINT8(s, tile->quantIdxY); /* quantIdxY (1 byte) */ + Stream_Write_UINT8(s, tile->quantIdxCb); /* quantIdxCb (1 byte) */ + Stream_Write_UINT8(s, tile->quantIdxCr); /* quantIdxCr (1 byte) */ + Stream_Write_UINT16(s, tile->xIdx); /* xIdx (2 bytes) */ + Stream_Write_UINT16(s, tile->yIdx); /* yIdx (2 bytes) */ + Stream_Write_UINT8(s, 0); /* flags (1 byte) */ + Stream_Write_UINT16(s, tile->YLen); /* YLen (2 bytes) */ + Stream_Write_UINT16(s, tile->CbLen); /* CbLen (2 bytes) */ + Stream_Write_UINT16(s, tile->CrLen); /* CrLen (2 bytes) */ + Stream_Write_UINT16(s, 0); /* tailLen (2 bytes) */ + Stream_Write(s, tile->YData, tile->YLen); /* YData */ + Stream_Write(s, tile->CbData, tile->CbLen); /* CbData */ + Stream_Write(s, tile->CrData, tile->CrLen); /* CrData */ + + return TRUE; +} + +const char* rfx_get_progressive_block_type_string(UINT16 blockType) +{ + switch (blockType) + { + case PROGRESSIVE_WBT_SYNC: + return "PROGRESSIVE_WBT_SYNC"; + + case PROGRESSIVE_WBT_FRAME_BEGIN: + return "PROGRESSIVE_WBT_FRAME_BEGIN"; + + case PROGRESSIVE_WBT_FRAME_END: + return "PROGRESSIVE_WBT_FRAME_END"; + + case PROGRESSIVE_WBT_CONTEXT: + return "PROGRESSIVE_WBT_CONTEXT"; + + case PROGRESSIVE_WBT_REGION: + return "PROGRESSIVE_WBT_REGION"; + + case PROGRESSIVE_WBT_TILE_SIMPLE: + return "PROGRESSIVE_WBT_TILE_SIMPLE"; + + case PROGRESSIVE_WBT_TILE_FIRST: + return "PROGRESSIVE_WBT_TILE_FIRST"; + + case PROGRESSIVE_WBT_TILE_UPGRADE: + return "PROGRESSIVE_WBT_TILE_UPGRADE"; + + default: + return "PROGRESSIVE_WBT_UNKNOWN"; + } +} + +BOOL rfx_write_message_progressive_simple(RFX_CONTEXT* context, wStream* s, const RFX_MESSAGE* msg) +{ + WINPR_ASSERT(s); + WINPR_ASSERT(msg); + WINPR_ASSERT(context); + + if (context->mode != RLGR1) + { + WLog_ERR(TAG, "error, RLGR1 mode is required!"); + return FALSE; + } + + if (!rfx_write_progressive_wb_sync(context, s)) + return FALSE; + + if (!rfx_write_progressive_wb_context(context, s)) + return FALSE; + + if (!rfx_write_progressive_frame_begin(context, s, msg)) + return FALSE; + + if (!rfx_write_progressive_region(context, s, msg)) + return FALSE; + + if (!rfx_write_progressive_frame_end(context, s)) + return FALSE; + + return TRUE; +} diff --git a/libfreerdp/codec/rfx_bitstream.h b/libfreerdp/codec/rfx_bitstream.h new file mode 100644 index 0000000..3e84242 --- /dev/null +++ b/libfreerdp/codec/rfx_bitstream.h @@ -0,0 +1,107 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX Codec Library - Bit Stream + * + * Copyright 2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_LIB_CODEC_RFX_BITSTREAM_H +#define FREERDP_LIB_CODEC_RFX_BITSTREAM_H + +#include <freerdp/codec/rfx.h> + +typedef struct +{ + BYTE* buffer; + int nbytes; + int byte_pos; + int bits_left; +} RFX_BITSTREAM; + +#define rfx_bitstream_attach(bs, _buffer, _nbytes) \ + do \ + { \ + bs->buffer = (BYTE*)(_buffer); \ + bs->nbytes = (_nbytes); \ + bs->byte_pos = 0; \ + bs->bits_left = 8; \ + } while (0) + +#define rfx_bitstream_get_bits(bs, _nbits, _r) \ + do \ + { \ + int nbits = _nbits; \ + int b; \ + UINT16 n = 0; \ + while (bs->byte_pos < bs->nbytes && nbits > 0) \ + { \ + b = nbits; \ + if (b > bs->bits_left) \ + b = bs->bits_left; \ + if (n) \ + n <<= b; \ + n |= (bs->buffer[bs->byte_pos] >> (bs->bits_left - b)) & ((1 << b) - 1); \ + bs->bits_left -= b; \ + nbits -= b; \ + if (bs->bits_left == 0) \ + { \ + bs->bits_left = 8; \ + bs->byte_pos++; \ + } \ + } \ + _r = n; \ + } while (0) + +#define rfx_bitstream_put_bits(bs, _bits, _nbits) \ + do \ + { \ + UINT16 bits = (_bits); \ + int nbits = (_nbits); \ + int b; \ + while (bs->byte_pos < bs->nbytes && nbits > 0) \ + { \ + b = nbits; \ + if (b > bs->bits_left) \ + b = bs->bits_left; \ + bs->buffer[bs->byte_pos] |= ((bits >> (nbits - b)) & ((1 << b) - 1)) \ + << (bs->bits_left - b); \ + bs->bits_left -= b; \ + nbits -= b; \ + if (bs->bits_left == 0) \ + { \ + bs->bits_left = 8; \ + bs->byte_pos++; \ + } \ + } \ + } while (0) +#define rfx_bitstream_flush(bs) \ + do \ + { \ + if (bs->bits_left != 8) \ + { \ + int _nbits = 8 - bs->bits_left; \ + rfx_bitstream_put_bits(bs, 0, _nbits); \ + } \ + } while (0) + +#define rfx_bitstream_eos(_bs) ((_bs)->byte_pos >= (_bs)->nbytes) +#define rfx_bitstream_left(_bs) \ + ((_bs)->byte_pos >= (_bs)->nbytes \ + ? 0 \ + : ((_bs)->nbytes - (_bs)->byte_pos - 1) * 8 + (_bs)->bits_left) +#define rfx_bitstream_get_processed_bytes(_bs) \ + ((_bs)->bits_left < 8 ? (_bs)->byte_pos + 1 : (_bs)->byte_pos) + +#endif /* FREERDP_LIB_CODEC_RFX_BITSTREAM_H */ diff --git a/libfreerdp/codec/rfx_constants.h b/libfreerdp/codec/rfx_constants.h new file mode 100644 index 0000000..e8405a8 --- /dev/null +++ b/libfreerdp/codec/rfx_constants.h @@ -0,0 +1,81 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX Codec Library - API Header + * + * Copyright 2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_LIB_CODEC_RFX_CONSTANTS_H +#define FREERDP_LIB_CODEC_RFX_CONSTANTS_H + +#include <freerdp/api.h> + +/* sync */ +#define WF_MAGIC 0xCACCACCA +#define WF_VERSION_1_0 0x0100 + +/* blockType */ +#define WBT_SYNC 0xCCC0 +#define WBT_CODEC_VERSIONS 0xCCC1 +#define WBT_CHANNELS 0xCCC2 +#define WBT_CONTEXT 0xCCC3 +#define WBT_FRAME_BEGIN 0xCCC4 +#define WBT_FRAME_END 0xCCC5 +#define WBT_REGION 0xCCC6 +#define WBT_EXTENSION 0xCCC7 +#define CBT_REGION 0xCAC1 +#define CBT_TILESET 0xCAC2 +#define CBT_TILE 0xCAC3 + +#define PROGRESSIVE_WBT_SYNC 0xCCC0 +#define PROGRESSIVE_WBT_FRAME_BEGIN 0xCCC1 +#define PROGRESSIVE_WBT_FRAME_END 0xCCC2 +#define PROGRESSIVE_WBT_CONTEXT 0xCCC3 +#define PROGRESSIVE_WBT_REGION 0xCCC4 +#define PROGRESSIVE_WBT_TILE_SIMPLE 0xCCC5 +#define PROGRESSIVE_WBT_TILE_FIRST 0xCCC6 +#define PROGRESSIVE_WBT_TILE_UPGRADE 0xCCC7 + +/* tileSize */ +#define CT_TILE_64x64 0x0040 + +/* properties.flags */ +#define CODEC_MODE 0x02 + +/* properties.cct */ +#define COL_CONV_ICT 0x1 + +/* properties.xft */ +#define CLW_XFORM_DWT_53_A 0x1 + +/* properties.et */ +#define CLW_ENTROPY_RLGR1 0x01 +#define CLW_ENTROPY_RLGR3 0x04 + +/* properties.qt */ +#define SCALAR_QUANTIZATION 0x1 + +#ifdef __cplusplus +extern "C" +{ +#endif + + FREERDP_LOCAL const char* rfx_get_progressive_block_type_string(UINT16 blockType); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_LIB_CODEC_RFX_CONSTANTS_H */ diff --git a/libfreerdp/codec/rfx_decode.c b/libfreerdp/codec/rfx_decode.c new file mode 100644 index 0000000..4aa3c3d --- /dev/null +++ b/libfreerdp/codec/rfx_decode.c @@ -0,0 +1,102 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX Codec Library - Decode + * + * Copyright 2011 Vic Lee + * Copyright 2011 Norbert Federa <norbert.federa@thincast.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <winpr/stream.h> +#include <freerdp/primitives.h> + +#include "rfx_types.h" +#include "rfx_rlgr.h" +#include "rfx_differential.h" +#include "rfx_quantization.h" +#include "rfx_dwt.h" + +#include "rfx_decode.h" + +void rfx_decode_component(RFX_CONTEXT* WINPR_RESTRICT context, + const UINT32* WINPR_RESTRICT quantization_values, + const BYTE* WINPR_RESTRICT data, size_t size, + INT16* WINPR_RESTRICT buffer) +{ + INT16* dwt_buffer = NULL; + dwt_buffer = BufferPool_Take(context->priv->BufferPool, -1); /* dwt_buffer */ + PROFILER_ENTER(context->priv->prof_rfx_decode_component) + PROFILER_ENTER(context->priv->prof_rfx_rlgr_decode) + context->rlgr_decode(context->mode, data, size, buffer, 4096); + PROFILER_EXIT(context->priv->prof_rfx_rlgr_decode) + PROFILER_ENTER(context->priv->prof_rfx_differential_decode) + rfx_differential_decode(buffer + 4032, 64); + PROFILER_EXIT(context->priv->prof_rfx_differential_decode) + PROFILER_ENTER(context->priv->prof_rfx_quantization_decode) + context->quantization_decode(buffer, quantization_values); + PROFILER_EXIT(context->priv->prof_rfx_quantization_decode) + PROFILER_ENTER(context->priv->prof_rfx_dwt_2d_decode) + context->dwt_2d_decode(buffer, dwt_buffer); + PROFILER_EXIT(context->priv->prof_rfx_dwt_2d_decode) + PROFILER_EXIT(context->priv->prof_rfx_decode_component) + BufferPool_Return(context->priv->BufferPool, dwt_buffer); +} + +/* rfx_decode_ycbcr_to_rgb code now resides in the primitives library. */ + +/* stride is bytes between rows in the output buffer. */ +BOOL rfx_decode_rgb(RFX_CONTEXT* context, const RFX_TILE* tile, BYTE* rgb_buffer, UINT32 stride) +{ + union + { + const INT16** cpv; + INT16** pv; + } cnv; + BOOL rc = TRUE; + BYTE* pBuffer = NULL; + INT16* pSrcDst[3]; + UINT32* y_quants = NULL; + UINT32* cb_quants = NULL; + UINT32* cr_quants = NULL; + static const prim_size_t roi_64x64 = { 64, 64 }; + const primitives_t* prims = primitives_get(); + PROFILER_ENTER(context->priv->prof_rfx_decode_rgb) + y_quants = context->quants + (tile->quantIdxY * 10); + cb_quants = context->quants + (tile->quantIdxCb * 10); + cr_quants = context->quants + (tile->quantIdxCr * 10); + pBuffer = (BYTE*)BufferPool_Take(context->priv->BufferPool, -1); + pSrcDst[0] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 0) + 16])); /* y_r_buffer */ + pSrcDst[1] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 1) + 16])); /* cb_g_buffer */ + pSrcDst[2] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 2) + 16])); /* cr_b_buffer */ + rfx_decode_component(context, y_quants, tile->YData, tile->YLen, pSrcDst[0]); /* YData */ + rfx_decode_component(context, cb_quants, tile->CbData, tile->CbLen, pSrcDst[1]); /* CbData */ + rfx_decode_component(context, cr_quants, tile->CrData, tile->CrLen, pSrcDst[2]); /* CrData */ + PROFILER_ENTER(context->priv->prof_rfx_ycbcr_to_rgb) + + cnv.pv = pSrcDst; + if (prims->yCbCrToRGB_16s8u_P3AC4R(cnv.cpv, 64 * sizeof(INT16), rgb_buffer, stride, + context->pixel_format, &roi_64x64) != PRIMITIVES_SUCCESS) + rc = FALSE; + + PROFILER_EXIT(context->priv->prof_rfx_ycbcr_to_rgb) + PROFILER_EXIT(context->priv->prof_rfx_decode_rgb) + BufferPool_Return(context->priv->BufferPool, pBuffer); + return rc; +} diff --git a/libfreerdp/codec/rfx_decode.h b/libfreerdp/codec/rfx_decode.h new file mode 100644 index 0000000..978b192 --- /dev/null +++ b/libfreerdp/codec/rfx_decode.h @@ -0,0 +1,36 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX Codec Library - Decode + * + * Copyright 2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_LIB_CODEC_RFX_DECODE_H +#define FREERDP_LIB_CODEC_RFX_DECODE_H + +#include <winpr/wtypes.h> + +#include <freerdp/codec/rfx.h> +#include <freerdp/api.h> + +/* stride is bytes between rows in the output buffer. */ +FREERDP_LOCAL BOOL rfx_decode_rgb(RFX_CONTEXT* WINPR_RESTRICT context, + const RFX_TILE* WINPR_RESTRICT tile, + BYTE* WINPR_RESTRICT rgb_buffer, UINT32 stride); +FREERDP_LOCAL void rfx_decode_component(RFX_CONTEXT* WINPR_RESTRICT context, + const UINT32* WINPR_RESTRICT quantization_values, + const BYTE* WINPR_RESTRICT data, size_t size, + INT16* WINPR_RESTRICT buffer); +#endif /* FREERDP_LIB_CODEC_RFX_DECODE_H */ diff --git a/libfreerdp/codec/rfx_differential.h b/libfreerdp/codec/rfx_differential.h new file mode 100644 index 0000000..70a1a8c --- /dev/null +++ b/libfreerdp/codec/rfx_differential.h @@ -0,0 +1,50 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX Codec Library - Differential Encoding + * + * Copyright 2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_LIB_CODEC_RFX_DIFFERENTIAL_H +#define FREERDP_LIB_CODEC_RFX_DIFFERENTIAL_H + +#include <freerdp/codec/rfx.h> +#include <freerdp/api.h> + +static INLINE void rfx_differential_decode(INT16* buffer, int size) +{ + INT16* ptr = buffer; + INT16* end = &buffer[size - 1]; + + while (ptr != end) + { + ptr[1] += ptr[0]; + ptr++; + } +} + +static INLINE void rfx_differential_encode(INT16* buffer, int size) +{ + INT16 n1 = buffer[0]; + for (int x = 0; x < size - 1; x++) + { + INT16* dst = &buffer[x + 1]; + const INT16 n2 = *dst; + *dst -= n1; + n1 = n2; + } +} + +#endif /* FREERDP_LIB_CODEC_RFX_DIFFERENTIAL_H */ diff --git a/libfreerdp/codec/rfx_dwt.c b/libfreerdp/codec/rfx_dwt.c new file mode 100644 index 0000000..e79e9da --- /dev/null +++ b/libfreerdp/codec/rfx_dwt.c @@ -0,0 +1,218 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX Codec Library - DWT + * + * Copyright 2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "rfx_dwt.h" + +static void rfx_dwt_2d_decode_block(INT16* buffer, INT16* idwt, size_t subband_width) +{ + INT16* dst = NULL; + INT16* l = NULL; + INT16* h = NULL; + INT16* l_dst = NULL; + INT16* h_dst = NULL; + INT16* hl = NULL; + INT16* lh = NULL; + INT16* hh = NULL; + INT16* ll = NULL; + + const size_t total_width = subband_width << 1; + + /* Inverse DWT in horizontal direction, results in 2 sub-bands in L, H order in tmp buffer idwt. + */ + /* The 4 sub-bands are stored in HL(0), LH(1), HH(2), LL(3) order. */ + /* The lower part L uses LL(3) and HL(0). */ + /* The higher part H uses LH(1) and HH(2). */ + + ll = buffer + subband_width * subband_width * 3; + hl = buffer; + l_dst = idwt; + + lh = buffer + subband_width * subband_width; + hh = buffer + subband_width * subband_width * 2; + h_dst = idwt + subband_width * subband_width * 2; + + for (size_t y = 0; y < subband_width; y++) + { + /* Even coefficients */ + l_dst[0] = ll[0] - ((hl[0] + hl[0] + 1) >> 1); + h_dst[0] = lh[0] - ((hh[0] + hh[0] + 1) >> 1); + for (size_t n = 1; n < subband_width; n++) + { + const size_t x = n << 1; + l_dst[x] = ll[n] - ((hl[n - 1] + hl[n] + 1) >> 1); + h_dst[x] = lh[n] - ((hh[n - 1] + hh[n] + 1) >> 1); + } + + /* Odd coefficients */ + size_t n = 0; + for (; n < subband_width - 1; n++) + { + const size_t x = n << 1; + l_dst[x + 1] = (hl[n] << 1) + ((l_dst[x] + l_dst[x + 2]) >> 1); + h_dst[x + 1] = (hh[n] << 1) + ((h_dst[x] + h_dst[x + 2]) >> 1); + } + + const size_t x = n << 1; + l_dst[x + 1] = (hl[n] << 1) + (l_dst[x]); + h_dst[x + 1] = (hh[n] << 1) + (h_dst[x]); + + ll += subband_width; + hl += subband_width; + l_dst += total_width; + + lh += subband_width; + hh += subband_width; + h_dst += total_width; + } + + /* Inverse DWT in vertical direction, results are stored in original buffer. */ + for (size_t x = 0; x < total_width; x++) + { + l = idwt + x; + h = idwt + x + subband_width * total_width; + dst = buffer + x; + + *dst = *l - ((*h * 2 + 1) >> 1); + + for (size_t n = 1; n < subband_width; n++) + { + l += total_width; + h += total_width; + + /* Even coefficients */ + dst[2 * total_width] = *l - ((*(h - total_width) + *h + 1) >> 1); + + /* Odd coefficients */ + dst[total_width] = (*(h - total_width) << 1) + ((*dst + dst[2 * total_width]) >> 1); + + dst += 2 * total_width; + } + + dst[total_width] = (*h << 1) + ((*dst * 2) >> 1); + } +} + +void rfx_dwt_2d_decode(INT16* buffer, INT16* dwt_buffer) +{ + WINPR_ASSERT(buffer); + WINPR_ASSERT(dwt_buffer); + + rfx_dwt_2d_decode_block(&buffer[3840], dwt_buffer, 8); + rfx_dwt_2d_decode_block(&buffer[3072], dwt_buffer, 16); + rfx_dwt_2d_decode_block(&buffer[0], dwt_buffer, 32); +} + +static void rfx_dwt_2d_encode_block(INT16* buffer, INT16* dwt, UINT32 subband_width) +{ + INT16* src = NULL; + INT16* l = NULL; + INT16* h = NULL; + INT16* l_src = NULL; + INT16* h_src = NULL; + INT16* hl = NULL; + INT16* lh = NULL; + INT16* hh = NULL; + INT16* ll = NULL; + + const UINT32 total_width = subband_width << 1; + + /* DWT in vertical direction, results in 2 sub-bands in L, H order in tmp buffer dwt. */ + for (UINT32 x = 0; x < total_width; x++) + { + for (UINT32 n = 0; n < subband_width; n++) + { + UINT32 y = n << 1; + l = dwt + n * total_width + x; + h = l + subband_width * total_width; + src = buffer + y * total_width + x; + + /* H */ + *h = (src[total_width] - + ((src[0] + src[n < subband_width - 1 ? 2 * total_width : 0]) >> 1)) >> + 1; + + /* L */ + *l = src[0] + (n == 0 ? *h : (*(h - total_width) + *h) >> 1); + } + } + + /* DWT in horizontal direction, results in 4 sub-bands in HL(0), LH(1), HH(2), LL(3) order, + * stored in original buffer. */ + /* The lower part L generates LL(3) and HL(0). */ + /* The higher part H generates LH(1) and HH(2). */ + + ll = buffer + subband_width * subband_width * 3; + hl = buffer; + l_src = dwt; + + lh = buffer + subband_width * subband_width; + hh = buffer + subband_width * subband_width * 2; + h_src = dwt + subband_width * subband_width * 2; + + for (UINT32 y = 0; y < subband_width; y++) + { + /* L */ + for (UINT32 n = 0; n < subband_width; n++) + { + UINT32 x = n << 1; + + /* HL */ + hl[n] = + (l_src[x + 1] - ((l_src[x] + l_src[n < subband_width - 1 ? x + 2 : x]) >> 1)) >> 1; + /* LL */ + ll[n] = l_src[x] + (n == 0 ? hl[n] : (hl[n - 1] + hl[n]) >> 1); + } + + /* H */ + for (UINT32 n = 0; n < subband_width; n++) + { + UINT32 x = n << 1; + + /* HH */ + hh[n] = + (h_src[x + 1] - ((h_src[x] + h_src[n < subband_width - 1 ? x + 2 : x]) >> 1)) >> 1; + /* LH */ + lh[n] = h_src[x] + (n == 0 ? hh[n] : (hh[n - 1] + hh[n]) >> 1); + } + + ll += subband_width; + hl += subband_width; + l_src += total_width; + + lh += subband_width; + hh += subband_width; + h_src += total_width; + } +} + +void rfx_dwt_2d_encode(INT16* buffer, INT16* dwt_buffer) +{ + WINPR_ASSERT(buffer); + WINPR_ASSERT(dwt_buffer); + + rfx_dwt_2d_encode_block(&buffer[0], dwt_buffer, 32); + rfx_dwt_2d_encode_block(&buffer[3072], dwt_buffer, 16); + rfx_dwt_2d_encode_block(&buffer[3840], dwt_buffer, 8); +} diff --git a/libfreerdp/codec/rfx_dwt.h b/libfreerdp/codec/rfx_dwt.h new file mode 100644 index 0000000..bd2104b --- /dev/null +++ b/libfreerdp/codec/rfx_dwt.h @@ -0,0 +1,31 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX Codec Library - DWT + * + * Copyright 2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_LIB_CODEC_RFX_DWT_H +#define FREERDP_LIB_CODEC_RFX_DWT_H + +#include <winpr/wtypes.h> +#include <freerdp/codec/rfx.h> +#include <freerdp/api.h> + +FREERDP_LOCAL void rfx_dwt_2d_decode(INT16* buffer, INT16* dwt_buffer); +FREERDP_LOCAL void rfx_dwt_2d_encode(INT16* buffer, INT16* dwt_buffer); +FREERDP_LOCAL void rfx_dwt_2d_extrapolate_decode(INT16* buffer, INT16* dwt_buffer); + +#endif /* FREERDP_LIB_CODEC_RFX_DWT_H */ diff --git a/libfreerdp/codec/rfx_encode.c b/libfreerdp/codec/rfx_encode.c new file mode 100644 index 0000000..12bc77b --- /dev/null +++ b/libfreerdp/codec/rfx_encode.c @@ -0,0 +1,313 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX Codec Library - Encode + * + * Copyright 2011 Vic Lee + * Copyright 2011 Norbert Federa <norbert.federa@thincast.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <winpr/crt.h> +#include <winpr/collections.h> + +#include <freerdp/primitives.h> + +#include "rfx_types.h" +#include "rfx_rlgr.h" +#include "rfx_differential.h" +#include "rfx_quantization.h" +#include "rfx_dwt.h" + +#include "rfx_encode.h" + +#define MINMAX(_v, _l, _h) ((_v) < (_l) ? (_l) : ((_v) > (_h) ? (_h) : (_v))) + +static void rfx_encode_format_rgb(const BYTE* rgb_data, int width, int height, int rowstride, + UINT32 pixel_format, const BYTE* palette, INT16* r_buf, + INT16* g_buf, INT16* b_buf) +{ + int x_exceed = 0; + int y_exceed = 0; + const BYTE* src = NULL; + INT16 r = 0; + INT16 g = 0; + INT16 b = 0; + INT16* r_last = NULL; + INT16* g_last = NULL; + INT16* b_last = NULL; + x_exceed = 64 - width; + y_exceed = 64 - height; + + for (int y = 0; y < height; y++) + { + src = rgb_data + y * rowstride; + + switch (pixel_format) + { + case PIXEL_FORMAT_BGRX32: + case PIXEL_FORMAT_BGRA32: + for (int x = 0; x < width; x++) + { + *b_buf++ = (INT16)(*src++); + *g_buf++ = (INT16)(*src++); + *r_buf++ = (INT16)(*src++); + src++; + } + + break; + + case PIXEL_FORMAT_XBGR32: + case PIXEL_FORMAT_ABGR32: + for (int x = 0; x < width; x++) + { + src++; + *b_buf++ = (INT16)(*src++); + *g_buf++ = (INT16)(*src++); + *r_buf++ = (INT16)(*src++); + } + + break; + + case PIXEL_FORMAT_RGBX32: + case PIXEL_FORMAT_RGBA32: + for (int x = 0; x < width; x++) + { + *r_buf++ = (INT16)(*src++); + *g_buf++ = (INT16)(*src++); + *b_buf++ = (INT16)(*src++); + src++; + } + + break; + + case PIXEL_FORMAT_XRGB32: + case PIXEL_FORMAT_ARGB32: + for (int x = 0; x < width; x++) + { + src++; + *r_buf++ = (INT16)(*src++); + *g_buf++ = (INT16)(*src++); + *b_buf++ = (INT16)(*src++); + } + + break; + + case PIXEL_FORMAT_BGR24: + for (int x = 0; x < width; x++) + { + *b_buf++ = (INT16)(*src++); + *g_buf++ = (INT16)(*src++); + *r_buf++ = (INT16)(*src++); + } + + break; + + case PIXEL_FORMAT_RGB24: + for (int x = 0; x < width; x++) + { + *r_buf++ = (INT16)(*src++); + *g_buf++ = (INT16)(*src++); + *b_buf++ = (INT16)(*src++); + } + + break; + + case PIXEL_FORMAT_BGR16: + for (int x = 0; x < width; x++) + { + *b_buf++ = (INT16)(((*(src + 1)) & 0xF8) | ((*(src + 1)) >> 5)); + *g_buf++ = (INT16)((((*(src + 1)) & 0x07) << 5) | (((*src) & 0xE0) >> 3)); + *r_buf++ = (INT16)((((*src) & 0x1F) << 3) | (((*src) >> 2) & 0x07)); + src += 2; + } + + break; + + case PIXEL_FORMAT_RGB16: + for (int x = 0; x < width; x++) + { + *r_buf++ = (INT16)(((*(src + 1)) & 0xF8) | ((*(src + 1)) >> 5)); + *g_buf++ = (INT16)((((*(src + 1)) & 0x07) << 5) | (((*src) & 0xE0) >> 3)); + *b_buf++ = (INT16)((((*src) & 0x1F) << 3) | (((*src) >> 2) & 0x07)); + src += 2; + } + + break; + + case PIXEL_FORMAT_RGB8: + if (!palette) + break; + + for (int x = 0; x < width; x++) + { + int shift = 0; + BYTE idx = 0; + shift = (7 - (x % 8)); + idx = ((*src) >> shift) & 1; + idx |= (((*(src + 1)) >> shift) & 1) << 1; + idx |= (((*(src + 2)) >> shift) & 1) << 2; + idx |= (((*(src + 3)) >> shift) & 1) << 3; + idx *= 3; + *r_buf++ = (INT16)palette[idx]; + *g_buf++ = (INT16)palette[idx + 1]; + *b_buf++ = (INT16)palette[idx + 2]; + + if (shift == 0) + src += 4; + } + + break; + + case PIXEL_FORMAT_A4: + if (!palette) + break; + + for (int x = 0; x < width; x++) + { + int idx = (*src) * 3; + *r_buf++ = (INT16)palette[idx]; + *g_buf++ = (INT16)palette[idx + 1]; + *b_buf++ = (INT16)palette[idx + 2]; + src++; + } + + break; + + default: + break; + } + + /* Fill the horizontal region outside of 64x64 tile size with the right-most pixel for best + * quality */ + if (x_exceed > 0) + { + r = *(r_buf - 1); + g = *(g_buf - 1); + b = *(b_buf - 1); + + for (int x = 0; x < x_exceed; x++) + { + *r_buf++ = r; + *g_buf++ = g; + *b_buf++ = b; + } + } + } + + /* Fill the vertical region outside of 64x64 tile size with the last line. */ + if (y_exceed > 0) + { + r_last = r_buf - 64; + g_last = g_buf - 64; + b_last = b_buf - 64; + + while (y_exceed > 0) + { + CopyMemory(r_buf, r_last, 64 * sizeof(INT16)); + CopyMemory(g_buf, g_last, 64 * sizeof(INT16)); + CopyMemory(b_buf, b_last, 64 * sizeof(INT16)); + r_buf += 64; + g_buf += 64; + b_buf += 64; + y_exceed--; + } + } +} + +/* rfx_encode_rgb_to_ycbcr code now resides in the primitives library. */ + +static void rfx_encode_component(RFX_CONTEXT* context, const UINT32* quantization_values, + INT16* data, BYTE* buffer, int buffer_size, int* size) +{ + INT16* dwt_buffer = NULL; + dwt_buffer = BufferPool_Take(context->priv->BufferPool, -1); /* dwt_buffer */ + PROFILER_ENTER(context->priv->prof_rfx_encode_component) + PROFILER_ENTER(context->priv->prof_rfx_dwt_2d_encode) + context->dwt_2d_encode(data, dwt_buffer); + PROFILER_EXIT(context->priv->prof_rfx_dwt_2d_encode) + PROFILER_ENTER(context->priv->prof_rfx_quantization_encode) + context->quantization_encode(data, quantization_values); + PROFILER_EXIT(context->priv->prof_rfx_quantization_encode) + PROFILER_ENTER(context->priv->prof_rfx_differential_encode) + rfx_differential_encode(data + 4032, 64); + PROFILER_EXIT(context->priv->prof_rfx_differential_encode) + PROFILER_ENTER(context->priv->prof_rfx_rlgr_encode) + *size = context->rlgr_encode(context->mode, data, 4096, buffer, buffer_size); + PROFILER_EXIT(context->priv->prof_rfx_rlgr_encode) + PROFILER_EXIT(context->priv->prof_rfx_encode_component) + BufferPool_Return(context->priv->BufferPool, dwt_buffer); +} + +void rfx_encode_rgb(RFX_CONTEXT* context, RFX_TILE* tile) +{ + union + { + const INT16** cpv; + INT16** pv; + } cnv; + BYTE* pBuffer = NULL; + INT16* pSrcDst[3]; + int YLen = 0; + int CbLen = 0; + int CrLen = 0; + UINT32* YQuant = NULL; + UINT32* CbQuant = NULL; + UINT32* CrQuant = NULL; + primitives_t* prims = primitives_get(); + static const prim_size_t roi_64x64 = { 64, 64 }; + + if (!(pBuffer = (BYTE*)BufferPool_Take(context->priv->BufferPool, -1))) + return; + + YLen = CbLen = CrLen = 0; + YQuant = context->quants + (tile->quantIdxY * 10); + CbQuant = context->quants + (tile->quantIdxCb * 10); + CrQuant = context->quants + (tile->quantIdxCr * 10); + pSrcDst[0] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 0) + 16])); /* y_r_buffer */ + pSrcDst[1] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 1) + 16])); /* cb_g_buffer */ + pSrcDst[2] = (INT16*)((BYTE*)(&pBuffer[((8192 + 32) * 2) + 16])); /* cr_b_buffer */ + PROFILER_ENTER(context->priv->prof_rfx_encode_rgb) + PROFILER_ENTER(context->priv->prof_rfx_encode_format_rgb) + rfx_encode_format_rgb(tile->data, tile->width, tile->height, tile->scanline, + context->pixel_format, context->palette, pSrcDst[0], pSrcDst[1], + pSrcDst[2]); + PROFILER_EXIT(context->priv->prof_rfx_encode_format_rgb) + PROFILER_ENTER(context->priv->prof_rfx_rgb_to_ycbcr) + + cnv.pv = pSrcDst; + prims->RGBToYCbCr_16s16s_P3P3(cnv.cpv, 64 * sizeof(INT16), pSrcDst, 64 * sizeof(INT16), + &roi_64x64); + PROFILER_EXIT(context->priv->prof_rfx_rgb_to_ycbcr) + /** + * We need to clear the buffers as the RLGR encoder expects it to be initialized to zero. + * This allows simplifying and improving the performance of the encoding process. + */ + ZeroMemory(tile->YData, 4096); + ZeroMemory(tile->CbData, 4096); + ZeroMemory(tile->CrData, 4096); + rfx_encode_component(context, YQuant, pSrcDst[0], tile->YData, 4096, &YLen); + rfx_encode_component(context, CbQuant, pSrcDst[1], tile->CbData, 4096, &CbLen); + rfx_encode_component(context, CrQuant, pSrcDst[2], tile->CrData, 4096, &CrLen); + tile->YLen = (UINT16)YLen; + tile->CbLen = (UINT16)CbLen; + tile->CrLen = (UINT16)CrLen; + PROFILER_EXIT(context->priv->prof_rfx_encode_rgb) + BufferPool_Return(context->priv->BufferPool, pBuffer); +} diff --git a/libfreerdp/codec/rfx_encode.h b/libfreerdp/codec/rfx_encode.h new file mode 100644 index 0000000..0080645 --- /dev/null +++ b/libfreerdp/codec/rfx_encode.h @@ -0,0 +1,28 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX Codec Library - Encode + * + * Copyright 2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_LIB_CODEC_RFX_ENCODE_H +#define FREERDP_LIB_CODEC_RFX_ENCODE_H + +#include <freerdp/codec/rfx.h> +#include <freerdp/api.h> + +FREERDP_LOCAL void rfx_encode_rgb(RFX_CONTEXT* context, RFX_TILE* tile); + +#endif /* FREERDP_LIB_CODEC_RFX_ENCODE_H */ diff --git a/libfreerdp/codec/rfx_neon.c b/libfreerdp/codec/rfx_neon.c new file mode 100644 index 0000000..f723efd --- /dev/null +++ b/libfreerdp/codec/rfx_neon.c @@ -0,0 +1,536 @@ +/* + FreeRDP: A Remote Desktop Protocol Implementation + RemoteFX Codec Library - NEON Optimizations + + Copyright 2011 Martin Fleisz <martin.fleisz@thincast.com> + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#include <freerdp/config.h> + +#if defined(WITH_NEON) + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <arm_neon.h> +#include <winpr/sysinfo.h> + +#include "rfx_types.h" +#include "rfx_neon.h" + +/* rfx_decode_YCbCr_to_RGB_NEON code now resides in the primitives library. */ + +static __inline void __attribute__((__gnu_inline__, __always_inline__, __artificial__)) +rfx_quantization_decode_block_NEON(INT16* buffer, const size_t buffer_size, const UINT32 factor) +{ + int16x8_t quantFactors = vdupq_n_s16(factor); + int16x8_t* buf = (int16x8_t*)buffer; + int16x8_t* buf_end = (int16x8_t*)(buffer + buffer_size); + + do + { + int16x8_t val = vld1q_s16((INT16*)buf); + val = vshlq_s16(val, quantFactors); + vst1q_s16((INT16*)buf, val); + buf++; + } while (buf < buf_end); +} + +static void rfx_quantization_decode_NEON(INT16* buffer, const UINT32* WINPR_RESTRICT quantVals) +{ + WINPR_ASSERT(buffer); + WINPR_ASSERT(quantVals); + + rfx_quantization_decode_block_NEON(&buffer[0], 1024, quantVals[8] - 1); /* HL1 */ + rfx_quantization_decode_block_NEON(&buffer[1024], 1024, quantVals[7] - 1); /* LH1 */ + rfx_quantization_decode_block_NEON(&buffer[2048], 1024, quantVals[9] - 1); /* HH1 */ + rfx_quantization_decode_block_NEON(&buffer[3072], 256, quantVals[5] - 1); /* HL2 */ + rfx_quantization_decode_block_NEON(&buffer[3328], 256, quantVals[4] - 1); /* LH2 */ + rfx_quantization_decode_block_NEON(&buffer[3584], 256, quantVals[6] - 1); /* HH2 */ + rfx_quantization_decode_block_NEON(&buffer[3840], 64, quantVals[2] - 1); /* HL3 */ + rfx_quantization_decode_block_NEON(&buffer[3904], 64, quantVals[1] - 1); /* LH3 */ + rfx_quantization_decode_block_NEON(&buffer[3968], 64, quantVals[3] - 1); /* HH3 */ + rfx_quantization_decode_block_NEON(&buffer[4032], 64, quantVals[0] - 1); /* LL3 */ +} + +static __inline void __attribute__((__gnu_inline__, __always_inline__, __artificial__)) +rfx_dwt_2d_decode_block_horiz_NEON(INT16* WINPR_RESTRICT l, INT16* WINPR_RESTRICT h, + INT16* WINPR_RESTRICT dst, size_t subband_width) +{ + INT16* l_ptr = l; + INT16* h_ptr = h; + INT16* dst_ptr = dst; + + for (size_t y = 0; y < subband_width; y++) + { + /* Even coefficients */ + for (size_t n = 0; n < subband_width; n += 8) + { + // dst[2n] = l[n] - ((h[n-1] + h[n] + 1) >> 1); + int16x8_t l_n = vld1q_s16(l_ptr); + int16x8_t h_n = vld1q_s16(h_ptr); + int16x8_t h_n_m = vld1q_s16(h_ptr - 1); + + if (n == 0) + { + int16_t first = vgetq_lane_s16(h_n_m, 1); + h_n_m = vsetq_lane_s16(first, h_n_m, 0); + } + + int16x8_t tmp_n = vaddq_s16(h_n, h_n_m); + tmp_n = vaddq_s16(tmp_n, vdupq_n_s16(1)); + tmp_n = vshrq_n_s16(tmp_n, 1); + int16x8_t dst_n = vsubq_s16(l_n, tmp_n); + vst1q_s16(l_ptr, dst_n); + l_ptr += 8; + h_ptr += 8; + } + + l_ptr -= subband_width; + h_ptr -= subband_width; + + /* Odd coefficients */ + for (size_t n = 0; n < subband_width; n += 8) + { + // dst[2n + 1] = (h[n] << 1) + ((dst[2n] + dst[2n + 2]) >> 1); + int16x8_t h_n = vld1q_s16(h_ptr); + h_n = vshlq_n_s16(h_n, 1); + int16x8x2_t dst_n; + dst_n.val[0] = vld1q_s16(l_ptr); + int16x8_t dst_n_p = vld1q_s16(l_ptr + 1); + + if (n == subband_width - 8) + { + int16_t last = vgetq_lane_s16(dst_n_p, 6); + dst_n_p = vsetq_lane_s16(last, dst_n_p, 7); + } + + dst_n.val[1] = vaddq_s16(dst_n_p, dst_n.val[0]); + dst_n.val[1] = vshrq_n_s16(dst_n.val[1], 1); + dst_n.val[1] = vaddq_s16(dst_n.val[1], h_n); + vst2q_s16(dst_ptr, dst_n); + l_ptr += 8; + h_ptr += 8; + dst_ptr += 16; + } + } +} + +static __inline void __attribute__((__gnu_inline__, __always_inline__, __artificial__)) +rfx_dwt_2d_decode_block_vert_NEON(INT16* WINPR_RESTRICT l, INT16* WINPR_RESTRICT h, + INT16* WINPR_RESTRICT dst, size_t subband_width) +{ + INT16* l_ptr = l; + INT16* h_ptr = h; + INT16* dst_ptr = dst; + const size_t total_width = subband_width + subband_width; + + /* Even coefficients */ + for (size_t n = 0; n < subband_width; n++) + { + for (size_t x = 0; x < total_width; x += 8) + { + // dst[2n] = l[n] - ((h[n-1] + h[n] + 1) >> 1); + int16x8_t l_n = vld1q_s16(l_ptr); + int16x8_t h_n = vld1q_s16(h_ptr); + int16x8_t tmp_n = vaddq_s16(h_n, vdupq_n_s16(1)); + + if (n == 0) + tmp_n = vaddq_s16(tmp_n, h_n); + else + { + int16x8_t h_n_m = vld1q_s16((h_ptr - total_width)); + tmp_n = vaddq_s16(tmp_n, h_n_m); + } + + tmp_n = vshrq_n_s16(tmp_n, 1); + int16x8_t dst_n = vsubq_s16(l_n, tmp_n); + vst1q_s16(dst_ptr, dst_n); + l_ptr += 8; + h_ptr += 8; + dst_ptr += 8; + } + + dst_ptr += total_width; + } + + h_ptr = h; + dst_ptr = dst + total_width; + + /* Odd coefficients */ + for (size_t n = 0; n < subband_width; n++) + { + for (size_t x = 0; x < total_width; x += 8) + { + // dst[2n + 1] = (h[n] << 1) + ((dst[2n] + dst[2n + 2]) >> 1); + int16x8_t h_n = vld1q_s16(h_ptr); + int16x8_t dst_n_m = vld1q_s16(dst_ptr - total_width); + h_n = vshlq_n_s16(h_n, 1); + int16x8_t tmp_n = dst_n_m; + + if (n == subband_width - 1) + tmp_n = vaddq_s16(tmp_n, dst_n_m); + else + { + int16x8_t dst_n_p = vld1q_s16((dst_ptr + total_width)); + tmp_n = vaddq_s16(tmp_n, dst_n_p); + } + + tmp_n = vshrq_n_s16(tmp_n, 1); + int16x8_t dst_n = vaddq_s16(tmp_n, h_n); + vst1q_s16(dst_ptr, dst_n); + h_ptr += 8; + dst_ptr += 8; + } + + dst_ptr += total_width; + } +} + +static __inline void __attribute__((__gnu_inline__, __always_inline__, __artificial__)) +rfx_dwt_2d_decode_block_NEON(INT16* WINPR_RESTRICT buffer, INT16* WINPR_RESTRICT idwt, + size_t subband_width) +{ + INT16 *hl, *lh, *hh, *ll; + INT16 *l_dst, *h_dst; + /* Inverse DWT in horizontal direction, results in 2 sub-bands in L, H order in tmp buffer idwt. + */ + /* The 4 sub-bands are stored in HL(0), LH(1), HH(2), LL(3) order. */ + /* The lower part L uses LL(3) and HL(0). */ + /* The higher part H uses LH(1) and HH(2). */ + ll = buffer + subband_width * subband_width * 3; + hl = buffer; + l_dst = idwt; + rfx_dwt_2d_decode_block_horiz_NEON(ll, hl, l_dst, subband_width); + lh = buffer + subband_width * subband_width; + hh = buffer + subband_width * subband_width * 2; + h_dst = idwt + subband_width * subband_width * 2; + rfx_dwt_2d_decode_block_horiz_NEON(lh, hh, h_dst, subband_width); + /* Inverse DWT in vertical direction, results are stored in original buffer. */ + rfx_dwt_2d_decode_block_vert_NEON(l_dst, h_dst, buffer, subband_width); +} + +static void rfx_dwt_2d_decode_NEON(INT16* buffer, INT16* dwt_buffer) +{ + rfx_dwt_2d_decode_block_NEON(buffer + 3840, dwt_buffer, 8); + rfx_dwt_2d_decode_block_NEON(buffer + 3072, dwt_buffer, 16); + rfx_dwt_2d_decode_block_NEON(buffer, dwt_buffer, 32); +} + +static INLINE void rfx_idwt_extrapolate_horiz_neon(INT16* restrict pLowBand, size_t nLowStep, + const INT16* restrict pHighBand, + size_t nHighStep, INT16* restrict pDstBand, + size_t nDstStep, size_t nLowCount, + size_t nHighCount, size_t nDstCount) +{ + WINPR_ASSERT(pLowBand); + WINPR_ASSERT(pHighBand); + WINPR_ASSERT(pDstBand); + + INT16* l_ptr = pLowBand; + const INT16* h_ptr = pHighBand; + INT16* dst_ptr = pDstBand; + size_t batchSize = (nLowCount + nHighCount) >> 1; + + for (size_t y = 0; y < nDstCount; y++) + { + /* Even coefficients */ + size_t n = 0; + for (; n < batchSize; n += 8) + { + // dst[2n] = l[n] - ((h[n-1] + h[n] + 1) >> 1); + int16x8_t l_n = vld1q_s16(l_ptr); + int16x8_t h_n = vld1q_s16(h_ptr); + int16x8_t h_n_m = vld1q_s16(h_ptr - 1); + + if (n == 0) + { + int16_t first = vgetq_lane_s16(h_n_m, 1); + h_n_m = vsetq_lane_s16(first, h_n_m, 0); + } + else if (n == 24) + h_n = vsetq_lane_s16(0, h_n, 7); + + int16x8_t tmp_n = vaddq_s16(h_n, h_n_m); + tmp_n = vaddq_s16(tmp_n, vdupq_n_s16(1)); + tmp_n = vshrq_n_s16(tmp_n, 1); + int16x8_t dst_n = vsubq_s16(l_n, tmp_n); + vst1q_s16(l_ptr, dst_n); + l_ptr += 8; + h_ptr += 8; + } + if (n < 32) + *l_ptr -= *(h_ptr - 1); + + l_ptr -= batchSize; + h_ptr -= batchSize; + + /* Odd coefficients */ + n = 0; + for (; n < batchSize; n += 8) + { + // dst[2n + 1] = (h[n] << 1) + ((dst[2n] + dst[2n + 2]) >> 1); + int16x8_t h_n = vld1q_s16(h_ptr); + h_n = vshlq_n_s16(h_n, 1); + int16x8x2_t dst_n; + dst_n.val[0] = vld1q_s16(l_ptr); + int16x8_t dst_n_p = vld1q_s16(l_ptr + 1); + + if (n == 24) + h_n = vsetq_lane_s16(0, h_n, 7); + + dst_n.val[1] = vaddq_s16(dst_n_p, dst_n.val[0]); + dst_n.val[1] = vshrq_n_s16(dst_n.val[1], 1); + dst_n.val[1] = vaddq_s16(dst_n.val[1], h_n); + vst2q_s16(dst_ptr, dst_n); + l_ptr += 8; + h_ptr += 8; + dst_ptr += 16; + } + if (n == 32) + { + h_ptr -= 1; + l_ptr += 1; + } + else + { + *dst_ptr = *l_ptr; + l_ptr += 1; + dst_ptr += 1; + } + } +} + +static INLINE void rfx_idwt_extrapolate_vert_neon(const INT16* restrict pLowBand, size_t nLowStep, + const INT16* restrict pHighBand, size_t nHighStep, + INT16* restrict pDstBand, size_t nDstStep, + size_t nLowCount, size_t nHighCount, + size_t nDstCount) +{ + WINPR_ASSERT(pLowBand); + WINPR_ASSERT(pHighBand); + WINPR_ASSERT(pDstBand); + + const INT16* l_ptr = pLowBand; + const INT16* h_ptr = pHighBand; + INT16* dst_ptr = pDstBand; + size_t batchSize = (nDstCount >> 3) << 3; + size_t forceBandSize = (nLowCount + nHighCount) >> 1; + + /* Even coefficients */ + for (size_t n = 0; n < forceBandSize; n++) + { + for (size_t x = 0; x < batchSize; x += 8) + { + // dst[2n] = l[n] - ((h[n-1] + h[n] + 1) >> 1); + int16x8_t l_n = vld1q_s16(l_ptr); + int16x8_t h_n = vld1q_s16((n == 31) ? (h_ptr - nHighStep) : h_ptr); + int16x8_t tmp_n = vaddq_s16(h_n, vdupq_n_s16(1)); + + if (n == 0) + tmp_n = vaddq_s16(tmp_n, h_n); + else if (n < 31) + { + int16x8_t h_n_m = vld1q_s16((h_ptr - nHighStep)); + tmp_n = vaddq_s16(tmp_n, h_n_m); + } + + tmp_n = vshrq_n_s16(tmp_n, 1); + int16x8_t dst_n = vsubq_s16(l_n, tmp_n); + vst1q_s16(dst_ptr, dst_n); + l_ptr += 8; + h_ptr += 8; + dst_ptr += 8; + } + + if (nDstCount > batchSize) + { + int16_t h_n = (n == 31) ? *(h_ptr - nHighStep) : *h_ptr; + int16_t tmp_n = h_n + 1; + if (n == 0) + tmp_n += h_n; + else if (n < 31) + tmp_n += *(h_ptr - nHighStep); + tmp_n >>= 1; + *dst_ptr = *l_ptr - tmp_n; + l_ptr += 1; + h_ptr += 1; + dst_ptr += 1; + } + + dst_ptr += nDstStep; + } + + if (forceBandSize < 32) + { + for (size_t x = 0; x < batchSize; x += 8) + { + int16x8_t l_n = vld1q_s16(l_ptr); + int16x8_t h_n = vld1q_s16(h_ptr - nHighStep); + int16x8_t tmp_n = vsubq_s16(l_n, h_n); + vst1q_s16(dst_ptr, tmp_n); + l_ptr += 8; + h_ptr += 8; + dst_ptr += 8; + } + + if (nDstCount > batchSize) + { + *dst_ptr = *l_ptr - *(h_ptr - nHighStep); + l_ptr += 1; + h_ptr += 1; + dst_ptr += 1; + } + } + + h_ptr = pHighBand; + dst_ptr = pDstBand + nDstStep; + + /* Odd coefficients */ + for (size_t n = 0; n < forceBandSize; n++) + { + for (size_t x = 0; x < batchSize; x += 8) + { + // dst[2n + 1] = (h[n] << 1) + ((dst[2n] + dst[2n + 2]) >> 1); + int16x8_t tmp_n = vld1q_s16(dst_ptr - nDstStep); + if (n == 31) + { + int16x8_t dst_n_p = vld1q_s16(l_ptr); + l_ptr += 8; + tmp_n = vaddq_s16(tmp_n, dst_n_p); + tmp_n = vshrq_n_s16(tmp_n, 1); + } + else + { + int16x8_t dst_n_p = vld1q_s16(dst_ptr + nDstStep); + tmp_n = vaddq_s16(tmp_n, dst_n_p); + tmp_n = vshrq_n_s16(tmp_n, 1); + int16x8_t h_n = vld1q_s16(h_ptr); + h_n = vshlq_n_s16(h_n, 1); + tmp_n = vaddq_s16(tmp_n, h_n); + } + vst1q_s16(dst_ptr, tmp_n); + h_ptr += 8; + dst_ptr += 8; + } + + if (nDstCount > batchSize) + { + int16_t tmp_n = *(dst_ptr - nDstStep); + if (n == 31) + { + int16_t dst_n_p = *l_ptr; + l_ptr += 1; + tmp_n += dst_n_p; + tmp_n >>= 1; + } + else + { + int16_t dst_n_p = *(dst_ptr + nDstStep); + tmp_n += dst_n_p; + tmp_n >>= 1; + int16_t h_n = *h_ptr; + h_n <<= 1; + tmp_n += h_n; + } + *dst_ptr = tmp_n; + h_ptr += 1; + dst_ptr += 1; + } + + dst_ptr += nDstStep; + } +} + +static INLINE size_t prfx_get_band_l_count(size_t level) +{ + return (64 >> level) + 1; +} + +static INLINE size_t prfx_get_band_h_count(size_t level) +{ + if (level == 1) + return (64 >> 1) - 1; + else + return (64 + (1 << (level - 1))) >> level; +} + +static INLINE void rfx_dwt_2d_decode_extrapolate_block_neon(INT16* buffer, INT16* temp, + size_t level) +{ + size_t nDstStepX; + size_t nDstStepY; + INT16 *HL, *LH; + INT16 *HH, *LL; + INT16 *L, *H, *LLx; + + const size_t nBandL = prfx_get_band_l_count(level); + const size_t nBandH = prfx_get_band_h_count(level); + size_t offset = 0; + + WINPR_ASSERT(buffer); + WINPR_ASSERT(temp); + + HL = &buffer[offset]; + offset += (nBandH * nBandL); + LH = &buffer[offset]; + offset += (nBandL * nBandH); + HH = &buffer[offset]; + offset += (nBandH * nBandH); + LL = &buffer[offset]; + nDstStepX = (nBandL + nBandH); + nDstStepY = (nBandL + nBandH); + offset = 0; + L = &temp[offset]; + offset += (nBandL * nDstStepX); + H = &temp[offset]; + LLx = &buffer[0]; + + /* horizontal (LL + HL -> L) */ + rfx_idwt_extrapolate_horiz_neon(LL, nBandL, HL, nBandH, L, nDstStepX, nBandL, nBandH, nBandL); + + /* horizontal (LH + HH -> H) */ + rfx_idwt_extrapolate_horiz_neon(LH, nBandL, HH, nBandH, H, nDstStepX, nBandL, nBandH, nBandH); + + /* vertical (L + H -> LL) */ + rfx_idwt_extrapolate_vert_neon(L, nDstStepX, H, nDstStepX, LLx, nDstStepY, nBandL, nBandH, + nBandL + nBandH); +} + +static void rfx_dwt_2d_extrapolate_decode_neon(INT16* buffer, INT16* temp) +{ + WINPR_ASSERT(buffer); + WINPR_ASSERT(temp); + rfx_dwt_2d_decode_extrapolate_block_neon(&buffer[3807], temp, 3); + rfx_dwt_2d_decode_extrapolate_block_neon(&buffer[3007], temp, 2); + rfx_dwt_2d_decode_extrapolate_block_neon(&buffer[0], temp, 1); +} + +void rfx_init_neon(RFX_CONTEXT* context) +{ + if (IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE)) + { + DEBUG_RFX("Using NEON optimizations"); + PROFILER_RENAME(context->priv->prof_rfx_ycbcr_to_rgb, "rfx_decode_YCbCr_to_RGB_NEON"); + PROFILER_RENAME(context->priv->prof_rfx_quantization_decode, + "rfx_quantization_decode_NEON"); + PROFILER_RENAME(context->priv->prof_rfx_dwt_2d_decode, "rfx_dwt_2d_decode_NEON"); + context->quantization_decode = rfx_quantization_decode_NEON; + context->dwt_2d_decode = rfx_dwt_2d_decode_NEON; + context->dwt_2d_extrapolate_decode = rfx_dwt_2d_extrapolate_decode_neon; + } +} + +#endif // WITH_NEON diff --git a/libfreerdp/codec/rfx_neon.h b/libfreerdp/codec/rfx_neon.h new file mode 100644 index 0000000..ecb3ec0 --- /dev/null +++ b/libfreerdp/codec/rfx_neon.h @@ -0,0 +1,34 @@ +/* + FreeRDP: A Remote Desktop Protocol Implementation + RemoteFX Codec Library - NEON Optimizations + + Copyright 2011 Martin Fleisz <martin.fleisz@thincast.com> + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef FREERDP_LIB_CODEC_RFX_NEON_H +#define FREERDP_LIB_CODEC_RFX_NEON_H + +#include <freerdp/codec/rfx.h> +#include <freerdp/api.h> + +FREERDP_LOCAL void rfx_init_neon(RFX_CONTEXT* context); + +#ifndef RFX_INIT_SIMD +#if defined(WITH_NEON) +#define RFX_INIT_SIMD(_rfx_context) rfx_init_neon(_rfx_context) +#endif +#endif + +#endif /* FREERDP_LIB_CODEC_RFX_NEON_H */ diff --git a/libfreerdp/codec/rfx_quantization.c b/libfreerdp/codec/rfx_quantization.c new file mode 100644 index 0000000..fb871a2 --- /dev/null +++ b/libfreerdp/codec/rfx_quantization.c @@ -0,0 +1,104 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX Codec Library - Quantization + * + * Copyright 2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <freerdp/primitives.h> + +#include "rfx_quantization.h" + +/* + * Band Offset Dimensions Size + * + * HL1 0 32x32 1024 + * LH1 1024 32x32 1024 + * HH1 2048 32x32 1024 + * + * HL2 3072 16x16 256 + * LH2 3328 16x16 256 + * HH2 3584 16x16 256 + * + * HL3 3840 8x8 64 + * LH3 3904 8x8 64 + * HH3 3968 8x8 64 + * + * LL3 4032 8x8 64 + */ + +static void rfx_quantization_decode_block(const primitives_t* WINPR_RESTRICT prims, INT16* buffer, + UINT32 buffer_size, UINT32 factor) +{ + if (factor == 0) + return; + + prims->lShiftC_16s(buffer, factor, buffer, buffer_size); +} + +void rfx_quantization_decode(INT16* buffer, const UINT32* WINPR_RESTRICT quantVals) +{ + const primitives_t* prims = primitives_get(); + WINPR_ASSERT(buffer); + WINPR_ASSERT(quantVals); + + rfx_quantization_decode_block(prims, &buffer[0], 1024, quantVals[8] - 1); /* HL1 */ + rfx_quantization_decode_block(prims, &buffer[1024], 1024, quantVals[7] - 1); /* LH1 */ + rfx_quantization_decode_block(prims, &buffer[2048], 1024, quantVals[9] - 1); /* HH1 */ + rfx_quantization_decode_block(prims, &buffer[3072], 256, quantVals[5] - 1); /* HL2 */ + rfx_quantization_decode_block(prims, &buffer[3328], 256, quantVals[4] - 1); /* LH2 */ + rfx_quantization_decode_block(prims, &buffer[3584], 256, quantVals[6] - 1); /* HH2 */ + rfx_quantization_decode_block(prims, &buffer[3840], 64, quantVals[2] - 1); /* HL3 */ + rfx_quantization_decode_block(prims, &buffer[3904], 64, quantVals[1] - 1); /* LH3 */ + rfx_quantization_decode_block(prims, &buffer[3968], 64, quantVals[3] - 1); /* HH3 */ + rfx_quantization_decode_block(prims, &buffer[4032], 64, quantVals[0] - 1); /* LL3 */ +} + +static void rfx_quantization_encode_block(INT16* buffer, size_t buffer_size, UINT32 factor) +{ + INT16 half = 0; + + if (factor == 0) + return; + + half = (1 << (factor - 1)); + /* Could probably use prims->rShiftC_16s(dst+half, factor, dst, buffer_size); */ + for (INT16* dst = buffer; buffer_size > 0; dst++, buffer_size--) + { + *dst = (*dst + half) >> factor; + } +} + +void rfx_quantization_encode(INT16* buffer, const UINT32* WINPR_RESTRICT quantization_values) +{ + WINPR_ASSERT(buffer); + WINPR_ASSERT(quantization_values); + + rfx_quantization_encode_block(buffer, 1024, quantization_values[8] - 6); /* HL1 */ + rfx_quantization_encode_block(buffer + 1024, 1024, quantization_values[7] - 6); /* LH1 */ + rfx_quantization_encode_block(buffer + 2048, 1024, quantization_values[9] - 6); /* HH1 */ + rfx_quantization_encode_block(buffer + 3072, 256, quantization_values[5] - 6); /* HL2 */ + rfx_quantization_encode_block(buffer + 3328, 256, quantization_values[4] - 6); /* LH2 */ + rfx_quantization_encode_block(buffer + 3584, 256, quantization_values[6] - 6); /* HH2 */ + rfx_quantization_encode_block(buffer + 3840, 64, quantization_values[2] - 6); /* HL3 */ + rfx_quantization_encode_block(buffer + 3904, 64, quantization_values[1] - 6); /* LH3 */ + rfx_quantization_encode_block(buffer + 3968, 64, quantization_values[3] - 6); /* HH3 */ + rfx_quantization_encode_block(buffer + 4032, 64, quantization_values[0] - 6); /* LL3 */ + + /* The coefficients are scaled by << 5 at RGB->YCbCr phase, so we round it back here */ + rfx_quantization_encode_block(buffer, 4096, 5); +} diff --git a/libfreerdp/codec/rfx_quantization.h b/libfreerdp/codec/rfx_quantization.h new file mode 100644 index 0000000..4f7ae7e --- /dev/null +++ b/libfreerdp/codec/rfx_quantization.h @@ -0,0 +1,29 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX Codec Library - Quantization + * + * Copyright 2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_LIB_CODEC_RFX_QUANTIZATION_H +#define FREERDP_LIB_CODEC_RFX_QUANTIZATION_H + +#include <freerdp/codec/rfx.h> +#include <freerdp/api.h> + +FREERDP_LOCAL void rfx_quantization_decode(INT16* buffer, const UINT32* quantization_values); +FREERDP_LOCAL void rfx_quantization_encode(INT16* buffer, const UINT32* quantization_values); + +#endif /* FREERDP_LIB_CODEC_RFX_QUANTIZATION_H */ diff --git a/libfreerdp/codec/rfx_rlgr.c b/libfreerdp/codec/rfx_rlgr.c new file mode 100644 index 0000000..da1b63f --- /dev/null +++ b/libfreerdp/codec/rfx_rlgr.c @@ -0,0 +1,772 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX Codec Library - RLGR + * + * Copyright 2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * This implementation of RLGR refers to + * [MS-RDPRFX] 3.1.8.1.7.3 RLGR1/RLGR3 Pseudocode + */ + +#include <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <winpr/crt.h> +#include <winpr/print.h> +#include <winpr/sysinfo.h> +#include <winpr/bitstream.h> +#include <winpr/intrin.h> + +#include "rfx_bitstream.h" + +#include "rfx_rlgr.h" + +/* Constants used in RLGR1/RLGR3 algorithm */ +#define KPMAX (80) /* max value for kp or krp */ +#define LSGR (3) /* shift count to convert kp to k */ +#define UP_GR (4) /* increase in kp after a zero run in RL mode */ +#define DN_GR (6) /* decrease in kp after a nonzero symbol in RL mode */ +#define UQ_GR (3) /* increase in kp after nonzero symbol in GR mode */ +#define DQ_GR (3) /* decrease in kp after zero symbol in GR mode */ + +/* Returns the least number of bits required to represent a given value */ +#define GetMinBits(_val, _nbits) \ + do \ + { \ + UINT32 _v = _val; \ + _nbits = 0; \ + while (_v) \ + { \ + _v >>= 1; \ + _nbits++; \ + } \ + } while (0) + +/* + * Update the passed parameter and clamp it to the range [0, KPMAX] + * Return the value of parameter right-shifted by LSGR + */ +#define UpdateParam(_param, _deltaP, _k) \ + do \ + { \ + _param += _deltaP; \ + if (_param > KPMAX) \ + _param = KPMAX; \ + if (_param < 0) \ + _param = 0; \ + _k = (_param >> LSGR); \ + } while (0) + +static BOOL g_LZCNT = FALSE; + +static INIT_ONCE rfx_rlgr_init_once = INIT_ONCE_STATIC_INIT; + +static BOOL CALLBACK rfx_rlgr_init(PINIT_ONCE once, PVOID param, PVOID* context) +{ + g_LZCNT = IsProcessorFeaturePresentEx(PF_EX_LZCNT); + return TRUE; +} + +static INLINE UINT32 lzcnt_s(UINT32 x) +{ + if (!x) + return 32; + + if (!g_LZCNT) + { + UINT32 y = 0; + UINT32 n = 32; + y = x >> 16; + if (y != 0) + { + WINPR_ASSERT(n >= 16); + n = n - 16; + x = y; + } + y = x >> 8; + if (y != 0) + { + WINPR_ASSERT(n >= 8); + n = n - 8; + x = y; + } + y = x >> 4; + if (y != 0) + { + WINPR_ASSERT(n >= 4); + n = n - 4; + x = y; + } + y = x >> 2; + if (y != 0) + { + WINPR_ASSERT(n >= 2); + n = n - 2; + x = y; + } + y = x >> 1; + if (y != 0) + { + WINPR_ASSERT(n >= 2); + return n - 2; + } + + WINPR_ASSERT(n >= x); + return n - x; + } + + return __lzcnt(x); +} + +int rfx_rlgr_decode(RLGR_MODE mode, const BYTE* WINPR_RESTRICT pSrcData, UINT32 SrcSize, + INT16* WINPR_RESTRICT pDstData, UINT32 rDstSize) +{ + int vk = 0; + size_t run = 0; + int cnt = 0; + size_t size = 0; + int nbits = 0; + size_t offset = 0; + INT16 mag = 0; + UINT32 k = 0; + INT32 kp = 0; + UINT32 kr = 0; + INT32 krp = 0; + UINT16 code = 0; + UINT32 sign = 0; + UINT32 nIdx = 0; + UINT32 val1 = 0; + UINT32 val2 = 0; + INT16* pOutput = NULL; + wBitStream* bs = NULL; + wBitStream s_bs = { 0 }; + const SSIZE_T DstSize = rDstSize; + + InitOnceExecuteOnce(&rfx_rlgr_init_once, rfx_rlgr_init, NULL, NULL); + + k = 1; + kp = k << LSGR; + + kr = 1; + krp = kr << LSGR; + + if ((mode != RLGR1) && (mode != RLGR3)) + mode = RLGR1; + + if (!pSrcData || !SrcSize) + return -1; + + if (!pDstData || !DstSize) + return -1; + + pOutput = pDstData; + + bs = &s_bs; + + BitStream_Attach(bs, pSrcData, SrcSize); + BitStream_Fetch(bs); + + while ((BitStream_GetRemainingLength(bs) > 0) && ((pOutput - pDstData) < DstSize)) + { + if (k) + { + /* Run-Length (RL) Mode */ + + run = 0; + + /* count number of leading 0s */ + + cnt = lzcnt_s(bs->accumulator); + + nbits = BitStream_GetRemainingLength(bs); + + if (cnt > nbits) + cnt = nbits; + + vk = cnt; + + while ((cnt == 32) && (BitStream_GetRemainingLength(bs) > 0)) + { + BitStream_Shift32(bs); + + cnt = lzcnt_s(bs->accumulator); + + nbits = BitStream_GetRemainingLength(bs); + + if (cnt > nbits) + cnt = nbits; + + vk += cnt; + } + + BitStream_Shift(bs, (vk % 32)); + + if (BitStream_GetRemainingLength(bs) < 1) + break; + + BitStream_Shift(bs, 1); + + while (vk--) + { + const UINT32 add = (1 << k); /* add (1 << k) to run length */ + run += add; + + /* update k, kp params */ + + kp += UP_GR; + + if (kp > KPMAX) + kp = KPMAX; + + k = kp >> LSGR; + } + + /* next k bits contain run length remainder */ + + if (BitStream_GetRemainingLength(bs) < k) + break; + + bs->mask = ((1 << k) - 1); + run += ((bs->accumulator >> (32 - k)) & bs->mask); + BitStream_Shift(bs, k); + + /* read sign bit */ + + if (BitStream_GetRemainingLength(bs) < 1) + break; + + sign = (bs->accumulator & 0x80000000) ? 1 : 0; + BitStream_Shift(bs, 1); + + /* count number of leading 1s */ + + cnt = lzcnt_s(~(bs->accumulator)); + + nbits = BitStream_GetRemainingLength(bs); + + if (cnt > nbits) + cnt = nbits; + + vk = cnt; + + while ((cnt == 32) && (BitStream_GetRemainingLength(bs) > 0)) + { + BitStream_Shift32(bs); + + cnt = lzcnt_s(~(bs->accumulator)); + + nbits = BitStream_GetRemainingLength(bs); + + if (cnt > nbits) + cnt = nbits; + + vk += cnt; + } + + BitStream_Shift(bs, (vk % 32)); + + if (BitStream_GetRemainingLength(bs) < 1) + break; + + BitStream_Shift(bs, 1); + + /* next kr bits contain code remainder */ + + if (BitStream_GetRemainingLength(bs) < kr) + break; + + bs->mask = ((1 << kr) - 1); + if (kr > 0) + code = (UINT16)((bs->accumulator >> (32 - kr)) & bs->mask); + else + code = 0; + BitStream_Shift(bs, kr); + + /* add (vk << kr) to code */ + + code |= (vk << kr); + + if (!vk) + { + /* update kr, krp params */ + + krp -= 2; + + if (krp < 0) + krp = 0; + + kr = krp >> LSGR; + } + else if (vk != 1) + { + /* update kr, krp params */ + + krp += vk; + + if (krp > KPMAX) + krp = KPMAX; + + kr = krp >> LSGR; + } + + /* update k, kp params */ + + kp -= DN_GR; + + if (kp < 0) + kp = 0; + + k = kp >> LSGR; + + /* compute magnitude from code */ + + if (sign) + mag = ((INT16)(code + 1)) * -1; + else + mag = (INT16)(code + 1); + + /* write to output stream */ + + offset = (pOutput - pDstData); + size = run; + + if ((offset + size) > rDstSize) + size = DstSize - offset; + + if (size) + { + ZeroMemory(pOutput, size * sizeof(INT16)); + pOutput += size; + } + + if ((pOutput - pDstData) < DstSize) + { + *pOutput = mag; + pOutput++; + } + } + else + { + /* Golomb-Rice (GR) Mode */ + + /* count number of leading 1s */ + + cnt = lzcnt_s(~(bs->accumulator)); + + nbits = BitStream_GetRemainingLength(bs); + + if (cnt > nbits) + cnt = nbits; + + vk = cnt; + + while ((cnt == 32) && (BitStream_GetRemainingLength(bs) > 0)) + { + BitStream_Shift32(bs); + + cnt = lzcnt_s(~(bs->accumulator)); + + nbits = BitStream_GetRemainingLength(bs); + + if (cnt > nbits) + cnt = nbits; + + vk += cnt; + } + + BitStream_Shift(bs, (vk % 32)); + + if (BitStream_GetRemainingLength(bs) < 1) + break; + + BitStream_Shift(bs, 1); + + /* next kr bits contain code remainder */ + + if (BitStream_GetRemainingLength(bs) < kr) + break; + + bs->mask = ((1 << kr) - 1); + if (kr > 0) + code = (UINT16)((bs->accumulator >> (32 - kr)) & bs->mask); + else + code = 0; + BitStream_Shift(bs, kr); + + /* add (vk << kr) to code */ + + code |= (vk << kr); + + if (!vk) + { + /* update kr, krp params */ + + krp -= 2; + + if (krp < 0) + krp = 0; + + kr = krp >> LSGR; + } + else if (vk != 1) + { + /* update kr, krp params */ + + krp += vk; + + if (krp > KPMAX) + krp = KPMAX; + + kr = krp >> LSGR; + } + + if (mode == RLGR1) /* RLGR1 */ + { + if (!code) + { + /* update k, kp params */ + + kp += UQ_GR; + + if (kp > KPMAX) + kp = KPMAX; + + k = kp >> LSGR; + + mag = 0; + } + else + { + /* update k, kp params */ + + kp -= DQ_GR; + + if (kp < 0) + kp = 0; + + k = kp >> LSGR; + + /* + * code = 2 * mag - sign + * sign + code = 2 * mag + */ + + if (code & 1) + mag = ((INT16)((code + 1) >> 1)) * -1; + else + mag = (INT16)(code >> 1); + } + + if ((pOutput - pDstData) < DstSize) + { + *pOutput = mag; + pOutput++; + } + } + else if (mode == RLGR3) /* RLGR3 */ + { + nIdx = 0; + + if (code) + { + mag = (UINT32)code; + nIdx = 32 - lzcnt_s(mag); + } + + if (BitStream_GetRemainingLength(bs) < nIdx) + break; + + bs->mask = ((1 << nIdx) - 1); + if (nIdx > 0) + val1 = ((bs->accumulator >> (32 - nIdx)) & bs->mask); + else + val1 = 0; + BitStream_Shift(bs, nIdx); + + val2 = code - val1; + + if (val1 && val2) + { + /* update k, kp params */ + + kp -= (2 * DQ_GR); + + if (kp < 0) + kp = 0; + + k = kp >> LSGR; + } + else if (!val1 && !val2) + { + /* update k, kp params */ + + kp += (2 * UQ_GR); + + if (kp > KPMAX) + kp = KPMAX; + + k = kp >> LSGR; + } + + if (val1 & 1) + mag = ((INT16)((val1 + 1) >> 1)) * -1; + else + mag = (INT16)(val1 >> 1); + + if ((pOutput - pDstData) < DstSize) + { + *pOutput = mag; + pOutput++; + } + + if (val2 & 1) + mag = ((INT16)((val2 + 1) >> 1)) * -1; + else + mag = (INT16)(val2 >> 1); + + if ((pOutput - pDstData) < DstSize) + { + *pOutput = mag; + pOutput++; + } + } + } + } + + offset = (pOutput - pDstData); + + if (offset < rDstSize) + { + size = DstSize - offset; + ZeroMemory(pOutput, size * 2); + pOutput += size; + } + + offset = (pOutput - pDstData); + + if (offset != DstSize) + return -1; + + return 1; +} + +/* Returns the next coefficient (a signed int) to encode, from the input stream */ +#define GetNextInput(_n) \ + do \ + { \ + if (data_size > 0) \ + { \ + _n = *data++; \ + data_size--; \ + } \ + else \ + { \ + _n = 0; \ + } \ + } while (0) + +/* Emit bitPattern to the output bitstream */ +#define OutputBits(numBits, bitPattern) rfx_bitstream_put_bits(bs, bitPattern, numBits) + +/* Emit a bit (0 or 1), count number of times, to the output bitstream */ +#define OutputBit(count, bit) \ + do \ + { \ + UINT16 _b = (bit ? 0xFFFF : 0); \ + int _c = (count); \ + for (; _c > 0; _c -= 16) \ + rfx_bitstream_put_bits(bs, _b, (_c > 16 ? 16 : _c)); \ + } while (0) + +/* Converts the input value to (2 * abs(input) - sign(input)), where sign(input) = (input < 0 ? 1 : + * 0) and returns it */ +#define Get2MagSign(input) ((input) >= 0 ? 2 * (input) : -2 * (input)-1) + +/* Outputs the Golomb/Rice encoding of a non-negative integer */ +#define CodeGR(krp, val) rfx_rlgr_code_gr(bs, krp, val) + +static void rfx_rlgr_code_gr(RFX_BITSTREAM* bs, int* krp, UINT32 val) +{ + int kr = *krp >> LSGR; + + /* unary part of GR code */ + + UINT32 vk = (val) >> kr; + OutputBit(vk, 1); + OutputBit(1, 0); + + /* remainder part of GR code, if needed */ + if (kr) + { + OutputBits(kr, val & ((1 << kr) - 1)); + } + + /* update krp, only if it is not equal to 1 */ + if (vk == 0) + { + UpdateParam(*krp, -2, kr); + } + else if (vk > 1) + { + UpdateParam(*krp, vk, kr); + } +} + +int rfx_rlgr_encode(RLGR_MODE mode, const INT16* WINPR_RESTRICT data, UINT32 data_size, + BYTE* WINPR_RESTRICT buffer, UINT32 buffer_size) +{ + int k = 0; + int kp = 0; + int krp = 0; + RFX_BITSTREAM* bs = NULL; + int processed_size = 0; + + if (!(bs = (RFX_BITSTREAM*)winpr_aligned_calloc(1, sizeof(RFX_BITSTREAM), 32))) + return 0; + + rfx_bitstream_attach(bs, buffer, buffer_size); + + /* initialize the parameters */ + k = 1; + kp = 1 << LSGR; + krp = 1 << LSGR; + + /* process all the input coefficients */ + while (data_size > 0) + { + int input = 0; + + if (k) + { + int numZeros = 0; + int runmax = 0; + int sign = 0; + + /* RUN-LENGTH MODE */ + + /* collect the run of zeros in the input stream */ + numZeros = 0; + GetNextInput(input); + while (input == 0 && data_size > 0) + { + numZeros++; + GetNextInput(input); + } + + // emit output zeros + runmax = 1 << k; + while (numZeros >= runmax) + { + OutputBit(1, 0); /* output a zero bit */ + numZeros -= runmax; + UpdateParam(kp, UP_GR, k); /* update kp, k */ + runmax = 1 << k; + } + + /* output a 1 to terminate runs */ + OutputBit(1, 1); + + /* output the remaining run length using k bits */ + OutputBits(k, numZeros); + + /* note: when we reach here and the last byte being encoded is 0, we still + need to output the last two bits, otherwise mstsc will crash */ + + /* encode the nonzero value using GR coding */ + const UINT32 mag = + (UINT32)(input < 0 ? -input : input); /* absolute value of input coefficient */ + sign = (input < 0 ? 1 : 0); /* sign of input coefficient */ + + OutputBit(1, sign); /* output the sign bit */ + CodeGR(&krp, mag ? mag - 1 : 0); /* output GR code for (mag - 1) */ + + UpdateParam(kp, -DN_GR, k); + } + else + { + /* GOLOMB-RICE MODE */ + + if (mode == RLGR1) + { + UINT32 twoMs = 0; + + /* RLGR1 variant */ + + /* convert input to (2*magnitude - sign), encode using GR code */ + GetNextInput(input); + twoMs = Get2MagSign(input); + CodeGR(&krp, twoMs); + + /* update k, kp */ + /* NOTE: as of Aug 2011, the algorithm is still wrongly documented + and the update direction is reversed */ + if (twoMs) + { + UpdateParam(kp, -DQ_GR, k); + } + else + { + UpdateParam(kp, UQ_GR, k); + } + } + else /* mode == RLGR3 */ + { + UINT32 twoMs1 = 0; + UINT32 twoMs2 = 0; + UINT32 sum2Ms = 0; + UINT32 nIdx = 0; + + /* RLGR3 variant */ + + /* convert the next two input values to (2*magnitude - sign) and */ + /* encode their sum using GR code */ + + GetNextInput(input); + twoMs1 = Get2MagSign(input); + GetNextInput(input); + twoMs2 = Get2MagSign(input); + sum2Ms = twoMs1 + twoMs2; + + CodeGR(&krp, sum2Ms); + + /* encode binary representation of the first input (twoMs1). */ + GetMinBits(sum2Ms, nIdx); + OutputBits(nIdx, twoMs1); + + /* update k,kp for the two input values */ + + if (twoMs1 && twoMs2) + { + UpdateParam(kp, -2 * DQ_GR, k); + } + else if (!twoMs1 && !twoMs2) + { + UpdateParam(kp, 2 * UQ_GR, k); + } + } + } + } + + rfx_bitstream_flush(bs); + processed_size = rfx_bitstream_get_processed_bytes(bs); + winpr_aligned_free(bs); + + return processed_size; +} diff --git a/libfreerdp/codec/rfx_rlgr.h b/libfreerdp/codec/rfx_rlgr.h new file mode 100644 index 0000000..93738ce --- /dev/null +++ b/libfreerdp/codec/rfx_rlgr.h @@ -0,0 +1,32 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX Codec Library - RLGR + * + * Copyright 2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_LIB_CODEC_RFX_RLGR_H +#define FREERDP_LIB_CODEC_RFX_RLGR_H + +#include <freerdp/codec/rfx.h> +#include <freerdp/api.h> + +FREERDP_LOCAL int rfx_rlgr_encode(RLGR_MODE mode, const INT16* data, UINT32 data_size, BYTE* buffer, + UINT32 buffer_size); + +FREERDP_LOCAL int rfx_rlgr_decode(RLGR_MODE mode, const BYTE* pSrcData, UINT32 SrcSize, + INT16* pDstData, UINT32 DstSize); + +#endif /* FREERDP_LIB_CODEC_RFX_RLGR_H */ diff --git a/libfreerdp/codec/rfx_sse2.c b/libfreerdp/codec/rfx_sse2.c new file mode 100644 index 0000000..06cf8f4 --- /dev/null +++ b/libfreerdp/codec/rfx_sse2.c @@ -0,0 +1,484 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX Codec Library - SSE2 Optimizations + * + * Copyright 2011 Stephen Erisman + * Copyright 2011 Norbert Federa <norbert.federa@thincast.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <winpr/sysinfo.h> + +#include <xmmintrin.h> +#include <emmintrin.h> + +#include "rfx_types.h" +#include "rfx_sse2.h" + +#ifdef _MSC_VER +#define __attribute__(...) +#endif + +#define CACHE_LINE_BYTES 64 + +#ifndef __clang__ +#define ATTRIBUTES __gnu_inline__, __always_inline__, __artificial__ +#else +#define ATTRIBUTES __gnu_inline__, __always_inline__ +#endif + +#define _mm_between_epi16(_val, _min, _max) \ + do \ + { \ + _val = _mm_min_epi16(_max, _mm_max_epi16(_val, _min)); \ + } while (0) + +static __inline void __attribute__((ATTRIBUTES)) _mm_prefetch_buffer(char* buffer, int num_bytes) +{ + __m128i* buf = (__m128i*)buffer; + + for (unsigned int i = 0; i < (num_bytes / sizeof(__m128i)); + i += (CACHE_LINE_BYTES / sizeof(__m128i))) + { + _mm_prefetch((char*)(&buf[i]), _MM_HINT_NTA); + } +} + +/* rfx_decode_ycbcr_to_rgb_sse2 code now resides in the primitives library. */ +/* rfx_encode_rgb_to_ycbcr_sse2 code now resides in the primitives library. */ + +static __inline void __attribute__((ATTRIBUTES)) +rfx_quantization_decode_block_sse2(INT16* buffer, const size_t buffer_size, const UINT32 factor) +{ + __m128i a; + __m128i* ptr = (__m128i*)buffer; + __m128i* buf_end = (__m128i*)(buffer + buffer_size); + + if (factor == 0) + return; + + do + { + a = _mm_load_si128(ptr); + a = _mm_slli_epi16(a, factor); + _mm_store_si128(ptr, a); + ptr++; + } while (ptr < buf_end); +} + +static void rfx_quantization_decode_sse2(INT16* buffer, const UINT32* WINPR_RESTRICT quantVals) +{ + WINPR_ASSERT(buffer); + WINPR_ASSERT(quantVals); + + _mm_prefetch_buffer((char*)buffer, 4096 * sizeof(INT16)); + rfx_quantization_decode_block_sse2(&buffer[0], 1024, quantVals[8] - 1); /* HL1 */ + rfx_quantization_decode_block_sse2(&buffer[1024], 1024, quantVals[7] - 1); /* LH1 */ + rfx_quantization_decode_block_sse2(&buffer[2048], 1024, quantVals[9] - 1); /* HH1 */ + rfx_quantization_decode_block_sse2(&buffer[3072], 256, quantVals[5] - 1); /* HL2 */ + rfx_quantization_decode_block_sse2(&buffer[3328], 256, quantVals[4] - 1); /* LH2 */ + rfx_quantization_decode_block_sse2(&buffer[3584], 256, quantVals[6] - 1); /* HH2 */ + rfx_quantization_decode_block_sse2(&buffer[3840], 64, quantVals[2] - 1); /* HL3 */ + rfx_quantization_decode_block_sse2(&buffer[3904], 64, quantVals[1] - 1); /* LH3 */ + rfx_quantization_decode_block_sse2(&buffer[3968], 64, quantVals[3] - 1); /* HH3 */ + rfx_quantization_decode_block_sse2(&buffer[4032], 64, quantVals[0] - 1); /* LL3 */ +} + +static __inline void __attribute__((ATTRIBUTES)) +rfx_quantization_encode_block_sse2(INT16* buffer, const int buffer_size, const UINT32 factor) +{ + __m128i a; + __m128i* ptr = (__m128i*)buffer; + __m128i* buf_end = (__m128i*)(buffer + buffer_size); + __m128i half; + + if (factor == 0) + return; + + half = _mm_set1_epi16(1 << (factor - 1)); + + do + { + a = _mm_load_si128(ptr); + a = _mm_add_epi16(a, half); + a = _mm_srai_epi16(a, factor); + _mm_store_si128(ptr, a); + ptr++; + } while (ptr < buf_end); +} + +static void rfx_quantization_encode_sse2(INT16* buffer, + const UINT32* WINPR_RESTRICT quantization_values) +{ + WINPR_ASSERT(buffer); + WINPR_ASSERT(quantization_values); + + _mm_prefetch_buffer((char*)buffer, 4096 * sizeof(INT16)); + rfx_quantization_encode_block_sse2(buffer, 1024, quantization_values[8] - 6); /* HL1 */ + rfx_quantization_encode_block_sse2(buffer + 1024, 1024, quantization_values[7] - 6); /* LH1 */ + rfx_quantization_encode_block_sse2(buffer + 2048, 1024, quantization_values[9] - 6); /* HH1 */ + rfx_quantization_encode_block_sse2(buffer + 3072, 256, quantization_values[5] - 6); /* HL2 */ + rfx_quantization_encode_block_sse2(buffer + 3328, 256, quantization_values[4] - 6); /* LH2 */ + rfx_quantization_encode_block_sse2(buffer + 3584, 256, quantization_values[6] - 6); /* HH2 */ + rfx_quantization_encode_block_sse2(buffer + 3840, 64, quantization_values[2] - 6); /* HL3 */ + rfx_quantization_encode_block_sse2(buffer + 3904, 64, quantization_values[1] - 6); /* LH3 */ + rfx_quantization_encode_block_sse2(buffer + 3968, 64, quantization_values[3] - 6); /* HH3 */ + rfx_quantization_encode_block_sse2(buffer + 4032, 64, quantization_values[0] - 6); /* LL3 */ + rfx_quantization_encode_block_sse2(buffer, 4096, 5); +} + +static __inline void __attribute__((ATTRIBUTES)) +rfx_dwt_2d_decode_block_horiz_sse2(INT16* l, INT16* h, INT16* dst, int subband_width) +{ + INT16* l_ptr = l; + INT16* h_ptr = h; + INT16* dst_ptr = dst; + int first = 0; + int last = 0; + __m128i l_n; + __m128i h_n; + __m128i h_n_m; + __m128i tmp_n; + __m128i dst_n; + __m128i dst_n_p; + __m128i dst1; + __m128i dst2; + + for (int y = 0; y < subband_width; y++) + { + /* Even coefficients */ + for (int n = 0; n < subband_width; n += 8) + { + /* dst[2n] = l[n] - ((h[n-1] + h[n] + 1) >> 1); */ + l_n = _mm_load_si128((__m128i*)l_ptr); + h_n = _mm_load_si128((__m128i*)h_ptr); + h_n_m = _mm_loadu_si128((__m128i*)(h_ptr - 1)); + + if (n == 0) + { + first = _mm_extract_epi16(h_n_m, 1); + h_n_m = _mm_insert_epi16(h_n_m, first, 0); + } + + tmp_n = _mm_add_epi16(h_n, h_n_m); + tmp_n = _mm_add_epi16(tmp_n, _mm_set1_epi16(1)); + tmp_n = _mm_srai_epi16(tmp_n, 1); + dst_n = _mm_sub_epi16(l_n, tmp_n); + _mm_store_si128((__m128i*)l_ptr, dst_n); + l_ptr += 8; + h_ptr += 8; + } + + l_ptr -= subband_width; + h_ptr -= subband_width; + + /* Odd coefficients */ + for (int n = 0; n < subband_width; n += 8) + { + /* dst[2n + 1] = (h[n] << 1) + ((dst[2n] + dst[2n + 2]) >> 1); */ + h_n = _mm_load_si128((__m128i*)h_ptr); + h_n = _mm_slli_epi16(h_n, 1); + dst_n = _mm_load_si128((__m128i*)(l_ptr)); + dst_n_p = _mm_loadu_si128((__m128i*)(l_ptr + 1)); + + if (n == subband_width - 8) + { + last = _mm_extract_epi16(dst_n_p, 6); + dst_n_p = _mm_insert_epi16(dst_n_p, last, 7); + } + + tmp_n = _mm_add_epi16(dst_n_p, dst_n); + tmp_n = _mm_srai_epi16(tmp_n, 1); + tmp_n = _mm_add_epi16(tmp_n, h_n); + dst1 = _mm_unpacklo_epi16(dst_n, tmp_n); + dst2 = _mm_unpackhi_epi16(dst_n, tmp_n); + _mm_store_si128((__m128i*)dst_ptr, dst1); + _mm_store_si128((__m128i*)(dst_ptr + 8), dst2); + l_ptr += 8; + h_ptr += 8; + dst_ptr += 16; + } + } +} + +static __inline void __attribute__((ATTRIBUTES)) +rfx_dwt_2d_decode_block_vert_sse2(INT16* l, INT16* h, INT16* dst, int subband_width) +{ + INT16* l_ptr = l; + INT16* h_ptr = h; + INT16* dst_ptr = dst; + __m128i l_n; + __m128i h_n; + __m128i tmp_n; + __m128i h_n_m; + __m128i dst_n; + __m128i dst_n_m; + __m128i dst_n_p; + int total_width = subband_width + subband_width; + + /* Even coefficients */ + for (int n = 0; n < subband_width; n++) + { + for (int x = 0; x < total_width; x += 8) + { + /* dst[2n] = l[n] - ((h[n-1] + h[n] + 1) >> 1); */ + l_n = _mm_load_si128((__m128i*)l_ptr); + h_n = _mm_load_si128((__m128i*)h_ptr); + tmp_n = _mm_add_epi16(h_n, _mm_set1_epi16(1)); + + if (n == 0) + tmp_n = _mm_add_epi16(tmp_n, h_n); + else + { + h_n_m = _mm_loadu_si128((__m128i*)(h_ptr - total_width)); + tmp_n = _mm_add_epi16(tmp_n, h_n_m); + } + + tmp_n = _mm_srai_epi16(tmp_n, 1); + dst_n = _mm_sub_epi16(l_n, tmp_n); + _mm_store_si128((__m128i*)dst_ptr, dst_n); + l_ptr += 8; + h_ptr += 8; + dst_ptr += 8; + } + + dst_ptr += total_width; + } + + h_ptr = h; + dst_ptr = dst + total_width; + + /* Odd coefficients */ + for (int n = 0; n < subband_width; n++) + { + for (int x = 0; x < total_width; x += 8) + { + /* dst[2n + 1] = (h[n] << 1) + ((dst[2n] + dst[2n + 2]) >> 1); */ + h_n = _mm_load_si128((__m128i*)h_ptr); + dst_n_m = _mm_load_si128((__m128i*)(dst_ptr - total_width)); + h_n = _mm_slli_epi16(h_n, 1); + tmp_n = dst_n_m; + + if (n == subband_width - 1) + tmp_n = _mm_add_epi16(tmp_n, dst_n_m); + else + { + dst_n_p = _mm_loadu_si128((__m128i*)(dst_ptr + total_width)); + tmp_n = _mm_add_epi16(tmp_n, dst_n_p); + } + + tmp_n = _mm_srai_epi16(tmp_n, 1); + dst_n = _mm_add_epi16(tmp_n, h_n); + _mm_store_si128((__m128i*)dst_ptr, dst_n); + h_ptr += 8; + dst_ptr += 8; + } + + dst_ptr += total_width; + } +} + +static __inline void __attribute__((ATTRIBUTES)) +rfx_dwt_2d_decode_block_sse2(INT16* buffer, INT16* idwt, int subband_width) +{ + INT16* hl = NULL; + INT16* lh = NULL; + INT16* hh = NULL; + INT16* ll = NULL; + INT16* l_dst = NULL; + INT16* h_dst = NULL; + _mm_prefetch_buffer((char*)idwt, subband_width * 4 * sizeof(INT16)); + /* Inverse DWT in horizontal direction, results in 2 sub-bands in L, H order in tmp buffer idwt. + */ + /* The 4 sub-bands are stored in HL(0), LH(1), HH(2), LL(3) order. */ + /* The lower part L uses LL(3) and HL(0). */ + /* The higher part H uses LH(1) and HH(2). */ + ll = buffer + subband_width * subband_width * 3; + hl = buffer; + l_dst = idwt; + rfx_dwt_2d_decode_block_horiz_sse2(ll, hl, l_dst, subband_width); + lh = buffer + subband_width * subband_width; + hh = buffer + subband_width * subband_width * 2; + h_dst = idwt + subband_width * subband_width * 2; + rfx_dwt_2d_decode_block_horiz_sse2(lh, hh, h_dst, subband_width); + /* Inverse DWT in vertical direction, results are stored in original buffer. */ + rfx_dwt_2d_decode_block_vert_sse2(l_dst, h_dst, buffer, subband_width); +} + +static void rfx_dwt_2d_decode_sse2(INT16* buffer, INT16* dwt_buffer) +{ + WINPR_ASSERT(buffer); + WINPR_ASSERT(dwt_buffer); + + _mm_prefetch_buffer((char*)buffer, 4096 * sizeof(INT16)); + rfx_dwt_2d_decode_block_sse2(&buffer[3840], dwt_buffer, 8); + rfx_dwt_2d_decode_block_sse2(&buffer[3072], dwt_buffer, 16); + rfx_dwt_2d_decode_block_sse2(&buffer[0], dwt_buffer, 32); +} + +static __inline void __attribute__((ATTRIBUTES)) +rfx_dwt_2d_encode_block_vert_sse2(INT16* src, INT16* l, INT16* h, int subband_width) +{ + int total_width = 0; + __m128i src_2n; + __m128i src_2n_1; + __m128i src_2n_2; + __m128i h_n; + __m128i h_n_m; + __m128i l_n; + total_width = subband_width << 1; + + for (int n = 0; n < subband_width; n++) + { + for (int x = 0; x < total_width; x += 8) + { + src_2n = _mm_load_si128((__m128i*)src); + src_2n_1 = _mm_load_si128((__m128i*)(src + total_width)); + + if (n < subband_width - 1) + src_2n_2 = _mm_load_si128((__m128i*)(src + 2 * total_width)); + else + src_2n_2 = src_2n; + + /* h[n] = (src[2n + 1] - ((src[2n] + src[2n + 2]) >> 1)) >> 1 */ + h_n = _mm_add_epi16(src_2n, src_2n_2); + h_n = _mm_srai_epi16(h_n, 1); + h_n = _mm_sub_epi16(src_2n_1, h_n); + h_n = _mm_srai_epi16(h_n, 1); + _mm_store_si128((__m128i*)h, h_n); + + if (n == 0) + h_n_m = h_n; + else + h_n_m = _mm_load_si128((__m128i*)(h - total_width)); + + /* l[n] = src[2n] + ((h[n - 1] + h[n]) >> 1) */ + l_n = _mm_add_epi16(h_n_m, h_n); + l_n = _mm_srai_epi16(l_n, 1); + l_n = _mm_add_epi16(l_n, src_2n); + _mm_store_si128((__m128i*)l, l_n); + src += 8; + l += 8; + h += 8; + } + + src += total_width; + } +} + +static __inline void __attribute__((ATTRIBUTES)) +rfx_dwt_2d_encode_block_horiz_sse2(INT16* src, INT16* l, INT16* h, int subband_width) +{ + int first = 0; + __m128i src_2n; + __m128i src_2n_1; + __m128i src_2n_2; + __m128i h_n; + __m128i h_n_m; + __m128i l_n; + + for (int y = 0; y < subband_width; y++) + { + for (int n = 0; n < subband_width; n += 8) + { + /* The following 3 Set operations consumes more than half of the total DWT processing + * time! */ + src_2n = + _mm_set_epi16(src[14], src[12], src[10], src[8], src[6], src[4], src[2], src[0]); + src_2n_1 = + _mm_set_epi16(src[15], src[13], src[11], src[9], src[7], src[5], src[3], src[1]); + src_2n_2 = _mm_set_epi16(n == subband_width - 8 ? src[14] : src[16], src[14], src[12], + src[10], src[8], src[6], src[4], src[2]); + /* h[n] = (src[2n + 1] - ((src[2n] + src[2n + 2]) >> 1)) >> 1 */ + h_n = _mm_add_epi16(src_2n, src_2n_2); + h_n = _mm_srai_epi16(h_n, 1); + h_n = _mm_sub_epi16(src_2n_1, h_n); + h_n = _mm_srai_epi16(h_n, 1); + _mm_store_si128((__m128i*)h, h_n); + h_n_m = _mm_loadu_si128((__m128i*)(h - 1)); + + if (n == 0) + { + first = _mm_extract_epi16(h_n_m, 1); + h_n_m = _mm_insert_epi16(h_n_m, first, 0); + } + + /* l[n] = src[2n] + ((h[n - 1] + h[n]) >> 1) */ + l_n = _mm_add_epi16(h_n_m, h_n); + l_n = _mm_srai_epi16(l_n, 1); + l_n = _mm_add_epi16(l_n, src_2n); + _mm_store_si128((__m128i*)l, l_n); + src += 16; + l += 8; + h += 8; + } + } +} + +static __inline void __attribute__((ATTRIBUTES)) +rfx_dwt_2d_encode_block_sse2(INT16* buffer, INT16* dwt, int subband_width) +{ + INT16* hl = NULL; + INT16* lh = NULL; + INT16* hh = NULL; + INT16* ll = NULL; + INT16* l_src = NULL; + INT16* h_src = NULL; + _mm_prefetch_buffer((char*)dwt, subband_width * 4 * sizeof(INT16)); + /* DWT in vertical direction, results in 2 sub-bands in L, H order in tmp buffer dwt. */ + l_src = dwt; + h_src = dwt + subband_width * subband_width * 2; + rfx_dwt_2d_encode_block_vert_sse2(buffer, l_src, h_src, subband_width); + /* DWT in horizontal direction, results in 4 sub-bands in HL(0), LH(1), HH(2), LL(3) order, + * stored in original buffer. */ + /* The lower part L generates LL(3) and HL(0). */ + /* The higher part H generates LH(1) and HH(2). */ + ll = buffer + subband_width * subband_width * 3; + hl = buffer; + lh = buffer + subband_width * subband_width; + hh = buffer + subband_width * subband_width * 2; + rfx_dwt_2d_encode_block_horiz_sse2(l_src, ll, hl, subband_width); + rfx_dwt_2d_encode_block_horiz_sse2(h_src, lh, hh, subband_width); +} + +static void rfx_dwt_2d_encode_sse2(INT16* buffer, INT16* dwt_buffer) +{ + WINPR_ASSERT(buffer); + WINPR_ASSERT(dwt_buffer); + + _mm_prefetch_buffer((char*)buffer, 4096 * sizeof(INT16)); + rfx_dwt_2d_encode_block_sse2(buffer, dwt_buffer, 32); + rfx_dwt_2d_encode_block_sse2(buffer + 3072, dwt_buffer, 16); + rfx_dwt_2d_encode_block_sse2(buffer + 3840, dwt_buffer, 8); +} + +void rfx_init_sse2(RFX_CONTEXT* context) +{ + if (!IsProcessorFeaturePresent(PF_XMMI64_INSTRUCTIONS_AVAILABLE)) + return; + + PROFILER_RENAME(context->priv->prof_rfx_quantization_decode, "rfx_quantization_decode_sse2") + PROFILER_RENAME(context->priv->prof_rfx_quantization_encode, "rfx_quantization_encode_sse2") + PROFILER_RENAME(context->priv->prof_rfx_dwt_2d_decode, "rfx_dwt_2d_decode_sse2") + PROFILER_RENAME(context->priv->prof_rfx_dwt_2d_encode, "rfx_dwt_2d_encode_sse2") + context->quantization_decode = rfx_quantization_decode_sse2; + context->quantization_encode = rfx_quantization_encode_sse2; + context->dwt_2d_decode = rfx_dwt_2d_decode_sse2; + context->dwt_2d_encode = rfx_dwt_2d_encode_sse2; +} diff --git a/libfreerdp/codec/rfx_sse2.h b/libfreerdp/codec/rfx_sse2.h new file mode 100644 index 0000000..b0d3998 --- /dev/null +++ b/libfreerdp/codec/rfx_sse2.h @@ -0,0 +1,34 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX Codec Library - SSE2 Optimizations + * + * Copyright 2011 Stephen Erisman + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_LIB_CODEC_RFX_SSE2_H +#define FREERDP_LIB_CODEC_RFX_SSE2_H + +#include <freerdp/codec/rfx.h> +#include <freerdp/api.h> + +FREERDP_LOCAL void rfx_init_sse2(RFX_CONTEXT* context); + +#ifdef WITH_SSE2 +#ifndef RFX_INIT_SIMD +#define RFX_INIT_SIMD(_rfx_context) rfx_init_sse2(_rfx_context) +#endif +#endif + +#endif /* FREERDP_LIB_CODEC_RFX_SSE2_H */ diff --git a/libfreerdp/codec/rfx_types.h b/libfreerdp/codec/rfx_types.h new file mode 100644 index 0000000..5762b30 --- /dev/null +++ b/libfreerdp/codec/rfx_types.h @@ -0,0 +1,182 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * RemoteFX Codec Library + * + * Copyright 2011 Vic Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_LIB_CODEC_RFX_TYPES_H +#define FREERDP_LIB_CODEC_RFX_TYPES_H + +#include <freerdp/config.h> + +#include <winpr/crt.h> +#include <winpr/pool.h> +#include <winpr/wlog.h> +#include <winpr/collections.h> + +#include <freerdp/codec/rfx.h> +#include <freerdp/log.h> +#include <freerdp/utils/profiler.h> + +#define RFX_TAG FREERDP_TAG("codec.rfx") +#ifdef WITH_DEBUG_RFX +#define DEBUG_RFX(...) WLog_DBG(RFX_TAG, __VA_ARGS__) +#else +#define DEBUG_RFX(...) \ + do \ + { \ + } while (0) +#endif + +#define RFX_DECODED_SYNC 0x00000001 +#define RFX_DECODED_CONTEXT 0x00000002 +#define RFX_DECODED_VERSIONS 0x00000004 +#define RFX_DECODED_CHANNELS 0x00000008 +#define RFX_DECODED_HEADERS 0x0000000F + +typedef enum +{ + RFX_STATE_INITIAL, + RFX_STATE_SERVER_UNINITIALIZED, + RFX_STATE_SEND_HEADERS, + RFX_STATE_SEND_FRAME_DATA, + RFX_STATE_FRAME_DATA_SENT, + RFX_STATE_FINAL +} RFX_STATE; + +typedef struct S_RFX_TILE_COMPOSE_WORK_PARAM RFX_TILE_COMPOSE_WORK_PARAM; + +typedef struct S_RFX_CONTEXT_PRIV RFX_CONTEXT_PRIV; +struct S_RFX_CONTEXT_PRIV +{ + wLog* log; + wObjectPool* TilePool; + + BOOL UseThreads; + PTP_WORK* workObjects; + RFX_TILE_COMPOSE_WORK_PARAM* tileWorkParams; + + DWORD MinThreadCount; + DWORD MaxThreadCount; + + PTP_POOL ThreadPool; + TP_CALLBACK_ENVIRON ThreadPoolEnv; + + wBufferPool* BufferPool; + + /* profilers */ + PROFILER_DEFINE(prof_rfx_decode_rgb) + PROFILER_DEFINE(prof_rfx_decode_component) + PROFILER_DEFINE(prof_rfx_rlgr_decode) + PROFILER_DEFINE(prof_rfx_differential_decode) + PROFILER_DEFINE(prof_rfx_quantization_decode) + PROFILER_DEFINE(prof_rfx_dwt_2d_decode) + PROFILER_DEFINE(prof_rfx_ycbcr_to_rgb) + + PROFILER_DEFINE(prof_rfx_encode_rgb) + PROFILER_DEFINE(prof_rfx_encode_component) + PROFILER_DEFINE(prof_rfx_rlgr_encode) + PROFILER_DEFINE(prof_rfx_differential_encode) + PROFILER_DEFINE(prof_rfx_quantization_encode) + PROFILER_DEFINE(prof_rfx_dwt_2d_encode) + PROFILER_DEFINE(prof_rfx_rgb_to_ycbcr) + PROFILER_DEFINE(prof_rfx_encode_format_rgb) +}; + +struct S_RFX_MESSAGE +{ + UINT32 frameIdx; + + /** + * The rects array represents the updated region of the frame. The UI + * requires to clip drawing destination base on the union of the rects. + */ + UINT16 numRects; + RFX_RECT* rects; + + /** + * The tiles array represents the actual frame data. Each tile is always + * 64x64. Note that only pixels inside the updated region (represented as + * rects described above) are valid. Pixels outside of the region may + * contain arbitrary data. + */ + UINT16 numTiles; + size_t allocatedTiles; + RFX_TILE** tiles; + + UINT16 numQuant; + UINT32* quantVals; + + UINT32 tilesDataSize; + + BOOL freeArray; +}; + +struct S_RFX_MESSAGE_LIST +{ + struct S_RFX_MESSAGE* list; + size_t count; + RFX_CONTEXT* context; +}; + +struct S_RFX_CONTEXT +{ + RFX_STATE state; + + BOOL encoder; + UINT16 flags; + UINT16 properties; + UINT16 width; + UINT16 height; + RLGR_MODE mode; + UINT32 version; + UINT32 codec_id; + UINT32 codec_version; + UINT32 pixel_format; + BYTE bits_per_pixel; + + /* color palette allocated by the application */ + const BYTE* palette; + + /* temporary data within a frame */ + UINT32 frameIdx; + BYTE numQuant; + UINT32* quants; + BYTE quantIdxY; + BYTE quantIdxCb; + BYTE quantIdxCr; + + /* decoded header blocks */ + UINT32 decodedHeaderBlocks; + UINT16 expectedDataBlockType; + struct S_RFX_MESSAGE currentMessage; + + /* routines */ + void (*quantization_decode)(INT16* buffer, const UINT32* WINPR_RESTRICT quantization_values); + void (*quantization_encode)(INT16* buffer, const UINT32* WINPR_RESTRICT quantization_values); + void (*dwt_2d_decode)(INT16* buffer, INT16* dwt_buffer); + void (*dwt_2d_extrapolate_decode)(INT16* src, INT16* temp); + void (*dwt_2d_encode)(INT16* buffer, INT16* dwt_buffer); + int (*rlgr_decode)(RLGR_MODE mode, const BYTE* WINPR_RESTRICT data, UINT32 data_size, + INT16* WINPR_RESTRICT buffer, UINT32 buffer_size); + int (*rlgr_encode)(RLGR_MODE mode, const INT16* WINPR_RESTRICT data, UINT32 data_size, + BYTE* WINPR_RESTRICT buffer, UINT32 buffer_size); + + /* private definitions */ + RFX_CONTEXT_PRIV* priv; +}; + +#endif /* FREERDP_LIB_CODEC_RFX_TYPES_H */ diff --git a/libfreerdp/codec/test/CMakeLists.txt b/libfreerdp/codec/test/CMakeLists.txt new file mode 100644 index 0000000..4258b50 --- /dev/null +++ b/libfreerdp/codec/test/CMakeLists.txt @@ -0,0 +1,37 @@ + +set(MODULE_NAME "TestFreeRDPCodec") +set(MODULE_PREFIX "TEST_FREERDP_CODEC") + +set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c) + +set(${MODULE_PREFIX}_TESTS + TestFreeRDPRegion.c + TestFreeRDPCodecMppc.c + TestFreeRDPCodecNCrush.c + TestFreeRDPCodecXCrush.c + TestFreeRDPCodecZGfx.c + TestFreeRDPCodecPlanar.c + TestFreeRDPCodecClear.c + TestFreeRDPCodecInterleaved.c + TestFreeRDPCodecProgressive.c + TestFreeRDPCodecRemoteFX.c) + +create_test_sourcelist(${MODULE_PREFIX}_SRCS + ${${MODULE_PREFIX}_DRIVER} + ${${MODULE_PREFIX}_TESTS}) + +add_definitions(-DCMAKE_CURRENT_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}") +add_definitions(-DCMAKE_CURRENT_BINARY_DIR="${CMAKE_CURRENT_BINARY_DIR}") +add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS}) + +target_link_libraries(${MODULE_NAME} freerdp winpr) + +set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}") + +foreach(test ${${MODULE_PREFIX}_TESTS}) + get_filename_component(TestName ${test} NAME_WE) + add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName}) +endforeach() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "FreeRDP/Test") + diff --git a/libfreerdp/codec/test/TestFreeRDPCodecClear.c b/libfreerdp/codec/test/TestFreeRDPCodecClear.c new file mode 100644 index 0000000..ee4bc3b --- /dev/null +++ b/libfreerdp/codec/test/TestFreeRDPCodecClear.c @@ -0,0 +1,91 @@ +#include <winpr/crt.h> +#include <winpr/print.h> +#include <winpr/platform.h> + +#include <freerdp/codec/clear.h> + +WINPR_PRAGMA_DIAG_PUSH +WINPR_PRAGMA_DIAG_IGNORED_UNUSED_CONST_VAR +/* [MS-RDPEGFX] 4.1.1.1 Example 1 */ +static const BYTE PREPARE_CLEAR_EXAMPLE_1[] = "\x03\xc3\x11\x00"; +static const BYTE TEST_CLEAR_EXAMPLE_1[] = "\x03\xc3\x11\x00"; +WINPR_PRAGMA_DIAG_POP + +/* [MS-RDPEGFX] 4.1.1.1 Example 2 */ +static const BYTE TEST_CLEAR_EXAMPLE_2[] = + "\x00\x0d\x00\x00\x00\x00\x00\x00\x00\x00\x82\x00\x00\x00\x00\x00" + "\x00\x00\x4e\x00\x11\x00\x75\x00\x00\x00\x02\x0e\xff\xff\xff\x00" + "\x00\x00\xdb\xff\xff\x00\x3a\x90\xff\xb6\x66\x66\xb6\xff\xb6\x66" + "\x00\x90\xdb\xff\x00\x00\x3a\xdb\x90\x3a\x3a\x90\xdb\x66\x00\x00" + "\xff\xff\xb6\x64\x64\x64\x11\x04\x11\x4c\x11\x4c\x11\x4c\x11\x4c" + "\x11\x4c\x00\x47\x13\x00\x01\x01\x04\x00\x01\x00\x00\x47\x16\x00" + "\x11\x02\x00\x47\x29\x00\x11\x01\x00\x49\x0a\x00\x01\x00\x04\x00" + "\x01\x00\x00\x4a\x0a\x00\x09\x00\x01\x00\x00\x47\x05\x00\x01\x01" + "\x1c\x00\x01\x00\x11\x4c\x11\x4c\x11\x4c\x00\x47\x0d\x4d\x00\x4d"; + +/* [MS-RDPEGFX] 4.1.1.1 Example 3 */ +static const BYTE TEST_CLEAR_EXAMPLE_3[] = + "\x00\xdf\x0e\x00\x00\x00\x8b\x00\x00\x00\x00\x00\x00\x00\xfe\xfe" + "\xfe\xff\x80\x05\xff\xff\xff\x40\xfe\xfe\xfe\x40\x00\x00\x3f\x00" + "\x03\x00\x0b\x00\xfe\xfe\xfe\xc5\xd0\xc6\xd0\xc7\xd0\x68\xd4\x69" + "\xd4\x6a\xd4\x6b\xd4\x6c\xd4\x6d\xd4\x1a\xd4\x1a\xd4\xa6\xd0\x6e" + "\xd4\x6f\xd4\x70\xd4\x71\xd4\x72\xd4\x73\xd4\x74\xd4\x21\xd4\x22" + "\xd4\x23\xd4\x24\xd4\x25\xd4\xd9\xd0\xda\xd0\xdb\xd0\xc5\xd0\xc5" + "\xd0\xdc\xd0\xc2\xd0\x21\xd4\x22\xd4\x23\xd4\x24\xd4\x25\xd4\xc9" + "\xd0\xca\xd0\x5a\xd4\x2b\xd1\x28\xd1\x2c\xd1\x75\xd4\x27\xd4\x28" + "\xd4\x29\xd4\x2a\xd4\x1a\xd4\x1a\xd4\x1a\xd4\xb7\xd0\xb8\xd0\xb9" + "\xd0\xba\xd0\xbb\xd0\xbc\xd0\xbd\xd0\xbe\xd0\xbf\xd0\xc0\xd0\xc1" + "\xd0\xc2\xd0\xc3\xd0\xc4\xd0"; + +/* [MS-RDPEGFX] 4.1.1.1 Example 4 */ +static const BYTE TEST_CLEAR_EXAMPLE_4[] = + "\x01\x0b\x78\x00\x00\x00\x00\x00\x46\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x06\x00\x00\x00\x0e\x00\x00\x00\x00\x00\x0f\xff\xff\xff" + "\xff\xff\xff\xff\xff\xff\xb6\xff\xff\xff\xff\xff\xff\xff\xff\xff" + "\xb6\x66\xff\xff\xff\xff\xff\xff\xff\xb6\x66\xdb\x90\x3a\xff\xff" + "\xb6\xff\xff\xff\xff\xff\xff\xff\xff\xff\x46\x91\x47\x91\x48\x91" + "\x49\x91\x4a\x91\x1b\x91"; + +static BOOL test_ClearDecompressExample(UINT32 nr, UINT32 width, UINT32 height, + const BYTE* pSrcData, const UINT32 SrcSize) +{ + BOOL rc = FALSE; + int status = 0; + BYTE* pDstData = calloc(width * height, 4); + CLEAR_CONTEXT* clear = clear_context_new(FALSE); + + if (!clear || !pDstData) + goto fail; + + status = clear_decompress(clear, pSrcData, SrcSize, width, height, pDstData, + PIXEL_FORMAT_XRGB32, 0, 0, 0, width, height, NULL); + printf("clear_decompress example %" PRIu32 " status: %d\n", nr, status); + fflush(stdout); + rc = (status == 0); +fail: + clear_context_free(clear); + free(pDstData); + return rc; +} + +int TestFreeRDPCodecClear(int argc, char* argv[]) +{ + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + /* Example 1 needs a filled glyph cache + if (!test_ClearDecompressExample(1, 8, 9, TEST_CLEAR_EXAMPLE_1, + sizeof(TEST_CLEAR_EXAMPLE_1))) + return -1; + */ + if (!test_ClearDecompressExample(2, 78, 17, TEST_CLEAR_EXAMPLE_2, sizeof(TEST_CLEAR_EXAMPLE_2))) + return -1; + + if (!test_ClearDecompressExample(3, 64, 24, TEST_CLEAR_EXAMPLE_3, sizeof(TEST_CLEAR_EXAMPLE_3))) + return -1; + + if (!test_ClearDecompressExample(4, 7, 15, TEST_CLEAR_EXAMPLE_4, sizeof(TEST_CLEAR_EXAMPLE_4))) + return -1; + + return 0; +} diff --git a/libfreerdp/codec/test/TestFreeRDPCodecInterleaved.c b/libfreerdp/codec/test/TestFreeRDPCodecInterleaved.c new file mode 100644 index 0000000..e8cbb6a --- /dev/null +++ b/libfreerdp/codec/test/TestFreeRDPCodecInterleaved.c @@ -0,0 +1,219 @@ + +#include <freerdp/config.h> + +#include <math.h> + +#include <winpr/crt.h> +#include <winpr/print.h> + +#include <freerdp/freerdp.h> +#include <freerdp/codec/color.h> +#include <freerdp/codec/bitmap.h> +#include <freerdp/codec/interleaved.h> +#include <winpr/crypto.h> +#include <freerdp/utils/profiler.h> + +static BOOL run_encode_decode_single(UINT16 bpp, BITMAP_INTERLEAVED_CONTEXT* encoder, + BITMAP_INTERLEAVED_CONTEXT* decoder +#if defined(WITH_PROFILER) + , + PROFILER* profiler_comp, PROFILER* profiler_decomp +#endif +) +{ + BOOL rc2 = FALSE; + BOOL rc = 0; + const UINT32 w = 64; + const UINT32 h = 64; + const UINT32 x = 0; + const UINT32 y = 0; + const UINT32 format = PIXEL_FORMAT_RGBX32; + const UINT32 bstep = FreeRDPGetBytesPerPixel(format); + const size_t step = (w + 13) * 4; + const size_t SrcSize = step * h; + const float maxDiff = 4.0f * ((bpp < 24) ? 2.0f : 1.0f); + UINT32 DstSize = SrcSize; + BYTE* pSrcData = calloc(1, SrcSize); + BYTE* pDstData = calloc(1, SrcSize); + BYTE* tmp = calloc(1, SrcSize); + + if (!pSrcData || !pDstData || !tmp) + goto fail; + + winpr_RAND(pSrcData, SrcSize); + + if (!bitmap_interleaved_context_reset(encoder) || !bitmap_interleaved_context_reset(decoder)) + goto fail; + + PROFILER_ENTER(profiler_comp) + rc = + interleaved_compress(encoder, tmp, &DstSize, w, h, pSrcData, format, step, x, y, NULL, bpp); + PROFILER_EXIT(profiler_comp) + + if (!rc) + goto fail; + + PROFILER_ENTER(profiler_decomp) + rc = interleaved_decompress(decoder, tmp, DstSize, w, h, bpp, pDstData, format, step, x, y, w, + h, NULL); + PROFILER_EXIT(profiler_decomp) + + if (!rc) + goto fail; + + for (UINT32 i = 0; i < h; i++) + { + const BYTE* srcLine = &pSrcData[i * step]; + const BYTE* dstLine = &pDstData[i * step]; + + for (UINT32 j = 0; j < w; j++) + { + BYTE r = 0; + BYTE g = 0; + BYTE b = 0; + BYTE dr = 0; + BYTE dg = 0; + BYTE db = 0; + const UINT32 srcColor = FreeRDPReadColor(&srcLine[j * bstep], format); + const UINT32 dstColor = FreeRDPReadColor(&dstLine[j * bstep], format); + FreeRDPSplitColor(srcColor, format, &r, &g, &b, NULL, NULL); + FreeRDPSplitColor(dstColor, format, &dr, &dg, &db, NULL, NULL); + + if (fabsf((float)r - dr) > maxDiff) + goto fail; + + if (fabsf((float)g - dg) > maxDiff) + goto fail; + + if (fabsf((float)b - db) > maxDiff) + goto fail; + } + } + + rc2 = TRUE; +fail: + free(pSrcData); + free(pDstData); + free(tmp); + return rc2; +} + +static const char* get_profiler_name(BOOL encode, UINT16 bpp) +{ + switch (bpp) + { + case 24: + if (encode) + return "interleaved_compress 24bpp"; + else + return "interleaved_decompress 24bpp"; + + case 16: + if (encode) + return "interleaved_compress 16bpp"; + else + return "interleaved_decompress 16bpp"; + + case 15: + if (encode) + return "interleaved_compress 15bpp"; + else + return "interleaved_decompress 15bpp"; + + default: + return "configuration error!"; + } +} + +static BOOL run_encode_decode(UINT16 bpp, BITMAP_INTERLEAVED_CONTEXT* encoder, + BITMAP_INTERLEAVED_CONTEXT* decoder) +{ + BOOL rc = FALSE; + PROFILER_DEFINE(profiler_comp) + PROFILER_DEFINE(profiler_decomp) + PROFILER_CREATE(profiler_comp, get_profiler_name(TRUE, bpp)) + PROFILER_CREATE(profiler_decomp, get_profiler_name(FALSE, bpp)) + + for (UINT32 x = 0; x < 50; x++) + { + if (!run_encode_decode_single(bpp, encoder, decoder +#if defined(WITH_PROFILER) + , + profiler_comp, profiler_decomp +#endif + )) + goto fail; + } + + rc = TRUE; +fail: + PROFILER_PRINT_HEADER + PROFILER_PRINT(profiler_comp) + PROFILER_PRINT(profiler_decomp) + PROFILER_PRINT_FOOTER + PROFILER_FREE(profiler_comp) + PROFILER_FREE(profiler_decomp) + return rc; +} + +static BOOL TestColorConversion(void) +{ + const UINT32 formats[] = { PIXEL_FORMAT_RGB15, PIXEL_FORMAT_BGR15, PIXEL_FORMAT_ABGR15, + PIXEL_FORMAT_ARGB15, PIXEL_FORMAT_BGR16, PIXEL_FORMAT_RGB16 }; + + /* Check color conversion 15/16 -> 32bit maps to proper values */ + for (UINT32 x = 0; x < ARRAYSIZE(formats); x++) + { + const UINT32 dstFormat = PIXEL_FORMAT_RGBA32; + const UINT32 format = formats[x]; + const UINT32 colorLow = FreeRDPGetColor(format, 0, 0, 0, 255); + const UINT32 colorHigh = FreeRDPGetColor(format, 255, 255, 255, 255); + const UINT32 colorLow32 = FreeRDPConvertColor(colorLow, format, dstFormat, NULL); + const UINT32 colorHigh32 = FreeRDPConvertColor(colorHigh, format, dstFormat, NULL); + BYTE r = 0; + BYTE g = 0; + BYTE b = 0; + BYTE a = 0; + FreeRDPSplitColor(colorLow32, dstFormat, &r, &g, &b, &a, NULL); + if ((r != 0) || (g != 0) || (b != 0)) + return FALSE; + + FreeRDPSplitColor(colorHigh32, dstFormat, &r, &g, &b, &a, NULL); + if ((r != 255) || (g != 255) || (b != 255)) + return FALSE; + } + + return TRUE; +} + +int TestFreeRDPCodecInterleaved(int argc, char* argv[]) +{ + BITMAP_INTERLEAVED_CONTEXT* encoder = NULL; + BITMAP_INTERLEAVED_CONTEXT* decoder = NULL; + int rc = -1; + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + encoder = bitmap_interleaved_context_new(TRUE); + decoder = bitmap_interleaved_context_new(FALSE); + + if (!encoder || !decoder) + goto fail; + + if (!run_encode_decode(24, encoder, decoder)) + goto fail; + + if (!run_encode_decode(16, encoder, decoder)) + goto fail; + + if (!run_encode_decode(15, encoder, decoder)) + goto fail; + + if (!TestColorConversion()) + goto fail; + + rc = 0; +fail: + bitmap_interleaved_context_free(encoder); + bitmap_interleaved_context_free(decoder); + return rc; +} diff --git a/libfreerdp/codec/test/TestFreeRDPCodecMppc.c b/libfreerdp/codec/test/TestFreeRDPCodecMppc.c new file mode 100644 index 0000000..b0d70d7 --- /dev/null +++ b/libfreerdp/codec/test/TestFreeRDPCodecMppc.c @@ -0,0 +1,1093 @@ +#include <winpr/crt.h> +#include <winpr/print.h> +#include <winpr/bitstream.h> + +#include <freerdp/freerdp.h> +#include <freerdp/log.h> + +#include "../mppc.h" + +static const BYTE TEST_RDP5_COMPRESSED_DATA[] = { + 0x24, 0x02, 0x03, 0x09, 0x00, 0x20, 0x0c, 0x05, 0x10, 0x01, 0x40, 0x0a, 0xbf, 0xdf, 0xc3, 0x20, + 0x80, 0x00, 0x1f, 0x0a, 0x00, 0x00, 0x07, 0x43, 0x4e, 0x00, 0x68, 0x02, 0x00, 0x22, 0x00, 0x34, + 0xcb, 0xfb, 0xf8, 0x18, 0x40, 0x01, 0x00, 0x27, 0xe2, 0x90, 0x0f, 0xc3, 0x91, 0xa8, 0x00, 0x08, + 0x00, 0x00, 0x68, 0x50, 0x60, 0x65, 0xfc, 0x0e, 0xfe, 0x04, 0x00, 0x08, 0x00, 0x06, 0x0c, 0x00, + 0x01, 0x00, 0xf8, 0x40, 0x20, 0x00, 0x00, 0x90, 0x00, 0xcf, 0x95, 0x1f, 0x44, 0x90, 0x00, 0x6e, + 0x03, 0xf4, 0x40, 0x21, 0x9f, 0x26, 0x01, 0xbf, 0x88, 0x10, 0x90, 0x00, 0x08, 0x04, 0x00, 0x04, + 0x30, 0x03, 0xe4, 0xc7, 0xea, 0x05, 0x1e, 0x87, 0xf8, 0x20, 0x1c, 0x00, 0x10, 0x84, 0x22, 0x1f, + 0x71, 0x0d, 0x0e, 0xb9, 0x88, 0x9f, 0x5c, 0xee, 0x41, 0x97, 0xfb, 0xf8, 0x88, 0x68, 0x08, 0x6d, + 0xd0, 0x44, 0xfc, 0x34, 0x06, 0xe6, 0x16, 0x21, 0x04, 0x11, 0x0f, 0xb9, 0x85, 0x86, 0x5d, 0x44, + 0x4f, 0xae, 0xb7, 0x40, 0xa8, 0xcd, 0x5b, 0xed, 0x02, 0xee, 0xc2, 0x21, 0x40, 0x21, 0x21, 0x23, + 0x17, 0xb7, 0x00, 0x60, 0x00, 0x3b, 0xfd, 0xfc, 0x00, 0x0c, 0x00, 0x08, 0x00, 0x34, 0x00, 0x33, + 0xc7, 0xe0, 0xc0, 0x0f, 0x07, 0x12, 0x42, 0x01, 0xe8, 0x6c, 0xc7, 0x83, 0x07, 0x8c, 0xd4, 0x30, + 0x07, 0x20, 0x01, 0x90, 0xa3, 0xf1, 0xdb, 0xf5, 0xd4, 0x13, 0xc2, 0x4f, 0x0f, 0xe5, 0xe2, 0xc7, + 0x87, 0xf2, 0xf0, 0x93, 0xc3, 0xf9, 0x78, 0xb0, 0x1a, 0x03, 0xe1, 0xf1, 0xd0, 0x08, 0x4c, 0x66, + 0xac, 0x32, 0x31, 0x70, 0x60, 0x11, 0x01, 0x11, 0x01, 0x01, 0x01, 0xf0, 0x36, 0x1f, 0xe5, 0xe0, + 0x6c, 0xbc, 0x26, 0xf0, 0x36, 0x5f, 0xe5, 0xe0, 0x6c, 0xbc, 0x26, 0xf0, 0x34, 0xf9, 0x94, 0x32, + 0x31, 0x74, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xbf, 0x87, 0xdf, 0xef, 0xfe, 0x4b, 0xbf, 0x02, 0xfa, + 0xde, 0xa7, 0x79, 0x32, 0x44, 0x7c, 0x20, 0x82, 0x00, 0x5f, 0xef, 0xff, 0x09, 0xe1, 0x05, 0x74, + 0x32, 0xea, 0x09, 0xe1, 0x0f, 0x55, 0x83, 0x85, 0x2a, 0xa0, 0x1d, 0x50, 0x0e, 0x0e, 0x0b, 0x01, + 0x01, 0x43, 0x06, 0x02, 0xbe, 0x5f, 0x00, 0x00, 0x0c, 0x3d, 0x4d, 0x87, 0xa6, 0x5e, 0xa6, 0xcb, + 0xc3, 0xcf, 0x53, 0x65, 0xe9, 0x97, 0xa9, 0xb2, 0xf5, 0x9b, 0xd4, 0xd3, 0xee, 0xcd, 0xc0, 0x7c, + 0xae, 0xe0, 0x65, 0x1f, 0xe5, 0xe0, 0x6c, 0xbc, 0x26, 0xf0, 0x36, 0x5f, 0xe5, 0xe0, 0x6c, 0xbc, + 0x26, 0xf0, 0x34, 0xfb, 0xb3, 0xf2, 0x41, 0x30, 0x20, 0x04, 0xa0, 0x80, 0x93, 0xf3, 0xf2, 0x1b, + 0xed, 0xf6, 0x0f, 0x04, 0x82, 0x7b, 0xcc, 0x00, 0x65, 0xef, 0x4f, 0x86, 0x02, 0xf7, 0xa7, 0xe0, + 0x0a, 0x88, 0x1c, 0x34, 0x02, 0x02, 0x02, 0x60, 0x60, 0x49, 0x40, 0xc1, 0x2f, 0x14, 0xca, 0x60, + 0xc1, 0x81, 0x80, 0x07, 0xc3, 0x00, 0x00, 0x39, 0xfa, 0x86, 0x38, 0x93, 0x47, 0x08, 0x27, 0x08, + 0xfc, 0xb8, 0x4e, 0x38, 0x47, 0xe5, 0xc2, 0x09, 0xc2, 0x3f, 0x2e, 0x13, 0x8e, 0x11, 0xf3, 0xc3, + 0x57, 0x1a, 0x88, 0x7d, 0x44, 0x3c, 0x3c, 0x04, 0x0f, 0xd4, 0x3f, 0x83, 0x8d, 0x82, 0x00, 0x25, + 0x04, 0x84, 0xdf, 0xe0, 0x17, 0xf8, 0x04, 0x03, 0xe1, 0x47, 0xc4, 0xaf, 0x9c, 0x00, 0x00, 0x31, + 0xf5, 0x4c, 0x71, 0x78, 0x8f, 0x54, 0xfb, 0x1c, 0x97, 0xa4, 0x04, 0x13, 0xd5, 0x2f, 0x77, 0xc7, + 0xb8, 0x9e, 0xef, 0xcb, 0xc2, 0x6f, 0x77, 0xe5, 0xee, 0x27, 0xbb, 0xf2, 0xf7, 0xe3, 0xdd, 0xf3, + 0xc6, 0xfb, 0x2a, 0x78, 0x6d, 0x3c, 0x34, 0x37, 0xc0, 0xaf, 0x25, 0xc7, 0x81, 0x7d, 0x6e, 0x5d, + 0x5c, 0xd6, 0xe3, 0x43, 0xc0, 0x82, 0xd0, 0x95, 0x90, 0xd8, 0xbd, 0xfc, 0x00, 0x09, 0xc0, 0x34, + 0x39, 0x46, 0x84, 0x20, 0x40, 0x38, 0xa3, 0x42, 0x12, 0xb0, 0x55, 0xbe, 0x28, 0xc0, 0x70, 0x64, + 0x28, 0xc8, 0x48, 0x42, 0x08, 0xb2, 0x1b, 0x46, 0xa6, 0x09, 0x54, 0x2e, 0x5f, 0x73, 0x84, 0xfc, + 0x28, 0x4a, 0x73, 0x79, 0xf2, 0x6c, 0x5d, 0x82, 0x82, 0x6e, 0xc2, 0x27, 0xd7, 0x6b, 0xb8, 0x4f, + 0xa4, 0xa4, 0x22, 0xee, 0x22, 0x7e, 0x10, 0x03, 0x78, 0x08, 0xf4, 0x94, 0x5e, 0x02, 0x01, 0xef, + 0x02, 0x27, 0xd7, 0x8b, 0xc8, 0x3f, 0xa4, 0xa4, 0x1a, 0xf3, 0xd1, 0x84, 0x0c, 0x32, 0x31, 0x75, + 0x60, 0x05, 0xe2, 0x30, 0xb7, 0xad, 0x5b, 0x15, 0xd5, 0xc3, 0xc0, 0x00, 0x11, 0x81, 0x81, 0x69, + 0x8f, 0x06, 0x0f, 0x14, 0xcf, 0xa6, 0xe8, 0xb1, 0x22, 0x77, 0xeb, 0xd7, 0x45, 0x89, 0xf0, 0xb6, + 0x3e, 0x23, 0x06, 0x80, 0xf8, 0x5b, 0x0f, 0x04, 0x83, 0xfc, 0x2d, 0x8f, 0x88, 0xc1, 0xa0, 0x3e, + 0x16, 0x1d, 0x00, 0x83, 0x74, 0x58, 0xa0, 0xc0, 0x10, 0xce, 0x8b, 0x17, 0xe0, 0x68, 0xff, 0x20, + 0xff, 0x03, 0x63, 0xe5, 0xcf, 0x1f, 0xa0, 0x40, 0x00, 0x00, 0x2a, 0xff, 0xd6, 0xd1, 0xc0, 0xb9, + 0xe0, 0x5f, 0x6b, 0x81, 0x73, 0xc9, 0x93, 0xd1, 0x63, 0x50, 0xf0, 0x9b, 0xf0, 0x48, 0x4f, 0xaf, + 0xe0, 0x1b, 0xef, 0x82, 0x6f, 0xc2, 0x40, 0xe0, 0xe4, 0x60, 0xa0, 0x69, 0xa1, 0xa1, 0xbe, 0xba, + 0x04, 0x00, 0x08, 0x00, 0x10, 0x00, 0x20, 0x00, 0x42, 0x00, 0x44, 0x00, 0x88, 0x01, 0x10, 0x02, + 0x21, 0x02, 0x22, 0x04, 0x44, 0x08, 0x9c, 0x8f, 0xcd, 0xe0, 0x02, 0x20, 0x88, 0x02, 0x10, 0x40, + 0x01, 0xf0, 0x60, 0x44, 0xc0, 0xce, 0xb1, 0x8f, 0xd0, 0x30, 0x00, 0x60, 0x00, 0xa0, 0x00, 0xc4, + 0x00, 0xcc, 0x01, 0x98, 0x03, 0x28, 0x03, 0x31, 0x03, 0x33, 0x06, 0x66, 0x07, 0x0e, 0x2c, 0xe3, + 0x7b, 0x18, 0x85, 0xc7, 0xd6, 0x51, 0x71, 0x0f, 0x0e, 0xb8, 0x88, 0x9f, 0x5c, 0x6e, 0x41, 0xde, + 0xeb, 0x71, 0x20, 0x5c, 0xba, 0xf7, 0xc8, 0x6f, 0xba, 0xc1, 0xf7, 0x30, 0xd0, 0xce, 0xc1, 0x31, + 0x74, 0xec, 0x13, 0x41, 0x77, 0x41, 0x13, 0xa0, 0x10, 0xbf, 0x7c, 0x45, 0xd3, 0xa5, 0xbc, 0x55, + 0x84, 0xaa, 0x41, 0xc1, 0xc1, 0xe0, 0xe0, 0x29, 0x01, 0x20, 0x81, 0x00, 0x03, 0x80, 0x07, 0xc0, + 0x0f, 0xe0, 0x06, 0xbe, 0x16, 0x75, 0xe7, 0x9f, 0xfb, 0x1e, 0x17, 0x90, 0xef, 0x0b, 0xbb, 0x15, + 0x03, 0x7c, 0x2b, 0x7e, 0x22, 0x78, 0x56, 0x83, 0xae, 0x77, 0x40, 0xcf, 0xb0, 0xf0, 0x98, 0x28, + 0x04, 0x2f, 0xaf, 0x0e, 0x40, 0xfc, 0x01, 0x1c, 0x5c, 0xb1, 0xf2, 0xbf, 0xa5, 0xd7, 0x8f, 0x97, + 0xc0, 0xfe, 0x9f, 0x02, 0xe7, 0x24, 0x79, 0xe0, 0x9b, 0xa9, 0xfd, 0x74, 0x3b, 0xaf, 0x2d, 0xf8, + 0x4b, 0xd2, 0xf7, 0x84, 0x54, 0x04, 0x2a, 0x02, 0x02, 0x01, 0xe1, 0x1e, 0xf0, 0x87, 0xff, 0x77, + 0x07, 0x00, 0x02, 0x00, 0x0d, 0xbd, 0xe1, 0xf0, 0x01, 0x1e, 0xf0, 0xfd, 0x80, 0x4c, 0x24, 0x11, + 0x2c, 0x10, 0x24, 0x02, 0x01, 0x40, 0xb0, 0x5c, 0x2c, 0x14, 0x08, 0x07, 0x1b, 0x80, 0x01, 0xa7, + 0xbd, 0x3e, 0x00, 0x27, 0xde, 0x9f, 0xb0, 0x85, 0x01, 0xfb, 0xd2, 0x04, 0x0c, 0x1c, 0x2e, 0x0e, + 0x06, 0x18, 0x03, 0xd4, 0x00, 0x00, 0x67, 0xef, 0x4f, 0x80, 0x0a, 0xf7, 0xa7, 0xe3, 0x94, 0xe0, + 0xe0, 0x10, 0x1b, 0xfd, 0xfc, 0x74, 0x62, 0xe8, 0xc0, 0x1d, 0x62, 0x00, 0x0b, 0x00, 0xb7, 0x70, + 0xe6, 0x8a, 0x68, 0x75, 0x38, 0x3c, 0x3c, 0x4c, 0x2f, 0x87, 0xef, 0x01, 0xc7, 0xb2, 0x40, 0x21, + 0xa3, 0x23, 0x0a, 0x08, 0x01, 0xa1, 0xa1, 0xe1, 0x80, 0x69, 0x40, 0xe1, 0x00, 0x00, 0x40, 0xd0, + 0xea, 0xe5, 0xe1, 0xc0, 0x81, 0x87, 0xed, 0x68, 0x1a, 0x08, 0x94, 0x0c, 0x0c, 0xf1, 0x7c, 0xbe, + 0x5f, 0x2f, 0x8f, 0x00, 0x00, 0x0d, 0x1f, 0x68, 0x7a, 0x1a, 0x04, 0x05, 0xce, 0xe6, 0x2a, 0x0c, + 0x01, 0xc2, 0x00, 0x40, 0x42, 0x61, 0xc0, 0x49, 0x41, 0x60, 0xa0, 0x80, 0x01, 0xc0, 0x03, 0xe0, + 0x07, 0xf0, 0x07, 0xfa, 0x00, 0x07, 0x3b, 0x99, 0x01, 0x0f, 0x19, 0x18, 0x54, 0x40, 0xe0, 0x60, + 0xee, 0xd0, 0x0e, 0x19, 0x0a, 0x03, 0xa5, 0x7d, 0x05, 0xd0, 0x83, 0x98, 0x5a, 0x96, 0x21, 0x4b, + 0x10, 0x10, 0xe6, 0x17, 0xaf, 0xeb, 0xaf, 0x34, 0x3c, 0xc8, 0x0f, 0xf0, 0x64, 0x3f, 0xd0, 0x0f, + 0xe0, 0x03, 0xfe, 0x10, 0x02, 0x7d, 0x47, 0x2d, 0x58, 0xfc, 0x35, 0xe0, 0xca, 0x0f, 0x19, 0x0a, + 0xf9, 0xf1, 0xe0, 0xb9, 0xc0, 0x81, 0x10, 0x03, 0xe0, 0xbd, 0x4f, 0xea, 0x61, 0xf7, 0xeb, 0xf6, + 0x02, 0xd4, 0x7a, 0xf9, 0xff, 0x15, 0x30, 0xfa, 0x88, 0x68, 0x68, 0xd8, 0x80, 0x12, 0x60, 0x50, + 0x50, 0xf0, 0x03, 0xfc, 0x01, 0xfe, 0x01, 0x7f, 0xa0, 0x7c, 0x28, 0xbf, 0xd0, 0x3e, 0x64, 0x0f, + 0x00, 0x37, 0x00, 0x08, 0x80, 0x20, 0x0b, 0x88, 0x81, 0xa5, 0x04, 0x84, 0x60, 0x40, 0x36, 0x04, + 0x1b, 0x8f, 0x88, 0x01, 0x00, 0xa1, 0x80, 0x1e, 0x00, 0x36, 0xfd, 0xb9, 0x12, 0x02, 0x4c, 0x09, + 0x08, 0x1e, 0x00, 0x61, 0x80, 0x20, 0x60, 0x44, 0x17, 0xdc, 0x7c, 0x62, 0x00, 0x03, 0x67, 0xdb, + 0x81, 0xb1, 0x30, 0x34, 0xb0, 0xa0, 0xaf, 0xa0, 0x80, 0x75, 0x35, 0x20, 0x7c, 0x49, 0xfc, 0x0f, + 0xf5, 0x0d, 0x7f, 0x7e, 0x45, 0x00, 0x53, 0x42, 0x82, 0x83, 0xc0, 0x0c, 0x28, 0x1f, 0x72, 0x3e, + 0xd3, 0xf5, 0x62, 0xd4, 0x00, 0x22, 0xa8, 0x81, 0xec, 0x67, 0x96, 0x02, 0xa0, 0x49, 0x7d, 0xfd, + 0x6b, 0xbf, 0xcc, 0x7c, 0x4a, 0xf8, 0xd0, 0x00, 0x00, 0xcf, 0xd5, 0xd2, 0x23, 0x35, 0x60, 0x01, + 0xf1, 0x60, 0x14, 0xc0, 0xb0, 0xbe, 0xb3, 0x02, 0x0f, 0x89, 0x5f, 0x1b, 0x00, 0x02, 0x0b, 0xfd, + 0x80, 0x00, 0x01, 0x9b, 0xf3, 0x40, 0x42, 0x10, 0x00, 0xd8, 0xb8, 0x0f, 0xa8, 0x17, 0xfe, 0x59, + 0xef, 0x14, 0x61, 0xf2, 0x30, 0x65, 0xfc, 0x51, 0xe2, 0xc1, 0x18, 0xc0, 0x07, 0x5e, 0x68, 0x08, + 0xe8, 0x46, 0xf8, 0x95, 0xf1, 0xb0, 0xf9, 0x13, 0x7f, 0xbc, 0x00, 0x00, 0x32, 0x7e, 0xa8, 0xeb, + 0xcd, 0x03, 0x20, 0x09, 0xa1, 0x81, 0x97, 0xfb, 0x87, 0x80, 0xb0, 0xf9, 0x19, 0x7c, 0xa8, 0x63, + 0xf3, 0xe6, 0x20, 0x22, 0xbd, 0x85, 0x9e, 0x62, 0x00, 0x8b, 0x7c, 0x87, 0x91, 0x00, 0x22, 0xff, + 0x21, 0xe2, 0xa0, 0x08, 0xc7, 0xc8, 0x78, 0x20, 0x02, 0x33, 0xf2, 0x1c, 0x10, 0x41, 0xe3, 0x40, + 0x69, 0x7c, 0x45, 0x72, 0x62, 0xf0, 0x04, 0x7f, 0x60, 0x68, 0x6f, 0x80, 0x00, 0x08, 0x1f, 0xf7, + 0xad, 0x51, 0x03, 0xf3, 0xf8, 0xa0, 0x9d, 0xa8, 0x40, 0x00, 0x23, 0x42, 0x37, 0x46, 0x0f, 0xde, + 0xa6, 0x06, 0xd3, 0x3c, 0x33, 0xe1, 0x78, 0xd8, 0x34, 0x32, 0x14, 0x67, 0xdb, 0xd2, 0x38, 0xaf, + 0xc7, 0x9c, 0xdf, 0xd0, 0x21, 0xe6, 0xd7, 0x80, 0x40, 0x22, 0x3f, 0x21, 0xe8, 0xd8, 0x12, 0xf9, + 0x0f, 0xb4, 0x01, 0x13, 0xf9, 0x0f, 0x46, 0xc0, 0xa7, 0x13, 0x37, 0x1e, 0x67, 0x07, 0x8b, 0x01, + 0xfd, 0xfe, 0x0f, 0xf7, 0x7a, 0xf0, 0x16, 0x36, 0x0a, 0x92, 0x08, 0x08, 0xc1, 0x70, 0xb8, 0x30, + 0x34, 0xf1, 0xf3, 0x72, 0x27, 0x8f, 0x4b, 0x60, 0x21, 0xc4, 0xdd, 0xe2, 0xdf, 0x0b, 0xca, 0x4f, + 0x2e, 0x4f, 0x9c, 0xde, 0x59, 0xe9, 0xf1, 0x55, 0x00, 0x8d, 0xf2, 0x20, 0x53, 0x3c, 0xc4, 0xf6, + 0x46, 0x7e, 0x24, 0xee, 0xf2, 0x0c, 0x0d, 0x81, 0x83, 0xf9, 0x98, 0x0e, 0x00, 0x02, 0x10, 0x11, + 0x01, 0x08, 0x95, 0x2a, 0xfc, 0x28, 0x95, 0x2a, 0x84, 0x80, 0xbf, 0x81, 0x06, 0x80, 0x0d, 0x00, + 0x86, 0xe0, 0x6b, 0xa5, 0xc3, 0xd8, 0x8f, 0x22, 0xa0, 0x3e, 0xe9, 0x8f, 0x90, 0xf2, 0x6b, 0x85, + 0x77, 0x57, 0x99, 0x43, 0x5c, 0x66, 0x5f, 0x9e, 0x85, 0x7c, 0x3f, 0x1f, 0xb3, 0xce, 0xc0, 0x0e, + 0x64, 0x20, 0x0e, 0x20, 0xdc, 0x7e, 0x18, 0x81, 0x90, 0xa3, 0x13, 0x4e, 0x52, 0x71, 0x81, 0x03, + 0xa4, 0x30, 0x30, 0x6c, 0x73, 0x8f, 0xc4, 0x50, 0x60, 0x16, 0x38, 0x03, 0xbf, 0x6f, 0x89, 0x3e, + 0x00, 0x77, 0x00, 0xb1, 0xc0, 0x28, 0x3d, 0x73, 0x98, 0x06, 0xfe, 0x00, 0xe9, 0x81, 0xa3, 0xb8, + 0x1c, 0x85, 0x20, 0x45, 0x45, 0xe1, 0xa1, 0x23, 0x63, 0xa0, 0x29, 0x61, 0x41, 0x27, 0xf4, 0x03, + 0xfa, 0x01, 0x02, 0x05, 0xff, 0xe1, 0x20, 0x34, 0x08, 0x08, 0x04, 0x04, 0x02, 0xff, 0xeb, 0x96, + 0x05, 0x24, 0x8e, 0x0a, 0xb1, 0xce, 0xf2, 0x06, 0xc7, 0xb9, 0x01, 0xd7, 0x20, 0x52, 0x04, 0x03, + 0xe1, 0x47, 0xc4, 0xa4, 0x0b, 0xfd, 0x03, 0x01, 0xc0, 0x47, 0xe6, 0xc0, 0x2c, 0x7c, 0x09, 0x10, + 0x1c, 0x0a, 0xfd, 0x7e, 0xc0, 0xd2, 0x94, 0x7a, 0x1a, 0x06, 0x07, 0xcf, 0x12, 0x2a, 0x8c, 0x1e, + 0xe7, 0x07, 0x08, 0x81, 0x81, 0x91, 0x90, 0x72, 0x26, 0x9e, 0x55, 0x44, 0x0e, 0x4d, 0x21, 0x00, + 0x08, 0x40, 0x02, 0x20, 0x01, 0x17, 0x2c, 0xd4, 0x22, 0x00, 0x88, 0x80, 0x44, 0x40, 0x23, 0xcd, + 0xf8, 0xf1, 0xc8, 0x9b, 0x02, 0x10, 0x0c, 0x02, 0x99, 0x30, 0x00, 0x0a, 0x06, 0x01, 0x4b, 0x18, + 0x00, 0x46, 0x00, 0x29, 0x9c, 0xa3, 0x86, 0x60, 0x11, 0x98, 0x05, 0x32, 0x80, 0xcc, 0xc0, 0xf3, + 0xc3, 0xb8, 0x7a, 0x21, 0x7d, 0xbe, 0xfa, 0xce, 0x2a, 0x9d, 0xfa, 0xa0, 0x3c, 0x32, 0xfb, 0x7d, + 0x13, 0x22, 0x05, 0xeb, 0x0b, 0xbb, 0xb8, 0x00, 0x15, 0xfe, 0xfe, 0x1a, 0x14, 0x7e, 0x1c, 0x00, + 0x01, 0x82, 0x3a, 0xa7, 0xd2, 0x6c, 0x11, 0xdd, 0x00, 0x00, 0x00, 0xc0, 0x40, 0x18, 0x23, 0x5a, + 0x00, 0x80, 0xb0, 0x47, 0x84, 0x7c, 0xa8, 0x03, 0xa7, 0x82, 0x48, 0x83, 0x01, 0x50, 0x11, 0x2a, + 0x37, 0xfb, 0xfc, 0x03, 0x03, 0xd1, 0xa3, 0x35, 0x68, 0xcd, 0x58, 0x40, 0x03, 0xe3, 0x47, 0xc4, + 0xaf, 0x8d, 0x1f, 0x42, 0x84, 0x20, 0x81, 0x08, 0x57, 0xfb, 0xff, 0xd0, 0x98, 0x27, 0xc8, 0xaf, + 0x99, 0x1f, 0x12, 0x04, 0x3e, 0x84, 0xfe, 0x08, 0x1c, 0xc1, 0x31, 0x58, 0x80, 0x3a, 0xd1, 0x99, + 0x8a, 0x40, 0x02, 0x5a, 0x04, 0x00, 0x02, 0x1a, 0x38, 0xf3, 0x08, 0x00, 0x01, 0xda, 0xe3, 0x35, + 0x60, 0x5f, 0x88, 0x00, 0x03, 0x6e, 0xbf, 0xdf, 0xc0, 0xbe, 0x20, 0x00, 0x42, 0x80, 0x01, 0x77, + 0x9e, 0x80, 0xd0, 0x30, 0x4a, 0x32, 0x81, 0xe3, 0x94, 0x04, 0x21, 0x0a, 0x9c, 0xcc, 0x52, 0x03, + 0x7d, 0xa7, 0x0c, 0x51, 0x80, 0x6f, 0xa5, 0xc0, 0x3f, 0x3e, 0x80, 0xa0, 0x22, 0x10, 0x40, 0x68, + 0x17, 0x9f, 0x60, 0x1e, 0x9b, 0x09, 0x52, 0x03, 0x2d, 0x03, 0x81, 0x88, 0x41, 0x3c, 0x65, 0x14, + 0x98, 0xcd, 0x58, 0x6a, 0x04, 0x21, 0x80, 0x9b, 0x81, 0x45, 0x21, 0x24, 0xe1, 0x8c, 0xf1, 0x9a, + 0xb0, 0xa9, 0x38, 0xef, 0xe7, 0x90, 0xdf, 0x98, 0x00, 0x19, 0xa8, 0x18, 0x42, 0x6a, 0xc0, 0x7f, + 0xda, 0x00, 0x00, 0x2b, 0x1e, 0x36, 0x7c, 0xaa, 0xa0, 0x00, 0xc0, 0xf8, 0xa0, 0xbe, 0x60, 0x2e, + 0xb1, 0x09, 0xab, 0x60, 0x3e, 0x38, 0xf9, 0x6f, 0xa9, 0x3e, 0x08, 0x81, 0xa6, 0x8c, 0x13, 0xae, + 0x83, 0x7e, 0x0a, 0xfb, 0x0f, 0x60, 0x86, 0x3e, 0x90, 0x6d, 0xa2, 0x33, 0x56, 0x06, 0xfa, 0xcf, + 0xc5, 0x1f, 0x12, 0x38, 0x49, 0x3d, 0x04, 0x03, 0xa6, 0x42, 0x54, 0x82, 0x3e, 0xd3, 0xd1, 0xd0, + 0x08, 0x58, 0x06, 0xdc, 0x10, 0x85, 0xe8, 0xf8, 0xf8, 0x94, 0x10, 0x84, 0x21, 0xe7, 0xa3, 0x85, + 0xfe, 0xfe, 0xc1, 0xe9, 0x77, 0xa3, 0x27, 0xe7, 0xbd, 0x31, 0x98, 0x17, 0xa1, 0xe2, 0x13, 0xe8, + 0x5a, 0xf1, 0x44, 0x7c, 0x4a, 0x00, 0x00, 0x07, 0x2d, 0x03, 0x2d, 0x05, 0xa3, 0x46, 0x6a, 0xc1, + 0x9e, 0x9f, 0x9f, 0x51, 0xc0, 0x55, 0x1a, 0x13, 0x56, 0x0e, 0xf4, 0xa4, 0x85, 0xfd, 0x4c, 0x47, + 0x10, 0x0d, 0x70, 0x24, 0x9b, 0xfa, 0x45, 0x41, 0x3a, 0x33, 0xea, 0x28, 0x60, 0x00, 0x80, 0x00, + 0xbc, 0x00, 0x80, 0x7b, 0x2e, 0x43, 0x10, 0x0b, 0x00, 0xec, 0x1e, 0x98, 0x8a, 0xb4, 0x26, 0xac, + 0x5f, 0xf9, 0x20, 0x03, 0xf2, 0xc1, 0xdf, 0xca, 0x14, 0x40, 0x07, 0x40, 0x1e, 0x00, 0x3d, 0x10, + 0xe1, 0x37, 0x90, 0x64, 0x17, 0xec, 0x3d, 0x4c, 0xf5, 0x94, 0x20, 0x15, 0x80, 0xdc, 0x3e, 0x74, + 0x7f, 0x87, 0x87, 0xa9, 0xa6, 0x33, 0x56, 0x16, 0xfd, 0xcf, 0xa9, 0x1f, 0x12, 0x23, 0x35, 0x60, + 0xaf, 0xa4, 0x04, 0xf5, 0xb0, 0x1f, 0xe4, 0x3d, 0x75, 0x1c, 0x20, 0xeb, 0xd7, 0x19, 0x00, 0xb8, + 0x04, 0x21, 0x7a, 0xd3, 0xbe, 0x15, 0xeb, 0x4a, 0xf1, 0x84, 0x78, 0x52, 0x3e, 0x25, 0x03, 0x16, + 0x81, 0xc3, 0x7d, 0x59, 0x1f, 0x12, 0x30, 0x50, 0xe3, 0xe1, 0xcf, 0xc5, 0x8f, 0xa1, 0x1c, 0x0e, + 0x9e, 0xd0, 0x0d, 0x7b, 0x18, 0x14, 0xcc, 0x21, 0x04, 0x1b, 0x6a, 0x8c, 0xd5, 0x86, 0xe0, 0x31, + 0x9a, 0xb0, 0x4f, 0xc8, 0x0b, 0x7c, 0x40, 0x37, 0xc4, 0x5c, 0x22, 0x80, 0x3e, 0x54, 0x71, 0x10, + 0xbf, 0x26, 0xf9, 0xa2, 0x1c, 0x0b, 0x82, 0xf0, 0x8f, 0x22, 0x47, 0x8a, 0xab, 0xca, 0xd4, 0x31, + 0x08, 0xf1, 0xe6, 0x51, 0x9a, 0xb7, 0xcc, 0x80, 0x7f, 0xc9, 0xc2, 0x13, 0x08, 0xfd, 0x95, 0xfe, + 0x23, 0xc0, 0x14, 0x0f, 0x08, 0xe1, 0xb5, 0x5f, 0x4a, 0x38, 0x10, 0x47, 0x1b, 0x17, 0x0a, 0x07, + 0x1d, 0x38, 0xe3, 0xcb, 0x42, 0x10, 0x4f, 0x5d, 0x40, 0x3f, 0xf8, 0xe1, 0x0a, 0xe0, 0x45, 0xa8, + 0x47, 0xe0, 0x78, 0x23, 0x0f, 0x91, 0x5f, 0x4a, 0x7f, 0xe3, 0xc9, 0x11, 0xe0, 0x4a, 0x09, 0xfe, + 0x5a, 0xf0, 0xea, 0x8f, 0x21, 0x57, 0x82, 0xa3, 0xfa, 0x47, 0xc4, 0x8e, 0x0d, 0x8f, 0xcc, 0xfe, + 0x11, 0xf1, 0x22, 0x33, 0x56, 0xe1, 0xf9, 0x1f, 0x9a, 0x83, 0x79, 0x2d, 0xe3, 0xf5, 0x23, 0xf6, + 0x50, 0x64, 0x17, 0xce, 0x4f, 0x12, 0x58, 0x5f, 0xe0, 0xc4, 0x32, 0x0d, 0xfc, 0xab, 0xd5, 0x54, + 0x15, 0x04, 0xfd, 0x91, 0xf1, 0x20, 0x32, 0x0d, 0xe1, 0x48, 0xf8, 0x91, 0xe5, 0x48, 0x09, 0xfc, + 0xdb, 0x7b, 0xab, 0x84, 0x22, 0x0d, 0xfd, 0x23, 0xda, 0xd1, 0xf2, 0x20, 0x2a, 0x11, 0xfe, 0x23, + 0xe7, 0x4f, 0x8c, 0x2f, 0x80, 0xe7, 0x1f, 0x09, 0x40, 0x2f, 0x00, 0xee, 0x7f, 0xf5, 0x1f, 0x12, + 0x3c, 0x0d, 0x40, 0xff, 0xa9, 0xc3, 0x1b, 0x01, 0x42, 0xce, 0x18, 0x5b, 0x52, 0xd9, 0x8a, 0x79, + 0xa7, 0xbc, 0xc5, 0x01, 0x08, 0x41, 0x21, 0xb5, 0xfc, 0x1b, 0x93, 0x1e, 0x8f, 0x60, 0x02, 0x98, + 0xf8, 0xe0, 0x0c, 0x1c, 0x2e, 0x15, 0x00, 0xe7, 0x61, 0x08, 0x02, 0xfd, 0x16, 0x5c, 0xdb, 0xf2, + 0xb8, 0x4f, 0x03, 0xfd, 0x81, 0x8a, 0x88, 0x52, 0x05, 0x20, 0x0e, 0xe9, 0xf9, 0xaa, 0xed, 0x7f, + 0xbf, 0xd0, 0x0b, 0x0b, 0x42, 0x60, 0x85, 0xa1, 0x3f, 0x0a, 0x0b, 0x42, 0x40, 0x08, 0xa8, 0x02, + 0x04, 0xa9, 0x60, 0x46, 0x00, 0x45, 0x40, 0x5c, 0xa7, 0xa6, 0xfa, 0x5c, 0x07, 0xf0, 0xe0, 0xa4, + 0x0f, 0x94, 0xc4, 0x16, 0x82, 0x96, 0x82, 0x94, 0x83, 0x71, 0x76, 0x04, 0x94, 0x8f, 0xa1, 0xf3, + 0x40, 0x00, 0x93, 0x85, 0xa2, 0x50, 0xc0, 0x00, 0x28, 0x1c, 0xbb, 0x03, 0x09, 0x12, 0x5e, 0x91, + 0xaf, 0x21, 0x42, 0x05, 0x09, 0x6b, 0xe5, 0x59, 0x27, 0xcf, 0x8f, 0x88, 0x24, 0x00, 0x90, 0x7c, + 0x60, 0x00, 0x00, 0x17, 0x1a, 0x02, 0x40, 0x2c, 0x03, 0x94, 0x1a, 0xf8, 0x02, 0xa0, 0x80, 0xd2, + 0x15, 0xf5, 0x64, 0x00, 0xc0, 0x32, 0x01, 0x83, 0xa4, 0xc0, 0x5e, 0xb2, 0x0e, 0x70, 0x9a, 0x7b, + 0x12, 0x23, 0x35, 0x6f, 0x26, 0x43, 0x7f, 0x40, 0x6a, 0x04, 0xe8, 0x14, 0x04, 0xa4, 0xb3, 0x14, + 0x81, 0x30, 0x2f, 0x16, 0x84, 0xd0, 0x0c, 0x0b, 0x42, 0x6e, 0x14, 0x00, 0x9a, 0x00, 0x87, 0x76, + 0x80, 0x07, 0x98, 0x2c, 0x03, 0x99, 0x9c, 0xf3, 0xbb, 0x7f, 0xb8, 0xa4, 0xdb, 0xde, 0xfc, 0x4a, + 0x00, 0x05, 0xa4, 0xc2, 0x6a, 0xc0, 0xed, 0x3d, 0x15, 0xc1, 0x04, 0xe1, 0x30, 0x2e, 0x2c, 0xf1, + 0x50, 0x69, 0x84, 0xa9, 0x0f, 0xf8, 0xc2, 0xbe, 0x35, 0xa8, 0x87, 0x50, 0x10, 0x0e, 0x00, 0xe5, + 0x1e, 0xc6, 0xa9, 0x55, 0xfe, 0xff, 0x48, 0xf5, 0xe0, 0x53, 0xdc, 0x78, 0x80, 0x10, 0x51, 0x89, + 0x52, 0xc0, 0x06, 0xab, 0x03, 0x14, 0x6f, 0xed, 0x85, 0xde, 0x80, 0x03, 0x09, 0x52, 0xe5, 0xff, + 0x5e, 0x02, 0xbf, 0x8f, 0x8f, 0xc9, 0xcf, 0xe5, 0xeb, 0xf3, 0x72, 0xbb, 0x80, 0x00, 0xc6, 0x6a, + 0xd8, 0x08, 0x95, 0xf4, 0xb2, 0xf9, 0x4f, 0xa1, 0xc1, 0xc2, 0x5a, 0xef, 0xf7, 0xfa, 0x81, 0xdd, + 0xbd, 0xef, 0xee, 0xe0, 0xd1, 0xe5, 0x72, 0xc5, 0xcd, 0xf0, 0x2c, 0x00, 0x03, 0xcb, 0x98, 0xf0, + 0x7f, 0x52, 0x00 +}; + +static const BYTE TEST_RDP5_UNCOMPRESSED_DATA[] = { + 0x24, 0x02, 0x03, 0x09, 0x00, 0x20, 0x0c, 0x05, 0x10, 0x01, 0x40, 0x0a, 0xff, 0xff, 0x0c, 0x84, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x0d, 0x38, 0x01, 0xc0, 0x10, 0x01, 0x10, + 0x01, 0xcc, 0xff, 0x7f, 0x03, 0x08, 0x00, 0x20, 0x04, 0x05, 0x10, 0x01, 0x40, 0x0a, 0x00, 0x0c, + 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x35, 0x00, 0x01, 0x00, 0x00, 0x0d, 0x0a, + 0x0c, 0x0c, 0xff, 0x03, 0xff, 0x02, 0x00, 0x04, 0x00, 0x03, 0x06, 0x00, 0x00, 0x80, 0x00, 0x80, + 0x00, 0x02, 0x00, 0x00, 0x09, 0x00, 0x0c, 0x80, 0x00, 0x80, 0x00, 0x06, 0x00, 0x00, 0x48, 0x00, + 0x37, 0x01, 0x02, 0x00, 0x00, 0x01, 0x0c, 0x48, 0x00, 0x37, 0x01, 0x06, 0x01, 0x00, 0x00, 0x04, + 0x24, 0x00, 0x02, 0x01, 0x00, 0x01, 0x0c, 0x00, 0x04, 0x24, 0x00, 0x02, 0x00, 0x00, 0x09, 0x0a, + 0x3d, 0x0f, 0x00, 0x01, 0x00, 0x0e, 0x00, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, + 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, + 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, + 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x09, 0x18, 0xfb, 0x70, 0x06, 0x00, 0x03, + 0xff, 0xff, 0x00, 0x03, 0x00, 0x02, 0x00, 0x0d, 0x00, 0x0c, 0x00, 0x00, 0x80, 0x0c, 0x00, 0x0f, + 0x00, 0x01, 0x49, 0x08, 0x07, 0xc3, 0x66, 0x3c, 0x18, 0x3c, 0x66, 0xc3, 0x00, 0x72, 0x00, 0x19, + 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0xff, 0xff, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, + 0xf3, 0xf2, 0x0c, 0x08, 0x42, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, + 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x10, 0x84, 0x11, + 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x3a, 0x01, 0x09, 0x99, 0xd6, 0x19, 0x18, 0xf0, 0x60, 0x11, 0x01, + 0x11, 0x01, 0x01, 0x01, 0x00, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, + 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4, + 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, + 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19, 0x18, + 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, + 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, + 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, + 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, + 0x0a, 0x01, 0x09, 0x19, 0x18, 0xf4, 0x20, 0xff, 0xff, 0x00, 0x11, 0x01, 0x11, 0x01, 0x01, 0x11, + 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0xdd, 0x0c, 0xf5, + 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, + 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, + 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, + 0x0a, 0x01, 0x09, 0x19, 0x18, 0xf4, 0x60, 0x00, 0x00, 0x00, 0xd0, 0x0e, 0xd0, 0x0e, 0x0e, 0x0b, + 0x01, 0x01, 0x43, 0x06, 0x02, 0xfc, 0xfc, 0x00, 0x00, 0x30, 0x00, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, + 0xf5, 0x04, 0xff, 0xff, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0x08, + 0x42, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x99, 0xd6, 0x11, 0x0f, + 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x10, 0x84, 0x11, 0x0d, 0x01, 0x0b, 0xf6, + 0x11, 0x3a, 0x01, 0x09, 0x99, 0xd6, 0x19, 0x18, 0xf0, 0x60, 0x11, 0x01, 0x11, 0x01, 0x01, 0x01, + 0x01, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, + 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, + 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99, + 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00, + 0x00, 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, + 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, + 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, + 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19, + 0x18, 0xf4, 0x20, 0xff, 0xff, 0x00, 0x11, 0x01, 0x11, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84, + 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0xdd, 0x0c, 0xf5, 0x04, 0x08, 0x42, 0x11, + 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, + 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, + 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19, + 0x18, 0xf4, 0x60, 0x00, 0x00, 0x00, 0xd0, 0x0e, 0xd0, 0x0e, 0x0e, 0x13, 0x02, 0x00, 0x4a, 0x08, + 0x09, 0x3f, 0x3f, 0x21, 0xfd, 0xfd, 0x87, 0x84, 0x84, 0xfc, 0x00, 0x00, 0x00, 0x32, 0x00, 0x19, + 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0xff, 0xff, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, + 0xf3, 0xf2, 0x0c, 0x08, 0x42, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, + 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x10, 0x84, 0x11, + 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x3a, 0x01, 0x09, 0x99, 0xd6, 0x19, 0x18, 0xf0, 0x60, 0x11, 0x01, + 0x11, 0x01, 0x01, 0x01, 0x02, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, + 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4, + 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, + 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19, 0x18, + 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, + 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, + 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, + 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, + 0x0a, 0x01, 0x09, 0x19, 0x18, 0x54, 0x40, 0x00, 0x00, 0x00, 0x10, 0x10, 0x13, 0x03, 0x02, 0x4a, + 0x06, 0x09, 0x78, 0xcc, 0xcc, 0x18, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, 0x00, 0x00, 0x73, 0x00, + 0x19, 0x0a, 0x3f, 0xdd, 0x0c, 0xf5, 0x04, 0xff, 0xff, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, + 0x3e, 0xf3, 0xf2, 0x0c, 0x08, 0x42, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, + 0x0b, 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x10, 0x84, + 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x3a, 0x01, 0x09, 0x99, 0xd6, 0x19, 0x18, 0xf0, 0x60, 0xd1, + 0x0f, 0xd1, 0x0f, 0x0f, 0x01, 0x03, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, + 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, + 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, + 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19, + 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, + 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, + 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, + 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, + 0x11, 0x0a, 0x01, 0x09, 0x19, 0x18, 0x54, 0x40, 0x00, 0x00, 0x00, 0x10, 0x10, 0x1b, 0x04, 0x00, + 0x4a, 0x09, 0x09, 0xff, 0x80, 0xff, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, + 0x80, 0x80, 0x80, 0xff, 0x80, 0x00, 0x00, 0x31, 0x00, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, + 0xff, 0xff, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0x08, 0x42, 0x11, + 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0b, + 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x10, 0x84, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x3a, + 0x01, 0x09, 0x99, 0xd6, 0x19, 0x18, 0xf0, 0x60, 0x11, 0x01, 0x11, 0x01, 0x01, 0x01, 0x04, 0x19, + 0x0a, 0x3f, 0xdd, 0x0c, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0d, 0x0e, 0xf3, 0x11, 0x3e, + 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0b, + 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, 0xf4, 0x0a, 0x99, 0xd6, 0x11, + 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00, 0xcf, + 0x0d, 0xcf, 0x0d, 0x0d, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, + 0x0d, 0x0e, 0xf3, 0x11, 0x3e, 0xf3, 0xf2, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0d, 0xf4, 0x11, + 0x3f, 0x0d, 0x01, 0xf3, 0x0b, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0b, 0x0c, 0xf5, 0x11, 0x3e, 0xf5, + 0xf4, 0x0a, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0b, 0xf6, 0x11, 0x0a, 0x01, 0x09, 0x19, 0x18, 0xf4, + 0x20, 0xff, 0xff, 0x00, 0x11, 0x01, 0x11, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff, + 0xff, 0xff, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0xee, 0x34, 0x3c, 0x08, 0x2d, 0x09, 0x59, 0x0d, 0x97, + 0xff, 0x00, 0x02, 0x70, 0x0d, 0x0e, 0x51, 0xc2, 0x10, 0x20, 0x1c, 0x51, 0xc2, 0x12, 0xe0, 0xd6, + 0x51, 0xc2, 0x12, 0x30, 0x1c, 0x19, 0x0a, 0x32, 0x12, 0x10, 0x84, 0x59, 0x0d, 0xc6, 0xcc, 0x12, + 0xd0, 0xf2, 0x51, 0xc2, 0x10, 0x20, 0x1c, 0x51, 0xc2, 0x12, 0xe0, 0xd6, 0x51, 0xc2, 0x12, 0x30, + 0x1c, 0x19, 0x0a, 0x3f, 0x0a, 0x12, 0xb9, 0xf9, 0x08, 0x42, 0x11, 0x0f, 0xf6, 0x0a, 0x09, 0xf6, + 0x11, 0x3e, 0xf6, 0xf7, 0x09, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x08, 0xf7, 0x11, 0x3f, 0x08, 0x01, + 0xf8, 0x08, 0x10, 0x84, 0x11, 0x0f, 0xf8, 0x08, 0x07, 0xf8, 0x11, 0x3e, 0xf8, 0xf9, 0x07, 0x99, + 0xd6, 0x11, 0x0d, 0x01, 0x06, 0xf9, 0x11, 0x0a, 0x01, 0x06, 0x19, 0x18, 0xf5, 0x60, 0x05, 0x00, + 0x00, 0x00, 0xef, 0x5a, 0xec, 0x57, 0x57, 0x0f, 0x00, 0x00, 0x46, 0x06, 0x05, 0xcc, 0x78, 0x30, + 0x78, 0xcc, 0x00, 0x00, 0x00, 0x72, 0x00, 0x19, 0x0a, 0x3f, 0x13, 0xfe, 0xfa, 0x04, 0xff, 0xff, + 0x11, 0x0f, 0xf6, 0x0a, 0x09, 0xf6, 0x11, 0x3e, 0xf6, 0xf7, 0x09, 0x08, 0x42, 0x11, 0x0d, 0x01, + 0x08, 0xf7, 0x11, 0x3f, 0x08, 0x01, 0xf8, 0x08, 0x99, 0xd6, 0x11, 0x0f, 0xf8, 0x08, 0x07, 0xf8, + 0x11, 0x3e, 0xf8, 0xf9, 0x07, 0x10, 0x84, 0x11, 0x0d, 0x01, 0x06, 0xf9, 0x11, 0x3a, 0x01, 0x06, + 0x99, 0xd6, 0x19, 0x18, 0xf0, 0x60, 0x0c, 0x01, 0x0c, 0x01, 0x01, 0x01, 0x00, 0x19, 0x0a, 0x3f, + 0x13, 0xfe, 0xfa, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf6, 0x0a, 0x09, 0xf6, 0x11, 0x3e, 0xf6, 0xf7, + 0x09, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x08, 0xf7, 0x11, 0x3f, 0x08, 0x01, 0xf8, 0x08, 0x10, 0x84, + 0x11, 0x0f, 0xf8, 0x08, 0x07, 0xf8, 0x11, 0x3e, 0xf8, 0xf9, 0x07, 0x99, 0xd6, 0x11, 0x0d, 0x01, + 0x06, 0xf9, 0x11, 0x0a, 0x01, 0x06, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0a, 0xff, 0x0a, + 0xff, 0xff, 0x19, 0x0a, 0x3f, 0x13, 0xfe, 0xfa, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf6, 0x0a, 0x09, + 0xf6, 0x11, 0x3e, 0xf6, 0xf7, 0x09, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x08, 0xf7, 0x11, 0x3f, 0x08, + 0x01, 0xf8, 0x08, 0x10, 0x84, 0x11, 0x0f, 0xf8, 0x08, 0x07, 0xf8, 0x11, 0x3e, 0xf8, 0xf9, 0x07, + 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x06, 0xf9, 0x11, 0x0a, 0x01, 0x06, 0x19, 0x18, 0xf4, 0x20, 0xff, + 0xff, 0x00, 0x0c, 0x01, 0x0c, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff, 0xff, 0xff, + 0xff, 0xff, 0x19, 0x0a, 0x0f, 0x09, 0xfe, 0x09, 0x09, 0x19, 0x18, 0xf5, 0x60, 0x06, 0xff, 0xff, + 0x00, 0x09, 0xfe, 0x12, 0x07, 0x07, 0x23, 0x05, 0x03, 0x4d, 0x0d, 0x0d, 0x00, 0x00, 0x00, 0x08, + 0x00, 0x10, 0x00, 0x20, 0x00, 0x40, 0x00, 0x88, 0x01, 0x10, 0x02, 0x20, 0x04, 0x40, 0x08, 0x88, + 0x11, 0x10, 0x22, 0x20, 0x44, 0x40, 0x00, 0x00, 0x6f, 0x00, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, + 0x1f, 0x06, 0x04, 0x4c, 0x0c, 0x0c, 0x00, 0x00, 0x00, 0x10, 0x00, 0x30, 0x00, 0x60, 0x00, 0xc0, + 0x01, 0x90, 0x03, 0x30, 0x06, 0x60, 0x0c, 0xc0, 0x19, 0x90, 0x33, 0x30, 0x66, 0x60, 0x70, 0x00, + 0x19, 0x0a, 0x37, 0xe3, 0x10, 0xf1, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, + 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, + 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, + 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x60, 0x00, 0x00, + 0x00, 0xd6, 0x12, 0xd2, 0x0e, 0x0e, 0x0f, 0x07, 0x01, 0x48, 0x09, 0x04, 0x08, 0x00, 0x1c, 0x00, + 0x3e, 0x00, 0x7f, 0x00, 0x35, 0x00, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x10, 0x84, 0x11, + 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x0e, 0xf1, 0xf2, 0x0e, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, + 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x0e, 0xf3, + 0xf4, 0x0c, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x0a, 0x01, 0x0b, 0x19, 0x18, 0xf0, 0x60, 0x11, + 0x01, 0x11, 0x01, 0x01, 0x01, 0x07, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, + 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, + 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, + 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, + 0xd6, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f, + 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2, + 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84, + 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, + 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x20, 0xff, 0xff, 0x00, 0x11, + 0x01, 0x11, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, + 0x0a, 0x3f, 0xdd, 0x0e, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, + 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, + 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, + 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x60, 0x00, 0x00, + 0x00, 0xd0, 0x10, 0xd0, 0x10, 0x10, 0x0f, 0x08, 0x01, 0x48, 0x09, 0x04, 0x7f, 0x00, 0x3e, 0x00, + 0x1c, 0x00, 0x08, 0x00, 0x36, 0x00, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x10, 0x84, 0x11, + 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x0e, 0xf1, 0xf2, 0x0e, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, + 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x0e, 0xf3, + 0xf4, 0x0c, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x0a, 0x01, 0x0b, 0x19, 0x18, 0xf0, 0x60, 0x11, + 0x01, 0x11, 0x01, 0x01, 0x01, 0x08, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, + 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, + 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, + 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, + 0xd6, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f, + 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2, + 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84, + 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, + 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x20, 0xff, 0xff, 0x00, 0x11, + 0x01, 0x11, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, + 0x0a, 0x3f, 0xdd, 0x0e, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, + 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, + 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, + 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x60, 0x00, 0x00, + 0x00, 0xd0, 0x10, 0xd0, 0x10, 0x10, 0x13, 0x09, 0x04, 0x4b, 0x04, 0x09, 0x00, 0x80, 0xc0, 0xe0, + 0xf0, 0xe0, 0xc0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x34, 0x00, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, + 0x04, 0x10, 0x84, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x0e, 0xf1, 0xf2, 0x0e, 0x11, 0x0d, + 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x99, 0xd6, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, + 0xf3, 0x11, 0x0e, 0xf3, 0xf4, 0x0c, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x0a, 0x01, 0x0b, 0x19, + 0x18, 0xf0, 0x60, 0x11, 0x01, 0x11, 0x01, 0x01, 0x01, 0x09, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, + 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2, 0x0e, 0x99, 0xd6, + 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84, 0x11, 0x0f, 0xf3, + 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, + 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00, 0x0f, 0xff, 0x0f, 0xff, + 0xff, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, + 0x11, 0x3e, 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, + 0xf3, 0x0d, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, + 0xff, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x20, + 0xff, 0xff, 0x00, 0x11, 0x01, 0x11, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0xdd, 0x0e, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, + 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, + 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, + 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, + 0xf4, 0x60, 0x00, 0x00, 0x00, 0xd0, 0x10, 0xd0, 0x10, 0x10, 0x13, 0x0a, 0x03, 0x4b, 0x04, 0x09, + 0x00, 0x10, 0x30, 0x70, 0xf0, 0x70, 0x30, 0x10, 0x00, 0x00, 0x00, 0x00, 0x33, 0x00, 0x19, 0x0a, + 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x10, 0x84, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x0e, 0xf1, + 0xf2, 0x0e, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x99, 0xd6, 0x11, + 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x0e, 0xf3, 0xf4, 0x0c, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, + 0x0a, 0x01, 0x0b, 0x19, 0x18, 0xf0, 0x60, 0x11, 0x01, 0x11, 0x01, 0x01, 0x01, 0x0a, 0x19, 0x0a, + 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1, + 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10, + 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d, + 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, 0x19, 0x18, 0xf4, 0x20, 0x10, 0x00, 0x00, + 0x0f, 0xff, 0x0f, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0x1d, 0xfe, 0xf5, 0x04, 0x08, 0x42, 0x11, 0x0f, + 0xf1, 0x0f, 0x0e, 0xf1, 0x11, 0x3e, 0xf1, 0xf2, 0x0e, 0x99, 0xd6, 0x11, 0x0d, 0x01, 0x0d, 0xf2, + 0x11, 0x3f, 0x0d, 0x01, 0xf3, 0x0d, 0x10, 0x84, 0x11, 0x0f, 0xf3, 0x0d, 0x0c, 0xf3, 0x11, 0x3e, + 0xf3, 0xf4, 0x0c, 0xff, 0xff, 0x11, 0x0d, 0x01, 0x0b, 0xf4, 0x11, 0x3a, 0x01, 0x0b, 0x99, 0xd6, + 0x19, 0x18, 0xf4, 0x20, 0xff, 0xff, 0x00, 0x11, 0x01, 0x11, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, + 0x84, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0xce, 0x0e, 0x01, 0x01, 0xff, 0xff, + 0x1d, 0x18, 0xf4, 0x60, 0x0e, 0xe2, 0x00, 0x0b, 0x00, 0xee, 0x00, 0x00, 0x00, 0x00, 0xcd, 0x0e, + 0xce, 0x0f, 0x0f, 0x13, 0x0b, 0x04, 0x4b, 0x04, 0x09, 0x00, 0x80, 0xc0, 0xe0, 0xf0, 0xe0, 0xc0, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x19, 0x0a, 0x01, 0x0d, 0x19, 0x18, 0x50, 0x40, 0x0d, + 0x0d, 0x0f, 0x0c, 0x03, 0x4a, 0x07, 0x08, 0x00, 0x02, 0x06, 0x8e, 0xdc, 0xf8, 0x70, 0x20, 0x61, + 0x00, 0x19, 0x0a, 0x01, 0x0d, 0x19, 0x18, 0x50, 0x40, 0x0d, 0x0d, 0x0f, 0x0d, 0x04, 0x4a, 0x06, + 0x06, 0x78, 0xfc, 0xfc, 0xfc, 0xfc, 0x78, 0x00, 0x00, 0x68, 0x00, 0x19, 0x0a, 0x3d, 0x0d, 0x02, + 0x02, 0x99, 0xd6, 0x19, 0x18, 0xd0, 0x60, 0x0e, 0x10, 0x02, 0x02, 0x13, 0x0e, 0x02, 0x4a, 0x0b, + 0x05, 0x04, 0x00, 0x0e, 0x00, 0x1f, 0x00, 0x3f, 0x80, 0x7f, 0xc0, 0x00, 0x00, 0x35, 0x00, 0x19, + 0x0a, 0x01, 0x0f, 0x19, 0x18, 0x54, 0x40, 0x10, 0x00, 0x00, 0x0f, 0x0f, 0x01, 0x0e, 0x19, 0x0a, + 0x03, 0xca, 0x0f, 0x19, 0x18, 0xf4, 0x20, 0xff, 0xff, 0x00, 0xcb, 0x10, 0xcb, 0x10, 0x10, 0x11, + 0xf4, 0x20, 0x10, 0x84, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, 0x0a, 0x01, 0x0f, 0x19, 0x18, + 0x54, 0x40, 0x00, 0x00, 0x00, 0x0f, 0x0f, 0x13, 0x0f, 0x02, 0x4a, 0x0b, 0x05, 0x7f, 0xc0, 0x3f, + 0x80, 0x1f, 0x00, 0x0e, 0x00, 0x04, 0x00, 0x00, 0x00, 0x36, 0x00, 0x19, 0x0a, 0x01, 0x0f, 0x19, + 0x18, 0x54, 0x40, 0x10, 0x00, 0x00, 0x0f, 0x0f, 0x01, 0x0f, 0x19, 0x0a, 0x01, 0x0f, 0x19, 0x18, + 0xf4, 0x20, 0xff, 0xff, 0x00, 0x10, 0x01, 0x10, 0x01, 0x01, 0x11, 0xf4, 0x20, 0x10, 0x84, 0x00, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x19, 0x0a, 0x3f, 0xd3, 0x0f, 0xfe, 0xfe, 0xff, 0xff, 0x19, 0x18, + 0xf4, 0x60, 0x00, 0x00, 0x00, 0xd3, 0x0f, 0xd1, 0x0d, 0x0d, 0x1b, 0x10, 0x02, 0x4c, 0x0a, 0x0a, + 0x1e, 0x00, 0x7f, 0x80, 0x7f, 0x80, 0xff, 0xc0, 0xff, 0xc0, 0xff, 0xc0, 0xff, 0xc0, 0x7f, 0x80, + 0x7f, 0x80, 0x1e, 0x00, 0x6e, 0x00, 0x11, 0x00, 0x40, 0x17, 0x11, 0x03, 0x4a, 0x09, 0x08, 0x01, + 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x01, 0x00, 0xc3, 0x00, 0x3c, 0x00, 0x6d, + 0x00, 0x11, 0x00, 0x40, 0x17, 0x12, 0x02, 0x4c, 0x09, 0x08, 0x1e, 0x00, 0x61, 0x80, 0x40, 0x00, + 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x6c, 0x00, 0x11, 0x00, 0x40, 0x1b, + 0x13, 0x03, 0x4b, 0x0a, 0x0a, 0x00, 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, + 0x40, 0x00, 0x80, 0x00, 0x80, 0xc3, 0x00, 0x3c, 0x00, 0x6b, 0x00, 0x11, 0x00, 0x40, 0x1b, 0x14, + 0x01, 0x4d, 0x0a, 0x0a, 0x0f, 0x00, 0x30, 0xc0, 0x40, 0x00, 0x40, 0x00, 0x80, 0x00, 0x80, 0x00, + 0x80, 0x00, 0x80, 0x00, 0x40, 0x00, 0x40, 0x00, 0x6a, 0x00, 0x11, 0x54, 0x40, 0xff, 0xff, 0x00, + 0x0d, 0x0d, 0x1b, 0x15, 0x02, 0x4b, 0x09, 0x09, 0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0xff, 0x80, + 0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0xff, 0x80, 0x00, 0x00, 0x67, 0x00, 0x11, 0x04, + 0x40, 0x99, 0xd6, 0x00, 0x1f, 0x16, 0x01, 0x4c, 0x0b, 0x0b, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, + 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0xff, 0xe0, + 0x00, 0x00, 0x66, 0x00, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x1b, 0x17, 0x01, 0x4c, 0x0a, 0x0a, + 0xff, 0xc0, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, + 0x80, 0x00, 0x80, 0x00, 0x65, 0x00, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, 0x23, 0x18, 0x00, 0x4d, + 0x0d, 0x0d, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, + 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0x00, 0x08, 0xff, 0xf8, 0x00, 0x00, 0x64, 0x00, + 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x1f, 0x19, 0x00, 0x4d, 0x0c, 0x0c, 0xff, 0xf0, 0x80, 0x00, + 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, + 0x80, 0x00, 0x80, 0x00, 0x63, 0x00, 0x11, 0x54, 0x40, 0xff, 0xff, 0x00, 0x0d, 0x0d, 0x01, 0x15, + 0x11, 0x04, 0x40, 0x99, 0xd6, 0x00, 0x01, 0x16, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x17, + 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, 0x01, 0x18, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x19, + 0x11, 0x04, 0x40, 0x00, 0x00, 0x00, 0x0f, 0x1a, 0x03, 0x4b, 0x07, 0x08, 0x00, 0x02, 0x06, 0x8e, + 0xdc, 0xf8, 0x70, 0x20, 0x62, 0x00, 0x11, 0x54, 0x40, 0x99, 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x15, + 0x11, 0x00, 0x40, 0x01, 0x16, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x17, 0x11, 0x04, 0x40, + 0xff, 0xff, 0x00, 0x01, 0x18, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x19, 0x11, 0x54, 0x40, + 0x99, 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x15, 0x11, 0x00, 0x40, 0x01, 0x16, 0x11, 0x04, 0x40, 0x08, + 0x42, 0x00, 0x01, 0x17, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, 0x01, 0x18, 0x11, 0x04, 0x40, 0x10, + 0x84, 0x00, 0x01, 0x19, 0x11, 0x04, 0x40, 0x00, 0x00, 0x00, 0x01, 0x1a, 0x11, 0xf4, 0x60, 0x99, + 0xd6, 0x00, 0xcc, 0x0d, 0xcc, 0x0d, 0x0d, 0x01, 0x15, 0x11, 0x00, 0x40, 0x01, 0x16, 0x11, 0x04, + 0x40, 0x08, 0x42, 0x00, 0x01, 0x17, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, 0x01, 0x18, 0x11, 0x04, + 0x40, 0x10, 0x84, 0x00, 0x01, 0x19, 0x11, 0x00, 0x40, 0x01, 0x1a, 0x19, 0x0a, 0x33, 0x0d, 0x0d, + 0x00, 0x00, 0x19, 0x18, 0x54, 0x40, 0xff, 0xff, 0x00, 0x0d, 0x0d, 0x01, 0x10, 0x11, 0x04, 0x40, + 0x99, 0xd6, 0x00, 0x01, 0x11, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x12, 0x11, 0x04, 0x40, + 0xff, 0xff, 0x00, 0x01, 0x13, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x14, 0x19, 0x0a, 0x01, + 0x0d, 0x19, 0x18, 0x54, 0x40, 0xff, 0xff, 0x00, 0x0d, 0x0d, 0x01, 0x10, 0x11, 0x04, 0x40, 0x99, + 0xd6, 0x00, 0x01, 0x11, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x12, 0x11, 0x04, 0x40, 0xff, + 0xff, 0x00, 0x01, 0x13, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x14, 0x11, 0x04, 0x40, 0x00, + 0x00, 0x00, 0x0b, 0x1b, 0x05, 0x49, 0x04, 0x04, 0x60, 0xf0, 0xf0, 0x60, 0x69, 0x00, 0x19, 0x0a, + 0x01, 0x0d, 0x19, 0x18, 0x54, 0x40, 0x99, 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x10, 0x11, 0x00, 0x40, + 0x01, 0x11, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x12, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, + 0x01, 0x13, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x14, 0x19, 0x0a, 0x01, 0x0d, 0x19, 0x18, + 0x54, 0x40, 0x99, 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x10, 0x11, 0x00, 0x40, 0x01, 0x11, 0x11, 0x04, + 0x40, 0x08, 0x42, 0x00, 0x01, 0x12, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, 0x01, 0x13, 0x11, 0x04, + 0x40, 0x10, 0x84, 0x00, 0x01, 0x14, 0x11, 0x04, 0x40, 0x00, 0x00, 0x00, 0x01, 0x1b, 0x19, 0x0a, + 0x03, 0xcc, 0x0d, 0x19, 0x18, 0xf4, 0x60, 0x99, 0xd6, 0x00, 0xcc, 0x0d, 0xcc, 0x0d, 0x0d, 0x01, + 0x10, 0x11, 0x00, 0x40, 0x01, 0x11, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x12, 0x11, 0x04, + 0x40, 0xff, 0xff, 0x00, 0x01, 0x13, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x14, 0x11, 0x00, + 0x40, 0x01, 0x1b, 0x03, 0x07, 0x00, 0x00, 0x00, 0x07, 0x00, 0x01, 0x08, 0x08, 0x81, 0x08, 0xaa, + 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0x09, 0x01, 0x7f, 0x02, 0x0d, 0x00, 0x1a, 0x01, 0x0d, + 0x00, 0x0d, 0x00, 0xf0, 0xff, 0xff, 0x00, 0x99, 0xd6, 0x00, 0x81, 0x19, 0x18, 0x54, 0x40, 0x99, + 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x16, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x17, 0x11, 0x04, + 0x40, 0xff, 0xff, 0x00, 0x01, 0x18, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x19, 0x11, 0x00, + 0x40, 0x01, 0x1a, 0x11, 0x54, 0x40, 0x99, 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x15, 0x11, 0x00, 0x40, + 0x01, 0x16, 0x11, 0x04, 0x40, 0x08, 0x42, 0x00, 0x01, 0x17, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, + 0x01, 0x18, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x01, 0x19, 0x11, 0x00, 0x40, 0x01, 0x1a, 0x11, + 0x54, 0x40, 0x99, 0xd6, 0x00, 0x0d, 0x0d, 0x01, 0x15, 0x11, 0x00, 0x40, 0x01, 0x16, 0x11, 0x04, + 0x40, 0x08, 0x42, 0x00, 0x01, 0x17, 0x11, 0x04, 0x40, 0xff, 0xff, 0x00, 0x01, 0x18, 0x11, 0x04, + 0x40, 0x10, 0x84, 0x00, 0x01, 0x19, 0x11, 0x00, 0x40, 0x01, 0x1a, 0x19, 0x0a, 0x31, 0x34, 0xff, + 0xff, 0x19, 0x18, 0x54, 0x40, 0x00, 0x00, 0x00, 0x0c, 0x0c, 0x1b, 0x1c, 0x02, 0x4b, 0x09, 0x09, + 0xc1, 0x80, 0xe3, 0x80, 0x77, 0x00, 0x3e, 0x00, 0x1c, 0x00, 0x3e, 0x00, 0x77, 0x00, 0xe3, 0x80, + 0xc1, 0x80, 0x00, 0x00, 0x72, 0x00, 0x19, 0x0a, 0x03, 0xcc, 0x0d, 0x1d, 0x18, 0xf0, 0x60, 0xa0, + 0x45, 0x45, 0xcc, 0x0d, 0xcc, 0x0d, 0x0d, 0x1b, 0x1d, 0x01, 0x4b, 0x0a, 0x09, 0x3f, 0xc0, 0x3f, + 0xc0, 0x20, 0x40, 0xff, 0x40, 0xff, 0x40, 0x81, 0xc0, 0x81, 0x00, 0x81, 0x00, 0xff, 0x00, 0x00, + 0x00, 0x32, 0x00, 0x19, 0x0a, 0x01, 0x0d, 0x19, 0x18, 0x50, 0x40, 0x0d, 0x0d, 0x1b, 0x1e, 0x01, + 0x4c, 0x0a, 0x0a, 0xff, 0xc0, 0xff, 0xc0, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, 0x40, 0x80, + 0x40, 0x80, 0x40, 0x80, 0x40, 0xff, 0xc0, 0x31, 0x00, 0x19, 0x0a, 0x01, 0x0d, 0x19, 0x18, 0x50, + 0x40, 0x0d, 0x0d, 0x0b, 0x1f, 0x02, 0x44, 0x07, 0x02, 0xfe, 0xfe, 0x00, 0x00, 0x30, 0x00, 0x19, + 0x0a, 0x3d, 0x0d, 0x03, 0x03, 0x99, 0xd6, 0x19, 0x18, 0xd4, 0x60, 0xff, 0xff, 0x00, 0x0e, 0x11, + 0x03, 0x03, 0x23, 0x20, 0x00, 0x4d, 0x0d, 0x0d, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x20, 0x00, + 0x10, 0x00, 0x88, 0x00, 0x44, 0x00, 0x22, 0x00, 0x11, 0x00, 0x88, 0x80, 0x44, 0x40, 0x22, 0x20, + 0x11, 0x10, 0x00, 0x00, 0x78, 0x00, 0x11, 0x04, 0x40, 0x10, 0x84, 0x00, 0x1f, 0x21, 0x00, 0x4c, + 0x0c, 0x0c, 0x00, 0x00, 0x80, 0x00, 0xc0, 0x00, 0x60, 0x00, 0x30, 0x00, 0x98, 0x00, 0xcc, 0x00, + 0x66, 0x00, 0x33, 0x00, 0x99, 0x80, 0xcc, 0xc0, 0x66, 0x60, 0x79, 0x00, 0x19, 0x0a, 0x3d, 0x10, + 0xfd, 0xfd, 0xff, 0xff, 0x19, 0x18, 0xd4, 0x60, 0x00, 0x00, 0x00, 0x0f, 0x0c, 0xfd, 0xfd, 0x13, + 0x22, 0x05, 0x4b, 0x04, 0x09, 0x00, 0x10, 0x30, 0x70, 0xf0, 0x70, 0x30, 0x10, 0x00, 0x00, 0x00, + 0x00, 0x77, 0x00, 0x02, 0xff, 0xff, 0x0d, 0x0a, 0x3f, 0x0e, 0x00, 0x00, 0xff, 0x03, 0xff, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x03, 0x00, 0x00, 0x06, 0x02, 0x00, 0x48, 0x00, 0x37, + 0x01, 0x02, 0x02, 0x00, 0x09, 0x00, 0x0c, 0x48, 0x00, 0x37, 0x01, 0x03, 0xcf, 0x04, 0xa2, 0x0c, + 0x05, 0x40, 0x44, 0xd1, 0xff, 0xff, 0x80, 0x00, 0xff, 0xff, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00, + 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00, + 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x84, 0x08, 0x42, 0xff, 0xff, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x84, + 0xff, 0xff, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x84, + 0xff, 0xff, 0x99, 0xd6, 0x10, 0x84, 0x08, 0x42, 0x1c, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x75, 0xc6, 0x66, 0x29, 0x00, 0x09, 0x68, 0x10, 0x00, 0x08, 0x68, 0x10, 0x84, + 0x00, 0x20, 0x00, 0x07, 0x6b, 0x99, 0xd6, 0x05, 0x6b, 0x99, 0xd6, 0x00, 0x03, 0x6e, 0xff, 0xff, + 0x02, 0x6e, 0xff, 0xff, 0x00, 0x10, 0xc0, 0x00, 0xf7, 0xbd, 0x01, 0xc0, 0x00, 0x08, 0x42, 0xc0, + 0x00, 0xff, 0xff, 0x81, 0x08, 0x42, 0xce, 0x66, 0x29, 0x01, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0xfd, + 0x2e, 0x01, 0x81, 0x08, 0x42, 0xce, 0x66, 0x29, 0x02, 0x81, 0x10, 0x84, 0x06, 0x82, 0x00, 0x00, + 0x00, 0x00, 0x07, 0xcd, 0x89, 0x52, 0x03, 0x2d, 0x03, 0x83, 0x10, 0x84, 0x99, 0xd6, 0x99, 0xd6, + 0xc9, 0x99, 0xd6, 0x1a, 0x82, 0x10, 0x00, 0x10, 0x00, 0x0a, 0x29, 0x09, 0x27, 0x0c, 0x67, 0x99, + 0xd6, 0x15, 0x27, 0x1d, 0x82, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x67, 0x99, 0xd6, 0x00, 0x19, 0xd0, + 0x30, 0x89, 0xd6, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0x99, 0xd6, 0x05, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x83, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x0b, 0xd8, 0x89, + 0xd6, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x83, 0x99, 0xd6, 0x10, 0x00, 0x10, + 0x00, 0x1a, 0x68, 0x00, 0x00, 0x09, 0x86, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x99, 0xd6, 0x18, 0x68, 0x00, 0x00, 0x1b, 0x68, 0x99, 0xd6, 0x06, 0x86, 0x99, 0xd6, 0x10, + 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x99, 0xd6, 0x19, 0x6b, 0x99, 0xd6, 0x03, 0xcc, 0x89, + 0x52, 0x08, 0x68, 0x99, 0xd6, 0x05, 0x6b, 0x99, 0xd6, 0x04, 0x2c, 0x03, 0x6e, 0x08, 0x42, 0x02, + 0x6e, 0xff, 0xff, 0x02, 0x6e, 0xff, 0xff, 0x02, 0x6e, 0x08, 0x42, 0x10, 0x81, 0x08, 0x42, 0x70, + 0xff, 0xff, 0x60, 0x00, 0x08, 0x42, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0x81, 0x08, 0x42, 0xce, 0x66, + 0x29, 0x01, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0xfd, 0x2e, 0x02, 0xcd, 0x89, 0x52, 0x03, 0x89, 0x10, + 0x84, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x07, 0x2d, 0x03, 0x2d, 0x05, 0xc6, 0x99, 0xd6, 0x0c, 0x84, 0x99, 0xd6, 0x99, 0xd6, 0x99, + 0xd6, 0x99, 0xd6, 0x0a, 0xc6, 0x89, 0xd6, 0x0e, 0x82, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x84, 0x99, + 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x1c, 0x40, 0x35, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xd0, 0x4e, 0x99, 0xd6, 0x03, 0x00, 0x00, 0x60, 0x00, 0x80, 0x01, 0x78, 0x01, 0x00, 0x82, + 0x10, 0x00, 0x10, 0x00, 0x0c, 0x40, 0x2c, 0x03, 0xe0, 0x05, 0x00, 0x00, 0x00, 0xd6, 0x89, 0xd6, + 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x4e, 0x99, 0xd6, 0x3b, 0x00, 0x00, 0x00, 0x28, 0x80, + 0x1d, 0x00, 0x78, 0x00, 0x86, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x99, + 0xd6, 0x0c, 0x85, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x40, 0x2b, 0x01, + 0xf0, 0x00, 0x00, 0x00, 0x00, 0xd6, 0x89, 0xd6, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x69, 0x99, + 0xd6, 0x16, 0x86, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x0a, + 0x69, 0x99, 0xd6, 0x04, 0xcc, 0x89, 0x52, 0x07, 0x69, 0x99, 0xd6, 0x08, 0x68, 0x99, 0xd6, 0x03, + 0x6e, 0xff, 0xff, 0x02, 0x6e, 0x08, 0x42, 0x02, 0x6e, 0xff, 0xff, 0x02, 0x6e, 0xff, 0xff, 0x01, + 0x70, 0x08, 0x42, 0x70, 0xff, 0xff, 0x60, 0x00, 0x08, 0x42, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0x81, + 0x08, 0x42, 0xce, 0x66, 0x29, 0x01, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0xfd, 0x2e, 0x02, 0xcd, 0x89, + 0x52, 0x03, 0x8a, 0x10, 0x84, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x2d, 0x03, 0x8d, 0x99, 0xd6, 0x99, 0xd6, 0x99, + 0xd6, 0x99, 0xd6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x99, + 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x06, 0xc6, 0x99, 0xd6, 0x1a, 0xc6, 0x89, 0xd6, 0x0a, 0x66, 0x10, + 0x84, 0x1b, 0x6a, 0x99, 0xd6, 0x1b, 0x81, 0x99, 0xd6, 0x09, 0x6a, 0x99, 0xd6, 0x16, 0x6a, 0x99, + 0xd6, 0x06, 0x6a, 0x99, 0xd6, 0xf0, 0x94, 0x01, 0xcc, 0x89, 0x52, 0x00, 0x03, 0x6e, 0xff, 0xff, + 0x02, 0x6e, 0x08, 0x42, 0x02, 0x6e, 0xff, 0xff, 0x02, 0x6e, 0xff, 0xff, 0x01, 0x70, 0x08, 0x42, + 0x70, 0xff, 0xff, 0x60, 0x00, 0x08, 0x42, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0x81, 0x08, 0x42, 0xce, + 0x66, 0x29, 0x01, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0xfd, 0x2e, 0x02, 0xcd, 0x89, 0x52, 0x03, 0x10, + 0x2d, 0x03, 0x2d, 0x17, 0x88, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, + 0xd6, 0x00, 0x00, 0x00, 0x00, 0x18, 0x88, 0xff, 0xff, 0xff, 0xff, 0x99, 0xd6, 0x99, 0xd6, 0x99, + 0xd6, 0x99, 0xd6, 0xff, 0xff, 0xff, 0xff, 0x07, 0x88, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, + 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x09, 0x88, 0x99, 0xd6, 0x00, 0x00, 0x00, + 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x07, 0x88, 0x10, 0x00, 0x10, + 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00, 0x10, 0x00, 0x08, 0x89, 0x10, + 0x84, 0x10, 0x84, 0xff, 0xff, 0xff, 0xff, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x84, 0x10, 0x84, 0x99, + 0xd6, 0x07, 0x88, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00, + 0x00, 0x99, 0xd6, 0x0a, 0x86, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x99, + 0xd6, 0x08, 0x88, 0x99, 0xd6, 0x10, 0x00, 0x10, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00, 0x10, + 0x00, 0x99, 0xd6, 0x08, 0x88, 0x99, 0xd6, 0x10, 0x84, 0x10, 0x84, 0xff, 0xff, 0xff, 0xff, 0x10, + 0x84, 0x10, 0x84, 0x99, 0xd6, 0x09, 0x86, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x99, 0xd6, 0x0c, 0x84, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x0a, 0x86, 0x99, + 0xd6, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x10, 0x00, 0x99, 0xd6, 0x0a, 0x86, 0x99, 0xd6, 0x10, + 0x84, 0x10, 0x84, 0x10, 0x84, 0x10, 0x84, 0x99, 0xd6, 0x0b, 0x84, 0x99, 0xd6, 0x00, 0x00, 0x00, + 0x00, 0x99, 0xd6, 0x0d, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x84, 0x99, + 0xd6, 0x10, 0x00, 0x10, 0x00, 0x99, 0xd6, 0x0c, 0x85, 0x99, 0xd6, 0x10, 0x84, 0x10, 0x84, 0xff, + 0xff, 0xff, 0xff, 0x0b, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x86, 0x00, + 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x84, 0x10, 0x00, 0x10, + 0x00, 0x10, 0x00, 0x10, 0x00, 0x0c, 0x86, 0x10, 0x84, 0x10, 0x84, 0x10, 0x84, 0x10, 0x84, 0xff, + 0xff, 0xff, 0xff, 0x09, 0x86, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x00, 0x00, 0x00, + 0x00, 0x0a, 0x88, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x00, + 0x00, 0x00, 0x00, 0x08, 0x86, 0x10, 0x00, 0x10, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00, 0x10, + 0x00, 0x0a, 0x88, 0x10, 0x84, 0x10, 0x84, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x84, 0x10, 0x84, 0xff, + 0xff, 0xff, 0xff, 0x07, 0x88, 0x00, 0x00, 0x00, 0x00, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, + 0xd6, 0x00, 0x00, 0x00, 0x00, 0x09, 0x6a, 0x99, 0xd6, 0x05, 0x88, 0x10, 0x00, 0x10, 0x00, 0x99, + 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x00, 0x10, 0x00, 0x08, 0x89, 0x10, 0x84, 0x10, + 0x84, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x10, 0x84, 0x10, 0x84, 0x99, 0xd6, 0x07, + 0x6a, 0x99, 0xd6, 0x16, 0x6a, 0x99, 0xd6, 0x06, 0x6a, 0x99, 0xd6, 0x14, 0x2c, 0x00, 0x03, 0x6e, + 0xff, 0xff, 0x02, 0x6e, 0x08, 0x42, 0x02, 0x6e, 0xff, 0xff, 0x02, 0xcb, 0x66, 0x29, 0x84, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0x08, 0x42, 0x09, 0x0d, 0xdf, 0x01, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x40, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x7f, 0x03, 0x15, 0x00, 0xa2, 0x0c, + 0x05, 0x40, 0x40, 0x17, 0xff, 0xff, 0x00, 0x1c, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf0, 0xbc, 0x0f, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x0a, 0x40, + 0xc8, 0x03, 0xf4, 0x00, 0xa2, 0x0c, 0x05, 0x40, 0x40, 0xf6, 0xff, 0xff, 0xc0, 0x2c, 0x2d, 0x09, + 0x84, 0x2d, 0x09, 0x2d, 0x09, 0x2d, 0x09, 0x2d, 0x09, 0x00, 0x22, 0xc0, 0x10, 0x25, 0x4b, 0x02, + 0x30, 0x02, 0x2a, 0x02, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0xfd, 0x2e, 0x03, 0xfd, 0x2e, 0x03, 0xfd, + 0x29, 0x03, 0xcd, 0x89, 0x52, 0x03, 0x2d, 0x05, 0x2d, 0x05, 0x29, 0x06, 0xc6, 0x99, 0xd6, 0x09, + 0x29, 0x1f, 0x43, 0x03, 0x00, 0x00, 0x01, 0x27, 0x0b, 0x44, 0xc3, 0x00, 0x00, 0xc0, 0x68, 0x99, + 0xd6, 0x18, 0x48, 0xa5, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0xc2, 0x5a, 0x00, 0x60, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0xa0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x81, 0x99, 0xd6, 0x48, 0x05, 0x80, 0x05, 0x00, 0x00, 0xfc, + 0x01, 0x50, 0x40, 0x69, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x0c, 0x80, 0x06, 0x00, + 0x00, 0x02, 0x6a, 0x99, 0xd6, 0x1c, 0x86, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0x2d, + 0x09, 0x2d, 0x09, 0x6f, 0xff, 0xff, 0x02, 0x6e, 0xff, 0xff, 0x04, 0x6e, 0xff, 0xff, 0x04, 0xc9, + 0x66, 0x29, 0x02, 0x60, 0x5e, 0x2d, 0x09, 0xc0, 0x30, 0x2d, 0x09, 0xf0, 0xc0, 0x09, 0xc0, 0x10, + 0x08, 0x42, 0x00, 0x00, 0xfd, 0xce, 0x18, 0xc6, 0x01, 0xfd, 0x2e, 0x00, 0x02, 0xcd, 0x89, 0x52, + 0x03, 0x83, 0x99, 0xd6, 0x99, 0xd6, 0x99, 0xd6, 0xc9, 0xef, 0x7b, 0x81, 0x99, 0xd6, 0x00, 0x05, + 0xc9, 0x89, 0xd6, 0x07, 0x69, 0x10, 0x84, 0x00, 0x08, 0x27, 0x09, 0x82, 0xff, 0xff, 0x99, 0xd6, + 0xd0, 0x69, 0x89, 0x52, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x51, 0x0e, 0xc0, 0x40, 0x38, 0x03, 0xa8, 0x00, 0xa2, 0x0c, 0x05, 0x40, 0x40, 0xaa, + 0xff, 0xff, 0xc8, 0x2d, 0x09, 0x00, 0x14, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x20, 0xc6, 0x25, 0x4b, 0x00, 0x1a, 0xd8, 0x18, 0xc6, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xf7, 0xc0, 0x01, 0x89, 0x52, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf7, + 0x00, 0x01, 0x99, 0xd6, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x84, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xd0, 0x3b, 0xef, + 0x7b, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x2d, 0x09, 0x00, 0x58, 0xf3, 0x7c, + 0x0b, 0x00, 0x00, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x0a, 0x40, 0xc8, +}; + +/** + * + * for.whom.the.bell.tolls,.the.bell.tolls.for.thee! + * + * for.whom.the.bell.tolls,<16,15>.<40,4><19,3>e! ([MS-RDPBCGR]) + * + * <16,15> : ".the.bell.tolls" + * <40,4> : "for." + * <19,3> : "the" + * + * for.whom.the.bell.tolls,<16,15>.f<40,3><35,3>e! (Microsoft implementation) + * + * <16,15> : ".the.bell.tolls" + * <40,3> : "or." + * <19,3> " "the" + * + * RDP5: + * 01100110 Literal 'f' + * 01101111 Literal 'o' + * 01110010 Literal 'r' + * 00101110 Literal '.' + * 01110111 Literal 'w' + * 01101000 Literal 'h' + * 01101111 Literal 'o' + * 01101101 Literal 'm' + * 00101110 Literal '.' + * 01110100 Literal 't' + * 01101000 Literal 'h' + * 01100101 Literal 'e' + * 00101110 Literal '.' + * 01100010 Literal 'b' + * 01100101 Literal 'e' + * 01101100 Literal 'l' + * 01101100 Literal 'l' + * 00101110 Literal '.' + * 01110100 Literal 't' + * 01101111 Literal 'o' + * 01101100 Literal 'l' + * 01101100 Literal 'l' + * 01110011 Literal 's' + * 00101100 Literal ',' + * 11111+010000 CopyOffset 16 + * 110+111 LengthOfMatch 15 + * 00101110 Literal '.' + * 01100110 Literal 'f' + * 11111+101000 CopyOffset 40 + * 0 LengthOfMatch 3 + * 11111+100011 CopyOffset 35 + * 0 LengthOfMatch 3 + * 01100101 Literal 'e' + * 00100001 Literal '!' + * 0000000 Trailing Bits + * + * RDP4: + * 01100110 Literal 'f' + * 01101111 Literal 'o' + * 01110010 Literal 'r' + * 00101110 Literal '.' + * 01110111 Literal 'w' + * 01101000 Literal 'h' + * 01101111 Literal 'o' + * 01101101 Literal 'm' + * 00101110 Literal '.' + * 01110100 Literal 't' + * 01101000 Literal 'h' + * 01100101 Literal 'e' + * 00101110 Literal '.' + * 01100010 Literal 'b' + * 01100101 Literal 'e' + * 01101100 Literal 'l' + * 01101100 Literal 'l' + * 00101110 Literal '.' + * 01110100 Literal 't' + * 01101111 Literal 'o' + * 01101100 Literal 'l' + * 01101100 Literal 'l' + * 01110011 Literal 's' + * 00101100 Literal ',' + * 1111+010000 CopyOffset 16 + * 110+111 LengthOfMatch 15 + * 00101110 Literal '.' + * 01100110 Literal 'f' + * 1111+101000 CopyOffset 40 + * 0 LengthOfMatch 3 + * 1111+100011 CopyOffset 35 + * 0 LengthOfMatch 3 + * 01100101 Literal 'e' + * 00100001 Literal '!' + * 00 Trailing Bits + */ + +static const BYTE TEST_MPPC_BELLS[] = "for.whom.the.bell.tolls,.the.bell.tolls.for.thee!"; + +/* Flags: 0x0060 Length: 33 */ + +static const BYTE TEST_MPPC_BELLS_RDP4[] = + "\x66\x6f\x72\x2e\x77\x68\x6f\x6d\x2e\x74\x68\x65\x2e\x62\x65\x6c" + "\x6c\x2e\x74\x6f\x6c\x6c\x73\x2c\xf4\x37\x2e\x66\xfa\x1f\x19\x94" + "\x84"; + +/* Flags: 0x0061 Length: 34 */ + +static const BYTE TEST_MPPC_BELLS_RDP5[] = + "\x66\x6f\x72\x2e\x77\x68\x6f\x6d\x2e\x74\x68\x65\x2e\x62\x65\x6c" + "\x6c\x2e\x74\x6f\x6c\x6c\x73\x2c\xfa\x1b\x97\x33\x7e\x87\xe3\x32" + "\x90\x80"; + +static const BYTE TEST_ISLAND_DATA[] = "No man is an island entire of itself; every man " + "is a piece of the continent, a part of the main; " + "if a clod be washed away by the sea, Europe " + "is the less, as well as if a promontory were, as" + "well as any manner of thy friends or of thine " + "own were; any man's death diminishes me, " + "because I am involved in mankind. " + "And therefore never send to know for whom " + "the bell tolls; it tolls for thee."; + +static const BYTE TEST_ISLAND_DATA_RDP5[] = + "\x4e\x6f\x20\x6d\x61\x6e\x20\x69\x73\x20\xf8\xd2\xd8\xc2\xdc\xc8" + "\x40\xca\xdc\xe8\xd2\xe4\xca\x40\xde\xcc\x40\xd2\xe8\xe6\xca\xd8" + "\xcc\x76\x40\xca\xec\xca\xe4\xf3\xfa\x71\x20\x70\x69\x65\x63\xfc" + "\x12\xe8\xd0\xca\x40\xc6\xdf\xfb\xcd\xdf\xd0\x58\x40\xc2\x40\xe0" + "\xc2\xe4\xe9\xfe\x63\xec\xc3\x6b\x0b\x4b\x71\xd9\x03\x4b\x37\xd7" + "\x31\xb6\x37\xb2\x10\x31\x32\x90\x3b\xb0\xb9\xb4\x32\xb2\x10\x30" + "\xbb\xb0\xbc\x90\x31\x3c\x90\x7e\x68\x73\x65\x61\x2c\x20\x45\x75" + "\x72\x6f\x70\x65\xf2\x34\x7d\x38\x6c\x65\x73\x73\xf0\x69\xcc\x81" + "\xdd\x95\xb1\xb0\x81\x85\xcf\xc0\x94\xe0\xe4\xde\xdb\xe2\xb3\x7f" + "\x92\x4e\xec\xae\x4c\xbf\x86\x3f\x06\x0c\x2d\xde\x5d\x96\xe6\x57" + "\x2f\x1e\x53\xc9\x03\x33\x93\x4b\x2b\x73\x23\x99\x03\x7f\xd2\xb6" + "\x96\xef\x38\x1d\xdb\xbc\x24\x72\x65\x3b\xf5\x5b\xf8\x49\x3b\x99" + "\x03\x23\x2b\x0b\xa3\x41\x03\x23\x4b\x6b\x4b\x73\x4f\x96\xce\x64" + "\x0d\xbe\x19\x31\x32\xb1\xb0\xba\xb9\xb2\x90\x24\x90\x30\xb6\x90" + "\x34\xb7\x3b\x37\xb6\x3b\x79\xd4\xd2\xdd\xec\x18\x6b\x69\x6e\x64" + "\x2e\x20\x41\xf7\x33\xcd\x47\x26\x56\x66\xff\x74\x9b\xbd\xbf\x04" + "\x0e\x7e\x31\x10\x3a\x37\x90\x35\xb7\x37\xbb\x90\x7d\x81\x03\xbb" + "\x43\x7b\x6f\xa8\xe5\x8b\xd0\xf0\xe8\xde\xd8\xd8\xe7\xec\xf3\xa7" + "\xe4\x7c\xa7\xe2\x9f\x01\x99\x4b\x80"; + +static int test_MppcCompressBellsRdp5(void) +{ + int rc = -1; + int status = 0; + UINT32 Flags = 0; + BYTE OutputBuffer[65536] = { 0 }; + const UINT32 SrcSize = sizeof(TEST_MPPC_BELLS) - 1; + const BYTE* pSrcData = (const BYTE*)TEST_MPPC_BELLS; + UINT32 DstSize = sizeof(OutputBuffer); + const BYTE* pDstData = NULL; + const UINT32 expectedSize = sizeof(TEST_MPPC_BELLS_RDP5) - 1; + + MPPC_CONTEXT* mppc = mppc_context_new(1, TRUE); + + if (!mppc) + return -1; + + status = mppc_compress(mppc, pSrcData, SrcSize, OutputBuffer, &pDstData, &DstSize, &Flags); + + if (status < 0) + goto fail; + + printf("Flags: 0x%08" PRIX32 " DstSize: %" PRIu32 "\n", Flags, DstSize); + + if (DstSize != expectedSize) + { + printf("MppcCompressBellsRdp5: output size mismatch: Actual: %" PRIu32 + ", Expected: %" PRIu32 "\n", + DstSize, expectedSize); + goto fail; + } + + if (memcmp(pDstData, TEST_MPPC_BELLS_RDP5, DstSize) != 0) + { + printf("MppcCompressBellsRdp5: output mismatch\n"); + printf("Actual\n"); + BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0); + printf("Expected\n"); + BitDump(__func__, WLOG_INFO, TEST_MPPC_BELLS_RDP5, DstSize * 8, 0); + goto fail; + } + + rc = 0; +fail: + mppc_context_free(mppc); + return rc; +} + +static int test_MppcCompressBellsRdp4(void) +{ + int rc = -1; + int status = 0; + UINT32 Flags = 0; + BYTE OutputBuffer[65536] = { 0 }; + const BYTE* pSrcData = (const BYTE*)TEST_MPPC_BELLS; + const UINT32 SrcSize = sizeof(TEST_MPPC_BELLS) - 1; + UINT32 DstSize = sizeof(OutputBuffer); + const BYTE* pDstData = NULL; + const UINT32 expectedSize = sizeof(TEST_MPPC_BELLS_RDP4) - 1; + MPPC_CONTEXT* mppc = mppc_context_new(0, TRUE); + + if (!mppc) + return -1; + + status = mppc_compress(mppc, pSrcData, SrcSize, OutputBuffer, &pDstData, &DstSize, &Flags); + + if (status < 0) + goto fail; + + printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize); + + if (DstSize != expectedSize) + { + printf("MppcCompressBellsRdp4: output size mismatch: Actual: %" PRIu32 + ", Expected: %" PRIu32 "\n", + DstSize, expectedSize); + goto fail; + } + + if (memcmp(pDstData, TEST_MPPC_BELLS_RDP4, DstSize) != 0) + { + printf("MppcCompressBellsRdp4: output mismatch\n"); + printf("Actual\n"); + BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0); + printf("Expected\n"); + BitDump(__func__, WLOG_INFO, TEST_MPPC_BELLS_RDP4, DstSize * 8, 0); + goto fail; + } + + rc = 0; +fail: + mppc_context_free(mppc); + return rc; +} + +static int test_MppcDecompressBellsRdp5(void) +{ + int rc = -1; + int status = 0; + UINT32 Flags = 0; + const BYTE* pSrcData = NULL; + UINT32 SrcSize = 0; + UINT32 DstSize = 0; + MPPC_CONTEXT* mppc = NULL; + UINT32 expectedSize = 0; + const BYTE* pDstData = NULL; + mppc = mppc_context_new(1, FALSE); + + if (!mppc) + return -1; + + SrcSize = sizeof(TEST_MPPC_BELLS_RDP5) - 1; + pSrcData = TEST_MPPC_BELLS_RDP5; + Flags = PACKET_AT_FRONT | PACKET_COMPRESSED | 1; + expectedSize = sizeof(TEST_MPPC_BELLS) - 1; + status = mppc_decompress(mppc, pSrcData, SrcSize, &pDstData, &DstSize, Flags); + + if (status < 0) + goto fail; + + printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize); + + if (DstSize != expectedSize) + { + printf("MppcDecompressBellsRdp5: output size mismatch: Actual: %" PRIu32 + ", Expected: %" PRIu32 "\n", + DstSize, expectedSize); + goto fail; + } + + if (memcmp(pDstData, TEST_MPPC_BELLS, DstSize) != 0) + { + printf("MppcDecompressBellsRdp5: output mismatch\n"); + goto fail; + } + + rc = 0; +fail: + mppc_context_free(mppc); + return rc; +} + +static int test_MppcDecompressBellsRdp4(void) +{ + int rc = -1; + int status = 0; + UINT32 Flags = 0; + const BYTE* pSrcData = NULL; + UINT32 SrcSize = 0; + UINT32 DstSize = 0; + MPPC_CONTEXT* mppc = NULL; + UINT32 expectedSize = 0; + const BYTE* pDstData = NULL; + mppc = mppc_context_new(0, FALSE); + + if (!mppc) + return -1; + + SrcSize = sizeof(TEST_MPPC_BELLS_RDP4) - 1; + pSrcData = (const BYTE*)TEST_MPPC_BELLS_RDP4; + Flags = PACKET_AT_FRONT | PACKET_COMPRESSED | 0; + expectedSize = sizeof(TEST_MPPC_BELLS) - 1; + status = mppc_decompress(mppc, pSrcData, SrcSize, &pDstData, &DstSize, Flags); + + if (status < 0) + goto fail; + + printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize); + + if (DstSize != expectedSize) + { + printf("MppcDecompressBellsRdp4: output size mismatch: Actual: %" PRIu32 + ", Expected: %" PRIu32 "\n", + DstSize, expectedSize); + goto fail; + } + + if (memcmp(pDstData, TEST_MPPC_BELLS, DstSize) != 0) + { + printf("MppcDecompressBellsRdp4: output mismatch\n"); + goto fail; + } + + rc = 0; +fail: + mppc_context_free(mppc); + return rc; +} + +static int test_MppcCompressIslandRdp5(void) +{ + int rc = -1; + int status = 0; + UINT32 Flags = 0; + BYTE OutputBuffer[65536] = { 0 }; + const UINT32 SrcSize = sizeof(TEST_ISLAND_DATA) - 1; + const BYTE* pSrcData = (const BYTE*)TEST_ISLAND_DATA; + const UINT32 expectedSize = sizeof(TEST_ISLAND_DATA_RDP5) - 1; + UINT32 DstSize = sizeof(OutputBuffer); + const BYTE* pDstData = NULL; + MPPC_CONTEXT* mppc = mppc_context_new(1, TRUE); + + if (!mppc) + return -1; + + status = mppc_compress(mppc, pSrcData, SrcSize, OutputBuffer, &pDstData, &DstSize, &Flags); + + if (status < 0) + goto fail; + + printf("Flags: 0x%08" PRIX32 " DstSize: %" PRIu32 "\n", Flags, DstSize); + + if (DstSize != expectedSize) + { + printf("MppcCompressIslandRdp5: output size mismatch: Actual: %" PRIu32 + ", Expected: %" PRIu32 "\n", + DstSize, expectedSize); + goto fail; + } + + if (memcmp(pDstData, TEST_ISLAND_DATA_RDP5, DstSize) != 0) + { + printf("MppcCompressIslandRdp5: output mismatch\n"); + printf("Actual\n"); + BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0); + printf("Expected\n"); + BitDump(__func__, WLOG_INFO, TEST_ISLAND_DATA_RDP5, DstSize * 8, 0); + goto fail; + } + + rc = 0; +fail: + mppc_context_free(mppc); + return rc; +} + +static int test_MppcCompressBufferRdp5(void) +{ + int rc = -1; + int status = 0; + UINT32 Flags = 0; + BYTE OutputBuffer[65536] = { 0 }; + const UINT32 SrcSize = sizeof(TEST_RDP5_UNCOMPRESSED_DATA); + const BYTE* pSrcData = (const BYTE*)TEST_RDP5_UNCOMPRESSED_DATA; + const UINT32 expectedSize = sizeof(TEST_RDP5_COMPRESSED_DATA); + UINT32 DstSize = sizeof(OutputBuffer); + + const BYTE* pDstData = NULL; + MPPC_CONTEXT* mppc = mppc_context_new(1, TRUE); + + if (!mppc) + return -1; + + status = mppc_compress(mppc, pSrcData, SrcSize, OutputBuffer, &pDstData, &DstSize, &Flags); + + if (status < 0) + goto fail; + + printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize); + + if (DstSize != expectedSize) + { + printf("MppcCompressBufferRdp5: output size mismatch: Actual: %" PRIu32 + ", Expected: %" PRIu32 "\n", + DstSize, expectedSize); + goto fail; + } + + if (memcmp(pDstData, TEST_RDP5_COMPRESSED_DATA, DstSize) != 0) + { + printf("MppcCompressBufferRdp5: output mismatch: compressed output does not match " + "Microsoft implementation\n"); + goto fail; + } + + rc = 0; +fail: + mppc_context_free(mppc); + return rc; +} + +static int test_MppcDecompressBufferRdp5(void) +{ + int rc = -1; + int status = 0; + UINT32 Flags = 0; + const BYTE* pSrcData = NULL; + UINT32 SrcSize = 0; + UINT32 DstSize = 0; + MPPC_CONTEXT* mppc = NULL; + UINT32 expectedSize = 0; + const BYTE* pDstData = NULL; + mppc = mppc_context_new(1, FALSE); + + if (!mppc) + return -1; + + SrcSize = sizeof(TEST_RDP5_COMPRESSED_DATA); + pSrcData = (const BYTE*)TEST_RDP5_COMPRESSED_DATA; + Flags = PACKET_AT_FRONT | PACKET_COMPRESSED | 1; + expectedSize = sizeof(TEST_RDP5_UNCOMPRESSED_DATA); + status = mppc_decompress(mppc, pSrcData, SrcSize, &pDstData, &DstSize, Flags); + + if (status < 0) + goto fail; + + printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize); + + if (DstSize != expectedSize) + { + printf("MppcDecompressBufferRdp5: output size mismatch: Actual: %" PRIu32 + ", Expected: %" PRIu32 "\n", + DstSize, expectedSize); + goto fail; + } + + if (memcmp(pDstData, TEST_RDP5_UNCOMPRESSED_DATA, DstSize) != 0) + { + printf("MppcDecompressBufferRdp5: output mismatch\n"); + goto fail; + } + + rc = 0; +fail: + mppc_context_free(mppc); + return rc; +} + +int TestFreeRDPCodecMppc(int argc, char* argv[]) +{ + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + if (test_MppcCompressIslandRdp5() < 0) + return -1; + + if (test_MppcCompressBellsRdp5() < 0) + return -1; + + if (test_MppcDecompressBellsRdp5() < 0) + return -1; + + if (test_MppcCompressBellsRdp4() < 0) + return -1; + + if (test_MppcDecompressBellsRdp4() < 0) + return -1; + + if (test_MppcCompressBufferRdp5() < 0) + return -1; + + if (test_MppcDecompressBufferRdp5() < 0) + return -1; + + return 0; +} diff --git a/libfreerdp/codec/test/TestFreeRDPCodecNCrush.c b/libfreerdp/codec/test/TestFreeRDPCodecNCrush.c new file mode 100644 index 0000000..689b378 --- /dev/null +++ b/libfreerdp/codec/test/TestFreeRDPCodecNCrush.c @@ -0,0 +1,122 @@ +#include <winpr/crt.h> +#include <winpr/print.h> + +#include "../ncrush.h" + +static const BYTE TEST_BELLS_DATA[] = "for.whom.the.bell.tolls,.the.bell.tolls.for.thee!"; + +static const BYTE TEST_BELLS_NCRUSH[] = + "\xfb\x1d\x7e\xe4\xda\xc7\x1d\x70\xf8\xa1\x6b\x1f\x7d\xc0\xbe\x6b" + "\xef\xb5\xef\x21\x87\xd0\xc5\xe1\x85\x71\xd4\x10\x16\xe7\xda\xfb" + "\x1d\x7e\xe4\xda\x47\x1f\xb0\xef\xbe\xbd\xff\x2f"; + +static BOOL test_NCrushCompressBells(void) +{ + BOOL rc = FALSE; + int status = 0; + UINT32 Flags = 0; + const BYTE* pDstData = NULL; + BYTE OutputBuffer[65536] = { 0 }; + const UINT32 SrcSize = sizeof(TEST_BELLS_DATA) - 1; + const BYTE* pSrcData = TEST_BELLS_DATA; + const UINT32 expectedSize = sizeof(TEST_BELLS_NCRUSH) - 1; + UINT32 DstSize = sizeof(OutputBuffer); + NCRUSH_CONTEXT* ncrush = ncrush_context_new(TRUE); + + if (!ncrush) + return rc; + + status = ncrush_compress(ncrush, pSrcData, SrcSize, OutputBuffer, &pDstData, &DstSize, &Flags); + + if (status < 0) + goto fail; + + printf("status: %d Flags: 0x%08" PRIX32 " DstSize: %" PRIu32 "\n", status, Flags, DstSize); + + if (DstSize != expectedSize) + { + printf("NCrushCompressBells: output size mismatch: Actual: %" PRIu32 ", Expected: %" PRIu32 + "\n", + DstSize, expectedSize); + printf("Actual\n"); + BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0); + printf("Expected\n"); + BitDump(__func__, WLOG_INFO, TEST_BELLS_NCRUSH, expectedSize * 8, 0); + goto fail; + } + + if (memcmp(pDstData, TEST_BELLS_NCRUSH, DstSize) != 0) + { + printf("NCrushCompressBells: output mismatch\n"); + printf("Actual\n"); + BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0); + printf("Expected\n"); + BitDump(__func__, WLOG_INFO, TEST_BELLS_NCRUSH, expectedSize * 8, 0); + goto fail; + } + + rc = TRUE; +fail: + ncrush_context_free(ncrush); + return rc; +} + +static BOOL test_NCrushDecompressBells(void) +{ + BOOL rc = FALSE; + int status = 0; + UINT32 Flags = 0; + const BYTE* pSrcData = NULL; + UINT32 SrcSize = 0; + UINT32 DstSize = 0; + UINT32 expectedSize = 0; + const BYTE* pDstData = NULL; + NCRUSH_CONTEXT* ncrush = ncrush_context_new(FALSE); + + if (!ncrush) + return rc; + + SrcSize = sizeof(TEST_BELLS_NCRUSH) - 1; + pSrcData = (const BYTE*)TEST_BELLS_NCRUSH; + Flags = PACKET_COMPRESSED | 2; + expectedSize = sizeof(TEST_BELLS_DATA) - 1; + status = ncrush_decompress(ncrush, pSrcData, SrcSize, &pDstData, &DstSize, Flags); + + if (status < 0) + goto fail; + + printf("Flags: 0x%08" PRIX32 " DstSize: %" PRIu32 "\n", Flags, DstSize); + + if (DstSize != expectedSize) + { + printf("NCrushDecompressBells: output size mismatch: Actual: %" PRIu32 + ", Expected: %" PRIu32 "\n", + DstSize, expectedSize); + goto fail; + } + + if (memcmp(pDstData, TEST_BELLS_DATA, DstSize) != 0) + { + printf("NCrushDecompressBells: output mismatch\n"); + goto fail; + } + + rc = TRUE; +fail: + ncrush_context_free(ncrush); + return rc; +} + +int TestFreeRDPCodecNCrush(int argc, char* argv[]) +{ + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + if (!test_NCrushCompressBells()) + return -1; + + if (!test_NCrushDecompressBells()) + return -1; + + return 0; +} diff --git a/libfreerdp/codec/test/TestFreeRDPCodecPlanar.c b/libfreerdp/codec/test/TestFreeRDPCodecPlanar.c new file mode 100644 index 0000000..ba4c5c6 --- /dev/null +++ b/libfreerdp/codec/test/TestFreeRDPCodecPlanar.c @@ -0,0 +1,5785 @@ + +#include <math.h> + +#include <winpr/crt.h> +#include <winpr/print.h> +#include <winpr/crypto.h> + +#include <freerdp/freerdp.h> +#include <freerdp/codec/color.h> +#include <freerdp/codec/bitmap.h> +#include <freerdp/codec/planar.h> + +/** + * Experimental Case 01: 64x64 (32bpp) + */ + +static const BYTE TEST_RLE_BITMAP_EXPERIMENTAL_01[16384] = + "\x1B\x1A\x16\xFF\x1C\x1A\x16\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF\x17\x16\x12\xFF\x18\x16\x12" + "\xFF\x19\x18\x14\xFF\x19\x18\x14\xFF" + "\x1D\x1C\x17\xFF\x1D\x1B\x17\xFF\x1C\x1B\x17\xFF\x1B\x1A\x16\xFF\x1A\x19\x15\xFF\x1A\x19\x15" + "\xFF\x18\x17\x13\xFF\x1A\x19\x15\xFF" + "\x1B\x1A\x16\xFF\x19\x18\x14\xFF\x19\x18\x14\xFF\x18\x16\x14\xFF\x1C\x1A\x16\xFF\x1A\x18\x14" + "\xFF\x1B\x1A\x16\xFF\x19\x17\x13\xFF" + "\x1B\x19\x16\xFF\x1A\x18\x15\xFF\x1A\x18\x13\xFF\x19\x17\x13\xFF\x1B\x19\x15\xFF\x1B\x1A\x16" + "\xFF\x1A\x19\x15\xFF\x18\x16\x12\xFF" + "\x1A\x19\x15\xFF\x1C\x1B\x16\xFF\x1C\x1B\x17\xFF\x1D\x1C\x17\xFF\x1B\x1B\x16\xFF\x18\x17\x13" + "\xFF\x19\x18\x14\xFF\x1B\x19\x14\xFF" + "\x1A\x19\x15\xFF\x1A\x1A\x15\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x1C\x1B\x17\xFF\x1D\x1C\x17" + "\xFF\x1B\x1A\x15\xFF\x18\x17\x12\xFF" + "\x1B\x1A\x16\xFF\x18\x17\x13\xFF\x1A\x19\x15\xFF\x1E\x1D\x18\xFF\x19\x18\x14\xFF\x1C\x1B\x17" + "\xFF\x1C\x1A\x16\xFF\x1B\x1A\x18\xFF" + "\x1D\x1B\x16\xFF\x1E\x1B\x17\xFF\x1D\x1C\x18\xFF\x1B\x1A\x16\xFF\x1A\x1A\x14\xFF\x1B\x1B\x15" + "\xFF\x1E\x1C\x17\xFF\x1D\x1B\x17\xFF" + "\x1C\x19\x15\xFF\x1D\x1C\x17\xFF\x1A\x19\x14\xFF\x1C\x1B\x17\xFF\x1E\x1D\x19\xFF\x1C\x1C\x17" + "\xFF\x1B\x1C\x16\xFF\x1D\x1C\x18\xFF" + "\x1C\x1C\x16\xFF\x1D\x1C\x17\xFF\x1F\x1E\x19\xFF\x1A\x18\x14\xFF\x1B\x1A\x16\xFF\x1D\x1C\x18" + "\xFF\x1D\x1C\x17\xFF\x1E\x1B\x19\xFF" + "\x1F\x1C\x18\xFF\x1D\x1C\x18\xFF\x20\x1F\x19\xFF\x1F\x1E\x19\xFF\x1B\x1A\x16\xFF\x1F\x1E\x1A" + "\xFF\x1D\x1C\x17\xFF\x1F\x1C\x18\xFF" + "\x1F\x1D\x18\xFF\x1E\x1C\x18\xFF\x1D\x1C\x17\xFF\x1C\x1C\x16\xFF\x1D\x1C\x18\xFF\x1F\x1F\x19" + "\xFF\x1D\x1C\x18\xFF\x1B\x1B\x16\xFF" + "\x1D\x1D\x17\xFF\x1F\x1E\x19\xFF\x1D\x1C\x16\xFF\x1B\x1A\x15\xFF\x1F\x1D\x19\xFF\x1E\x1C\x18" + "\xFF\x1F\x1E\x1A\xFF\x22\x20\x1C\xFF" + "\x22\x20\x1B\xFF\x20\x1F\x1A\xFF\x1F\x1F\x1A\xFF\x1E\x1E\x1A\xFF\x1D\x1C\x18\xFF\x1F\x1D\x19" + "\xFF\x20\x20\x1A\xFF\x22\x21\x1C\xFF" + "\x20\x1F\x1A\xFF\x1D\x1D\x18\xFF\x1E\x1D\x18\xFF\x22\x20\x1B\xFF\x20\x20\x1A\xFF\x1E\x1D\x18" + "\xFF\x20\x20\x1B\xFF\x20\x1F\x1B\xFF" + "\x1F\x1D\x18\xFF\x1E\x1E\x19\xFF\x1E\x1E\x19\xFF\x1D\x1B\x18\xFF\x1D\x1B\x16\xFF\x1E\x1C\x17" + "\xFF\x1B\x1A\x16\xFF\x1F\x1D\x18\xFF" + "\x1B\x1A\x16\xFF\x1A\x19\x15\xFF\x19\x18\x15\xFF\x1B\x19\x15\xFF\x1B\x19\x15\xFF\x18\x18\x14" + "\xFF\x17\x17\x13\xFF\x19\x18\x15\xFF" + "\x15\x15\x11\xFF\x14\x13\x10\xFF\x4E\x4A\x3D\xFF\x21\x20\x1A\xFF\x25\x24\x20\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x24\x23\x1F\xFF" + "\x21\x20\x1A\xFF\x24\x23\x1F\xFF\x21\x20\x1A\xFF\x2A\x29\x23\xFF\x21\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x26\x25\x21\xFF\x25\x24\x20\xFF\x21\x20\x1A" + "\xFF\x22\x20\x1B\xFF\x26\x25\x20\xFF" + "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x23\x21\x1C\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x22\x21\x1C" + "\xFF\x26\x25\x21\xFF\x23\x21\x1C\xFF" + "\x22\x20\x1A\xFF\x22\x20\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x22\x22\x1C\xFF\x23\x22\x1D" + "\xFF\x24\x23\x1E\xFF\x24\x24\x1E\xFF" + "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x29\x28\x23\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E" + "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF" + "\x22\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x21\x1C" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1E\xFF" + "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x25\x24\x20\xFF\x23\x23\x1E\xFF\x24\x23\x1F" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x28\x26\x21" + "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF" + "\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x25\x24\x20\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x22\x22\x1D\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x22\x20\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1D" + "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B" + "\xFF\x26\x25\x20\xFF\x24\x22\x1D\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x23\x21\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x26\x24\x20\xFF\x24\x23\x1E\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x25\x24\x20" + "\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1D\xFF\x21\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C" + "\xFF\x23\x22\x1C\xFF\x23\x21\x1C\xFF" + "\x22\x21\x1B\xFF\x28\x26\x21\xFF\x23\x22\x1C\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x26\x25\x20" + "\xFF\x24\x23\x1E\xFF\x28\x26\x21\xFF" + "\x28\x26\x21\xFF\x22\x22\x1C\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x21\x1C\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x28\x26\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x26\x25\x21\xFF" + "\x24\x23\x1E\xFF\x26\x25\x21\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x23\x22\x1E\xFF\x25\x24\x20" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x25\x24\x20\xFF\x25\x25\x20\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x26\x25\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x23\x22\x1C" + "\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF" + "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x2A\x29\x23\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF" + "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x26\x26\x21\xFF\x26\x25\x20\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x27\x25\x20\xFF" + "\x22\x21\x1B\xFF\x26\x26\x20\xFF\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x27\x25\x20\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF" + "\x23\x22\x1C\xFF\x23\x22\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x27\x26\x21\xFF\x23\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x24\x23\x1E" + "\xFF\x25\x24\x1F\xFF\x25\x24\x1F\xFF" + "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x29\x28\x23\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x1F" + "\xFF\x22\x21\x1B\xFF\x29\x26\x21\xFF" + "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x22\x1C\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1E\xFF" + "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x26\x25\x20\xFF\x23\x23\x1E\xFF\x24\x23\x1F" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x25\x21\xFF\x29\x26\x21" + "\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF" + "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x27\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x1F" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x26\x24\x20\xFF\x23\x22\x1C\xFF\x26\x25\x21\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x23\x23\x1E\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1E\xFF\x22\x21\x1B\xFF\x27\x25\x20\xFF\x23\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1E" + "\xFF\x29\x26\x21\xFF\x23\x22\x1C\xFF" + "\x23\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B" + "\xFF\x27\x26\x21\xFF\x24\x23\x1E\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x29\x26\x21\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x27\x26\x21\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x26\x25\x20" + "\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C" + "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x28\x26\x21\xFF\x23\x22\x1C\xFF\x26\x25\x21\xFF\x23\x22\x1C\xFF\x26\x25\x20" + "\xFF\x25\x24\x1F\xFF\x28\x26\x21\xFF" + "\x28\x26\x21\xFF\x22\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x4E\x49\x3C" + "\xFF\x1A\x18\x14\xFF\x1A\x18\x15\xFF" + "\x1B\x1B\x16\xFF\x1D\x1B\x17\xFF\x1F\x1D\x19\xFF\x20\x1D\x19\xFF\x1E\x1D\x19\xFF\x1F\x1D\x19" + "\xFF\x21\x20\x1B\xFF\x23\x21\x1C\xFF" + "\x22\x21\x1C\xFF\x25\x22\x1D\xFF\x25\x22\x1E\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF\x25\x23\x1E" + "\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF" + "\x26\x23\x1E\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF\x25\x22\x1D\xFF\x26\x25\x1F\xFF\x27\x26\x20" + "\xFF\x26\x25\x1F\xFF\x27\x24\x1F\xFF" + "\x27\x25\x20\xFF\x29\x26\x21\xFF\x26\x23\x1F\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20" + "\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF" + "\x27\x24\x1F\xFF\x28\x25\x20\xFF\x27\x26\x1F\xFF\x27\x24\x1F\xFF\x26\x23\x1F\xFF\x28\x25\x20" + "\xFF\x29\x26\x21\xFF\x2A\x27\x22\xFF" + "\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF\x26\x25\x20\xFF\x26\x24\x1E" + "\xFF\x26\x23\x1D\xFF\x27\x24\x1F\xFF" + "\x26\x23\x1E\xFF\x26\x23\x1F\xFF\x24\x23\x1E\xFF\x26\x24\x1F\xFF\x27\x24\x1E\xFF\x27\x24\x20" + "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF" + "\x28\x25\x20\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x28\x25\x20\xFF\x24\x23\x1E\xFF\x25\x23\x1E" + "\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF" + "\x28\x26\x21\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF\x28\x26\x20\xFF\x25\x24\x1F\xFF\x27\x24\x1F" + "\xFF\x29\x26\x21\xFF\x27\x24\x1F\xFF" + "\x25\x22\x1D\xFF\x25\x24\x1E\xFF\x29\x27\x20\xFF\x29\x26\x20\xFF\x28\x26\x20\xFF\x27\x26\x1F" + "\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF" + "\x26\x25\x1F\xFF\x26\x25\x20\xFF\x27\x24\x1F\xFF\x25\x22\x1E\xFF\x24\x23\x1D\xFF\x23\x22\x1D" + "\xFF\x24\x21\x1C\xFF\x26\x23\x1E\xFF" + "\x27\x24\x1E\xFF\x28\x27\x20\xFF\x27\x25\x1F\xFF\x27\x25\x20\xFF\x25\x22\x1D\xFF\x26\x24\x1E" + "\xFF\x22\x20\x1A\xFF\x24\x24\x1D\xFF" + "\x28\x25\x20\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x26\x23\x1E\xFF\x26\x25\x1F" + "\xFF\x27\x24\x1F\xFF\x26\x25\x1F\xFF" + "\x27\x25\x20\xFF\x28\x27\x21\xFF\x27\x25\x20\xFF\x26\x25\x1F\xFF\x26\x23\x1E\xFF\x22\x21\x1C" + "\xFF\x25\x24\x1E\xFF\x26\x23\x1E\xFF" + "\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x25\x24\x1D\xFF\x23\x23\x1D\xFF\x25\x23\x1E" + "\xFF\x27\x25\x20\xFF\x26\x24\x1E\xFF" + "\x25\x23\x1E\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x27\x26\x20\xFF\x29\x2F\x26\xFF\x3C\x70\x55" + "\xFF\x54\xC5\x92\xFF\x57\xCD\x97\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF" + "\x56\xCC\x97\xFF\x4B\xA4\x7B\xFF\x2F\x47\x37\xFF\x26\x26\x20\xFF\x25\x23\x1D\xFF\x24\x23\x1D" + "\xFF\x24\x22\x1D\xFF\x24\x22\x1D\xFF" + "\x26\x23\x1E\xFF\x27\x25\x20\xFF\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x26\x24\x1F\xFF\x24\x23\x1D" + "\xFF\x26\x24\x1F\xFF\x25\x25\x1D\xFF" + "\x27\x2D\x26\xFF\x39\x68\x50\xFF\x54\xC0\x8F\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x97\xFF\x56\xCD\x97\xFF\x49\x9F\x76\xFF\x2D\x42\x33\xFF\x24\x25\x20\xFF\x23\x22\x1D" + "\xFF\x25\x22\x1D\xFF\x24\x21\x1C\xFF" + "\x22\x21\x1C\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x22\x20\x1B\xFF\x23\x22\x1C" + "\xFF\x25\x25\x1F\xFF\x26\x24\x1E\xFF" + "\x24\x23\x1D\xFF\x24\x23\x1D\xFF\x26\x23\x1F\xFF\x23\x20\x1C\xFF\x28\x26\x1F\xFF\x25\x24\x1F" + "\xFF\x25\x22\x1D\xFF\x24\x21\x1C\xFF" + "\x23\x23\x1D\xFF\x25\x22\x1E\xFF\x24\x22\x1E\xFF\x27\x24\x1F\xFF\x23\x21\x1B\xFF\x26\x25\x1F" + "\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF" + "\x26\x23\x1E\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF\x24\x21\x1C\xFF\x27\x26\x21\xFF\x25\x22\x1D" + "\xFF\x22\x20\x1C\xFF\x25\x22\x1D\xFF" + "\x26\x24\x1E\xFF\x24\x23\x1D\xFF\x26\x25\x1F\xFF\x24\x22\x1E\xFF\x23\x21\x1C\xFF\x23\x22\x1D" + "\xFF\x24\x22\x1D\xFF\x23\x21\x1C\xFF" + "\x24\x22\x1D\xFF\x24\x22\x1D\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF\x25\x23\x1E" + "\xFF\x23\x20\x1B\xFF\x25\x22\x1D\xFF" + "\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x25\x22\x1E\xFF\x24\x21\x1D\xFF\x25\x23\x1E\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1C\xFF\x23\x1F\x1B\xFF" + "\x21\x21\x1B\xFF\x24\x22\x1D\xFF\x24\x23\x1D\xFF\x25\x23\x1E\xFF\x23\x20\x1C\xFF\x26\x24\x1E" + "\xFF\x24\x22\x1D\xFF\x24\x22\x1D\xFF" + "\x21\x21\x1A\xFF\x21\x21\x1C\xFF\x20\x20\x1A\xFF\x22\x21\x1C\xFF\x26\x23\x1E\xFF\x21\x21\x1C" + "\xFF\x22\x21\x1C\xFF\x23\x22\x1D\xFF" + "\x25\x23\x1C\xFF\x26\x23\x1E\xFF\x24\x22\x1D\xFF\x22\x22\x1C\xFF\x22\x22\x1C\xFF\x24\x22\x1D" + "\xFF\x24\x23\x1D\xFF\x25\x22\x1D\xFF" + "\x22\x21\x1D\xFF\x21\x21\x1C\xFF\x23\x22\x1D\xFF\x23\x20\x1C\xFF\x21\x20\x1B\xFF\x24\x23\x1D" + "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF" + "\x22\x22\x1C\xFF\x24\x22\x1D\xFF\x23\x21\x1B\xFF\x22\x21\x1C\xFF\x24\x23\x1D\xFF\x23\x21\x1C" + "\xFF\x21\x21\x1B\xFF\x24\x24\x1E\xFF" + "\x24\x22\x1D\xFF\x23\x22\x1D\xFF\x23\x22\x1D\xFF\x21\x20\x1B\xFF\x23\x21\x1C\xFF\x25\x24\x1E" + "\xFF\x24\x21\x1C\xFF\x23\x21\x1C\xFF" + "\x21\x20\x1B\xFF\x22\x20\x1B\xFF\x21\x20\x1B\xFF\x23\x22\x1C\xFF\x23\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x22\x1C\xFF\x22\x22\x1D\xFF" + "\x24\x21\x1C\xFF\x21\x1F\x1B\xFF\x21\x1F\x1A\xFF\x20\x20\x19\xFF\x24\x22\x1B\xFF\x23\x22\x1C" + "\xFF\x27\x25\x1E\xFF\x25\x24\x1E\xFF" + "\x26\x29\x23\xFF\x2F\x45\x37\xFF\x2B\x3E\x2F\xFF\x27\x26\x20\xFF\x21\x1F\x1A\xFF\x21\x20\x1C" + "\xFF\x21\x20\x1B\xFF\x21\x21\x1B\xFF" + "\x20\x1F\x1A\xFF\x25\x23\x1E\xFF\x24\x23\x1D\xFF\x1F\x1E\x1A\xFF\x24\x22\x1C\xFF\x27\x23\x1F" + "\xFF\x24\x22\x1E\xFF\x21\x20\x1C\xFF" + "\x1F\x1E\x19\xFF\x21\x1F\x1B\xFF\x21\x20\x1B\xFF\x22\x21\x1B\xFF\x25\x22\x1D\xFF\x21\x1F\x1A" + "\xFF\x21\x1F\x1C\xFF\x21\x20\x1B\xFF" + "\x24\x21\x1C\xFF\x1F\x1D\x19\xFF\x22\x20\x1A\xFF\x22\x22\x1C\xFF\x22\x20\x1B\xFF\x21\x21\x1A" + "\xFF\x20\x20\x1A\xFF\x21\x20\x1C\xFF" + "\x25\x24\x1E\xFF\x24\x22\x1D\xFF\x21\x20\x1B\xFF\x22\x1F\x1B\xFF\x22\x21\x1C\xFF\x23\x21\x1D" + "\xFF\x20\x1F\x1C\xFF\x22\x21\x1C\xFF" + "\x15\x14\x11\xFF\x15\x13\x10\xFF\x15\x14\x10\xFF\x18\x17\x13\xFF\x16\x15\x11\xFF\x14\x13\x10" + "\xFF\x15\x14\x10\xFF\x14\x13\x0F\xFF" + "\x12\x10\x0E\xFF\x11\x10\x0E\xFF\x10\x0F\x0D\xFF\x14\x12\x10\xFF\x14\x12\x0E\xFF\x14\x13\x0F" + "\xFF\x15\x13\x10\xFF\x12\x10\x0D\xFF" + "\x12\x11\x0E\xFF\x12\x11\x0E\xFF\x17\x16\x12\xFF\x17\x15\x11\xFF\x18\x17\x13\xFF\x15\x13\x10" + "\xFF\x16\x15\x12\xFF\x13\x12\x0E\xFF" + "\x12\x11\x0E\xFF\x14\x13\x0F\xFF\x13\x12\x0F\xFF\x12\x12\x0F\xFF\x15\x15\x11\xFF\x13\x12\x0F" + "\xFF\x13\x11\x0E\xFF\x13\x12\x0F\xFF" + "\x13\x11\x0F\xFF\x13\x12\x0E\xFF\x14\x13\x0F\xFF\x15\x14\x10\xFF\x13\x11\x0F\xFF\x12\x12\x0F" + "\xFF\x14\x13\x10\xFF\x14\x13\x0F\xFF" + "\x17\x16\x12\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x15\x14\x0F" + "\xFF\x14\x13\x10\xFF\x14\x12\x10\xFF" + "\x15\x14\x10\xFF\x15\x13\x10\xFF\x15\x14\x10\xFF\x15\x13\x10\xFF\x11\x10\x0D\xFF\x14\x13\x10" + "\xFF\x13\x12\x0E\xFF\x14\x12\x0F\xFF" + "\x16\x15\x10\xFF\x14\x13\x0F\xFF\x12\x10\x0E\xFF\x13\x11\x0F\xFF\x12\x10\x0F\xFF\x15\x14\x10" + "\xFF\x15\x14\x11\xFF\x15\x13\x11\xFF" + "\x16\x15\x11\xFF\x17\x16\x12\xFF\x13\x12\x0E\xFF\x12\x11\x0D\xFF\x14\x13\x10\xFF\x17\x16\x12" + "\xFF\x18\x17\x14\xFF\x17\x16\x13\xFF" + "\x15\x14\x10\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x16\x14\x11\xFF\x16\x14\x12" + "\xFF\x14\x13\x0F\xFF\x14\x12\x10\xFF" + "\x14\x13\x10\xFF\x15\x14\x12\xFF\x16\x14\x11\xFF\x15\x13\x0F\xFF\x14\x13\x10\xFF\x15\x13\x10" + "\xFF\x18\x17\x13\xFF\x15\x13\x11\xFF" + "\x14\x13\x0F\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x15\x14\x11\xFF\x13\x12\x0F\xFF\x17\x15\x12" + "\xFF\x15\x13\x11\xFF\x13\x12\x0E\xFF" + "\x17\x16\x13\xFF\x19\x18\x14\xFF\x18\x17\x14\xFF\x17\x16\x13\xFF\x15\x14\x10\xFF\x15\x14\x12" + "\xFF\x14\x13\x0F\xFF\x15\x14\x11\xFF" + "\x16\x15\x11\xFF\x19\x18\x14\xFF\x18\x16\x13\xFF\x14\x13\x10\xFF\x14\x13\x0F\xFF\x15\x13\x11" + "\xFF\x15\x14\x11\xFF\x14\x13\x10\xFF" + "\x15\x14\x11\xFF\x18\x17\x14\xFF\x17\x15\x12\xFF\x16\x15\x11\xFF\x18\x17\x13\xFF\x1B\x1A\x16" + "\xFF\x1A\x19\x16\xFF\x16\x15\x11\xFF" + "\x16\x14\x12\xFF\x16\x15\x11\xFF\x17\x16\x13\xFF\x14\x13\x11\xFF\x17\x16\x12\xFF\x19\x19\x14" + "\xFF\x16\x15\x12\xFF\x19\x18\x14\xFF" + "\x18\x17\x14\xFF\x18\x18\x13\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x17\x15\x13\xFF\x18\x17\x14" + "\xFF\x17\x16\x13\xFF\x16\x15\x11\xFF" + "\x14\x13\x0F\xFF\x16\x15\x11\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF\x18\x17\x14\xFF\x15\x13\x12" + "\xFF\x16\x15\x11\xFF\x18\x16\x12\xFF" + "\x14\x13\x0F\xFF\x15\x14\x10\xFF\x16\x15\x11\xFF\x17\x16\x12\xFF\x1B\x1A\x16\xFF\x1A\x19\x15" + "\xFF\x19\x18\x14\xFF\x16\x15\x11\xFF" + "\x15\x14\x10\xFF\x17\x16\x12\xFF\x19\x18\x13\xFF\x19\x18\x13\xFF\x17\x16\x12\xFF\x17\x16\x12" + "\xFF\x18\x17\x13\xFF\x1B\x18\x15\xFF" + "\x17\x16\x12\xFF\x17\x16\x12\xFF\x19\x18\x14\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x1A\x19\x15" + "\xFF\x1B\x19\x15\xFF\x1A\x19\x14\xFF" + "\x16\x15\x11\xFF\x1A\x19\x15\xFF\x1A\x18\x15\xFF\x17\x16\x12\xFF\x19\x19\x14\xFF\x19\x18\x14" + "\xFF\x17\x16\x12\xFF\x18\x16\x12\xFF" + "\x19\x18\x14\xFF\x19\x18\x14\xFF\x1B\x1A\x16\xFF\x1C\x1A\x16\xFF\x17\x15\x11\xFF\x19\x19\x14" + "\xFF\x19\x19\x14\xFF\x19\x18\x14\xFF" + "\x1D\x1B\x17\xFF\x1A\x19\x14\xFF\x1A\x19\x15\xFF\x17\x16\x12\xFF\x19\x18\x14\xFF\x19\x17\x14" + "\xFF\x19\x18\x14\xFF\x19\x18\x13\xFF" + "\x1B\x1A\x16\xFF\x1B\x1A\x16\xFF\x19\x17\x13\xFF\x18\x17\x13\xFF\x1A\x18\x14\xFF\x1C\x1A\x16" + "\xFF\x1D\x1C\x17\xFF\x1F\x1E\x19\xFF" + "\x19\x18\x14\xFF\x18\x17\x12\xFF\x18\x17\x12\xFF\x17\x16\x12\xFF\x18\x17\x13\xFF\x19\x18\x14" + "\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF" + "\x1B\x19\x15\xFF\x1B\x19\x15\xFF\x1A\x18\x14\xFF\x17\x16\x12\xFF\x16\x16\x11\xFF\x1A\x18\x14" + "\xFF\x1B\x19\x15\xFF\x1E\x1C\x18\xFF" + "\x1B\x1A\x16\xFF\x1C\x1A\x15\xFF\x19\x18\x14\xFF\x1A\x18\x14\xFF\x1C\x1A\x16\xFF\x1C\x1A\x17" + "\xFF\x1C\x1B\x17\xFF\x1A\x18\x14\xFF" + "\x1B\x19\x15\xFF\x1A\x18\x14\xFF\x1A\x19\x15\xFF\x1B\x1A\x16\xFF\x1B\x1A\x16\xFF\x1B\x1A\x15" + "\xFF\x1C\x1C\x17\xFF\x1C\x1C\x17\xFF" + "\x1B\x19\x15\xFF\x1A\x19\x15\xFF\x1B\x19\x15\xFF\x1D\x1C\x17\xFF\x1C\x1A\x16\xFF\x1C\x1C\x17" + "\xFF\x1C\x1B\x16\xFF\x1D\x1C\x17\xFF" + "\x1D\x1B\x17\xFF\x19\x19\x14\xFF\x1B\x1A\x16\xFF\x1F\x1D\x18\xFF\x1E\x1E\x18\xFF\x1E\x1D\x18" + "\xFF\x1D\x1B\x18\xFF\x1D\x1B\x17\xFF" + "\x1B\x1A\x15\xFF\x1A\x1A\x15\xFF\x1C\x1B\x16\xFF\x1E\x1C\x18\xFF\x1A\x19\x15\xFF\x1A\x19\x15" + "\xFF\x1D\x1C\x17\xFF\x1C\x19\x15\xFF" + "\x1D\x1B\x17\xFF\x1B\x1A\x15\xFF\x1A\x1A\x15\xFF\x1C\x1B\x16\xFF\x22\x20\x1B\xFF\x1E\x1D\x18" + "\xFF\x1D\x1C\x18\xFF\x1C\x1C\x17\xFF" + "\x1B\x1B\x15\xFF\x1B\x1A\x16\xFF\x1A\x19\x15\xFF\x1A\x19\x15\xFF\x1D\x1C\x18\xFF\x1B\x19\x15" + "\xFF\x1D\x1B\x17\xFF\x1B\x1B\x16\xFF" + "\x1F\x1C\x18\xFF\x1E\x1C\x18\xFF\x1F\x1C\x18\xFF\x1E\x1C\x18\xFF\x1C\x1C\x18\xFF\x1D\x1C\x18" + "\xFF\x1D\x1B\x17\xFF\x1E\x1B\x17\xFF" + "\x1D\x1B\x17\xFF\x1D\x1C\x17\xFF\x1E\x1E\x19\xFF\x1E\x1D\x19\xFF\x1C\x1C\x18\xFF\x20\x1F\x19" + "\xFF\x1F\x1D\x1A\xFF\x1F\x1E\x19\xFF" + "\x1D\x1B\x17\xFF\x1C\x1B\x17\xFF\x1B\x1A\x16\xFF\x1B\x1A\x16\xFF\x1E\x1C\x17\xFF\x1A\x19\x15" + "\xFF\x20\x1F\x1A\xFF\x1E\x1D\x18\xFF" + "\x1E\x1D\x18\xFF\x1F\x1D\x19\xFF\x1F\x1E\x1A\xFF\x1F\x1D\x19\xFF\x1D\x1C\x18\xFF\x1E\x1C\x18" + "\xFF\x1D\x1C\x18\xFF\x21\x20\x1B\xFF" + "\x1E\x1E\x19\xFF\x20\x1F\x1A\xFF\x20\x1E\x1A\xFF\x20\x1F\x1A\xFF\x20\x20\x1A\xFF\x21\x20\x1A" + "\xFF\x23\x21\x1B\xFF\x21\x21\x1B\xFF" + "\x1E\x1D\x19\xFF\x1E\x1D\x18\xFF\x1F\x1E\x1A\xFF\x1C\x1B\x18\xFF\x1D\x1C\x17\xFF\x1E\x1C\x17" + "\xFF\x1C\x1B\x17\xFF\x1E\x1D\x18\xFF" + "\x1B\x1A\x16\xFF\x1B\x1B\x16\xFF\x1A\x1A\x15\xFF\x19\x19\x15\xFF\x18\x18\x14\xFF\x17\x16\x12" + "\xFF\x18\x17\x13\xFF\x1A\x19\x15\xFF" + "\x15\x14\x11\xFF\x15\x14\x11\xFF\x51\x4E\x41\xFF\x21\x20\x1A\xFF\x26\x25\x20\xFF\x28\x26\x21" + "\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF" + "\x21\x20\x1A\xFF\x22\x21\x1C\xFF\x21\x20\x1A\xFF\x21\x20\x1A\xFF\x21\x20\x1A\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF" + "\x28\x26\x21\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1D" + "\xFF\x24\x23\x1E\xFF\x21\x20\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x24\x23\x1E\xFF\x24\x22\x1D\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x26\x25\x20\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF" + "\x22\x21\x1B\xFF\x28\x26\x21\xFF\x23\x21\x1C\xFF\x22\x21\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1D" + "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x24\x23\x1D\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x23\x21\x1C\xFF" + "\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x25\x24\x20\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1D" + "\xFF\x24\x23\x1D\xFF\x25\x23\x1F\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x25\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x23\x21\x1C\xFF\x21\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B" + "\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x25\x24\x20\xFF\x26\x25\x20\xFF\x25\x24\x20\xFF\x23\x23\x1E\xFF\x22\x21\x1B" + "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF" + "\x25\x24\x20\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x26\x24\x20" + "\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x25\x24\x20" + "\xFF\x25\x25\x20\xFF\x22\x21\x1B\xFF" + "\x26\x24\x20\xFF\x22\x21\x1B\xFF\x22\x22\x1C\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF\x24\x23\x1E" + "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF\x29\x28\x23" + "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B" + "\xFF\x25\x25\x20\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x29\x28\x23" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x23\x21\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x24\x23\x1E" + "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF" + "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x27\x25\x20\xFF\x22\x21\x1B\xFF\x27\x25\x21\xFF\x23\x22\x1C" + "\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF" + "\x23\x22\x1D\xFF\x23\x22\x1D\xFF\x25\x24\x1F\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x28\x26\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x25\x24\x1F" + "\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF" + "\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x24\x23\x1E\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x28\x26\x21\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x28\x26\x21" + "\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF" + "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF" + "\x28\x26\x21\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x25\x20\xFF\x23\x22\x1E" + "\xFF\x25\x23\x1E\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x23\x1E\xFF\x24\x23\x1E\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x26\x25\x20\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF" + "\x23\x22\x1C\xFF\x28\x26\x21\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x23\x1E" + "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x27\x25\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x26\x25\x20\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E" + "\xFF\x24\x23\x1E\xFF\x25\x24\x1F\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x26\x26\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x22\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x27\x25\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C" + "\xFF\x26\x25\x21\xFF\x23\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x26\x25\x20\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B" + "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF" + "\x26\x25\x21\xFF\x29\x27\x21\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x25\x24\x20" + "\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x26\x25\x20" + "\xFF\x26\x26\x20\xFF\x22\x21\x1B\xFF" + "\x26\x24\x20\xFF\x22\x21\x1B\xFF\x22\x22\x1C\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF\x24\x23\x1E" + "\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x29\x28\x23" + "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF\x22\x21\x1B" + "\xFF\x26\x26\x21\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x29\x28\x23" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x27\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x1F" + "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF" + "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x27\x25\x20\xFF\x22\x21\x1B\xFF\x27\x25\x21\xFF\x4F\x4A\x3D" + "\xFF\x19\x18\x15\xFF\x1A\x19\x15\xFF" + "\x1D\x1B\x17\xFF\x1D\x1B\x17\xFF\x1E\x1C\x18\xFF\x1F\x1D\x18\xFF\x1D\x1B\x17\xFF\x1F\x1D\x19" + "\xFF\x21\x1E\x1A\xFF\x22\x21\x1C\xFF" + "\x24\x22\x1D\xFF\x25\x22\x1C\xFF\x23\x22\x1D\xFF\x25\x22\x1D\xFF\x27\x24\x1F\xFF\x26\x23\x1F" + "\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF" + "\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x24\x23\x1D\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x27\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF" + "\x25\x24\x1E\xFF\x29\x26\x21\xFF\x28\x25\x1F\xFF\x27\x25\x1F\xFF\x28\x26\x21\xFF\x28\x25\x20" + "\xFF\x26\x24\x1F\xFF\x28\x27\x21\xFF" + "\x28\x25\x20\xFF\x28\x25\x1F\xFF\x27\x24\x1F\xFF\x28\x26\x1F\xFF\x25\x24\x1E\xFF\x26\x25\x1F" + "\xFF\x26\x25\x1F\xFF\x28\x26\x1F\xFF" + "\x27\x24\x1F\xFF\x29\x26\x20\xFF\x27\x25\x1F\xFF\x25\x25\x1F\xFF\x27\x25\x1F\xFF\x27\x25\x1F" + "\xFF\x27\x25\x1F\xFF\x27\x25\x20\xFF" + "\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x24\x24\x1E\xFF\x29\x26\x21\xFF\x28\x26\x1F\xFF\x26\x25\x1F" + "\xFF\x25\x23\x1E\xFF\x26\x25\x1D\xFF" + "\x26\x23\x1E\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x27\x25\x20\xFF\x25\x24\x1E\xFF\x25\x24\x1E" + "\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF" + "\x26\x24\x1F\xFF\x28\x25\x20\xFF\x2A\x26\x21\xFF\x27\x26\x1F\xFF\x27\x24\x1F\xFF\x27\x24\x1F" + "\xFF\x28\x26\x20\xFF\x28\x25\x20\xFF" + "\x25\x23\x1E\xFF\x27\x25\x1F\xFF\x29\x26\x20\xFF\x24\x22\x1D\xFF\x26\x25\x20\xFF\x25\x24\x1F" + "\xFF\x26\x23\x1E\xFF\x26\x24\x1F\xFF" + "\x26\x24\x1F\xFF\x26\x25\x20\xFF\x26\x25\x20\xFF\x25\x23\x1E\xFF\x27\x26\x1F\xFF\x26\x24\x1E" + "\xFF\x24\x22\x1D\xFF\x26\x23\x1E\xFF" + "\x26\x24\x1E\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x26\x23\x1E" + "\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF" + "\x26\x22\x1D\xFF\x24\x21\x1C\xFF\x25\x23\x1E\xFF\x29\x28\x22\xFF\x25\x22\x1C\xFF\x27\x25\x1F" + "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF" + "\x27\x23\x1E\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x25\x23\x1D\xFF\x25\x22\x1D\xFF\x24\x22\x1D" + "\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF" + "\x29\x26\x20\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x25\x24\x1E\xFF\x26\x24\x1F" + "\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF" + "\x26\x25\x1F\xFF\x28\x25\x20\xFF\x25\x23\x1E\xFF\x25\x23\x1D\xFF\x28\x2B\x23\xFF\x35\x57\x43" + "\xFF\x4E\xB1\x84\xFF\x57\xCD\x98\xFF" + "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x51\xBB\x8A\xFF\x36\x60\x49\xFF\x25\x2A\x23\xFF\x27\x25\x1F\xFF\x26\x23\x1E" + "\xFF\x25\x22\x1D\xFF\x25\x23\x1E\xFF" + "\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x24\x21\x1D\xFF\x27\x24\x1F\xFF\x25\x25\x1F\xFF\x25\x23\x1E" + "\xFF\x25\x23\x1E\xFF\x26\x24\x1E\xFF" + "\x28\x2A\x23\xFF\x33\x50\x3D\xFF\x4E\xAC\x80\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x52\xBF\x8D\xFF\x34\x58\x43\xFF\x26\x28\x21\xFF\x24\x23\x1E" + "\xFF\x25\x23\x1E\xFF\x27\x24\x1F\xFF" + "\x22\x22\x1C\xFF\x22\x22\x1B\xFF\x25\x22\x1E\xFF\x27\x24\x1F\xFF\x24\x23\x1D\xFF\x26\x24\x1F" + "\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF" + "\x25\x22\x1D\xFF\x25\x23\x1E\xFF\x24\x22\x1E\xFF\x22\x21\x1B\xFF\x25\x22\x1D\xFF\x24\x23\x1E" + "\xFF\x23\x22\x1C\xFF\x24\x21\x1C\xFF" + "\x24\x23\x1D\xFF\x23\x21\x1C\xFF\x25\x23\x1E\xFF\x27\x24\x1F\xFF\x22\x22\x1C\xFF\x23\x23\x1D" + "\xFF\x24\x23\x1E\xFF\x25\x23\x1E\xFF" + "\x23\x21\x1C\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF\x23\x22\x1C\xFF\x26\x25\x1F\xFF\x23\x22\x1D" + "\xFF\x21\x21\x1C\xFF\x25\x23\x1E\xFF" + "\x26\x25\x1F\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF\x25\x22\x1D\xFF\x24\x23\x1D\xFF\x22\x21\x1C" + "\xFF\x25\x22\x1D\xFF\x23\x21\x1B\xFF" + "\x25\x22\x1D\xFF\x24\x21\x1C\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x24\x22\x1D\xFF\x24\x23\x1D" + "\xFF\x23\x22\x1C\xFF\x23\x22\x1D\xFF" + "\x24\x22\x1D\xFF\x25\x23\x1E\xFF\x22\x21\x1C\xFF\x23\x22\x1D\xFF\x25\x22\x1D\xFF\x24\x23\x1D" + "\xFF\x23\x22\x1C\xFF\x24\x22\x1D\xFF" + "\x24\x23\x1D\xFF\x24\x23\x1D\xFF\x25\x22\x1D\xFF\x23\x22\x1D\xFF\x25\x23\x1E\xFF\x25\x24\x1D" + "\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF" + "\x22\x22\x1D\xFF\x23\x22\x1D\xFF\x24\x23\x1D\xFF\x24\x22\x1E\xFF\x24\x23\x1E\xFF\x22\x22\x1C" + "\xFF\x24\x22\x1D\xFF\x23\x21\x1C\xFF" + "\x26\x23\x1E\xFF\x24\x23\x1E\xFF\x23\x21\x1C\xFF\x23\x22\x1C\xFF\x24\x22\x1D\xFF\x25\x22\x1D" + "\xFF\x25\x23\x1D\xFF\x25\x23\x1E\xFF" + "\x25\x23\x1E\xFF\x24\x22\x1E\xFF\x24\x23\x1E\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x25\x23\x1E" + "\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF" + "\x25\x23\x1D\xFF\x25\x23\x1D\xFF\x23\x22\x1C\xFF\x25\x22\x1D\xFF\x24\x23\x1D\xFF\x24\x22\x1C" + "\xFF\x22\x20\x1B\xFF\x23\x21\x1C\xFF" + "\x22\x22\x1C\xFF\x24\x21\x1D\xFF\x23\x22\x1D\xFF\x25\x22\x1D\xFF\x23\x22\x1C\xFF\x25\x23\x1D" + "\xFF\x24\x21\x1C\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1C\xFF\x26\x23\x1E\xFF\x22\x21\x1C\xFF\x22\x20\x1B\xFF\x24\x21\x1C\xFF\x23\x22\x1C" + "\xFF\x23\x22\x1C\xFF\x22\x22\x1D\xFF" + "\x24\x21\x1C\xFF\x24\x21\x1C\xFF\x23\x20\x1B\xFF\x20\x20\x19\xFF\x26\x23\x1E\xFF\x25\x23\x1C" + "\xFF\x23\x22\x1C\xFF\x21\x22\x1B\xFF" + "\x22\x22\x1D\xFF\x27\x2C\x24\xFF\x26\x2A\x23\xFF\x23\x24\x1C\xFF\x23\x24\x1E\xFF\x23\x22\x1D" + "\xFF\x24\x23\x1D\xFF\x22\x22\x1B\xFF" + "\x24\x22\x1D\xFF\x21\x1F\x1A\xFF\x22\x20\x1B\xFF\x21\x20\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x1E" + "\xFF\x23\x22\x1D\xFF\x1F\x1F\x19\xFF" + "\x22\x21\x1B\xFF\x23\x20\x1B\xFF\x24\x22\x1D\xFF\x24\x23\x1D\xFF\x28\x25\x1E\xFF\x25\x22\x1D" + "\xFF\x23\x22\x1D\xFF\x1F\x1E\x19\xFF" + "\x22\x22\x1C\xFF\x20\x1F\x1B\xFF\x22\x20\x1C\xFF\x20\x20\x1B\xFF\x21\x1F\x1B\xFF\x22\x21\x1C" + "\xFF\x21\x20\x1B\xFF\x24\x22\x1D\xFF" + "\x26\x23\x1E\xFF\x26\x23\x1F\xFF\x24\x22\x1D\xFF\x22\x21\x1B\xFF\x24\x23\x1D\xFF\x23\x22\x1D" + "\xFF\x23\x22\x1C\xFF\x25\x24\x1D\xFF" + "\x14\x12\x0F\xFF\x14\x13\x0F\xFF\x13\x12\x0F\xFF\x18\x17\x13\xFF\x13\x12\x0E\xFF\x14\x12\x10" + "\xFF\x16\x13\x10\xFF\x14\x14\x0E\xFF" + "\x12\x10\x0E\xFF\x0F\x0E\x0B\xFF\x11\x10\x0D\xFF\x15\x13\x11\xFF\x16\x14\x10\xFF\x14\x12\x10" + "\xFF\x15\x14\x10\xFF\x14\x13\x10\xFF" + "\x14\x13\x10\xFF\x11\x10\x0F\xFF\x14\x14\x10\xFF\x15\x14\x10\xFF\x16\x15\x11\xFF\x15\x14\x10" + "\xFF\x19\x18\x14\xFF\x15\x14\x10\xFF" + "\x12\x10\x0E\xFF\x10\x10\x0C\xFF\x10\x0F\x0D\xFF\x13\x12\x0F\xFF\x16\x15\x11\xFF\x14\x12\x10" + "\xFF\x14\x13\x10\xFF\x14\x13\x10\xFF" + "\x12\x10\x0F\xFF\x13\x12\x0F\xFF\x15\x13\x11\xFF\x14\x13\x0F\xFF\x14\x13\x10\xFF\x14\x13\x10" + "\xFF\x13\x11\x0F\xFF\x15\x13\x11\xFF" + "\x15\x14\x11\xFF\x13\x12\x0E\xFF\x12\x11\x0E\xFF\x12\x10\x0E\xFF\x12\x11\x0D\xFF\x14\x13\x0F" + "\xFF\x14\x14\x0F\xFF\x12\x11\x0D\xFF" + "\x12\x11\x0E\xFF\x15\x13\x11\xFF\x15\x14\x11\xFF\x15\x14\x11\xFF\x12\x12\x0F\xFF\x14\x14\x0F" + "\xFF\x14\x13\x11\xFF\x14\x13\x10\xFF" + "\x17\x16\x12\xFF\x15\x14\x10\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x14\x12\x10\xFF\x14\x12\x10" + "\xFF\x15\x14\x10\xFF\x13\x12\x0E\xFF" + "\x14\x13\x0F\xFF\x13\x12\x0E\xFF\x11\x10\x0D\xFF\x11\x10\x0C\xFF\x15\x14\x10\xFF\x17\x16\x12" + "\xFF\x16\x14\x11\xFF\x18\x17\x13\xFF" + "\x13\x11\x10\xFF\x14\x12\x10\xFF\x16\x15\x11\xFF\x14\x13\x0F\xFF\x12\x12\x10\xFF\x15\x13\x10" + "\xFF\x12\x12\x0F\xFF\x0F\x0F\x0D\xFF" + "\x13\x11\x10\xFF\x14\x13\x10\xFF\x17\x14\x11\xFF\x15\x14\x10\xFF\x13\x12\x0E\xFF\x12\x11\x0F" + "\xFF\x14\x13\x0F\xFF\x13\x11\x10\xFF" + "\x15\x14\x11\xFF\x16\x15\x11\xFF\x19\x18\x14\xFF\x15\x14\x11\xFF\x13\x11\x0E\xFF\x13\x11\x10" + "\xFF\x14\x13\x11\xFF\x12\x11\x0F\xFF" + "\x17\x16\x12\xFF\x19\x18\x14\xFF\x18\x16\x12\xFF\x18\x16\x13\xFF\x14\x13\x11\xFF\x14\x13\x10" + "\xFF\x17\x15\x14\xFF\x15\x14\x10\xFF" + "\x18\x17\x13\xFF\x18\x17\x12\xFF\x18\x17\x13\xFF\x1A\x19\x15\xFF\x14\x13\x0F\xFF\x16\x14\x12" + "\xFF\x17\x16\x12\xFF\x14\x13\x0F\xFF" + "\x14\x12\x11\xFF\x18\x17\x13\xFF\x15\x14\x11\xFF\x15\x13\x11\xFF\x14\x13\x0F\xFF\x19\x16\x12" + "\xFF\x1A\x19\x15\xFF\x17\x16\x12\xFF" + "\x15\x13\x11\xFF\x14\x13\x0F\xFF\x15\x14\x10\xFF\x14\x13\x10\xFF\x18\x17\x12\xFF\x15\x14\x11" + "\xFF\x16\x14\x11\xFF\x17\x16\x12\xFF" + "\x19\x17\x13\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x19\x18\x13\xFF\x17\x16\x12\xFF\x18\x17\x13" + "\xFF\x18\x18\x14\xFF\x17\x16\x12\xFF" + "\x15\x14\x10\xFF\x17\x16\x12\xFF\x18\x17\x13\xFF\x1B\x1A\x16\xFF\x1B\x1A\x16\xFF\x18\x17\x14" + "\xFF\x18\x16\x13\xFF\x1B\x19\x15\xFF" + "\x17\x15\x11\xFF\x14\x15\x11\xFF\x15\x15\x11\xFF\x15\x14\x10\xFF\x17\x16\x12\xFF\x17\x16\x12" + "\xFF\x17\x16\x12\xFF\x1B\x1A\x16\xFF" + "\x19\x18\x14\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x18\x17\x12\xFF\x14\x13\x0F\xFF\x16\x15\x11" + "\xFF\x17\x16\x12\xFF\x17\x15\x11\xFF" + "\x19\x18\x14\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x16\x15\x11" + "\xFF\x16\x14\x11\xFF\x1B\x1A\x16\xFF" + "\x1A\x19\x15\xFF\x1A\x19\x16\xFF\x19\x19\x15\xFF\x17\x15\x13\xFF\x19\x18\x14\xFF\x19\x18\x14" + "\xFF\x16\x15\x11\xFF\x1A\x19\x15\xFF" + "\x1F\x1D\x19\xFF\x19\x18\x14\xFF\x1B\x19\x15\xFF\x1B\x1A\x15\xFF\x18\x17\x13\xFF\x1A\x18\x14" + "\xFF\x19\x18\x14\xFF\x19\x17\x13\xFF" + "\x1B\x1A\x16\xFF\x19\x18\x14\xFF\x1A\x19\x15\xFF\x1A\x19\x15\xFF\x1D\x1C\x18\xFF\x19\x18\x14" + "\xFF\x18\x17\x14\xFF\x18\x16\x13\xFF" + "\x19\x17\x13\xFF\x1D\x1A\x16\xFF\x1A\x19\x15\xFF\x17\x16\x12\xFF\x1A\x18\x14\xFF\x19\x18\x14" + "\xFF\x18\x17\x14\xFF\x1A\x19\x15\xFF" + "\x19\x18\x14\xFF\x19\x18\x13\xFF\x19\x17\x13\xFF\x19\x17\x13\xFF\x1C\x1A\x16\xFF\x1B\x19\x15" + "\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF" + "\x1A\x18\x14\xFF\x1C\x1A\x16\xFF\x19\x17\x13\xFF\x17\x16\x12\xFF\x1B\x1A\x16\xFF\x1E\x1B\x17" + "\xFF\x1A\x17\x14\xFF\x19\x18\x14\xFF" + "\x1B\x1A\x16\xFF\x19\x18\x14\xFF\x1B\x19\x15\xFF\x1D\x1A\x16\xFF\x1C\x1B\x17\xFF\x1B\x1A\x16" + "\xFF\x1C\x1B\x17\xFF\x1C\x1B\x16\xFF" + "\x1D\x1C\x18\xFF\x18\x16\x12\xFF\x19\x17\x15\xFF\x19\x18\x15\xFF\x1A\x19\x15\xFF\x1C\x1A\x16" + "\xFF\x1E\x1C\x18\xFF\x20\x1F\x1A\xFF" + "\x18\x17\x13\xFF\x18\x16\x12\xFF\x1B\x19\x14\xFF\x1D\x1D\x17\xFF\x19\x1A\x15\xFF\x1F\x1E\x19" + "\xFF\x1F\x1D\x19\xFF\x20\x1E\x19\xFF" + "\x1A\x19\x14\xFF\x1A\x18\x14\xFF\x1A\x19\x15\xFF\x1D\x1B\x17\xFF\x20\x1D\x1A\xFF\x1F\x1F\x19" + "\xFF\x1E\x1D\x19\xFF\x1B\x19\x16\xFF" + "\x1C\x1B\x16\xFF\x1B\x1B\x16\xFF\x1D\x1C\x17\xFF\x1F\x1D\x18\xFF\x1B\x1B\x16\xFF\x1A\x19\x15" + "\xFF\x1C\x1A\x16\xFF\x1D\x1B\x17\xFF" + "\x1D\x1C\x16\xFF\x19\x18\x14\xFF\x1A\x1A\x15\xFF\x1D\x1D\x17\xFF\x21\x1E\x1A\xFF\x1D\x1D\x18" + "\xFF\x1F\x1E\x19\xFF\x1B\x1A\x16\xFF" + "\x1C\x1B\x16\xFF\x1D\x1C\x18\xFF\x1C\x1B\x16\xFF\x1A\x19\x15\xFF\x1A\x1A\x15\xFF\x1D\x1C\x18" + "\xFF\x1F\x1D\x19\xFF\x1F\x1C\x18\xFF" + "\x1D\x1C\x16\xFF\x1E\x1D\x19\xFF\x1D\x1C\x18\xFF\x1D\x1A\x16\xFF\x1E\x1F\x19\xFF\x1F\x1E\x1A" + "\xFF\x1E\x1C\x19\xFF\x20\x1E\x1A\xFF" + "\x1E\x1D\x18\xFF\x1E\x1C\x16\xFF\x1C\x1C\x17\xFF\x1D\x1D\x17\xFF\x1B\x1A\x16\xFF\x1C\x1A\x16" + "\xFF\x1E\x1C\x18\xFF\x22\x20\x1B\xFF" + "\x1F\x1D\x18\xFF\x1D\x1C\x18\xFF\x1F\x1E\x19\xFF\x20\x1E\x1A\xFF\x1E\x1E\x18\xFF\x1F\x1D\x19" + "\xFF\x1E\x1D\x18\xFF\x1D\x1C\x17\xFF" + "\x1F\x1E\x1A\xFF\x22\x21\x1C\xFF\x21\x20\x1A\xFF\x1E\x1E\x18\xFF\x1C\x1A\x16\xFF\x1F\x1D\x19" + "\xFF\x21\x1F\x1A\xFF\x23\x20\x1B\xFF" + "\x1F\x1F\x1A\xFF\x22\x21\x1C\xFF\x21\x20\x1B\xFF\x22\x22\x1C\xFF\x1C\x1C\x18\xFF\x1D\x1D\x19" + "\xFF\x22\x1F\x1A\xFF\x1F\x1E\x19\xFF" + "\x1A\x1A\x15\xFF\x1C\x1B\x16\xFF\x1D\x1A\x17\xFF\x1C\x1B\x18\xFF\x1F\x1E\x19\xFF\x1C\x1B\x16" + "\xFF\x1B\x1A\x16\xFF\x1E\x1B\x17\xFF" + "\x1A\x1B\x17\xFF\x1D\x1B\x16\xFF\x1C\x1B\x17\xFF\x19\x18\x15\xFF\x17\x16\x13\xFF\x17\x16\x13" + "\xFF\x19\x17\x14\xFF\x19\x19\x15\xFF" + "\x16\x16\x12\xFF\x15\x15\x11\xFF\x50\x4C\x40\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x21\x20\x1A" + "\xFF\x21\x20\x1B\xFF\x24\x22\x1D\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x28\x27\x21" + "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x23\x22\x1D\xFF\x21\x20\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x23\x21\x1C\xFF\x22\x22\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF" + "\x23\x22\x1C\xFF\x21\x20\x1B\xFF\x22\x20\x1B\xFF\x23\x21\x1C\xFF\x23\x21\x1C\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF" + "\x21\x20\x1A\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1D\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1D\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x29\x28\x23" + "\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF" + "\x26\x25\x20\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x28\x26\x21" + "\xFF\x23\x21\x1C\xFF\x23\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x21\x20\x1A\xFF\x21\x21\x1B\xFF" + "\x25\x24\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x23\x23\x1E\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF" + "\x23\x21\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x25\x24\x20\xFF\x22\x21\x1B" + "\xFF\x22\x20\x1B\xFF\x22\x21\x1B\xFF" + "\x24\x22\x1E\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x26\x25\x20\xFF\x22\x20\x1B\xFF\x26\x25\x20" + "\xFF\x29\x28\x23\xFF\x26\x25\x20\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x29\x28\x23\xFF\x22\x21\x1B\xFF" + "\x23\x21\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1D\xFF\x22\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF" + "\x24\x23\x1E\xFF\x22\x22\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x24\x23\x1D\xFF\x22\x21\x1B" + "\xFF\x24\x22\x1D\xFF\x24\x22\x1D\xFF" + "\x22\x20\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x21\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF" + "\x26\x24\x20\xFF\x27\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x28\x26\x21" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF" + "\x25\x24\x20\xFF\x25\x24\x1F\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x20" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x28\x26\x21\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x25\x25\x20\xFF\x22\x21\x1B" + "\xFF\x29\x28\x23\xFF\x25\x24\x1F\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x2A\x29\x23\xFF\x26\x25\x20\xFF\x22\x20\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF\x23\x21\x1C" + "\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF" + "\x23\x22\x1C\xFF\x27\x25\x21\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x26\x25\x20\xFF\x26\x24\x20\xFF" + "\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x27\x25\x20\xFF\x25\x24\x20\xFF" + "\x27\x25\x20\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF" + "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x22\x21\x1B\xFF\x29\x28\x22" + "\xFF\x27\x25\x20\xFF\x23\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1E\xFF\x22\x21\x1B\xFF" + "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x29\x28\x23" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x27\x25\x20\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x28\x26\x21" + "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF" + "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x27\x25\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x26\x24\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x23\x23\x1E\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF" + "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x26\x25\x20\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x25\x24\x1E\xFF\x27\x26\x20\xFF\x22\x21\x1B\xFF\x27\x25\x20" + "\xFF\x29\x28\x23\xFF\x26\x25\x21\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x29\x28\x23\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF" + "\x24\x23\x1E\xFF\x22\x22\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B" + "\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x29\x26\x21\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF" + "\x26\x25\x20\xFF\x26\x26\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x28\x26\x21" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF" + "\x26\x25\x20\xFF\x25\x24\x1F\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x25\x20" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x28\x26\x21\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x22\x21\x1B" + "\xFF\x29\x28\x23\xFF\x25\x24\x1F\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x2A\x29\x23\xFF\x26\x25\x20\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x27\x25\x21\xFF\x4F\x4A\x3D" + "\xFF\x19\x18\x15\xFF\x1B\x1A\x16\xFF" + "\x1D\x1B\x16\xFF\x1D\x1D\x17\xFF\x1D\x1C\x18\xFF\x1E\x1E\x19\xFF\x20\x1D\x19\xFF\x22\x1F\x1B" + "\xFF\x22\x20\x1B\xFF\x24\x21\x1D\xFF" + "\x22\x1F\x1B\xFF\x24\x22\x1D\xFF\x25\x22\x1E\xFF\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x23\x23\x1D" + "\xFF\x25\x22\x1D\xFF\x27\x23\x1E\xFF" + "\x26\x23\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x25\x25\x1F\xFF\x28\x27\x20" + "\xFF\x28\x26\x21\xFF\x27\x24\x1F\xFF" + "\x25\x24\x1E\xFF\x27\x24\x1F\xFF\x28\x25\x1F\xFF\x28\x26\x21\xFF\x27\x25\x20\xFF\x26\x24\x1F" + "\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF" + "\x27\x26\x20\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF\x27\x25\x20\xFF\x28\x25\x20" + "\xFF\x24\x23\x1E\xFF\x26\x24\x1E\xFF" + "\x25\x23\x1E\xFF\x27\x26\x20\xFF\x27\x25\x1F\xFF\x29\x27\x20\xFF\x27\x24\x1F\xFF\x28\x25\x20" + "\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF" + "\x28\x25\x20\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x24\x23\x1E" + "\xFF\x25\x23\x1E\xFF\x27\x25\x1F\xFF" + "\x27\x25\x1E\xFF\x27\x25\x20\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF\x25\x26\x1E\xFF\x26\x25\x1F" + "\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF" + "\x28\x26\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x23\x22\x1D\xFF\x28\x25\x20\xFF\x29\x27\x20" + "\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF" + "\x23\x22\x1C\xFF\x28\x25\x1F\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x25\x1E" + "\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF" + "\x27\x24\x1F\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x26\x25\x20\xFF\x26\x24\x1F" + "\xFF\x23\x23\x1E\xFF\x26\x23\x1E\xFF" + "\x24\x23\x1E\xFF\x25\x24\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x27\x26\x1F" + "\xFF\x26\x23\x1E\xFF\x25\x22\x1D\xFF" + "\x26\x23\x1E\xFF\x26\x24\x1E\xFF\x27\x26\x20\xFF\x28\x26\x21\xFF\x25\x22\x1D\xFF\x27\x26\x1F" + "\xFF\x27\x25\x1E\xFF\x24\x23\x1D\xFF" + "\x24\x22\x1E\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x25\x26\x1D\xFF\x29\x26\x21\xFF\x26\x24\x1F" + "\xFF\x26\x24\x1F\xFF\x26\x25\x1E\xFF" + "\x27\x24\x1F\xFF\x25\x23\x1D\xFF\x27\x25\x1E\xFF\x27\x27\x21\xFF\x27\x24\x21\xFF\x28\x26\x1F" + "\xFF\x26\x23\x1E\xFF\x24\x23\x1E\xFF" + "\x24\x23\x1E\xFF\x24\x22\x1D\xFF\x23\x21\x1D\xFF\x26\x23\x1E\xFF\x29\x29\x22\xFF\x2E\x3E\x31" + "\xFF\x48\x9C\x74\xFF\x56\xCC\x97\xFF" + "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x56\xCA\x96\xFF\x3F\x7F\x5F\xFF\x28\x33\x28\xFF\x27\x27\x20\xFF\x27\x24\x1F" + "\xFF\x25\x23\x1E\xFF\x24\x23\x1E\xFF" + "\x25\x22\x1D\xFF\x23\x21\x1C\xFF\x23\x21\x1C\xFF\x27\x24\x1F\xFF\x25\x24\x1E\xFF\x24\x24\x1E" + "\xFF\x23\x22\x1E\xFF\x24\x23\x1F\xFF" + "\x25\x27\x1F\xFF\x2B\x38\x2C\xFF\x46\x94\x6F\xFF\x57\xCB\x97\xFF\x57\xCD\x98\xFF\x56\xCD\x98" + "\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCC\x97\xFF\x3E\x7D\x5E\xFF\x28\x2E\x26\xFF\x24\x24\x1F" + "\xFF\x23\x22\x1D\xFF\x26\x23\x1E\xFF" + "\x27\x25\x20\xFF\x26\x24\x1E\xFF\x24\x23\x1D\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x22\x20\x1C" + "\xFF\x24\x22\x1D\xFF\x24\x24\x1E\xFF" + "\x25\x22\x1E\xFF\x26\x23\x1E\xFF\x25\x22\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E" + "\xFF\x22\x21\x1B\xFF\x27\x24\x1F\xFF" + "\x27\x24\x1F\xFF\x24\x21\x1D\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x24\x22\x1C\xFF\x24\x23\x1D" + "\xFF\x23\x23\x1D\xFF\x25\x22\x1D\xFF" + "\x21\x21\x1B\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x24\x21\x1D\xFF\x23\x22\x1C\xFF\x25\x23\x1E" + "\xFF\x23\x22\x1C\xFF\x25\x22\x1D\xFF" + "\x26\x25\x1F\xFF\x26\x23\x1F\xFF\x23\x22\x1D\xFF\x24\x21\x1C\xFF\x26\x24\x1F\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1C\xFF\x22\x22\x1C\xFF" + "\x24\x22\x1D\xFF\x23\x21\x1C\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x22\x22\x1B\xFF\x25\x23\x1C" + "\xFF\x26\x24\x1E\xFF\x22\x21\x1C\xFF" + "\x24\x22\x1D\xFF\x23\x22\x1C\xFF\x21\x21\x1C\xFF\x24\x22\x1D\xFF\x23\x22\x1C\xFF\x25\x25\x1E" + "\xFF\x24\x23\x1E\xFF\x26\x24\x1D\xFF" + "\x23\x20\x1C\xFF\x23\x21\x1C\xFF\x24\x21\x1C\xFF\x25\x22\x1E\xFF\x23\x23\x1D\xFF\x26\x24\x1E" + "\xFF\x24\x23\x1D\xFF\x24\x22\x1E\xFF" + "\x23\x23\x1D\xFF\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x23\x22\x1D\xFF\x20\x1F\x1A\xFF\x21\x20\x1B" + "\xFF\x25\x21\x1D\xFF\x25\x22\x1E\xFF" + "\x21\x20\x1B\xFF\x22\x20\x1C\xFF\x24\x22\x1D\xFF\x23\x21\x1C\xFF\x23\x20\x1C\xFF\x25\x22\x1D" + "\xFF\x23\x21\x1C\xFF\x21\x22\x1A\xFF" + "\x24\x21\x1C\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF\x25\x22\x1D\xFF\x24\x23\x1D\xFF\x25\x24\x1E" + "\xFF\x25\x24\x1E\xFF\x25\x22\x1D\xFF" + "\x27\x24\x1F\xFF\x25\x22\x1D\xFF\x23\x22\x1C\xFF\x24\x23\x1D\xFF\x22\x22\x1A\xFF\x25\x22\x1D" + "\xFF\x26\x22\x1D\xFF\x25\x22\x1D\xFF" + "\x23\x21\x1C\xFF\x22\x21\x1C\xFF\x24\x21\x1C\xFF\x24\x21\x1C\xFF\x22\x22\x1C\xFF\x22\x20\x1B" + "\xFF\x22\x20\x1B\xFF\x23\x23\x1D\xFF" + "\x26\x24\x1F\xFF\x22\x22\x1C\xFF\x21\x20\x1B\xFF\x22\x1F\x1A\xFF\x21\x20\x1B\xFF\x21\x20\x1B" + "\xFF\x22\x22\x1C\xFF\x23\x22\x1C\xFF" + "\x24\x21\x1C\xFF\x23\x20\x1B\xFF\x24\x21\x1C\xFF\x22\x20\x1B\xFF\x22\x21\x1B\xFF\x27\x24\x1F" + "\xFF\x28\x25\x1F\xFF\x25\x23\x1D\xFF" + "\x21\x21\x1B\xFF\x24\x23\x1D\xFF\x26\x23\x1E\xFF\x26\x24\x1C\xFF\x26\x26\x20\xFF\x28\x25\x20" + "\xFF\x23\x22\x1D\xFF\x20\x20\x1B\xFF" + "\x23\x22\x1D\xFF\x23\x21\x1C\xFF\x21\x21\x1B\xFF\x21\x21\x1C\xFF\x25\x23\x1D\xFF\x24\x22\x1D" + "\xFF\x23\x21\x1C\xFF\x20\x20\x1A\xFF" + "\x22\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x23\x1D\xFF\x22\x21\x1C\xFF\x25\x23\x1D\xFF\x23\x21\x1D" + "\xFF\x22\x20\x1B\xFF\x22\x22\x1D\xFF" + "\x25\x22\x1D\xFF\x22\x21\x1C\xFF\x21\x20\x1B\xFF\x20\x20\x1A\xFF\x21\x21\x1C\xFF\x21\x20\x1B" + "\xFF\x20\x20\x1B\xFF\x25\x22\x1D\xFF" + "\x22\x20\x1C\xFF\x26\x24\x20\xFF\x23\x23\x1D\xFF\x24\x23\x1D\xFF\x22\x1F\x1B\xFF\x24\x22\x1E" + "\xFF\x25\x22\x1E\xFF\x24\x23\x1D\xFF" + "\x0F\x0F\x0D\xFF\x13\x12\x0F\xFF\x12\x11\x0E\xFF\x12\x10\x0E\xFF\x11\x10\x0D\xFF\x12\x11\x0E" + "\xFF\x13\x12\x0F\xFF\x11\x0F\x0E\xFF" + "\x12\x11\x0E\xFF\x12\x11\x0F\xFF\x13\x11\x0E\xFF\x12\x11\x0D\xFF\x19\x18\x14\xFF\x16\x15\x12" + "\xFF\x12\x11\x0E\xFF\x11\x10\x0F\xFF" + "\x14\x13\x0F\xFF\x16\x14\x10\xFF\x13\x12\x0E\xFF\x16\x15\x11\xFF\x17\x15\x11\xFF\x13\x13\x0F" + "\xFF\x12\x12\x0E\xFF\x13\x11\x0F\xFF" + "\x13\x12\x0F\xFF\x13\x11\x0F\xFF\x12\x10\x0E\xFF\x16\x14\x11\xFF\x18\x19\x12\xFF\x14\x12\x0F" + "\xFF\x14\x10\x0F\xFF\x12\x10\x0F\xFF" + "\x12\x10\x0F\xFF\x14\x12\x11\xFF\x15\x14\x11\xFF\x18\x18\x12\xFF\x14\x13\x0F\xFF\x13\x12\x0E" + "\xFF\x15\x14\x11\xFF\x14\x12\x11\xFF" + "\x13\x12\x0E\xFF\x13\x12\x0E\xFF\x13\x12\x0E\xFF\x13\x12\x10\xFF\x12\x11\x0F\xFF\x14\x13\x0F" + "\xFF\x15\x14\x10\xFF\x19\x19\x14\xFF" + "\x14\x14\x0F\xFF\x12\x12\x0D\xFF\x12\x10\x0E\xFF\x14\x13\x0F\xFF\x11\x10\x0E\xFF\x13\x13\x10" + "\xFF\x16\x15\x11\xFF\x17\x16\x12\xFF" + "\x10\x0F\x0B\xFF\x11\x10\x0E\xFF\x15\x14\x10\xFF\x14\x12\x10\xFF\x15\x14\x10\xFF\x15\x14\x10" + "\xFF\x14\x13\x0F\xFF\x16\x15\x11\xFF" + "\x13\x12\x0E\xFF\x14\x13\x0F\xFF\x15\x13\x10\xFF\x15\x13\x10\xFF\x14\x13\x0F\xFF\x14\x13\x10" + "\xFF\x13\x12\x0F\xFF\x11\x10\x0C\xFF" + "\x14\x13\x0F\xFF\x14\x13\x10\xFF\x11\x11\x0F\xFF\x12\x11\x0D\xFF\x14\x11\x0D\xFF\x16\x13\x0F" + "\xFF\x15\x13\x12\xFF\x16\x15\x11\xFF" + "\x13\x13\x0F\xFF\x13\x12\x0F\xFF\x16\x15\x12\xFF\x16\x15\x11\xFF\x12\x10\x0F\xFF\x14\x13\x0F" + "\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF" + "\x15\x14\x11\xFF\x13\x12\x0E\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x14\x13\x10\xFF\x17\x16\x12" + "\xFF\x16\x15\x11\xFF\x14\x13\x10\xFF" + "\x15\x14\x11\xFF\x16\x15\x13\xFF\x16\x14\x11\xFF\x17\x14\x10\xFF\x16\x15\x11\xFF\x16\x15\x11" + "\xFF\x17\x16\x12\xFF\x15\x14\x11\xFF" + "\x17\x16\x13\xFF\x11\x10\x0E\xFF\x13\x12\x0E\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x16\x15\x11" + "\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF" + "\x13\x11\x0F\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x16\x15\x12\xFF\x15\x14\x10\xFF\x14\x13\x0E" + "\xFF\x17\x16\x11\xFF\x18\x17\x12\xFF" + "\x16\x15\x11\xFF\x16\x15\x11\xFF\x19\x18\x14\xFF\x16\x15\x11\xFF\x14\x12\x10\xFF\x14\x13\x0F" + "\xFF\x14\x13\x0E\xFF\x14\x13\x0F\xFF" + "\x18\x17\x13\xFF\x16\x16\x12\xFF\x12\x12\x0F\xFF\x14\x13\x0F\xFF\x16\x14\x11\xFF\x15\x13\x11" + "\xFF\x17\x15\x12\xFF\x18\x17\x13\xFF" + "\x18\x17\x13\xFF\x15\x14\x10\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x16\x15\x11\xFF\x13\x13\x0E" + "\xFF\x16\x15\x11\xFF\x16\x14\x10\xFF" + "\x15\x13\x11\xFF\x18\x17\x14\xFF\x1C\x1A\x16\xFF\x17\x15\x11\xFF\x14\x13\x0F\xFF\x18\x16\x12" + "\xFF\x1B\x18\x14\xFF\x17\x15\x11\xFF" + "\x14\x13\x0F\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x19\x18\x14\xFF\x17\x16\x12\xFF\x19\x18\x14" + "\xFF\x1B\x1A\x16\xFF\x1B\x1A\x16\xFF" + "\x17\x16\x13\xFF\x15\x14\x10\xFF\x16\x15\x10\xFF\x1B\x1A\x16\xFF\x17\x16\x12\xFF\x1B\x1A\x16" + "\xFF\x18\x17\x13\xFF\x18\x18\x12\xFF" + "\x1A\x17\x14\xFF\x1A\x18\x14\xFF\x1B\x1A\x15\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x17\x16\x12" + "\xFF\x18\x17\x13\xFF\x1B\x1A\x16\xFF" + "\x19\x17\x13\xFF\x18\x17\x13\xFF\x1A\x18\x14\xFF\x1B\x19\x16\xFF\x17\x16\x12\xFF\x18\x17\x13" + "\xFF\x1B\x1A\x16\xFF\x1B\x1B\x16\xFF" + "\x1B\x19\x15\xFF\x18\x16\x12\xFF\x16\x15\x11\xFF\x18\x17\x15\xFF\x1E\x1E\x18\xFF\x1D\x1C\x18" + "\xFF\x1A\x19\x14\xFF\x19\x18\x13\xFF" + "\x1A\x18\x14\xFF\x1A\x19\x15\xFF\x19\x18\x14\xFF\x19\x18\x14\xFF\x1A\x19\x15\xFF\x18\x17\x13" + "\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF" + "\x19\x18\x14\xFF\x1A\x19\x14\xFF\x1C\x1A\x15\xFF\x1A\x19\x14\xFF\x19\x1A\x15\xFF\x1B\x19\x15" + "\xFF\x1D\x1A\x16\xFF\x1E\x1C\x18\xFF" + "\x1F\x1D\x19\xFF\x1A\x18\x14\xFF\x1A\x18\x14\xFF\x1A\x19\x15\xFF\x1B\x19\x17\xFF\x1A\x19\x14" + "\xFF\x1B\x19\x15\xFF\x1A\x18\x14\xFF" + "\x1B\x1A\x16\xFF\x1B\x1A\x15\xFF\x1D\x1C\x17\xFF\x1F\x1E\x19\xFF\x1E\x1C\x17\xFF\x1B\x1B\x15" + "\xFF\x1C\x1B\x17\xFF\x1B\x19\x15\xFF" + "\x1B\x1A\x16\xFF\x1B\x19\x15\xFF\x19\x18\x14\xFF\x17\x16\x12\xFF\x1B\x1A\x16\xFF\x1C\x1A\x17" + "\xFF\x1D\x1B\x18\xFF\x1B\x1B\x15\xFF" + "\x20\x1E\x19\xFF\x1D\x1B\x17\xFF\x1D\x1C\x18\xFF\x1B\x1A\x16\xFF\x1D\x1B\x17\xFF\x1C\x1B\x17" + "\xFF\x1E\x1D\x19\xFF\x1A\x19\x15\xFF" + "\x19\x18\x14\xFF\x19\x19\x15\xFF\x19\x17\x13\xFF\x19\x18\x14\xFF\x1F\x1E\x19\xFF\x1E\x1C\x18" + "\xFF\x1F\x1D\x19\xFF\x1F\x1F\x1A\xFF" + "\x1D\x1B\x17\xFF\x1C\x1A\x16\xFF\x1A\x18\x16\xFF\x18\x17\x13\xFF\x19\x19\x14\xFF\x1E\x1C\x17" + "\xFF\x1B\x19\x15\xFF\x1C\x1B\x17\xFF" + "\x1F\x1C\x18\xFF\x1B\x1A\x16\xFF\x1D\x1C\x18\xFF\x1A\x19\x16\xFF\x1E\x1B\x17\xFF\x20\x1E\x1A" + "\xFF\x1F\x1E\x1A\xFF\x1C\x1B\x17\xFF" + "\x1C\x19\x15\xFF\x20\x1E\x1A\xFF\x22\x20\x1B\xFF\x1F\x1C\x17\xFF\x1F\x1C\x18\xFF\x1E\x1C\x18" + "\xFF\x1C\x1B\x17\xFF\x1A\x19\x15\xFF" + "\x1C\x1B\x17\xFF\x1E\x1C\x18\xFF\x1C\x1B\x18\xFF\x1C\x1C\x17\xFF\x1F\x1E\x1A\xFF\x1D\x1B\x17" + "\xFF\x1E\x1E\x19\xFF\x1D\x1B\x17\xFF" + "\x20\x1F\x1A\xFF\x1D\x1C\x17\xFF\x1D\x1C\x17\xFF\x20\x1D\x19\xFF\x1F\x1E\x19\xFF\x21\x21\x1A" + "\xFF\x1F\x1D\x18\xFF\x1C\x1C\x18\xFF" + "\x1D\x1D\x18\xFF\x20\x20\x1A\xFF\x21\x21\x1B\xFF\x22\x20\x1B\xFF\x1E\x1E\x19\xFF\x1E\x1E\x19" + "\xFF\x1E\x1D\x18\xFF\x1D\x1C\x18\xFF" + "\x20\x1F\x1A\xFF\x22\x20\x1C\xFF\x21\x1F\x1B\xFF\x1F\x1F\x19\xFF\x1E\x1E\x18\xFF\x1C\x1B\x17" + "\xFF\x1F\x1D\x19\xFF\x1E\x1C\x18\xFF" + "\x1D\x1C\x18\xFF\x1C\x1B\x16\xFF\x1D\x1D\x17\xFF\x1F\x1E\x1A\xFF\x1E\x1F\x17\xFF\x1E\x1D\x18" + "\xFF\x1F\x1E\x19\xFF\x20\x20\x1A\xFF" + "\x1F\x1E\x19\xFF\x1F\x1D\x18\xFF\x1F\x1D\x19\xFF\x1F\x1D\x1A\xFF\x1E\x1C\x17\xFF\x1D\x1B\x17" + "\xFF\x1D\x1C\x18\xFF\x1F\x1E\x19\xFF" + "\x1E\x1C\x18\xFF\x1B\x1B\x16\xFF\x1A\x19\x15\xFF\x1C\x1B\x18\xFF\x1A\x18\x15\xFF\x18\x18\x14" + "\xFF\x17\x17\x13\xFF\x17\x17\x13\xFF" + "\x16\x16\x12\xFF\x15\x14\x11\xFF\x4E\x4A\x3D\xFF\x23\x22\x1D\xFF\x21\x20\x1A\xFF\x21\x20\x1A" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF" + "\x22\x21\x1A\xFF\x26\x25\x20\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x21\x21\x1B\xFF" + "\x28\x27\x21\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x28\x26\x22\xFF\x26\x25\x21" + "\xFF\x28\x26\x21\xFF\x21\x20\x1A\xFF" + "\x28\x26\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x21\x20\x1B\xFF\x22\x21\x1C\xFF\x29\x28\x22" + "\xFF\x23\x22\x1C\xFF\x21\x20\x1A\xFF" + "\x25\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x25\x25\x20\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF" + "\x25\x24\x20\xFF\x28\x26\x21\xFF\x23\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF" + "\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x20\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x20\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x25\x24\x20\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF" + "\x24\x23\x1F\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x26\x25\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x22\x20\x1B\xFF\x25\x24\x20" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF" + "\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x23\x22\x1C\xFF\x22\x22\x1C\xFF\x2A\x29\x23" + "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF" + "\x26\x25\x21\xFF\x23\x22\x1C\xFF\x27\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x28\x26\x21\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x22\x22\x1C\xFF" + "\x24\x23\x1E\xFF\x29\x28\x22\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF" + "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x22\x1C\xFF\x23\x22\x1C\xFF\x28\x26\x22" + "\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x21\x20\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x25\x23\x1E\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x28\x26\x21\xFF\x27\x25\x20\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x24\x23\x1E" + "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF" + "\x25\x23\x1E\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x24\x23\x1E\xFF\x24\x23\x1E" + "\xFF\x29\x28\x23\xFF\x26\x25\x20\xFF" + "\x25\x24\x20\xFF\x26\x25\x20\xFF\x29\x28\x22\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x23\x22\x1C" + "\xFF\x2A\x29\x23\xFF\x25\x24\x20\xFF" + "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x24\x23\x1E\xFF\x2B\x2A\x24\xFF" + "\x26\x25\x21\xFF\x23\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x27\x26\x20\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x29\x28\x22" + "\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF" + "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF" + "\x26\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF" + "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x29\x28\x22\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x28\x26\x22\xFF\x26\x25\x21" + "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF" + "\x28\x26\x21\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x29\x28\x22" + "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF" + "\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x26\x25\x20\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF" + "\x25\x24\x20\xFF\x28\x26\x21\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF" + "\x23\x22\x1C\xFF\x25\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x27\x25\x20\xFF\x26\x24\x20\xFF\x23\x22\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF" + "\x25\x24\x20\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x26\x25\x21\xFF\x23\x22\x1C\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF" + "\x27\x25\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x26\x25\x20" + "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF" + "\x26\x25\x20\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x22\x1C\xFF\x2A\x29\x23" + "\xFF\x22\x21\x1B\xFF\x27\x25\x20\xFF" + "\x27\x25\x21\xFF\x23\x22\x1C\xFF\x27\x25\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF" + "\x28\x26\x21\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF" + "\x24\x23\x1E\xFF\x29\x28\x22\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF" + "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x22\x1C\xFF\x23\x22\x1C\xFF\x29\x27\x22" + "\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x28\x26\x21\xFF\x27\x26\x21\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x24\x23\x1E" + "\xFF\x22\x21\x1B\xFF\x27\x26\x21\xFF" + "\x25\x24\x1F\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x24\x23\x1E\xFF\x24\x23\x1E" + "\xFF\x29\x28\x23\xFF\x26\x25\x20\xFF" + "\x26\x24\x20\xFF\x27\x25\x20\xFF\x29\x28\x22\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x4F\x4A\x3D" + "\xFF\x1A\x19\x15\xFF\x19\x19\x15\xFF" + "\x1B\x1A\x16\xFF\x1D\x1B\x17\xFF\x1D\x1C\x18\xFF\x1D\x1C\x18\xFF\x21\x1E\x19\xFF\x21\x1F\x1B" + "\xFF\x21\x20\x1B\xFF\x21\x21\x1B\xFF" + "\x23\x20\x1C\xFF\x22\x22\x1C\xFF\x21\x21\x1C\xFF\x25\x22\x1D\xFF\x24\x24\x1D\xFF\x25\x24\x1F" + "\xFF\x27\x25\x1F\xFF\x24\x23\x1E\xFF" + "\x24\x23\x1D\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x25\x24\x1F\xFF\x25\x24\x1E" + "\xFF\x23\x23\x1D\xFF\x27\x25\x1F\xFF" + "\x28\x25\x20\xFF\x29\x26\x21\xFF\x25\x24\x1E\xFF\x24\x22\x1D\xFF\x28\x26\x20\xFF\x27\x25\x1F" + "\xFF\x26\x23\x1E\xFF\x27\x24\x1E\xFF" + "\x27\x24\x1F\xFF\x24\x23\x1E\xFF\x26\x24\x1F\xFF\x28\x28\x22\xFF\x28\x25\x20\xFF\x28\x25\x20" + "\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF" + "\x26\x23\x1E\xFF\x29\x27\x20\xFF\x2A\x27\x22\xFF\x29\x26\x1F\xFF\x26\x25\x1F\xFF\x27\x24\x1F" + "\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF" + "\x28\x25\x20\xFF\x27\x25\x1F\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF\x26\x25\x1F\xFF\x26\x24\x1F" + "\xFF\x28\x24\x1F\xFF\x29\x25\x20\xFF" + "\x27\x26\x20\xFF\x23\x23\x1D\xFF\x26\x24\x1E\xFF\x29\x26\x21\xFF\x25\x24\x1E\xFF\x24\x24\x1E" + "\xFF\x23\x24\x1E\xFF\x28\x28\x21\xFF" + "\x28\x25\x20\xFF\x27\x24\x1F\xFF\x25\x23\x1D\xFF\x25\x24\x1E\xFF\x27\x25\x20\xFF\x27\x25\x1F" + "\xFF\x26\x23\x1E\xFF\x27\x25\x20\xFF" + "\x25\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x24\x23\x1E\xFF\x26\x24\x1F\xFF\x26\x25\x20" + "\xFF\x28\x26\x21\xFF\x28\x26\x21\xFF" + "\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x26\x25\x1F\xFF\x27\x25\x20\xFF\x25\x25\x1F\xFF\x24\x24\x1D" + "\xFF\x27\x26\x1E\xFF\x27\x26\x20\xFF" + "\x25\x25\x1F\xFF\x26\x24\x1F\xFF\x25\x22\x1E\xFF\x26\x23\x1E\xFF\x24\x23\x1E\xFF\x26\x24\x1F" + "\xFF\x26\x24\x1F\xFF\x28\x26\x20\xFF" + "\x25\x22\x1E\xFF\x25\x23\x1E\xFF\x27\x25\x20\xFF\x28\x27\x20\xFF\x26\x26\x1E\xFF\x27\x24\x1F" + "\xFF\x26\x23\x1E\xFF\x25\x25\x1F\xFF" + "\x28\x26\x1F\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF\x23\x23\x1D\xFF\x25\x24\x20\xFF\x26\x25\x20" + "\xFF\x29\x25\x20\xFF\x26\x24\x1F\xFF" + "\x24\x22\x1D\xFF\x23\x23\x1D\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x24\x23\x1D" + "\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF" + "\x25\x23\x1E\xFF\x26\x23\x1E\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF\x25\x26\x1F\xFF\x28\x2F\x25" + "\xFF\x3E\x78\x5B\xFF\x54\xC4\x91\xFF" + "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x4C\xA5\x7B\xFF\x30\x46\x37\xFF\x27\x27\x21\xFF\x27\x25\x1F" + "\xFF\x25\x24\x1E\xFF\x26\x23\x1E\xFF" + "\x24\x23\x1C\xFF\x26\x23\x1E\xFF\x24\x22\x1D\xFF\x27\x24\x1F\xFF\x25\x24\x1E\xFF\x29\x26\x21" + "\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF" + "\x27\x26\x21\xFF\x28\x2C\x26\xFF\x39\x6C\x52\xFF\x53\xC1\x8F\xFF\x57\xCD\x98\xFF\x56\xCD\x98" + "\xFF\x56\xCD\x98\xFF\x56\xCD\x97\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCC\x97\xFF\x4F\xB5\x85\xFF\x29\x38\x2D\xFF\x27\x28\x21" + "\xFF\x24\x23\x1E\xFF\x26\x23\x1E\xFF" + "\x24\x24\x1E\xFF\x24\x22\x1D\xFF\x24\x23\x1D\xFF\x25\x25\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E" + "\xFF\x24\x22\x1D\xFF\x22\x21\x1C\xFF" + "\x25\x22\x1D\xFF\x25\x24\x1F\xFF\x24\x23\x1D\xFF\x26\x24\x1E\xFF\x27\x25\x1F\xFF\x25\x23\x1E" + "\xFF\x24\x22\x1E\xFF\x25\x25\x1E\xFF" + "\x26\x24\x1F\xFF\x23\x22\x1D\xFF\x25\x23\x1D\xFF\x25\x22\x1D\xFF\x25\x24\x1E\xFF\x27\x24\x1F" + "\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF" + "\x23\x23\x1D\xFF\x25\x25\x1F\xFF\x27\x26\x21\xFF\x26\x24\x1E\xFF\x24\x23\x1E\xFF\x24\x22\x1C" + "\xFF\x22\x21\x1C\xFF\x26\x23\x1E\xFF" + "\x25\x22\x1D\xFF\x24\x22\x1D\xFF\x26\x24\x1E\xFF\x24\x23\x1D\xFF\x27\x24\x1F\xFF\x26\x24\x1F" + "\xFF\x24\x22\x1D\xFF\x23\x22\x1D\xFF" + "\x24\x21\x1C\xFF\x23\x22\x1C\xFF\x25\x24\x1E\xFF\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x24\x21\x1C" + "\xFF\x26\x24\x1F\xFF\x27\x27\x1E\xFF" + "\x25\x24\x1E\xFF\x26\x23\x1E\xFF\x24\x23\x1D\xFF\x22\x23\x1E\xFF\x22\x21\x1B\xFF\x21\x20\x1C" + "\xFF\x23\x22\x1D\xFF\x24\x22\x1D\xFF" + "\x20\x20\x1A\xFF\x21\x20\x1B\xFF\x24\x22\x1D\xFF\x26\x23\x1E\xFF\x24\x24\x1E\xFF\x25\x25\x1E" + "\xFF\x23\x21\x1C\xFF\x21\x20\x1A\xFF" + "\x21\x20\x1B\xFF\x23\x21\x1C\xFF\x24\x22\x1D\xFF\x24\x21\x1D\xFF\x22\x22\x1C\xFF\x22\x21\x1C" + "\xFF\x22\x21\x1C\xFF\x22\x21\x1C\xFF" + "\x24\x21\x1C\xFF\x21\x20\x1B\xFF\x22\x20\x1B\xFF\x26\x24\x1F\xFF\x22\x20\x1B\xFF\x22\x22\x1C" + "\xFF\x24\x23\x1E\xFF\x21\x20\x1B\xFF" + "\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x23\x22\x1D\xFF\x22\x23\x1E\xFF\x22\x22\x1C\xFF\x24\x23\x1D" + "\xFF\x27\x24\x1F\xFF\x25\x22\x1D\xFF" + "\x25\x21\x1C\xFF\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x24\x21\x1D\xFF\x22\x1E\x1A\xFF\x26\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x25\x22\x1D\xFF" + "\x22\x22\x1C\xFF\x22\x22\x1D\xFF\x21\x21\x1B\xFF\x23\x23\x1C\xFF\x22\x20\x1B\xFF\x23\x22\x1C" + "\xFF\x25\x23\x1D\xFF\x24\x22\x1D\xFF" + "\x26\x23\x1E\xFF\x24\x22\x1D\xFF\x23\x20\x1B\xFF\x21\x1F\x1A\xFF\x23\x23\x1D\xFF\x22\x22\x1C" + "\xFF\x26\x23\x1E\xFF\x26\x25\x1F\xFF" + "\x27\x24\x1F\xFF\x21\x21\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x23\x21\x1C\xFF\x21\x20\x1A" + "\xFF\x22\x20\x1B\xFF\x20\x20\x1B\xFF" + "\x22\x22\x1C\xFF\x23\x20\x1B\xFF\x25\x22\x1D\xFF\x23\x22\x1C\xFF\x22\x22\x1C\xFF\x21\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x23\x20\x1B\xFF" + "\x22\x20\x1B\xFF\x24\x22\x1D\xFF\x23\x22\x1D\xFF\x20\x20\x1A\xFF\x25\x21\x1C\xFF\x24\x23\x1D" + "\xFF\x20\x21\x1B\xFF\x22\x22\x1C\xFF" + "\x22\x21\x1C\xFF\x1F\x1F\x19\xFF\x20\x1F\x1A\xFF\x1F\x20\x1A\xFF\x23\x20\x1C\xFF\x22\x21\x1C" + "\xFF\x23\x22\x1C\xFF\x23\x21\x1C\xFF" + "\x1F\x1F\x1B\xFF\x21\x22\x1D\xFF\x20\x1F\x1B\xFF\x21\x21\x1B\xFF\x21\x1E\x1A\xFF\x22\x20\x1C" + "\xFF\x23\x21\x1C\xFF\x21\x20\x1B\xFF" + "\x23\x22\x1D\xFF\x25\x21\x1D\xFF\x21\x20\x1B\xFF\x20\x1F\x1B\xFF\x22\x20\x1B\xFF\x25\x23\x1D" + "\xFF\x24\x23\x1E\xFF\x24\x23\x1C\xFF" + "\x14\x13\x10\xFF\x12\x11\x0E\xFF\x12\x12\x0E\xFF\x12\x11\x0E\xFF\x10\x10\x0D\xFF\x10\x10\x0C" + "\xFF\x10\x0F\x0B\xFF\x14\x13\x10\xFF" + "\x11\x0F\x0E\xFF\x12\x11\x0E\xFF\x14\x12\x10\xFF\x13\x11\x10\xFF\x14\x13\x0F\xFF\x11\x10\x0E" + "\xFF\x12\x11\x0F\xFF\x10\x0F\x0E\xFF" + "\x15\x14\x0F\xFF\x12\x11\x0D\xFF\x13\x11\x0E\xFF\x16\x15\x11\xFF\x15\x15\x11\xFF\x16\x15\x11" + "\xFF\x13\x12\x10\xFF\x13\x11\x0F\xFF" + "\x0F\x0F\x0D\xFF\x0F\x0E\x0C\xFF\x10\x0E\x0C\xFF\x14\x11\x10\xFF\x14\x13\x0E\xFF\x13\x12\x0E" + "\xFF\x14\x12\x11\xFF\x12\x10\x0E\xFF" + "\x0F\x0E\x0D\xFF\x13\x11\x10\xFF\x13\x12\x0F\xFF\x14\x12\x0F\xFF\x12\x11\x0D\xFF\x14\x12\x0F" + "\xFF\x15\x14\x10\xFF\x13\x11\x10\xFF" + "\x14\x12\x0E\xFF\x13\x12\x0F\xFF\x14\x13\x0F\xFF\x14\x13\x0E\xFF\x13\x11\x0F\xFF\x14\x13\x10" + "\xFF\x16\x14\x10\xFF\x15\x14\x10\xFF" + "\x13\x11\x0F\xFF\x12\x11\x0D\xFF\x13\x12\x0F\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x14\x13\x10" + "\xFF\x15\x13\x10\xFF\x13\x11\x0F\xFF" + "\x12\x12\x0E\xFF\x10\x0F\x0D\xFF\x14\x13\x0F\xFF\x15\x13\x11\xFF\x13\x12\x0F\xFF\x15\x13\x10" + "\xFF\x16\x15\x11\xFF\x15\x13\x10\xFF" + "\x15\x14\x10\xFF\x14\x13\x0F\xFF\x15\x14\x10\xFF\x15\x14\x10\xFF\x14\x13\x0F\xFF\x13\x12\x0E" + "\xFF\x14\x13\x0F\xFF\x15\x14\x10\xFF" + "\x16\x15\x11\xFF\x14\x13\x0F\xFF\x15\x14\x10\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x16\x15\x10" + "\xFF\x15\x14\x11\xFF\x15\x14\x10\xFF" + "\x11\x10\x0F\xFF\x14\x12\x0F\xFF\x13\x11\x0F\xFF\x13\x11\x0F\xFF\x15\x13\x10\xFF\x15\x14\x10" + "\xFF\x15\x14\x11\xFF\x16\x15\x11\xFF" + "\x15\x14\x11\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x16\x14\x10\xFF\x16\x15\x13\xFF\x16\x15\x11" + "\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF" + "\x12\x10\x0E\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x15\x14\x11\xFF\x15\x14\x10\xFF\x18\x17\x13" + "\xFF\x17\x16\x12\xFF\x15\x13\x10\xFF" + "\x17\x16\x13\xFF\x13\x11\x10\xFF\x16\x14\x11\xFF\x15\x14\x10\xFF\x13\x11\x10\xFF\x14\x13\x0F" + "\xFF\x15\x14\x10\xFF\x15\x14\x10\xFF" + "\x16\x15\x11\xFF\x16\x15\x10\xFF\x16\x15\x10\xFF\x15\x13\x11\xFF\x16\x15\x11\xFF\x17\x15\x12" + "\xFF\x15\x14\x11\xFF\x12\x12\x0E\xFF" + "\x15\x14\x10\xFF\x16\x15\x11\xFF\x1A\x19\x15\xFF\x19\x18\x14\xFF\x14\x13\x0F\xFF\x15\x14\x10" + "\xFF\x18\x17\x13\xFF\x15\x14\x11\xFF" + "\x17\x15\x11\xFF\x17\x16\x12\xFF\x18\x16\x12\xFF\x18\x16\x12\xFF\x14\x13\x10\xFF\x18\x17\x13" + "\xFF\x17\x16\x12\xFF\x1A\x19\x15\xFF" + "\x19\x18\x14\xFF\x17\x16\x12\xFF\x18\x17\x13\xFF\x16\x15\x11\xFF\x16\x15\x12\xFF\x17\x16\x13" + "\xFF\x18\x17\x13\xFF\x17\x15\x11\xFF" + "\x18\x16\x12\xFF\x16\x15\x12\xFF\x16\x14\x12\xFF\x18\x16\x13\xFF\x15\x14\x10\xFF\x17\x15\x11" + "\xFF\x17\x15\x11\xFF\x14\x13\x0F\xFF" + "\x16\x15\x11\xFF\x16\x15\x11\xFF\x16\x15\x12\xFF\x1B\x1A\x16\xFF\x16\x15\x11\xFF\x19\x18\x14" + "\xFF\x19\x18\x14\xFF\x1C\x1C\x17\xFF" + "\x17\x16\x12\xFF\x18\x17\x12\xFF\x18\x17\x13\xFF\x17\x15\x13\xFF\x16\x15\x12\xFF\x1A\x19\x14" + "\xFF\x1A\x19\x15\xFF\x1A\x19\x15\xFF" + "\x19\x18\x15\xFF\x19\x17\x13\xFF\x19\x17\x13\xFF\x15\x14\x11\xFF\x15\x14\x10\xFF\x17\x16\x12" + "\xFF\x19\x18\x14\xFF\x18\x17\x13\xFF" + "\x18\x17\x13\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x18\x17\x14\xFF\x1B\x1A\x16\xFF\x18\x17\x13" + "\xFF\x18\x17\x13\xFF\x1B\x1A\x15\xFF" + "\x17\x16\x12\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x1E\x1E\x19\xFF\x1D\x1D\x18" + "\xFF\x1A\x19\x16\xFF\x19\x18\x14\xFF"; + +/** + * Experimental Case 02: 64x64 (32bpp) + */ + +static const BYTE TEST_RLE_BITMAP_EXPERIMENTAL_02[16384] = + "\x1C\x1C\x17\xFF\x1D\x1B\x18\xFF\x1B\x19\x15\xFF\x19\x18\x13\xFF\x19\x18\x14\xFF\x17\x16\x12" + "\xFF\x17\x17\x13\xFF\x19\x17\x14\xFF" + "\x15\x14\x11\xFF\x13\x13\x10\xFF\x4F\x4B\x3E\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x22\x21\x1C" + "\xFF\x22\x21\x1B\xFF\x21\x21\x1A\xFF" + "\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x21\x20\x1A\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x23\x22\x1D" + "\xFF\x22\x20\x1A\xFF\x21\x20\x1A\xFF" + "\x21\x20\x1B\xFF\x24\x23\x1E\xFF\x21\x20\x1B\xFF\x22\x22\x1C\xFF\x25\x24\x20\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x29\x28\x23\xFF" + "\x26\x25\x21\xFF\x21\x20\x1B\xFF\x29\x28\x22\xFF\x21\x20\x1A\xFF\x26\x25\x20\xFF\x22\x21\x1B" + "\xFF\x24\x22\x1D\xFF\x26\x25\x20\xFF" + "\x23\x23\x1D\xFF\x2A\x29\x23\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x23\x22\x1D" + "\xFF\x24\x23\x1E\xFF\x21\x20\x1B\xFF" + "\x26\x25\x20\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B" + "\xFF\x25\x24\x20\xFF\x25\x24\x20\xFF" + "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1D\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20" + "\xFF\x28\x26\x21\xFF\x24\x22\x1D\xFF" + "\x23\x22\x1E\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF" + "\x24\x23\x1E\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x24\x23\x1E\xFF\x24\x23\x1E" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x21\x20\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1D" + "\xFF\x2A\x29\x24\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x22\x1D\xFF\x26\x25\x20\xFF\x23\x22\x1D" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x26\x25\x20\xFF\x21\x20\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x25\x20" + "\xFF\x28\x26\x21\xFF\x25\x24\x20\xFF" + "\x24\x23\x1D\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x29\x28\x23" + "\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x20" + "\xFF\x22\x20\x1B\xFF\x22\x21\x1B\xFF" + "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x21\x20\x1B\xFF\x29\x29\x23\xFF\x21\x20\x1A\xFF\x25\x24\x20" + "\xFF\x26\x24\x20\xFF\x21\x21\x1B\xFF" + "\x21\x20\x1B\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x25\x25\x20\xFF" + "\x29\x28\x22\xFF\x23\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x22\x1C\xFF\x25\x24\x20\xFF\x22\x21\x1B" + "\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF" + "\x25\x25\x20\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF\x23\x22\x1E" + "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x28\x26\x21" + "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF" + "\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF\x24\x23\x1D\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x25\x24\x1F\xFF\x25\x24\x20\xFF\x28\x26\x21\xFF\x25\x24\x20\xFF\x25\x24\x20\xFF\x22\x21\x1B" + "\xFF\x28\x26\x21\xFF\x25\x24\x20\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E" + "\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF" + "\x28\x26\x21\xFF\x23\x22\x1C\xFF\x25\x23\x1F\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x24\x23\x1E" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x23\x22\x1C" + "\xFF\x23\x22\x1C\xFF\x29\x28\x23\xFF" + "\x27\x25\x21\xFF\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B" + "\xFF\x24\x23\x1E\xFF\x26\x25\x20\xFF" + "\x24\x23\x1E\xFF\x2A\x29\x23\xFF\x25\x23\x1E\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x23\x23\x1E" + "\xFF\x24\x24\x1E\xFF\x22\x21\x1B\xFF" + "\x26\x25\x20\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF\x22\x21\x1B" + "\xFF\x26\x25\x20\xFF\x26\x25\x21\xFF" + "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x23\x22\x1E\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20" + "\xFF\x28\x26\x21\xFF\x24\x23\x1E\xFF" + "\x23\x23\x1E\xFF\x27\x25\x20\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF" + "\x25\x24\x1F\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x25\x24\x1F" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E" + "\xFF\x2A\x29\x24\xFF\x22\x21\x1B\xFF" + "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1E\xFF\x27\x25\x20\xFF\x24\x23\x1E" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x27\x25\x20\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x25\x20" + "\xFF\x28\x26\x21\xFF\x25\x24\x20\xFF" + "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x29\x28\x23" + "\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x26\x25\x20" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x2A\x29\x23\xFF\x22\x21\x1B\xFF\x26\x25\x21" + "\xFF\x26\x25\x21\xFF\x22\x23\x1C\xFF" + "\x23\x23\x1C\xFF\x24\x25\x1E\xFF\x27\x2B\x25\xFF\x2B\x2F\x27\xFF\x25\x2C\x23\xFF\x26\x2E\x24" + "\xFF\x26\x2F\x25\xFF\x2A\x35\x2B\xFF" + "\x2E\x39\x2E\xFF\x28\x34\x2A\xFF\x28\x33\x28\xFF\x28\x33\x28\xFF\x2A\x35\x2C\xFF\x27\x32\x27" + "\xFF\x2A\x35\x2C\xFF\x27\x32\x27\xFF" + "\x2A\x36\x2C\xFF\x2B\x36\x2D\xFF\x27\x32\x27\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF\x28\x33\x28" + "\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF" + "\x27\x32\x27\xFF\x27\x32\x27\xFF\x2A\x35\x2C\xFF\x2E\x39\x2E\xFF\x28\x33\x28\xFF\x28\x33\x2A" + "\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF" + "\x27\x32\x27\xFF\x28\x33\x28\xFF\x2D\x37\x2D\xFF\x27\x32\x27\xFF\x2A\x35\x2B\xFF\x54\x51\x43" + "\xFF\x37\x82\x60\xFF\x36\x7A\x5A\xFF" + "\x34\x71\x54\xFF\x32\x68\x4D\xFF\x30\x5D\x46\xFF\x2D\x53\x3F\xFF\x2B\x45\x35\xFF\x26\x35\x29" + "\xFF\x25\x2D\x23\xFF\x26\x2C\x25\xFF" + "\x27\x2A\x23\xFF\x23\x25\x1F\xFF\x23\x23\x1E\xFF\x24\x23\x1D\xFF\x25\x23\x1D\xFF\x25\x23\x1E" + "\xFF\x25\x23\x1D\xFF\x25\x24\x1F\xFF" + "\x28\x2B\x23\xFF\x37\x60\x4A\xFF\x4A\xA2\x78\xFF\x47\x97\x71\xFF\x41\x84\x64\xFF\x3D\x75\x58" + "\xFF\x38\x62\x4B\xFF\x33\x50\x3E\xFF" + "\x2E\x3C\x30\xFF\x2A\x34\x29\xFF\x28\x30\x26\xFF\x27\x2C\x24\xFF\x26\x29\x22\xFF\x27\x28\x21" + "\xFF\x27\x26\x20\xFF\x27\x25\x20\xFF" + "\x25\x25\x20\xFF\x24\x23\x1D\xFF\x26\x23\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1E\xFF\x27\x24\x1F" + "\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF" + "\x26\x25\x1F\xFF\x28\x25\x20\xFF\x28\x26\x21\xFF\x2A\x27\x22\xFF\x26\x25\x1E\xFF\x27\x24\x1F" + "\xFF\x27\x25\x1F\xFF\x28\x25\x1F\xFF" + "\x27\x27\x1F\xFF\x27\x24\x1F\xFF\x25\x24\x1F\xFF\x26\x24\x1F\xFF\x24\x24\x1E\xFF\x25\x24\x1D" + "\xFF\x27\x25\x1F\xFF\x28\x25\x20\xFF" + "\x27\x24\x1F\xFF\x28\x25\x1F\xFF\x27\x26\x1F\xFF\x27\x25\x1E\xFF\x25\x23\x1E\xFF\x26\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x27\x25\x20\xFF" + "\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x25\x22\x1D" + "\xFF\x26\x23\x1E\xFF\x23\x21\x1C\xFF" + "\x26\x24\x1F\xFF\x28\x25\x20\xFF\x28\x26\x1F\xFF\x28\x25\x1F\xFF\x28\x25\x20\xFF\x26\x24\x1F" + "\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF" + "\x26\x24\x1F\xFF\x25\x25\x1E\xFF\x27\x25\x1F\xFF\x28\x24\x1F\xFF\x29\x27\x1F\xFF\x25\x24\x1E" + "\xFF\x24\x24\x1E\xFF\x25\x24\x1E\xFF" + "\x25\x23\x1E\xFF\x25\x24\x1E\xFF\x25\x24\x1E\xFF\x27\x24\x1F\xFF\x2A\x27\x22\xFF\x2B\x28\x22" + "\xFF\x2B\x28\x21\xFF\x25\x24\x1E\xFF" + "\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x27\x23\x1E\xFF\x28\x25\x20\xFF\x25\x22\x1E\xFF\x25\x23\x1E" + "\xFF\x23\x22\x1C\xFF\x25\x25\x1E\xFF" + "\x26\x26\x20\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x26\x25\x1F\xFF\x26\x25\x1F" + "\xFF\x27\x25\x1E\xFF\x25\x23\x1C\xFF" + "\x25\x23\x1E\xFF\x25\x24\x1E\xFF\x24\x23\x1E\xFF\x24\x21\x1C\xFF\x25\x22\x1D\xFF\x27\x24\x1F" + "\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF" + "\x25\x22\x1E\xFF\x25\x22\x1E\xFF\x25\x22\x1D\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x26\x20" + "\xFF\x26\x26\x1F\xFF\x27\x25\x1F\xFF" + "\x27\x26\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x23\x22\x1C\xFF\x25\x23\x1D\xFF\x25\x22\x1E" + "\xFF\x26\x24\x1E\xFF\x24\x23\x1E\xFF" + "\x26\x24\x1F\xFF\x27\x26\x20\xFF\x29\x27\x21\xFF\x2A\x26\x21\xFF\x27\x24\x1F\xFF\x25\x23\x1E" + "\xFF\x26\x24\x1F\xFF\x28\x29\x23\xFF" + "\x2F\x46\x36\xFF\x4B\xA5\x7B\xFF\x57\xCD\x97\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x4D\xAC\x7F" + "\xFF\x32\x50\x3D\xFF\x26\x29\x22\xFF" + "\x23\x21\x1C\xFF\x26\x23\x1E\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x26\x24\x1F" + "\xFF\x25\x24\x1E\xFF\x25\x24\x1F\xFF" + "\x22\x21\x1C\xFF\x24\x23\x1D\xFF\x24\x24\x1D\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF\x26\x24\x1F" + "\xFF\x25\x27\x21\xFF\x2B\x36\x2B\xFF" + "\x4B\xA7\x7D\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x50\xB9\x89\xFF\x32\x54\x40\xFF\x27\x2A\x23\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x25\x22\x1D" + "\xFF\x25\x23\x1D\xFF\x25\x22\x1D\xFF" + "\x24\x23\x1D\xFF\x25\x23\x1E\xFF\x24\x22\x1D\xFF\x25\x22\x1D\xFF\x23\x22\x1C\xFF\x24\x23\x1D" + "\xFF\x27\x25\x1F\xFF\x28\x25\x1F\xFF" + "\x25\x23\x1E\xFF\x23\x22\x1D\xFF\x24\x22\x1C\xFF\x24\x23\x1D\xFF\x24\x22\x1D\xFF\x24\x21\x1C" + "\xFF\x25\x24\x1E\xFF\x23\x22\x1C\xFF" + "\x24\x21\x1C\xFF\x25\x23\x1E\xFF\x27\x25\x1F\xFF\x25\x25\x1F\xFF\x24\x22\x1D\xFF\x24\x21\x1C" + "\xFF\x25\x23\x1E\xFF\x22\x21\x1C\xFF" + "\x26\x23\x1F\xFF\x24\x21\x1D\xFF\x24\x22\x1D\xFF\x23\x22\x1C\xFF\x23\x21\x1C\xFF\x24\x21\x1C" + "\xFF\x26\x23\x1E\xFF\x23\x22\x1D\xFF" + "\x22\x21\x1C\xFF\x26\x25\x1F\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x23\x23\x1D\xFF\x25\x24\x1E" + "\xFF\x26\x25\x1E\xFF\x26\x23\x1E\xFF" + "\x24\x22\x1D\xFF\x24\x22\x1E\xFF\x24\x22\x1D\xFF\x25\x22\x1D\xFF\x23\x22\x1D\xFF\x24\x22\x1D" + "\xFF\x26\x23\x1E\xFF\x26\x24\x1F\xFF" + "\x24\x21\x1D\xFF\x23\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x23\x1E\xFF\x24\x22\x1D\xFF\x21\x1F\x1B" + "\xFF\x22\x20\x1B\xFF\x22\x21\x1B\xFF" + "\x25\x24\x1E\xFF\x24\x24\x1D\xFF\x24\x23\x1D\xFF\x25\x23\x1E\xFF\x23\x22\x1D\xFF\x23\x22\x1D" + "\xFF\x23\x21\x1C\xFF\x22\x21\x1C\xFF" + "\x20\x20\x1B\xFF\x25\x25\x1E\xFF\x24\x22\x1D\xFF\x24\x22\x1D\xFF\x26\x23\x1E\xFF\x23\x22\x1D" + "\xFF\x24\x22\x1D\xFF\x25\x22\x1D\xFF" + "\x24\x22\x1D\xFF\x24\x22\x1D\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x24\x23\x1D\xFF\x25\x24\x1E" + "\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF" + "\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x22\x22\x1C\xFF\x22\x20\x1B\xFF\x20\x20\x1A\xFF\x22\x20\x1B" + "\xFF\x21\x20\x1B\xFF\x23\x22\x1C\xFF" + "\x23\x22\x1D\xFF\x22\x21\x1C\xFF\x20\x1F\x1A\xFF\x20\x1F\x1A\xFF\x24\x21\x1C\xFF\x25\x24\x1E" + "\xFF\x25\x23\x1E\xFF\x24\x22\x1D\xFF" + "\x26\x23\x1E\xFF\x24\x22\x1D\xFF\x23\x20\x1B\xFF\x24\x22\x1C\xFF\x25\x23\x1D\xFF\x23\x22\x1D" + "\xFF\x20\x20\x1A\xFF\x20\x1F\x19\xFF" + "\x1F\x1F\x1A\xFF\x23\x23\x1D\xFF\x23\x21\x1C\xFF\x22\x1F\x1B\xFF\x21\x21\x1A\xFF\x22\x22\x1C" + "\xFF\x23\x22\x1D\xFF\x21\x21\x1B\xFF" + "\x21\x21\x1B\xFF\x23\x21\x1C\xFF\x23\x20\x1B\xFF\x20\x20\x1A\xFF\x21\x1F\x1B\xFF\x24\x21\x1C" + "\xFF\x25\x22\x1D\xFF\x22\x22\x1C\xFF" + "\x23\x21\x1C\xFF\x24\x22\x1D\xFF\x23\x21\x1C\xFF\x21\x1E\x19\xFF\x21\x1F\x1B\xFF\x22\x20\x1B" + "\xFF\x23\x21\x1C\xFF\x22\x20\x1B\xFF" + "\x22\x21\x1C\xFF\x20\x20\x1C\xFF\x1F\x1E\x1A\xFF\x22\x21\x1C\xFF\x22\x21\x1C\xFF\x21\x21\x1B" + "\xFF\x21\x21\x1B\xFF\x24\x22\x1F\xFF" + "\x12\x11\x0F\xFF\x11\x10\x0C\xFF\x11\x10\x0D\xFF\x13\x12\x0F\xFF\x12\x11\x0D\xFF\x12\x11\x0E" + "\xFF\x12\x11\x0F\xFF\x16\x15\x11\xFF" + "\x17\x15\x12\xFF\x12\x10\x0E\xFF\x14\x13\x0F\xFF\x14\x13\x10\xFF\x11\x11\x0D\xFF\x10\x10\x0D" + "\xFF\x13\x13\x0F\xFF\x13\x11\x0F\xFF" + "\x13\x12\x0F\xFF\x15\x13\x11\xFF\x13\x12\x0F\xFF\x10\x0E\x0E\xFF\x11\x0F\x0E\xFF\x13\x11\x10" + "\xFF\x13\x11\x10\xFF\x13\x12\x0F\xFF" + "\x15\x13\x12\xFF\x12\x11\x0E\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x14\x12\x0F\xFF\x15\x13\x11" + "\xFF\x17\x16\x12\xFF\x14\x13\x0F\xFF" + "\x14\x13\x11\xFF\x13\x13\x10\xFF\x14\x13\x10\xFF\x16\x16\x12\xFF\x0F\x0F\x0D\xFF\x11\x10\x0D" + "\xFF\x11\x10\x0D\xFF\x12\x10\x0D\xFF" + "\x12\x10\x0E\xFF\x14\x12\x0F\xFF\x15\x13\x11\xFF\x13\x12\x0E\xFF\x14\x13\x0F\xFF\x17\x15\x12" + "\xFF\x15\x14\x12\xFF\x16\x15\x11\xFF" + "\x15\x14\x10\xFF\x18\x17\x13\xFF\x16\x16\x11\xFF\x15\x14\x10\xFF\x15\x14\x10\xFF\x15\x14\x11" + "\xFF\x14\x12\x11\xFF\x13\x12\x10\xFF" + "\x15\x13\x12\xFF\x18\x17\x13\xFF\x16\x15\x11\xFF\x11\x10\x0D\xFF\x14\x13\x10\xFF\x17\x16\x12" + "\xFF\x14\x13\x0F\xFF\x14\x12\x10\xFF" + "\x16\x15\x11\xFF\x19\x17\x13\xFF\x16\x15\x11\xFF\x14\x12\x10\xFF\x14\x12\x10\xFF\x14\x13\x10" + "\xFF\x15\x13\x10\xFF\x13\x11\x10\xFF" + "\x13\x12\x0F\xFF\x13\x11\x0F\xFF\x12\x10\x0F\xFF\x16\x14\x11\xFF\x12\x11\x0F\xFF\x14\x13\x10" + "\xFF\x17\x17\x12\xFF\x18\x18\x12\xFF" + "\x18\x17\x13\xFF\x15\x14\x11\xFF\x16\x15\x11\xFF\x16\x14\x12\xFF\x15\x13\x10\xFF\x15\x13\x11" + "\xFF\x1A\x19\x15\xFF\x19\x18\x14\xFF" + "\x16\x14\x11\xFF\x14\x13\x10\xFF\x14\x13\x0F\xFF\x13\x12\x0E\xFF\x15\x13\x11\xFF\x14\x13\x10" + "\xFF\x15\x14\x10\xFF\x14\x13\x0F\xFF" + "\x15\x14\x10\xFF\x15\x14\x11\xFF\x16\x15\x11\xFF\x16\x14\x12\xFF\x16\x16\x10\xFF\x16\x15\x11" + "\xFF\x15\x14\x11\xFF\x14\x13\x10\xFF" + "\x15\x14\x10\xFF\x16\x14\x11\xFF\x14\x13\x10\xFF\x14\x13\x10\xFF\x17\x16\x12\xFF\x17\x15\x14" + "\xFF\x13\x12\x0F\xFF\x17\x15\x12\xFF" + "\x1A\x18\x14\xFF\x18\x16\x13\xFF\x18\x16\x13\xFF\x18\x16\x12\xFF\x15\x14\x10\xFF\x12\x11\x0E" + "\xFF\x15\x13\x12\xFF\x17\x16\x13\xFF" + "\x15\x14\x12\xFF\x18\x17\x13\xFF\x18\x16\x14\xFF\x12\x11\x10\xFF\x15\x14\x10\xFF\x18\x16\x13" + "\xFF\x17\x15\x12\xFF\x19\x18\x15\xFF" + "\x17\x16\x12\xFF\x17\x16\x12\xFF\x19\x17\x14\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x17\x16\x12" + "\xFF\x16\x15\x11\xFF\x17\x15\x11\xFF" + "\x16\x15\x11\xFF\x15\x14\x10\xFF\x15\x14\x10\xFF\x18\x16\x12\xFF\x16\x15\x11\xFF\x16\x15\x12" + "\xFF\x19\x18\x14\xFF\x18\x17\x13\xFF" + "\x18\x16\x13\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x14\x13\x0F\xFF\x17\x16\x13\xFF\x18\x17\x13" + "\xFF\x18\x16\x14\xFF\x18\x17\x14\xFF" + "\x16\x15\x11\xFF\x1C\x1A\x16\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x17\x16\x12\xFF\x1A\x18\x14" + "\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF" + "\x16\x15\x11\xFF\x15\x14\x10\xFF\x17\x16\x12\xFF\x1A\x19\x15\xFF\x19\x18\x14\xFF\x16\x15\x11" + "\xFF\x16\x14\x11\xFF\x16\x15\x11\xFF" + "\x1A\x19\x15\xFF\x15\x14\x11\xFF\x19\x17\x13\xFF\x1E\x1E\x19\xFF\x19\x18\x14\xFF\x1B\x19\x15" + "\xFF\x1C\x1A\x16\xFF\x17\x16\x13\xFF" + "\x16\x14\x12\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x19\x18\x14\xFF\x1A\x19\x15\xFF\x1A\x19\x15" + "\xFF\x1A\x19\x15\xFF\x14\x13\x0F\xFF" + "\x16\x15\x11\xFF\x1A\x18\x14\xFF\x18\x16\x12\xFF\x13\x12\x0E\xFF\x16\x16\x12\xFF\x19\x18\x14" + "\xFF\x19\x18\x14\xFF\x1A\x19\x15\xFF" + "\x1C\x1A\x16\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x18\x15\x11\xFF\x19\x18\x14\xFF\x1A\x19\x15" + "\xFF\x1B\x1A\x15\xFF\x22\x22\x1D\xFF" + "\x1F\x1D\x18\xFF\x1B\x18\x14\xFF\x1C\x1B\x16\xFF\x19\x18\x14\xFF\x1B\x1A\x16\xFF\x1B\x1A\x16" + "\xFF\x1B\x1A\x15\xFF\x1A\x19\x15\xFF" + "\x1A\x18\x14\xFF\x1C\x1B\x16\xFF\x1C\x1A\x16\xFF\x1C\x19\x15\xFF\x1B\x19\x15\xFF\x19\x18\x14" + "\xFF\x1E\x1C\x18\xFF\x1D\x1A\x16\xFF" + "\x17\x16\x11\xFF\x19\x18\x14\xFF\x18\x17\x13\xFF\x1C\x1B\x17\xFF\x1D\x1C\x17\xFF\x1A\x18\x14" + "\xFF\x19\x17\x14\xFF\x19\x19\x15\xFF" + "\x1B\x1A\x16\xFF\x1C\x1B\x17\xFF\x1E\x1D\x18\xFF\x1C\x1A\x16\xFF\x1A\x18\x14\xFF\x1B\x1A\x16" + "\xFF\x1D\x1C\x17\xFF\x1F\x1E\x19\xFF" + "\x1B\x1A\x16\xFF\x1B\x1A\x16\xFF\x1D\x1C\x17\xFF\x1D\x1B\x17\xFF\x1B\x1A\x16\xFF\x1B\x1A\x16" + "\xFF\x1A\x19\x15\xFF\x1D\x1C\x18\xFF" + "\x1B\x19\x15\xFF\x1C\x1B\x17\xFF\x1C\x1B\x18\xFF\x1A\x1A\x16\xFF\x1E\x1C\x18\xFF\x1E\x1C\x18" + "\xFF\x1F\x1C\x18\xFF\x1F\x1E\x19\xFF" + "\x1C\x1B\x17\xFF\x1B\x1B\x17\xFF\x1A\x19\x15\xFF\x1E\x1C\x18\xFF\x1F\x1F\x19\xFF\x1C\x1C\x17" + "\xFF\x1C\x1A\x18\xFF\x1F\x1C\x19\xFF" + "\x1F\x1D\x18\xFF\x1D\x1C\x18\xFF\x1D\x1B\x17\xFF\x1E\x1D\x19\xFF\x20\x1D\x19\xFF\x1E\x1C\x18" + "\xFF\x1F\x1E\x19\xFF\x1D\x1C\x18\xFF" + "\x1D\x1C\x18\xFF\x22\x21\x1C\xFF\x20\x1F\x1A\xFF\x1F\x1D\x18\xFF\x1C\x1B\x17\xFF\x20\x1F\x19" + "\xFF\x1B\x1B\x16\xFF\x1A\x19\x15\xFF" + "\x1C\x1A\x16\xFF\x1E\x1B\x17\xFF\x1F\x1D\x18\xFF\x1F\x1E\x19\xFF\x21\x1E\x1A\xFF\x1D\x1B\x17" + "\xFF\x1D\x1D\x18\xFF\x1E\x1E\x18\xFF" + "\x1E\x1E\x18\xFF\x1F\x1D\x19\xFF\x1E\x1C\x17\xFF\x1E\x1D\x18\xFF\x1E\x1C\x18\xFF\x1F\x1E\x19" + "\xFF\x1D\x1C\x18\xFF\x1F\x1D\x1A\xFF" + "\x1F\x1E\x18\xFF\x1E\x1D\x18\xFF\x1F\x1E\x19\xFF\x22\x22\x1C\xFF\x1D\x1D\x18\xFF\x1D\x1C\x18" + "\xFF\x20\x1F\x1C\xFF\x1E\x1E\x17\xFF" + "\x1D\x1C\x18\xFF\x1D\x1D\x18\xFF\x20\x21\x1B\xFF\x21\x20\x1B\xFF\x1D\x1C\x18\xFF\x1B\x19\x15" + "\xFF\x1D\x1B\x17\xFF\x20\x1D\x1B\xFF" + "\x1F\x1E\x19\xFF\x1E\x1D\x18\xFF\x20\x1F\x1A\xFF\x1F\x1F\x19\xFF\x22\x20\x1B\xFF\x1F\x1E\x19" + "\xFF\x1E\x1D\x19\xFF\x22\x1F\x1B\xFF" + "\x1E\x1E\x17\xFF\x1C\x1C\x16\xFF\x1B\x1C\x15\xFF\x1D\x1B\x18\xFF\x1E\x1C\x19\xFF\x20\x1E\x19" + "\xFF\x21\x1E\x1A\xFF\x1F\x1D\x18\xFF" + "\x1A\x1A\x15\xFF\x19\x17\x14\xFF\x1B\x1A\x16\xFF\x1B\x19\x15\xFF\x1B\x19\x15\xFF\x18\x18\x14" + "\xFF\x18\x17\x14\xFF\x18\x18\x14\xFF" + "\x19\x17\x14\xFF\x15\x16\x12\xFF\x52\x4E\x42\xFF\x22\x21\x1B\xFF\x22\x20\x1A\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x21\x20\x1B\xFF\x25\x24\x20\xFF\x26\x25\x21" + "\xFF\x23\x22\x1D\xFF\x24\x23\x1E\xFF" + "\x21\x20\x1A\xFF\x26\x25\x20\xFF\x23\x22\x1D\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x20\x1B" + "\xFF\x21\x20\x1A\xFF\x21\x21\x1B\xFF" + "\x26\x25\x20\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x25\x25\x20\xFF" + "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x21\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x28\x26\x22\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x21\x20\x1B" + "\xFF\x26\x25\x21\xFF\x21\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1D\xFF\x23\x22\x1D\xFF" + "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x24\x22\x1D" + "\xFF\x24\x23\x1F\xFF\x25\x24\x20\xFF" + "\x22\x20\x1B\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x20\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x26\x25\x20\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x26\x25\x20\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF" + "\x28\x26\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x24\x23\x1E" + "\xFF\x29\x28\x23\xFF\x25\x24\x20\xFF" + "\x24\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x23\x21\x1C\xFF\x23\x21\x1C\xFF" + "\x24\x23\x1E\xFF\x22\x20\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x22\x22\x1C\xFF\x26\x25\x20\xFF\x22\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B" + "\xFF\x21\x21\x1B\xFF\x26\x25\x20\xFF" + "\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF\x22\x21\x1B" + "\xFF\x22\x22\x1C\xFF\x26\x25\x21\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x21\x1C\xFF\x23\x22\x1C\xFF\x26\x25\x21\xFF\x25\x24\x20" + "\xFF\x25\x24\x1F\xFF\x24\x22\x1D\xFF" + "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF" + "\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x25\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x20" + "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF" + "\x23\x21\x1C\xFF\x23\x23\x1E\xFF\x23\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF" + "\x23\x21\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x28\x26\x21" + "\xFF\x25\x23\x1F\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1D\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x26\x25\x20" + "\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x26\x25\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x1F" + "\xFF\x25\x24\x1F\xFF\x26\x24\x20\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x21\x1C\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x26\x25\x21" + "\xFF\x24\x23\x1E\xFF\x25\x24\x1F\xFF" + "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x26\x25\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x25\x25\x20\xFF" + "\x23\x22\x1C\xFF\x27\x25\x20\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x28\x26\x22\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B" + "\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF" + "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x24\x23\x1E" + "\xFF\x25\x24\x1F\xFF\x26\x24\x20\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x27\x25\x20\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x27\x26\x21\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x26\x26\x20\xFF" + "\x28\x26\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E" + "\xFF\x29\x28\x23\xFF\x25\x25\x20\xFF" + "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF" + "\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x22\x22\x1C\xFF\x27\x26\x20\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF" + "\x22\x21\x1B\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x26\x24\x20\xFF\x23\x22\x1C" + "\xFF\x22\x22\x1C\xFF\x26\x25\x21\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x27\x26\x22\xFF\x26\x26\x22" + "\xFF\x26\x27\x21\xFF\x26\x2A\x23\xFF" + "\x26\x2E\x26\xFF\x26\x2F\x25\xFF\x27\x31\x27\xFF\x27\x31\x27\xFF\x28\x33\x28\xFF\x27\x32\x27" + "\xFF\x27\x32\x27\xFF\x29\x34\x2A\xFF" + "\x27\x32\x27\xFF\x2A\x35\x2B\xFF\x2A\x36\x2C\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF\x2A\x35\x2C" + "\xFF\x27\x32\x27\xFF\x2D\x37\x2D\xFF" + "\x28\x33\x28\xFF\x28\x34\x2A\xFF\x28\x34\x2A\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x27\x32\x27" + "\xFF\x2B\x36\x2C\xFF\x27\x32\x27\xFF" + "\x28\x33\x28\xFF\x27\x32\x27\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x2D\x37\x2D" + "\xFF\x2A\x35\x2C\xFF\x27\x32\x27\xFF" + "\x27\x32\x27\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x28\x33\x28\xFF\x29\x34\x2A\xFF\x4F\x4D\x3F" + "\xFF\x3A\x8B\x67\xFF\x3B\x8C\x68\xFF" + "\x3E\x93\x6D\xFF\x40\x98\x71\xFF\x42\x9A\x72\xFF\x42\x9A\x72\xFF\x42\x97\x70\xFF\x41\x93\x6D" + "\xFF\x3E\x87\x64\xFF\x3B\x75\x56\xFF" + "\x32\x51\x3F\xFF\x29\x37\x2C\xFF\x29\x31\x28\xFF\x27\x2C\x24\xFF\x26\x29\x21\xFF\x28\x28\x22" + "\xFF\x27\x27\x20\xFF\x27\x27\x21\xFF" + "\x29\x2F\x25\xFF\x3C\x73\x56\xFF\x55\xC9\x94\xFF\x56\xCC\x96\xFF\x56\xCB\x97\xFF\x54\xC5\x92" + "\xFF\x52\xBD\x8C\xFF\x50\xB5\x86\xFF" + "\x4D\xAA\x7F\xFF\x48\x99\x71\xFF\x40\x80\x61\xFF\x37\x66\x4D\xFF\x2F\x48\x38\xFF\x2C\x38\x2C" + "\xFF\x2B\x33\x29\xFF\x2A\x2E\x26\xFF" + "\x27\x29\x23\xFF\x27\x27\x21\xFF\x27\x27\x21\xFF\x27\x26\x20\xFF\x26\x25\x1F\xFF\x26\x25\x1F" + "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF" + "\x27\x26\x20\xFF\x28\x26\x20\xFF\x28\x25\x20\xFF\x28\x27\x20\xFF\x27\x25\x1F\xFF\x27\x25\x20" + "\xFF\x27\x25\x1F\xFF\x28\x26\x20\xFF" + "\x25\x23\x1E\xFF\x26\x24\x1E\xFF\x25\x23\x1D\xFF\x27\x25\x1F\xFF\x26\x24\x1E\xFF\x26\x23\x1E" + "\xFF\x29\x26\x21\xFF\x28\x25\x20\xFF" + "\x26\x25\x1F\xFF\x27\x25\x1F\xFF\x25\x25\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x26\x25\x1F" + "\xFF\x25\x23\x1D\xFF\x27\x24\x1F\xFF" + "\x25\x24\x1F\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x27\x24\x1E\xFF\x26\x26\x1E\xFF\x27\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF" + "\x29\x26\x20\xFF\x28\x25\x20\xFF\x27\x26\x1E\xFF\x26\x24\x1E\xFF\x28\x26\x21\xFF\x27\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF" + "\x28\x25\x20\xFF\x27\x24\x1F\xFF\x27\x26\x20\xFF\x27\x25\x1F\xFF\x28\x26\x20\xFF\x26\x23\x1E" + "\xFF\x25\x24\x1D\xFF\x26\x24\x1E\xFF" + "\x26\x24\x1F\xFF\x22\x21\x1B\xFF\x23\x23\x1D\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x26\x23\x1E" + "\xFF\x27\x24\x1E\xFF\x27\x24\x1F\xFF" + "\x27\x25\x1E\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x26\x23\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E" + "\xFF\x24\x23\x1D\xFF\x21\x21\x1C\xFF" + "\x26\x23\x1F\xFF\x26\x23\x1F\xFF\x26\x23\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x24\x1E" + "\xFF\x26\x24\x1E\xFF\x24\x24\x1D\xFF" + "\x25\x24\x1E\xFF\x26\x24\x1F\xFF\x27\x26\x1F\xFF\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x26\x24\x1E" + "\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF" + "\x26\x24\x1F\xFF\x24\x21\x1C\xFF\x23\x22\x1C\xFF\x23\x23\x1D\xFF\x27\x25\x20\xFF\x26\x25\x1F" + "\xFF\x27\x26\x20\xFF\x27\x27\x1F\xFF" + "\x29\x26\x21\xFF\x26\x25\x1F\xFF\x23\x23\x1D\xFF\x24\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x21\x1D" + "\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF" + "\x25\x24\x1F\xFF\x27\x25\x21\xFF\x25\x24\x1F\xFF\x28\x25\x20\xFF\x27\x26\x1F\xFF\x26\x23\x1E" + "\xFF\x25\x22\x1D\xFF\x26\x26\x20\xFF" + "\x2A\x37\x2D\xFF\x42\x87\x66\xFF\x56\xCD\x98\xFF\x56\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x52\xBF\x8E" + "\xFF\x37\x66\x4D\xFF\x28\x2C\x25\xFF" + "\x26\x26\x21\xFF\x27\x24\x1F\xFF\x24\x23\x1E\xFF\x24\x22\x1D\xFF\x27\x24\x1F\xFF\x26\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x25\x22\x1D\xFF" + "\x22\x22\x1C\xFF\x23\x21\x1C\xFF\x26\x23\x1E\xFF\x24\x23\x1D\xFF\x25\x22\x1C\xFF\x23\x21\x1C" + "\xFF\x28\x27\x20\xFF\x2B\x31\x27\xFF" + "\x3E\x79\x5B\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCC\x97\xFF\x57\xCD\x97" + "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF" + "\x54\xC4\x91\xFF\x3E\x79\x5B\xFF\x29\x30\x28\xFF\x25\x25\x1F\xFF\x25\x24\x1E\xFF\x27\x24\x1E" + "\xFF\x24\x23\x1D\xFF\x25\x23\x1F\xFF" + "\x25\x22\x1D\xFF\x26\x24\x1E\xFF\x24\x22\x1D\xFF\x22\x22\x1C\xFF\x22\x1F\x1A\xFF\x23\x21\x1C" + "\xFF\x23\x21\x1C\xFF\x27\x24\x1F\xFF" + "\x27\x26\x21\xFF\x27\x24\x1F\xFF\x24\x21\x1C\xFF\x22\x21\x1C\xFF\x24\x23\x1D\xFF\x23\x20\x1B" + "\xFF\x24\x23\x1D\xFF\x24\x23\x1E\xFF" + "\x25\x24\x1E\xFF\x26\x25\x1F\xFF\x26\x25\x1F\xFF\x28\x26\x21\xFF\x27\x24\x1F\xFF\x25\x23\x1E" + "\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF" + "\x26\x23\x1E\xFF\x24\x21\x1C\xFF\x24\x23\x1D\xFF\x22\x22\x1C\xFF\x22\x20\x1A\xFF\x22\x21\x1B" + "\xFF\x24\x24\x1D\xFF\x25\x23\x1E\xFF" + "\x24\x23\x1E\xFF\x25\x23\x1E\xFF\x23\x22\x1D\xFF\x21\x20\x1B\xFF\x25\x23\x1E\xFF\x24\x23\x1D" + "\xFF\x23\x22\x1C\xFF\x25\x22\x1D\xFF" + "\x23\x21\x1C\xFF\x22\x22\x1D\xFF\x23\x22\x1D\xFF\x27\x25\x20\xFF\x23\x22\x1D\xFF\x26\x22\x1F" + "\xFF\x23\x20\x1D\xFF\x24\x21\x1D\xFF" + "\x25\x22\x1D\xFF\x24\x23\x1E\xFF\x20\x1F\x1A\xFF\x20\x1E\x19\xFF\x23\x22\x1D\xFF\x23\x22\x1B" + "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF" + "\x24\x22\x1D\xFF\x24\x23\x1D\xFF\x24\x23\x1D\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x25\x22\x1E" + "\xFF\x21\x20\x1B\xFF\x21\x21\x1B\xFF" + "\x20\x20\x1A\xFF\x24\x23\x1D\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF\x22\x1F\x1A\xFF\x24\x21\x1C" + "\xFF\x23\x20\x1C\xFF\x20\x1F\x1A\xFF" + "\x24\x22\x1D\xFF\x23\x21\x1C\xFF\x24\x22\x1C\xFF\x24\x21\x1C\xFF\x22\x22\x1C\xFF\x22\x22\x1C" + "\xFF\x24\x21\x1C\xFF\x23\x21\x1C\xFF" + "\x23\x22\x1C\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x24\x21\x1C" + "\xFF\x22\x20\x1B\xFF\x21\x21\x1B\xFF" + "\x23\x21\x1C\xFF\x23\x21\x1C\xFF\x20\x1E\x19\xFF\x21\x1E\x1A\xFF\x23\x22\x1D\xFF\x26\x24\x1E" + "\xFF\x23\x23\x1C\xFF\x23\x21\x1C\xFF" + "\x22\x20\x1C\xFF\x22\x21\x1C\xFF\x23\x20\x1C\xFF\x24\x22\x1D\xFF\x24\x22\x1C\xFF\x22\x20\x1C" + "\xFF\x22\x20\x1B\xFF\x22\x22\x1C\xFF" + "\x22\x20\x1B\xFF\x22\x20\x1B\xFF\x25\x23\x1D\xFF\x26\x23\x1E\xFF\x21\x21\x1C\xFF\x20\x1F\x1A" + "\xFF\x20\x1F\x1B\xFF\x1F\x1E\x1A\xFF" + "\x22\x1F\x1A\xFF\x27\x25\x1F\xFF\x23\x22\x1D\xFF\x22\x23\x1B\xFF\x25\x22\x1D\xFF\x27\x24\x1F" + "\xFF\x24\x22\x1D\xFF\x23\x23\x1D\xFF" + "\x25\x23\x1D\xFF\x23\x22\x1C\xFF\x24\x22\x1D\xFF\x26\x23\x1C\xFF\x24\x21\x1D\xFF\x22\x20\x1B" + "\xFF\x20\x1E\x1B\xFF\x1D\x1C\x18\xFF" + "\x20\x20\x1A\xFF\x20\x1F\x1A\xFF\x1F\x1D\x19\xFF\x20\x1F\x1A\xFF\x23\x22\x1D\xFF\x23\x22\x1D" + "\xFF\x23\x22\x1D\xFF\x23\x22\x1C\xFF" + "\x13\x12\x0F\xFF\x13\x12\x0E\xFF\x13\x12\x0F\xFF\x12\x10\x0E\xFF\x12\x11\x0F\xFF\x13\x12\x0E" + "\xFF\x14\x13\x0F\xFF\x13\x12\x0E\xFF" + "\x14\x12\x0F\xFF\x12\x11\x0E\xFF\x12\x10\x0E\xFF\x12\x11\x0E\xFF\x13\x11\x0E\xFF\x17\x13\x10" + "\xFF\x13\x12\x0E\xFF\x0E\x0D\x0A\xFF" + "\x0E\x0E\x0C\xFF\x0F\x0F\x0D\xFF\x11\x10\x0D\xFF\x13\x11\x0F\xFF\x0F\x10\x0E\xFF\x11\x10\x0F" + "\xFF\x13\x11\x10\xFF\x13\x12\x10\xFF" + "\x11\x10\x0D\xFF\x12\x11\x0E\xFF\x14\x12\x10\xFF\x14\x12\x10\xFF\x10\x0F\x0D\xFF\x13\x11\x0F" + "\xFF\x14\x13\x10\xFF\x13\x12\x10\xFF" + "\x15\x13\x0F\xFF\x14\x12\x0F\xFF\x12\x10\x0E\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x14\x12\x10" + "\xFF\x13\x11\x0E\xFF\x12\x11\x0F\xFF" + "\x11\x10\x0C\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x14\x12\x10\xFF\x15\x14\x10\xFF\x15\x14\x10" + "\xFF\x15\x13\x10\xFF\x14\x13\x0F\xFF" + "\x12\x11\x0E\xFF\x13\x12\x0E\xFF\x15\x13\x10\xFF\x12\x11\x0D\xFF\x17\x16\x12\xFF\x17\x16\x13" + "\xFF\x12\x10\x0F\xFF\x14\x13\x0F\xFF" + "\x13\x13\x0F\xFF\x14\x14\x11\xFF\x14\x14\x11\xFF\x13\x12\x0E\xFF\x15\x13\x12\xFF\x14\x13\x10" + "\xFF\x13\x12\x0F\xFF\x15\x13\x11\xFF" + "\x18\x17\x13\xFF\x14\x13\x10\xFF\x17\x16\x13\xFF\x17\x16\x12\xFF\x16\x15\x12\xFF\x14\x12\x10" + "\xFF\x15\x15\x11\xFF\x12\x11\x0D\xFF" + "\x13\x10\x0F\xFF\x13\x12\x0F\xFF\x12\x11\x0D\xFF\x12\x13\x10\xFF\x11\x0F\x0D\xFF\x13\x12\x0E" + "\xFF\x14\x13\x0F\xFF\x12\x11\x0E\xFF" + "\x12\x11\x0D\xFF\x13\x12\x0F\xFF\x14\x12\x0F\xFF\x11\x10\x0D\xFF\x14\x13\x10\xFF\x13\x11\x10" + "\xFF\x11\x10\x0D\xFF\x15\x14\x10\xFF" + "\x18\x16\x14\xFF\x17\x17\x12\xFF\x14\x13\x0F\xFF\x15\x14\x11\xFF\x19\x18\x14\xFF\x16\x15\x11" + "\xFF\x17\x16\x11\xFF\x15\x14\x10\xFF" + "\x19\x17\x15\xFF\x1B\x1B\x16\xFF\x19\x18\x13\xFF\x16\x15\x13\xFF\x14\x13\x0F\xFF\x15\x14\x10" + "\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF" + "\x16\x14\x12\xFF\x15\x14\x10\xFF\x15\x13\x11\xFF\x14\x13\x0F\xFF\x18\x17\x13\xFF\x17\x16\x12" + "\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF" + "\x17\x16\x12\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x15\x14\x10" + "\xFF\x14\x13\x0F\xFF\x16\x15\x11\xFF" + "\x19\x17\x14\xFF\x16\x15\x12\xFF\x15\x14\x10\xFF\x15\x14\x10\xFF\x17\x16\x12\xFF\x17\x15\x13" + "\xFF\x18\x17\x12\xFF\x1B\x1A\x16\xFF" + "\x18\x15\x11\xFF\x14\x13\x0F\xFF\x17\x15\x11\xFF\x15\x13\x10\xFF\x15\x14\x10\xFF\x17\x15\x11" + "\xFF\x14\x13\x0F\xFF\x17\x16\x12\xFF" + "\x18\x17\x13\xFF\x16\x14\x12\xFF\x15\x14\x11\xFF\x16\x15\x11\xFF\x17\x16\x12\xFF\x16\x15\x12" + "\xFF\x18\x17\x14\xFF\x16\x15\x11\xFF" + "\x17\x16\x12\xFF\x19\x18\x14\xFF\x19\x18\x14\xFF\x15\x13\x11\xFF\x1B\x1A\x16\xFF\x1A\x19\x15" + "\xFF\x1B\x1A\x16\xFF\x19\x18\x14\xFF" + "\x19\x18\x14\xFF\x1A\x19\x15\xFF\x17\x16\x12\xFF\x13\x12\x0E\xFF\x1C\x1A\x16\xFF\x1B\x19\x15" + "\xFF\x1A\x19\x15\xFF\x16\x14\x10\xFF" + "\x14\x13\x10\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x19\x18\x14" + "\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF" + "\x16\x15\x11\xFF\x16\x15\x11\xFF\x1B\x19\x15\xFF\x1B\x19\x15\xFF\x17\x16\x12\xFF\x17\x16\x12" + "\xFF\x17\x16\x12\xFF\x18\x17\x13\xFF" + "\x1A\x19\x15\xFF\x19\x18\x14\xFF\x1B\x18\x16\xFF\x1C\x19\x16\xFF\x18\x17\x13\xFF\x18\x17\x13" + "\xFF\x19\x18\x13\xFF\x19\x17\x13\xFF" + "\x1B\x18\x14\xFF\x19\x16\x14\xFF\x16\x15\x11\xFF\x17\x16\x13\xFF\x19\x17\x13\xFF\x19\x18\x13" + "\xFF\x18\x17\x13\xFF\x17\x15\x11\xFF" + "\x16\x15\x12\xFF\x16\x16\x12\xFF\x18\x18\x13\xFF\x1B\x1A\x16\xFF\x1D\x1C\x17\xFF\x1A\x17\x13" + "\xFF\x1B\x19\x15\xFF\x1A\x18\x14\xFF" + "\x1A\x19\x15\xFF\x19\x18\x14\xFF\x19\x18\x14\xFF\x1A\x19\x15\xFF\x19\x18\x14\xFF\x18\x17\x13" + "\xFF\x19\x18\x14\xFF\x19\x17\x13\xFF" + "\x19\x18\x14\xFF\x1D\x1B\x17\xFF\x1C\x1B\x17\xFF\x1C\x1A\x16\xFF\x17\x16\x14\xFF\x1B\x1A\x16" + "\xFF\x1A\x19\x15\xFF\x1D\x1B\x17\xFF" + "\x1C\x1B\x17\xFF\x1C\x1A\x16\xFF\x1C\x1A\x16\xFF\x1C\x1A\x16\xFF\x1A\x19\x15\xFF\x19\x18\x14" + "\xFF\x17\x16\x12\xFF\x1A\x19\x15\xFF" + "\x1D\x1C\x17\xFF\x1B\x1A\x16\xFF\x1D\x1C\x18\xFF\x19\x18\x14\xFF\x19\x18\x14\xFF\x1A\x18\x14" + "\xFF\x1C\x1B\x17\xFF\x1A\x19\x15\xFF" + "\x1A\x1A\x16\xFF\x1C\x1B\x16\xFF\x1D\x1C\x18\xFF\x1B\x1A\x17\xFF\x1F\x1F\x19\xFF\x1C\x1C\x17" + "\xFF\x1D\x1B\x17\xFF\x1A\x19\x15\xFF" + "\x18\x19\x14\xFF\x1B\x19\x16\xFF\x1A\x19\x15\xFF\x1A\x1A\x16\xFF\x1C\x1B\x17\xFF\x1C\x1C\x17" + "\xFF\x1D\x1C\x16\xFF\x1E\x1D\x18\xFF" + "\x20\x1E\x19\xFF\x1E\x1B\x17\xFF\x1B\x19\x14\xFF\x1D\x1B\x17\xFF\x1F\x1E\x1A\xFF\x1B\x1A\x16" + "\xFF\x1D\x1C\x17\xFF\x1D\x1D\x17\xFF" + "\x1B\x1A\x16\xFF\x1C\x1B\x16\xFF\x1B\x1A\x15\xFF\x1F\x1F\x1A\xFF\x20\x1D\x19\xFF\x1E\x1C\x18" + "\xFF\x1E\x1C\x17\xFF\x1B\x1A\x16\xFF" + "\x1C\x1B\x17\xFF\x1C\x1C\x17\xFF\x1E\x1F\x19\xFF\x1F\x1D\x18\xFF\x1D\x1B\x19\xFF\x1D\x1C\x17" + "\xFF\x1D\x1C\x17\xFF\x1D\x1B\x17\xFF" + "\x1B\x1A\x17\xFF\x1D\x1D\x18\xFF\x1D\x1C\x17\xFF\x1E\x1D\x18\xFF\x1C\x1C\x17\xFF\x1E\x1C\x18" + "\xFF\x20\x1E\x1A\xFF\x1F\x1E\x19\xFF" + "\x1F\x1E\x19\xFF\x1D\x1C\x18\xFF\x1E\x1C\x18\xFF\x1F\x1D\x19\xFF\x1A\x19\x15\xFF\x1C\x1B\x16" + "\xFF\x1E\x1D\x19\xFF\x22\x21\x1B\xFF" + "\x20\x1F\x19\xFF\x1E\x1E\x18\xFF\x1E\x1E\x18\xFF\x1E\x1E\x18\xFF\x20\x20\x1A\xFF\x1C\x1A\x16" + "\xFF\x1D\x1B\x17\xFF\x1F\x1E\x19\xFF" + "\x20\x1F\x1A\xFF\x1F\x1C\x18\xFF\x20\x1E\x19\xFF\x21\x21\x1B\xFF\x1F\x1E\x19\xFF\x1D\x1D\x18" + "\xFF\x1C\x1B\x16\xFF\x1E\x1D\x18\xFF" + "\x1D\x1C\x18\xFF\x20\x20\x1A\xFF\x1E\x1D\x18\xFF\x1E\x1E\x19\xFF\x22\x20\x1C\xFF\x22\x1F\x1A" + "\xFF\x1E\x1C\x17\xFF\x20\x1D\x18\xFF" + "\x20\x1E\x1A\xFF\x1F\x1D\x19\xFF\x1F\x1E\x19\xFF\x20\x1F\x1B\xFF\x1F\x1E\x18\xFF\x1F\x1C\x17" + "\xFF\x1A\x19\x15\xFF\x1C\x1B\x16\xFF" + "\x1D\x1B\x18\xFF\x1C\x1B\x18\xFF\x1C\x1A\x16\xFF\x1A\x18\x15\xFF\x1C\x19\x15\xFF\x1B\x1B\x17" + "\xFF\x1A\x19\x15\xFF\x16\x16\x13\xFF" + "\x18\x15\x12\xFF\x14\x13\x10\xFF\x4F\x4B\x3F\xFF\x23\x22\x1D\xFF\x26\x25\x21\xFF\x21\x21\x1B" + "\xFF\x26\x25\x20\xFF\x22\x20\x1A\xFF" + "\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x28\x26\x21\xFF\x21\x20\x1A\xFF\x23\x22\x1D\xFF\x26\x25\x20" + "\xFF\x24\x23\x1E\xFF\x25\x24\x20\xFF" + "\x29\x28\x22\xFF\x25\x24\x20\xFF\x22\x20\x1B\xFF\x23\x22\x1C\xFF\x21\x20\x1B\xFF\x21\x20\x1B" + "\xFF\x21\x20\x1B\xFF\x21\x20\x1B\xFF" + "\x22\x21\x1B\xFF\x29\x28\x22\xFF\x21\x20\x1B\xFF\x21\x21\x1B\xFF\x21\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x25\x24\x20\xFF\x2A\x29\x23\xFF" + "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x28\x26\x22\xFF\x21\x20\x1A\xFF\x21\x20\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x25\x24\x20\xFF\x26\x25\x20\xFF\x25\x25\x20\xFF\x21\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B" + "\xFF\x25\x24\x20\xFF\x28\x26\x21\xFF" + "\x28\x26\x21\xFF\x25\x25\x20\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x24\x23\x1E" + "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF" + "\x26\x25\x20\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x26\x25\x21\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1C\xFF\x22\x21\x1B\xFF" + "\x28\x26\x21\xFF\x21\x21\x1B\xFF\x25\x24\x20\xFF\x24\x23\x1E\xFF\x27\x25\x21\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF" + "\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20" + "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF" + "\x26\x25\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1C\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x23\x22\x1C" + "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x29\x28\x23\xFF\x22\x21\x1B" + "\xFF\x25\x24\x20\xFF\x25\x24\x20\xFF" + "\x23\x22\x1C\xFF\x25\x24\x20\xFF\x24\x23\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x20\x1B" + "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF" + "\x26\x25\x21\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x25\x24\x20\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x24\x23\x1E\xFF\x28\x26\x21\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF\x24\x24\x1E" + "\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF" + "\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x25\x23\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF" + "\x26\x25\x20\xFF\x25\x24\x1F\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1D" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x25\x24\x1E\xFF\x29\x28\x22\xFF\x24\x23\x1E\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF" + "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x20" + "\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF" + "\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x28\x26\x21\xFF\x23\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x28\x26\x21\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x24\x23\x1F\xFF" + "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x26\x25\x21\xFF\x22\x21\x1B" + "\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF\x24\x22\x1E\xFF\x26\x25\x20" + "\xFF\x25\x24\x1F\xFF\x26\x25\x21\xFF" + "\x29\x28\x22\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x22\x1C\xFF\x23\x22\x1C" + "\xFF\x25\x24\x20\xFF\x2A\x29\x23\xFF" + "\x24\x22\x1D\xFF\x23\x22\x1C\xFF\x28\x26\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x25\x24\x20\xFF\x26\x25\x20\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B" + "\xFF\x26\x25\x20\xFF\x29\x27\x22\xFF" + "\x29\x26\x21\xFF\x26\x26\x21\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x25\x24\x1E" + "\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF" + "\x27\x25\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x24\x20\xFF\x26\x25\x21\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF" + "\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF" + "\x28\x26\x21\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x24\x23\x1E\xFF\x26\x25\x21\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x28\x26\x21\xFF" + "\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x21" + "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF" + "\x26\x25\x20\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x26\x25\x21\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF\x23\x22\x1C\xFF\x23\x22\x1C" + "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x29\x28\x23\xFF\x22\x21\x1B" + "\xFF\x26\x25\x20\xFF\x26\x25\x20\xFF" + "\x23\x22\x1C\xFF\x26\x25\x21\xFF\x25\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF" + "\x27\x26\x22\xFF\x22\x22\x1C\xFF\x29\x29\x23\xFF\x23\x26\x1F\xFF\x29\x2F\x27\xFF\x26\x2F\x25" + "\xFF\x27\x31\x27\xFF\x27\x32\x27\xFF" + "\x2A\x35\x2C\xFF\x27\x32\x27\xFF\x2A\x35\x2C\xFF\x29\x34\x2A\xFF\x2D\x37\x2D\xFF\x27\x32\x27" + "\xFF\x27\x32\x27\xFF\x28\x33\x28\xFF" + "\x27\x32\x27\xFF\x27\x32\x27\xFF\x2A\x35\x2C\xFF\x27\x32\x27\xFF\x2A\x35\x2C\xFF\x29\x35\x2B" + "\xFF\x2A\x35\x2C\xFF\x29\x34\x2B\xFF" + "\x29\x34\x2A\xFF\x29\x34\x2A\xFF\x29\x35\x2B\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x28\x33\x28" + "\xFF\x27\x32\x27\xFF\x29\x35\x2B\xFF" + "\x2B\x36\x2C\xFF\x2A\x35\x2B\xFF\x2A\x35\x2C\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x29\x34\x2A" + "\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF" + "\x2A\x35\x2B\xFF\x2E\x39\x2E\xFF\x2A\x35\x2B\xFF\x29\x34\x2A\xFF\x28\x33\x28\xFF\x4F\x4D\x3F" + "\xFF\x3B\x8B\x67\xFF\x3B\x8C\x68\xFF" + "\x3E\x93\x6D\xFF\x41\x99\x71\xFF\x43\x9F\x76\xFF\x45\xA4\x7A\xFF\x48\xAA\x7E\xFF\x49\xAE\x81" + "\xFF\x4B\xB2\x84\xFF\x4C\xB6\x87\xFF" + "\x4E\xBA\x89\xFF\x4D\xB3\x83\xFF\x45\x97\x70\xFF\x3D\x79\x5B\xFF\x35\x58\x44\xFF\x30\x48\x39" + "\xFF\x2D\x3A\x2F\xFF\x29\x31\x27\xFF" + "\x2B\x37\x2C\xFF\x3E\x79\x5B\xFF\x56\xCA\x95\xFF\x56\xCC\x96\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x52\xBC\x8B" + "\xFF\x49\x9D\x76\xFF\x3E\x7A\x5C\xFF" + "\x36\x5C\x47\xFF\x31\x4C\x3B\xFF\x2D\x3D\x2F\xFF\x29\x30\x28\xFF\x27\x2B\x23\xFF\x27\x29\x22" + "\xFF\x27\x28\x21\xFF\x28\x27\x21\xFF" + "\x27\x25\x20\xFF\x27\x24\x1F\xFF\x24\x24\x1E\xFF\x25\x23\x1F\xFF\x28\x25\x20\xFF\x29\x26\x20" + "\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF" + "\x26\x23\x1F\xFF\x27\x24\x1F\xFF\x29\x27\x21\xFF\x2A\x27\x21\xFF\x27\x24\x1F\xFF\x25\x24\x1E" + "\xFF\x24\x24\x1F\xFF\x26\x24\x1F\xFF" + "\x28\x25\x1F\xFF\x27\x25\x1E\xFF\x27\x25\x20\xFF\x25\x25\x1F\xFF\x29\x26\x21\xFF\x27\x27\x20" + "\xFF\x26\x25\x1F\xFF\x25\x23\x1E\xFF" + "\x26\x23\x1E\xFF\x23\x23\x1D\xFF\x26\x25\x1E\xFF\x27\x24\x1E\xFF\x25\x22\x1E\xFF\x27\x24\x1F" + "\xFF\x29\x26\x21\xFF\x26\x26\x1E\xFF" + "\x28\x27\x21\xFF\x28\x26\x21\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x25\x23\x1E" + "\xFF\x25\x23\x1F\xFF\x27\x24\x1F\xFF" + "\x27\x24\x1D\xFF\x29\x26\x20\xFF\x27\x26\x20\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x26\x23\x1E" + "\xFF\x25\x24\x1D\xFF\x26\x26\x20\xFF" + "\x25\x22\x1D\xFF\x26\x25\x1E\xFF\x27\x24\x1F\xFF\x25\x22\x1D\xFF\x27\x25\x1F\xFF\x27\x24\x1D" + "\xFF\x24\x22\x1D\xFF\x24\x22\x1D\xFF" + "\x26\x24\x1E\xFF\x25\x24\x1E\xFF\x27\x25\x1F\xFF\x28\x24\x1E\xFF\x27\x24\x1F\xFF\x28\x25\x20" + "\xFF\x27\x24\x1F\xFF\x29\x28\x20\xFF" + "\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x24\x23\x1D\xFF\x25\x24\x1E\xFF\x25\x22\x1D\xFF\x25\x23\x1E" + "\xFF\x26\x24\x1F\xFF\x26\x24\x1E\xFF" + "\x22\x23\x1D\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x25\x24\x1F\xFF\x24\x23\x1E\xFF\x26\x23\x1E" + "\xFF\x27\x25\x20\xFF\x25\x24\x1E\xFF" + "\x27\x25\x20\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x24\x21\x1C\xFF\x25\x24\x1E\xFF\x24\x24\x1E" + "\xFF\x23\x23\x1D\xFF\x25\x23\x1E\xFF" + "\x26\x25\x1F\xFF\x23\x21\x1C\xFF\x24\x23\x1D\xFF\x26\x24\x1F\xFF\x23\x22\x1D\xFF\x23\x21\x1C" + "\xFF\x25\x23\x1E\xFF\x28\x25\x20\xFF" + "\x28\x26\x1F\xFF\x28\x25\x1E\xFF\x25\x24\x1D\xFF\x26\x25\x20\xFF\x24\x22\x1E\xFF\x25\x23\x1E" + "\xFF\x24\x21\x1D\xFF\x26\x23\x1E\xFF" + "\x27\x2D\x26\xFF\x39\x6A\x50\xFF\x53\xC1\x90\xFF\x58\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98" + "\xFF\x41\x85\x65\xFF\x2A\x35\x2C\xFF" + "\x27\x27\x21\xFF\x26\x25\x1F\xFF\x24\x23\x1E\xFF\x25\x22\x1D\xFF\x26\x22\x1F\xFF\x24\x23\x1E" + "\xFF\x24\x24\x1E\xFF\x22\x21\x1C\xFF" + "\x20\x20\x1A\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x29\x26\x21\xFF\x27\x24\x1F\xFF\x26\x25\x1E" + "\xFF\x27\x27\x1F\xFF\x27\x2A\x24\xFF" + "\x31\x52\x3E\xFF\x51\xBA\x8A\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x97" + "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF" + "\x56\xCC\x97\xFF\x48\x9C\x75\xFF\x2D\x3E\x31\xFF\x27\x28\x22\xFF\x25\x22\x1E\xFF\x25\x23\x1E" + "\xFF\x25\x23\x1E\xFF\x27\x24\x1F\xFF" + "\x28\x26\x20\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x24\x23\x1E\xFF\x26\x23\x1E\xFF\x25\x24\x1E" + "\xFF\x25\x22\x1E\xFF\x25\x22\x1E\xFF" + "\x27\x24\x1F\xFF\x25\x22\x1D\xFF\x24\x22\x1E\xFF\x22\x22\x1D\xFF\x24\x23\x1D\xFF\x21\x21\x1B" + "\xFF\x1F\x1E\x19\xFF\x23\x20\x1B\xFF" + "\x20\x1F\x1A\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x24\x21\x1D\xFF\x23\x23\x1D\xFF\x24\x23\x1D" + "\xFF\x23\x21\x1C\xFF\x24\x21\x1C\xFF" + "\x27\x24\x1F\xFF\x24\x22\x1D\xFF\x22\x21\x1B\xFF\x26\x23\x1E\xFF\x26\x25\x1E\xFF\x23\x23\x1D" + "\xFF\x25\x22\x1D\xFF\x28\x24\x20\xFF" + "\x24\x21\x1F\xFF\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x23\x23\x1D\xFF\x23\x21\x1C\xFF\x24\x21\x1C" + "\xFF\x24\x21\x1C\xFF\x21\x20\x1A\xFF" + "\x22\x21\x1B\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x23\x21\x1F\xFF\x23\x21\x1C\xFF\x23\x22\x1C" + "\xFF\x24\x23\x1E\xFF\x23\x21\x1C\xFF" + "\x24\x24\x1E\xFF\x23\x23\x1D\xFF\x21\x21\x1B\xFF\x24\x23\x1D\xFF\x22\x22\x1C\xFF\x25\x23\x1E" + "\xFF\x24\x21\x1C\xFF\x21\x1D\x1A\xFF" + "\x23\x22\x1D\xFF\x25\x22\x1D\xFF\x21\x20\x1B\xFF\x21\x1F\x1B\xFF\x21\x1F\x1B\xFF\x25\x23\x1E" + "\xFF\x24\x21\x1D\xFF\x24\x23\x1D\xFF" + "\x26\x24\x1E\xFF\x22\x21\x1C\xFF\x23\x21\x1C\xFF\x22\x22\x1C\xFF\x22\x20\x1B\xFF\x24\x24\x1E" + "\xFF\x25\x23\x1E\xFF\x23\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x21\x21\x1C\xFF\x22\x21\x1C\xFF\x23\x22\x1C\xFF\x21\x1F\x1B\xFF\x23\x20\x1C" + "\xFF\x24\x23\x1D\xFF\x21\x21\x1B\xFF" + "\x22\x22\x1D\xFF\x25\x24\x1E\xFF\x26\x23\x1E\xFF\x25\x22\x1E\xFF\x22\x22\x1C\xFF\x23\x23\x1D" + "\xFF\x23\x23\x1D\xFF\x24\x21\x1C\xFF" + "\x1F\x1E\x1A\xFF\x21\x20\x1A\xFF\x1F\x1D\x18\xFF\x1E\x1D\x19\xFF\x22\x22\x1C\xFF\x21\x21\x1B" + "\xFF\x21\x20\x1B\xFF\x20\x1F\x1A\xFF" + "\x24\x23\x1D\xFF\x21\x20\x1A\xFF\x21\x1F\x1A\xFF\x22\x21\x1C\xFF\x25\x23\x1E\xFF\x23\x22\x1D" + "\xFF\x23\x21\x1D\xFF\x24\x21\x1C\xFF" + "\x23\x21\x1C\xFF\x25\x23\x1E\xFF\x23\x21\x1C\xFF\x22\x22\x1C\xFF\x21\x20\x1B\xFF\x21\x21\x1B" + "\xFF\x23\x21\x1B\xFF\x24\x21\x1C\xFF" + "\x23\x23\x1D\xFF\x23\x21\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x20\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1D\xFF\x20\x1F\x1B\xFF" + "\x25\x23\x1E\xFF\x23\x23\x1E\xFF\x23\x21\x1C\xFF\x28\x25\x20\xFF\x21\x1F\x1B\xFF\x20\x1E\x1A" + "\xFF\x23\x21\x1C\xFF\x23\x21\x1C\xFF" + "\x22\x20\x1B\xFF\x21\x20\x1B\xFF\x23\x22\x1D\xFF\x23\x22\x1C\xFF\x23\x21\x1C\xFF\x24\x23\x1D" + "\xFF\x24\x22\x1C\xFF\x23\x21\x1D\xFF" + "\x12\x11\x0E\xFF\x13\x11\x0F\xFF\x13\x11\x0F\xFF\x12\x11\x0D\xFF\x15\x13\x12\xFF\x15\x14\x11" + "\xFF\x15\x15\x10\xFF\x11\x0F\x0E\xFF" + "\x11\x0E\x0D\xFF\x11\x10\x0E\xFF\x12\x10\x0E\xFF\x11\x0E\x0D\xFF\x10\x0E\x0D\xFF\x12\x10\x0E" + "\xFF\x11\x0F\x0D\xFF\x11\x10\x0E\xFF" + "\x13\x12\x0F\xFF\x13\x12\x0E\xFF\x13\x12\x0F\xFF\x13\x12\x0F\xFF\x10\x10\x0C\xFF\x12\x11\x0E" + "\xFF\x13\x12\x0F\xFF\x12\x12\x0F\xFF" + "\x11\x0F\x0E\xFF\x14\x13\x0F\xFF\x13\x12\x0F\xFF\x14\x13\x0F\xFF\x13\x12\x0F\xFF\x13\x11\x10" + "\xFF\x12\x11\x0F\xFF\x11\x11\x0E\xFF" + "\x11\x11\x0E\xFF\x16\x14\x11\xFF\x13\x11\x0F\xFF\x13\x12\x10\xFF\x16\x14\x10\xFF\x15\x13\x10" + "\xFF\x15\x14\x11\xFF\x12\x12\x0E\xFF" + "\x14\x13\x0F\xFF\x16\x14\x11\xFF\x14\x12\x0F\xFF\x15\x15\x11\xFF\x16\x15\x11\xFF\x15\x14\x10" + "\xFF\x13\x12\x0F\xFF\x12\x11\x0E\xFF" + "\x13\x11\x0F\xFF\x14\x12\x10\xFF\x14\x12\x11\xFF\x13\x13\x10\xFF\x16\x15\x11\xFF\x15\x15\x10" + "\xFF\x13\x13\x0E\xFF\x17\x15\x11\xFF" + "\x10\x10\x0D\xFF\x14\x13\x0F\xFF\x15\x13\x10\xFF\x13\x12\x0F\xFF\x15\x14\x11\xFF\x15\x14\x10" + "\xFF\x11\x10\x0D\xFF\x14\x13\x0F\xFF" + "\x16\x14\x10\xFF\x12\x11\x0E\xFF\x11\x10\x0D\xFF\x14\x12\x10\xFF\x14\x13\x10\xFF\x14\x13\x10" + "\xFF\x13\x12\x0F\xFF\x13\x11\x0E\xFF" + "\x17\x15\x11\xFF\x18\x16\x13\xFF\x18\x17\x13\xFF\x12\x11\x0E\xFF\x15\x14\x10\xFF\x13\x12\x0E" + "\xFF\x14\x13\x0F\xFF\x14\x12\x0F\xFF" + "\x18\x17\x13\xFF\x19\x18\x14\xFF\x16\x15\x11\xFF\x12\x11\x0D\xFF\x14\x13\x10\xFF\x15\x14\x11" + "\xFF\x14\x13\x10\xFF\x13\x13\x0E\xFF" + "\x15\x14\x11\xFF\x18\x17\x13\xFF\x14\x12\x0F\xFF\x13\x12\x0F\xFF\x13\x12\x0E\xFF\x14\x13\x10" + "\xFF\x16\x15\x11\xFF\x18\x16\x13\xFF" + "\x16\x14\x12\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x15\x14\x10\xFF\x17\x16\x12\xFF\x15\x14\x11" + "\xFF\x17\x15\x13\xFF\x15\x14\x11\xFF" + "\x16\x14\x10\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x14\x13\x10\xFF\x16\x16\x12\xFF\x17\x16\x12" + "\xFF\x16\x15\x11\xFF\x14\x13\x0F\xFF" + "\x17\x16\x12\xFF\x17\x16\x12\xFF\x15\x14\x10\xFF\x14\x13\x0F\xFF\x16\x15\x11\xFF\x13\x12\x0F" + "\xFF\x13\x12\x0E\xFF\x13\x11\x0F\xFF" + "\x15\x14\x11\xFF\x15\x14\x11\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF\x1A\x19\x15\xFF\x18\x17\x13" + "\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF" + "\x13\x13\x0F\xFF\x17\x15\x11\xFF\x18\x16\x14\xFF\x15\x14\x11\xFF\x15\x14\x10\xFF\x19\x17\x13" + "\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF" + "\x1A\x19\x15\xFF\x19\x18\x14\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x16\x15\x11" + "\xFF\x15\x14\x10\xFF\x16\x15\x11\xFF" + "\x19\x18\x14\xFF\x17\x16\x12\xFF\x17\x16\x12\xFF\x16\x14\x11\xFF\x19\x18\x14\xFF\x17\x16\x12" + "\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF" + "\x1B\x1A\x16\xFF\x16\x16\x12\xFF\x17\x16\x12\xFF\x16\x15\x11\xFF\x17\x16\x12\xFF\x18\x17\x13" + "\xFF\x19\x18\x14\xFF\x17\x16\x12\xFF" + "\x15\x14\x10\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF\x17\x16\x12\xFF\x1A\x18\x14\xFF\x1A\x19\x15" + "\xFF\x19\x18\x14\xFF\x19\x17\x13\xFF" + "\x19\x18\x14\xFF\x16\x15\x11\xFF\x17\x16\x12\xFF\x19\x18\x14\xFF\x18\x17\x13\xFF\x19\x18\x14" + "\xFF\x18\x17\x13\xFF\x19\x18\x14\xFF" + "\x1A\x18\x14\xFF\x1A\x17\x14\xFF\x1A\x18\x14\xFF\x13\x13\x11\xFF\x19\x19\x15\xFF\x19\x18\x14" + "\xFF\x19\x19\x14\xFF\x1B\x1A\x16\xFF" + "\x1A\x18\x14\xFF\x18\x17\x13\xFF\x15\x14\x11\xFF\x19\x18\x14\xFF\x1A\x18\x14\xFF\x1B\x1C\x16" + "\xFF\x1A\x19\x15\xFF\x18\x16\x12\xFF" + "\x18\x17\x13\xFF\x1A\x19\x14\xFF\x19\x18\x13\xFF\x1A\x19\x15\xFF\x19\x18\x15\xFF\x1A\x19\x15" + "\xFF\x19\x18\x14\xFF\x1A\x1A\x15\xFF" + "\x1A\x18\x14\xFF\x19\x19\x15\xFF\x1A\x1A\x16\xFF\x1A\x19\x15\xFF\x17\x17\x13\xFF\x19\x19\x15" + "\xFF\x1B\x19\x15\xFF\x19\x17\x13\xFF" + "\x1A\x1A\x15\xFF\x1A\x19\x14\xFF\x1D\x1C\x18\xFF\x1E\x1C\x18\xFF\x19\x17\x14\xFF\x19\x17\x13" + "\xFF\x1A\x19\x15\xFF\x1C\x1A\x16\xFF" + "\x1B\x19\x15\xFF\x18\x16\x13\xFF\x18\x16\x13\xFF\x19\x18\x14\xFF\x18\x17\x13\xFF\x18\x17\x13" + "\xFF\x1A\x19\x14\xFF\x1C\x1B\x16\xFF" + "\x1E\x1B\x17\xFF\x1A\x1A\x15\xFF\x1C\x1A\x16\xFF\x1B\x1A\x15\xFF\x1C\x1B\x17\xFF\x1B\x1B\x16" + "\xFF\x1E\x1C\x18\xFF\x1D\x1B\x17\xFF" + "\x1D\x1B\x16\xFF\x1C\x1B\x16\xFF\x1B\x1A\x17\xFF\x1A\x18\x16\xFF\x1B\x19\x15\xFF\x1B\x1B\x16" + "\xFF\x1A\x19\x15\xFF\x1B\x1A\x16\xFF" + "\x20\x1F\x19\xFF\x1B\x1A\x17\xFF\x1B\x1A\x16\xFF\x19\x19\x15\xFF\x1D\x1C\x18\xFF\x20\x20\x19" + "\xFF\x1F\x1F\x18\xFF\x1D\x1C\x18\xFF" + "\x1E\x1E\x18\xFF\x1C\x1A\x16\xFF\x1A\x19\x14\xFF\x19\x18\x14\xFF\x1C\x1A\x17\xFF\x1B\x1A\x16" + "\xFF\x1E\x1E\x19\xFF\x1F\x1E\x18\xFF" + "\x1C\x1B\x17\xFF\x1B\x1A\x16\xFF\x1C\x1C\x17\xFF\x1F\x1E\x19\xFF\x1F\x1F\x19\xFF\x1F\x1C\x18" + "\xFF\x1E\x1C\x17\xFF\x1C\x1C\x17\xFF" + "\x1D\x1B\x16\xFF\x1F\x1D\x19\xFF\x1F\x1E\x19\xFF\x1C\x1B\x16\xFF\x1E\x1C\x17\xFF\x1B\x1A\x16" + "\xFF\x1D\x1B\x18\xFF\x1E\x1C\x18\xFF" + "\x1C\x1C\x17\xFF\x1F\x1F\x19\xFF\x1D\x1C\x17\xFF\x1E\x1B\x17\xFF\x1F\x1D\x18\xFF\x1D\x1C\x18" + "\xFF\x1D\x1D\x18\xFF\x21\x20\x1B\xFF" + "\x21\x1F\x1C\xFF\x1E\x1D\x19\xFF\x1E\x1C\x18\xFF\x1F\x1D\x19\xFF\x1D\x1C\x18\xFF\x1E\x1C\x17" + "\xFF\x1F\x1D\x18\xFF\x20\x1F\x19\xFF" + "\x20\x1D\x19\xFF\x1C\x1B\x16\xFF\x20\x1F\x1A\xFF\x22\x1F\x1B\xFF\x21\x1F\x1A\xFF\x1E\x1C\x18" + "\xFF\x1D\x1B\x17\xFF\x1E\x1C\x18\xFF" + "\x1D\x1C\x18\xFF\x1E\x1C\x18\xFF\x1F\x1D\x19\xFF\x20\x1F\x1B\xFF\x1F\x1E\x1A\xFF\x1F\x1E\x1A" + "\xFF\x21\x1F\x1A\xFF\x1E\x1D\x19\xFF" + "\x20\x1F\x1B\xFF\x1E\x1E\x18\xFF\x1D\x1D\x18\xFF\x20\x1E\x1A\xFF\x20\x1E\x1A\xFF\x20\x1E\x19" + "\xFF\x1D\x1C\x17\xFF\x1C\x1C\x16\xFF" + "\x1C\x1B\x17\xFF\x1F\x1C\x18\xFF\x20\x1E\x19\xFF\x20\x1E\x1A\xFF\x1D\x1B\x16\xFF\x1C\x1C\x16" + "\xFF\x1C\x1D\x18\xFF\x1D\x1D\x18\xFF" + "\x1B\x19\x16\xFF\x1B\x1B\x15\xFF\x1A\x19\x15\xFF\x1B\x19\x16\xFF\x1C\x1A\x16\xFF\x1D\x1B\x18" + "\xFF\x17\x16\x13\xFF\x16\x15\x11\xFF" + "\x15\x13\x10\xFF\x14\x13\x10\xFF\x4F\x4B\x3E\xFF\x28\x26\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x28\x26\x21\xFF\x26\x25\x20\xFF" + "\x29\x28\x23\xFF\x22\x22\x1D\xFF\x26\x25\x20\xFF\x22\x21\x1C\xFF\x24\x23\x1E\xFF\x25\x24\x20" + "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF" + "\x22\x20\x1B\xFF\x25\x25\x20\xFF\x26\x25\x20\xFF\x24\x22\x1D\xFF\x28\x26\x21\xFF\x23\x22\x1D" + "\xFF\x24\x23\x1E\xFF\x28\x26\x21\xFF" + "\x21\x20\x1A\xFF\x28\x26\x21\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x24\x23\x1D\xFF" + "\x23\x22\x1C\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF\x25\x24\x20" + "\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF" + "\x23\x22\x1C\xFF\x21\x20\x1B\xFF\x22\x20\x1B\xFF\x21\x20\x1A\xFF\x22\x21\x1B\xFF\x26\x25\x20" + "\xFF\x23\x21\x1C\xFF\x23\x22\x1D\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x23\x1E\xFF\x28\x26\x21\xFF\x23\x22\x1E\xFF\x24\x22\x1D" + "\xFF\x22\x21\x1B\xFF\x24\x23\x1D\xFF" + "\x28\x26\x21\xFF\x24\x22\x1D\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x20\x1B\xFF\x23\x21\x1C" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x25\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x21" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1E\xFF" + "\x21\x21\x1B\xFF\x26\x25\x20\xFF\x25\x24\x20\xFF\x22\x22\x1C\xFF\x25\x23\x1E\xFF\x23\x22\x1C" + "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF" + "\x24\x22\x1D\xFF\x23\x22\x1D\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x25\x24\x20" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF" + "\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x21\x20\x1A\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x24\x23\x1F\xFF" + "\x26\x25\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x23\x1F\xFF\x25\x24\x1F\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x25\x24\x20\xFF" + "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x28\x26\x21" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1D\xFF" + "\x22\x22\x1C\xFF\x28\x26\x22\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x23\x22\x1D\xFF\x22\x21\x1B" + "\xFF\x26\x25\x21\xFF\x22\x21\x1B\xFF" + "\x24\x23\x1E\xFF\x26\x24\x20\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x26\x25\x21\xFF\x23\x22\x1C\xFF\x24\x24\x1E\xFF\x22\x21\x1B\xFF\x24\x23\x1E" + "\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF" + "\x24\x23\x1E\xFF\x26\x25\x20\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF" + "\x23\x22\x1C\xFF\x26\x25\x20\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF" + "\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x22\x21\x1B" + "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF" + "\x28\x26\x21\xFF\x23\x22\x1C\xFF\x23\x23\x1E\xFF\x25\x23\x1E\xFF\x24\x23\x1E\xFF\x29\x28\x22" + "\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x22\x22\x1C\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x28\x26\x22" + "\xFF\x22\x21\x1B\xFF\x24\x22\x1D\xFF" + "\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x26\x24\x20" + "\xFF\x29\x28\x22\xFF\x22\x21\x1C\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x28\x26\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x28\x26\x21\xFF\x26\x25\x20\xFF" + "\x29\x28\x23\xFF\x23\x23\x1E\xFF\x27\x25\x20\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x25\x24\x20" + "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x25\x25\x20\xFF\x27\x25\x20\xFF\x24\x22\x1D\xFF\x28\x26\x21\xFF\x24\x23\x1E" + "\xFF\x25\x23\x1E\xFF\x28\x26\x21\xFF" + "\x22\x21\x1B\xFF\x28\x26\x21\xFF\x24\x22\x1D\xFF\x23\x22\x1C\xFF\x23\x22\x1C\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x25\x24\x20" + "\xFF\x29\x28\x22\xFF\x23\x22\x1C\xFF" + "\x23\x22\x1C\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x27\x26\x21" + "\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x24\x1F\xFF\x28\x26\x21\xFF\x23\x22\x1E\xFF\x24\x23\x1E" + "\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF" + "\x28\x26\x21\xFF\x24\x23\x1E\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x23\x22\x1C" + "\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF" + "\x23\x22\x1C\xFF\x25\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x26\x21" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1E\xFF" + "\x22\x21\x1B\xFF\x26\x25\x20\xFF\x26\x24\x20\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x23\x22\x1C" + "\xFF\x28\x26\x21\xFF\x22\x21\x1B\xFF" + "\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x25\x24\x1F\xFF\x23\x22\x1C\xFF" + "\x24\x22\x1D\xFF\x24\x23\x1E\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x26\x25\x20" + "\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF" + "\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x29\x28\x22\xFF\x22\x21\x1B\xFF\x22\x21\x1B\xFF\x22\x21\x1B" + "\xFF\x22\x21\x1B\xFF\x25\x24\x20\xFF" + "\x27\x26\x21\xFF\x22\x21\x1B\xFF\x23\x22\x1C\xFF\x25\x24\x1F\xFF\x25\x24\x1F\xFF\x22\x21\x1B" + "\xFF\x23\x22\x1C\xFF\x26\x25\x20\xFF" + "\x22\x21\x1B\xFF\x27\x25\x20\xFF\x22\x21\x1B\xFF\x25\x24\x1F\xFF\x22\x21\x1B\xFF\x28\x27\x21" + "\xFF\x22\x22\x1B\xFF\x24\x24\x1F\xFF" + "\x23\x26\x1F\xFF\x2A\x2E\x28\xFF\x25\x2D\x24\xFF\x27\x31\x27\xFF\x29\x34\x2A\xFF\x27\x32\x27" + "\xFF\x2B\x36\x2D\xFF\x27\x32\x27\xFF" + "\x29\x34\x2A\xFF\x2A\x35\x2C\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF\x27\x32\x27" + "\xFF\x27\x32\x27\xFF\x27\x32\x27\xFF" + "\x27\x32\x27\xFF\x2B\x36\x2D\xFF\x28\x33\x28\xFF\x2A\x35\x2B\xFF\x27\x32\x27\xFF\x29\x34\x2A" + "\xFF\x28\x33\x28\xFF\x28\x33\x28\xFF" + "\x2A\x35\x2B\xFF\x2A\x35\x2C\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF\x28\x33\x28\xFF\x27\x32\x27" + "\xFF\x27\x32\x27\xFF\x28\x33\x28\xFF" + "\x28\x33\x28\xFF\x2B\x36\x2C\xFF\x27\x32\x27\xFF\x2A\x35\x2B\xFF\x27\x32\x27\xFF\x27\x32\x27" + "\xFF\x28\x33\x28\xFF\x27\x32\x27\xFF" + "\x29\x34\x2A\xFF\x28\x33\x28\xFF\x29\x34\x2A\xFF\x28\x33\x28\xFF\x2A\x35\x2B\xFF\x4F\x4D\x3F" + "\xFF\x3B\x8B\x67\xFF\x3B\x8C\x68\xFF" + "\x3E\x93\x6D\xFF\x41\x99\x71\xFF\x43\x9F\x76\xFF\x45\xA4\x7A\xFF\x48\xAA\x7E\xFF\x4A\xAF\x81" + "\xFF\x4C\xB3\x84\xFF\x4D\xB7\x87\xFF" + "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x50\xC0\x8E\xFF\x51\xC2\x8F\xFF\x52\xBC\x8B\xFF\x4A\xA7\x7C" + "\xFF\x44\x90\x6B\xFF\x3E\x78\x5B\xFF" + "\x36\x63\x4B\xFF\x41\x87\x66\xFF\x55\xCB\x96\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x53\xC2\x90\xFF\x4D\xAD\x81\xFF\x47\x96\x70\xFF\x40\x7E\x5F\xFF\x39\x63\x4C\xFF\x31\x48\x39" + "\xFF\x2D\x36\x2B\xFF\x27\x2C\x24\xFF" + "\x25\x27\x21\xFF\x27\x27\x21\xFF\x26\x24\x1E\xFF\x25\x24\x1E\xFF\x28\x25\x1F\xFF\x26\x24\x1E" + "\xFF\x25\x25\x20\xFF\x26\x24\x1F\xFF" + "\x25\x22\x1E\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x29\x26\x21\xFF\x25\x23\x1D\xFF\x25\x23\x1D" + "\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF" + "\x2A\x27\x21\xFF\x29\x26\x21\xFF\x28\x25\x20\xFF\x26\x26\x20\xFF\x29\x26\x21\xFF\x29\x26\x21" + "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF" + "\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x25\x23\x1E\xFF\x24\x22\x1E\xFF\x28\x25\x20\xFF\x27\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x25\x24\x1F\xFF" + "\x28\x27\x1F\xFF\x27\x25\x1F\xFF\x25\x25\x1F\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF\x26\x23\x1F" + "\xFF\x25\x23\x1E\xFF\x28\x25\x20\xFF" + "\x28\x25\x20\xFF\x28\x26\x20\xFF\x26\x24\x1F\xFF\x26\x25\x1E\xFF\x26\x24\x1E\xFF\x26\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x27\x25\x20\xFF" + "\x25\x23\x1E\xFF\x27\x25\x1E\xFF\x27\x26\x1F\xFF\x26\x24\x1D\xFF\x25\x24\x1D\xFF\x28\x26\x1F" + "\xFF\x28\x27\x21\xFF\x26\x25\x1F\xFF" + "\x25\x23\x1E\xFF\x26\x25\x1F\xFF\x27\x24\x1F\xFF\x27\x25\x1E\xFF\x24\x23\x1E\xFF\x27\x25\x1F" + "\xFF\x27\x25\x20\xFF\x27\x26\x1F\xFF" + "\x29\x26\x20\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF\x26\x23\x1D\xFF\x23\x22\x1C\xFF\x26\x25\x1F" + "\xFF\x25\x25\x1E\xFF\x25\x24\x1E\xFF" + "\x25\x24\x1E\xFF\x24\x23\x1D\xFF\x24\x23\x1E\xFF\x25\x25\x1F\xFF\x25\x24\x1E\xFF\x25\x23\x1E" + "\xFF\x23\x23\x1E\xFF\x25\x24\x1F\xFF" + "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x23\x1F\xFF\x25\x22\x1D\xFF\x27\x24\x1E\xFF\x27\x25\x1F" + "\xFF\x25\x24\x1E\xFF\x25\x23\x1E\xFF" + "\x26\x24\x1F\xFF\x23\x22\x1D\xFF\x26\x24\x1E\xFF\x24\x24\x1E\xFF\x26\x24\x1F\xFF\x25\x23\x1E" + "\xFF\x26\x23\x1E\xFF\x26\x24\x1E\xFF" + "\x26\x24\x1E\xFF\x27\x25\x1E\xFF\x25\x23\x1E\xFF\x28\x25\x20\xFF\x24\x21\x1E\xFF\x25\x23\x1E" + "\xFF\x26\x23\x1F\xFF\x25\x22\x1C\xFF" + "\x26\x29\x22\xFF\x32\x53\x3F\xFF\x4E\xAF\x82\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x4A\xA4\x7A\xFF\x2C\x43\x35\xFF" + "\x28\x28\x22\xFF\x26\x25\x20\xFF\x26\x24\x1F\xFF\x25\x23\x1F\xFF\x25\x23\x1C\xFF\x25\x22\x1D" + "\xFF\x25\x24\x1D\xFF\x23\x23\x1D\xFF" + "\x23\x23\x1D\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x27\x25\x20\xFF\x23\x22\x1C\xFF\x24\x22\x1D" + "\xFF\x24\x23\x1D\xFF\x24\x25\x1F\xFF" + "\x2C\x40\x30\xFF\x47\x98\x71\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x56\xCC\x98\xFF\x56\xCC\x97" + "\xFF\x57\xCC\x97\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x4E\xB2\x84\xFF\x32\x56\x42\xFF\x25\x2A\x22\xFF\x25\x25\x1F\xFF\x26\x24\x1F" + "\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF" + "\x24\x24\x1E\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x27\x24\x1E\xFF\x25\x23\x1D" + "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF" + "\x25\x22\x1D\xFF\x24\x23\x1E\xFF\x25\x23\x1E\xFF\x23\x22\x1E\xFF\x24\x22\x1E\xFF\x23\x20\x1B" + "\xFF\x23\x22\x1C\xFF\x26\x23\x1E\xFF" + "\x25\x23\x1E\xFF\x23\x22\x1C\xFF\x24\x23\x1E\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF\x27\x26\x1F" + "\xFF\x25\x24\x1E\xFF\x22\x20\x1B\xFF" + "\x23\x20\x1B\xFF\x23\x21\x1C\xFF\x23\x21\x1C\xFF\x24\x22\x1D\xFF\x23\x21\x1C\xFF\x23\x22\x1C" + "\xFF\x26\x24\x1E\xFF\x25\x23\x1E\xFF" + "\x23\x21\x1C\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF\x22\x21\x1B\xFF\x22\x22\x1C\xFF\x24\x22\x1C" + "\xFF\x24\x21\x1C\xFF\x23\x21\x1C\xFF" + "\x25\x23\x1E\xFF\x26\x22\x1D\xFF\x25\x22\x1D\xFF\x23\x21\x1D\xFF\x24\x22\x1D\xFF\x21\x21\x1B" + "\xFF\x23\x22\x1D\xFF\x24\x23\x1D\xFF" + "\x23\x22\x1D\xFF\x22\x21\x1C\xFF\x23\x20\x1C\xFF\x23\x21\x1D\xFF\x21\x21\x1C\xFF\x25\x23\x1E" + "\xFF\x28\x25\x20\xFF\x23\x20\x1B\xFF" + "\x23\x21\x1C\xFF\x25\x22\x1D\xFF\x26\x23\x1E\xFF\x24\x23\x1E\xFF\x22\x21\x1B\xFF\x22\x20\x1C" + "\xFF\x21\x20\x1B\xFF\x23\x22\x1D\xFF" + "\x26\x24\x1E\xFF\x24\x21\x1D\xFF\x25\x22\x1E\xFF\x24\x22\x1D\xFF\x24\x23\x1E\xFF\x23\x22\x1D" + "\xFF\x23\x22\x1D\xFF\x25\x24\x1E\xFF" + "\x24\x21\x1D\xFF\x27\x24\x20\xFF\x25\x22\x1D\xFF\x26\x24\x1E\xFF\x22\x21\x1B\xFF\x22\x21\x1C" + "\xFF\x24\x21\x1D\xFF\x25\x23\x1F\xFF" + "\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF\x23\x21\x1D\xFF\x24\x22\x1D" + "\xFF\x22\x1F\x1A\xFF\x22\x21\x1B\xFF" + "\x20\x1E\x1A\xFF\x21\x20\x1B\xFF\x22\x20\x1B\xFF\x21\x20\x1A\xFF\x22\x22\x1C\xFF\x23\x21\x1C" + "\xFF\x23\x21\x1C\xFF\x21\x20\x1B\xFF" + "\x23\x20\x1C\xFF\x21\x20\x1B\xFF\x22\x20\x1B\xFF\x21\x1F\x1B\xFF\x23\x21\x1C\xFF\x23\x21\x1C" + "\xFF\x24\x22\x1D\xFF\x21\x20\x1B\xFF" + "\x20\x1F\x1A\xFF\x26\x23\x1E\xFF\x25\x22\x1D\xFF\x23\x22\x1B\xFF\x21\x1F\x1B\xFF\x22\x1F\x1B" + "\xFF\x24\x21\x1D\xFF\x25\x23\x1D\xFF" + "\x25\x22\x1D\xFF\x21\x21\x1B\xFF\x21\x20\x1B\xFF\x22\x20\x1C\xFF\x22\x21\x1B\xFF\x1F\x1E\x19" + "\xFF\x22\x20\x1B\xFF\x21\x20\x1B\xFF" + "\x25\x23\x1E\xFF\x21\x21\x1C\xFF\x20\x1F\x1B\xFF\x21\x20\x1B\xFF\x24\x23\x1E\xFF\x22\x22\x1C" + "\xFF\x21\x20\x1B\xFF\x20\x1F\x1A\xFF" + "\x23\x21\x1C\xFF\x24\x22\x1D\xFF\x24\x21\x1D\xFF\x25\x24\x1E\xFF\x22\x21\x1C\xFF\x23\x22\x1C" + "\xFF\x24\x22\x1E\xFF\x22\x21\x1D\xFF" + "\x18\x18\x13\xFF\x15\x14\x10\xFF\x15\x13\x10\xFF\x13\x12\x0E\xFF\x12\x10\x0D\xFF\x12\x11\x0D" + "\xFF\x16\x15\x10\xFF\x14\x12\x10\xFF" + "\x12\x10\x10\xFF\x12\x10\x0E\xFF\x12\x11\x0E\xFF\x11\x10\x0D\xFF\x14\x13\x0F\xFF\x10\x0F\x0C" + "\xFF\x10\x0E\x0D\xFF\x10\x0E\x0D\xFF" + "\x13\x12\x0E\xFF\x10\x0F\x0D\xFF\x10\x0F\x0C\xFF\x13\x11\x0F\xFF\x13\x12\x0E\xFF\x13\x12\x0E" + "\xFF\x14\x13\x0F\xFF\x12\x11\x0F\xFF" + "\x12\x10\x0E\xFF\x13\x12\x0E\xFF\x11\x10\x0D\xFF\x14\x13\x0F\xFF\x14\x13\x10\xFF\x12\x10\x0E" + "\xFF\x13\x11\x0F\xFF\x14\x12\x10\xFF" + "\x13\x10\x0D\xFF\x17\x15\x12\xFF\x17\x17\x13\xFF\x12\x11\x0E\xFF\x16\x15\x11\xFF\x14\x13\x0F" + "\xFF\x13\x11\x0F\xFF\x13\x12\x0E\xFF" + "\x16\x15\x11\xFF\x14\x13\x0F\xFF\x14\x13\x0F\xFF\x1E\x1E\x18\xFF\x17\x15\x13\xFF\x16\x15\x11" + "\xFF\x13\x12\x0E\xFF\x12\x11\x0D\xFF" + "\x14\x12\x10\xFF\x13\x12\x0F\xFF\x14\x13\x0F\xFF\x13\x12\x0F\xFF\x14\x12\x10\xFF\x15\x14\x10" + "\xFF\x14\x14\x10\xFF\x15\x13\x0F\xFF" + "\x15\x14\x10\xFF\x12\x11\x0E\xFF\x12\x10\x0D\xFF\x14\x13\x0F\xFF\x14\x12\x11\xFF\x15\x14\x11" + "\xFF\x12\x11\x0E\xFF\x16\x14\x0F\xFF" + "\x15\x14\x10\xFF\x11\x10\x0C\xFF\x12\x11\x0D\xFF\x16\x15\x12\xFF\x15\x14\x10\xFF\x14\x13\x0F" + "\xFF\x14\x13\x0F\xFF\x12\x11\x0F\xFF" + "\x13\x12\x10\xFF\x14\x12\x11\xFF\x14\x12\x0F\xFF\x12\x11\x0E\xFF\x16\x15\x11\xFF\x17\x16\x12" + "\xFF\x15\x14\x11\xFF\x15\x15\x0F\xFF" + "\x17\x16\x12\xFF\x1A\x18\x15\xFF\x17\x16\x13\xFF\x14\x13\x0F\xFF\x11\x0F\x0E\xFF\x12\x11\x0F" + "\xFF\x14\x13\x10\xFF\x16\x15\x11\xFF" + "\x16\x15\x11\xFF\x16\x15\x11\xFF\x18\x17\x13\xFF\x15\x13\x10\xFF\x12\x11\x0E\xFF\x16\x15\x11" + "\xFF\x17\x16\x12\xFF\x15\x13\x11\xFF" + "\x16\x15\x12\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x18\x17\x13\xFF\x17\x16\x12" + "\xFF\x18\x17\x13\xFF\x14\x12\x0F\xFF" + "\x16\x15\x11\xFF\x15\x14\x10\xFF\x15\x14\x10\xFF\x13\x11\x0F\xFF\x15\x13\x11\xFF\x17\x16\x12" + "\xFF\x18\x17\x13\xFF\x17\x16\x12\xFF" + "\x16\x15\x11\xFF\x17\x16\x12\xFF\x18\x17\x13\xFF\x1C\x1B\x17\xFF\x17\x16\x12\xFF\x16\x15\x11" + "\xFF\x16\x15\x11\xFF\x18\x17\x13\xFF" + "\x14\x13\x0F\xFF\x15\x13\x11\xFF\x14\x12\x0F\xFF\x14\x13\x0F\xFF\x19\x18\x15\xFF\x17\x16\x13" + "\xFF\x15\x14\x11\xFF\x1B\x1A\x16\xFF" + "\x13\x12\x0E\xFF\x17\x15\x11\xFF\x16\x15\x11\xFF\x13\x12\x0F\xFF\x16\x15\x11\xFF\x18\x16\x13" + "\xFF\x18\x16\x13\xFF\x17\x16\x12\xFF" + "\x17\x16\x12\xFF\x16\x15\x11\xFF\x16\x15\x11\xFF\x1B\x1A\x16\xFF\x17\x16\x12\xFF\x17\x16\x12" + "\xFF\x16\x15\x11\xFF\x15\x14\x10\xFF" + "\x17\x16\x12\xFF\x1A\x1A\x15\xFF\x18\x17\x13\xFF\x18\x17\x14\xFF\x16\x15\x11\xFF\x18\x17\x13" + "\xFF\x19\x18\x14\xFF\x16\x15\x11\xFF" + "\x19\x18\x14\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x1C\x1B\x17\xFF\x1D\x1B\x17\xFF\x18\x17\x13" + "\xFF\x1C\x1A\x16\xFF\x19\x16\x12\xFF" + "\x16\x15\x11\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF\x1A\x18\x14\xFF\x18\x17\x13" + "\xFF\x19\x18\x14\xFF\x1A\x19\x15\xFF" + "\x19\x18\x14\xFF\x18\x16\x12\xFF\x1A\x18\x14\xFF\x18\x16\x12\xFF\x15\x14\x10\xFF\x17\x16\x12" + "\xFF\x16\x15\x11\xFF\x18\x16\x12\xFF" + "\x17\x17\x13\xFF\x18\x17\x13\xFF\x1B\x1A\x15\xFF\x1B\x1B\x16\xFF\x18\x18\x14\xFF\x18\x18\x14" + "\xFF\x1A\x19\x15\xFF\x19\x18\x14\xFF" + "\x1B\x19\x15\xFF\x1B\x1A\x16\xFF\x19\x18\x14\xFF\x1C\x1A\x16\xFF\x1B\x1A\x16\xFF\x1A\x19\x15" + "\xFF\x18\x17\x13\xFF\x18\x17\x13\xFF" + "\x1A\x19\x15\xFF\x19\x18\x14\xFF\x19\x19\x15\xFF\x1A\x18\x14\xFF\x1D\x1C\x18\xFF\x1B\x1A\x16" + "\xFF\x1E\x1C\x18\xFF\x1D\x1B\x17\xFF" + "\x1B\x18\x14\xFF\x1B\x1A\x16\xFF\x1B\x19\x15\xFF\x1B\x1A\x15\xFF\x18\x16\x12\xFF\x19\x18\x13" + "\xFF\x1B\x1A\x16\xFF\x1A\x19\x15\xFF" + "\x1B\x1A\x16\xFF\x20\x1D\x19\xFF\x1D\x1B\x17\xFF\x18\x18\x13\xFF\x1E\x1B\x17\xFF\x1B\x19\x15" + "\xFF\x1B\x1A\x16\xFF\x1A\x19\x14\xFF" + "\x19\x17\x14\xFF\x1C\x19\x16\xFF\x1B\x19\x15\xFF\x19\x17\x15\xFF\x1A\x19\x15\xFF\x1A\x19\x15" + "\xFF\x19\x19\x14\xFF\x1A\x1A\x15\xFF" + "\x1B\x1A\x15\xFF\x1E\x1C\x17\xFF\x1B\x1A\x15\xFF\x1B\x1A\x15\xFF\x1D\x1D\x17\xFF\x1E\x1D\x17" + "\xFF\x1C\x1B\x16\xFF\x17\x15\x11\xFF" + "\x19\x19\x15\xFF\x1C\x19\x15\xFF\x1D\x1B\x17\xFF\x1D\x1B\x18\xFF\x19\x18\x14\xFF\x1B\x1A\x16" + "\xFF\x1C\x1B\x17\xFF\x18\x17\x13\xFF" + "\x1B\x1A\x16\xFF\x1D\x1C\x17\xFF\x1B\x1A\x15\xFF\x17\x17\x12\xFF\x1A\x19\x15\xFF\x1A\x19\x15" + "\xFF\x17\x17\x13\xFF\x18\x17\x13\xFF" + "\x1D\x1D\x17\xFF\x1F\x1E\x19\xFF\x1A\x19\x14\xFF\x1A\x18\x16\xFF\x1D\x1A\x16\xFF\x1C\x1A\x16" + "\xFF\x1A\x1A\x16\xFF\x1D\x1C\x17\xFF" + "\x1B\x1A\x16\xFF\x1C\x1A\x16\xFF\x1D\x1B\x17\xFF\x1B\x1A\x16\xFF\x1C\x18\x14\xFF\x1D\x1B\x17" + "\xFF\x1C\x1B\x17\xFF\x1E\x1C\x18\xFF" + "\x1F\x1D\x19\xFF\x1E\x1E\x19\xFF\x1F\x1F\x1A\xFF\x1C\x1B\x17\xFF\x1E\x1E\x18\xFF\x1D\x1B\x18" + "\xFF\x1D\x1C\x17\xFF\x1D\x1C\x18\xFF" + "\x1A\x1A\x18\xFF\x1D\x1D\x18\xFF\x1D\x1C\x18\xFF\x1D\x1D\x17\xFF\x1E\x1D\x19\xFF\x1E\x1D\x18" + "\xFF\x1B\x1A\x15\xFF\x1F\x1E\x19\xFF" + "\x1B\x1A\x16\xFF\x1C\x1B\x17\xFF\x1B\x1A\x16\xFF\x1E\x1D\x18\xFF\x1E\x1D\x19\xFF\x1C\x1B\x16" + "\xFF\x1D\x1B\x17\xFF\x1F\x1E\x19\xFF" + "\x1D\x1A\x17\xFF\x1F\x1D\x19\xFF\x22\x21\x1B\xFF\x21\x20\x1B\xFF\x1C\x1B\x17\xFF\x1E\x1D\x18" + "\xFF\x1C\x1B\x17\xFF\x1D\x1D\x17\xFF" + "\x1D\x1C\x16\xFF\x1F\x1D\x18\xFF\x1C\x1B\x16\xFF\x1C\x1B\x17\xFF\x1E\x1C\x18\xFF\x20\x1F\x1A" + "\xFF\x24\x21\x1D\xFF\x21\x21\x1B\xFF" + "\x24\x22\x1D\xFF\x1E\x1D\x18\xFF\x1D\x1C\x18\xFF\x1D\x1C\x17\xFF\x20\x1F\x1B\xFF\x1F\x1E\x19" + "\xFF\x1B\x1A\x16\xFF\x1E\x1C\x17\xFF" + "\x1E\x1D\x19\xFF\x1E\x1D\x18\xFF\x1F\x1D\x18\xFF\x1E\x1B\x18\xFF\x1D\x1C\x17\xFF\x1D\x1C\x17" + "\xFF\x1D\x1D\x19\xFF\x1E\x1E\x18\xFF"; + +/** + * Experimental Case 03: 64x64 (32bpp) + */ + +static const BYTE TEST_RLE_BITMAP_EXPERIMENTAL_03[16384] = + "\x27\x2A\x23\xFF\x23\x25\x1F\xFF\x23\x23\x1E\xFF\x24\x23\x1D\xFF\x25\x23\x1D\xFF\x25\x23\x1E" + "\xFF\x25\x23\x1D\xFF\x25\x24\x1F\xFF" + "\x28\x2B\x23\xFF\x37\x60\x4A\xFF\x4A\xA2\x78\xFF\x47\x97\x71\xFF\x41\x84\x64\xFF\x3D\x75\x58" + "\xFF\x38\x62\x4B\xFF\x33\x50\x3E\xFF" + "\x2E\x3C\x30\xFF\x2A\x34\x29\xFF\x28\x30\x26\xFF\x27\x2C\x24\xFF\x26\x29\x22\xFF\x27\x28\x21" + "\xFF\x27\x26\x20\xFF\x27\x25\x20\xFF" + "\x25\x25\x20\xFF\x24\x23\x1D\xFF\x26\x23\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1E\xFF\x27\x24\x1F" + "\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF" + "\x26\x25\x1F\xFF\x28\x25\x20\xFF\x28\x26\x21\xFF\x2A\x27\x22\xFF\x26\x25\x1E\xFF\x27\x24\x1F" + "\xFF\x27\x25\x1F\xFF\x28\x25\x1F\xFF" + "\x27\x27\x1F\xFF\x27\x24\x1F\xFF\x25\x24\x1F\xFF\x26\x24\x1F\xFF\x24\x24\x1E\xFF\x25\x24\x1D" + "\xFF\x27\x25\x1F\xFF\x28\x25\x20\xFF" + "\x27\x24\x1F\xFF\x28\x25\x1F\xFF\x27\x26\x1F\xFF\x27\x25\x1E\xFF\x25\x23\x1E\xFF\x26\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x27\x25\x20\xFF" + "\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x25\x22\x1D" + "\xFF\x26\x23\x1E\xFF\x23\x21\x1C\xFF" + "\x32\x51\x3F\xFF\x29\x37\x2C\xFF\x29\x31\x28\xFF\x27\x2C\x24\xFF\x26\x29\x21\xFF\x28\x28\x22" + "\xFF\x27\x27\x20\xFF\x27\x27\x21\xFF" + "\x29\x2F\x25\xFF\x3C\x73\x56\xFF\x55\xC9\x94\xFF\x56\xCC\x96\xFF\x56\xCB\x97\xFF\x54\xC5\x92" + "\xFF\x52\xBD\x8C\xFF\x50\xB5\x86\xFF" + "\x4D\xAA\x7F\xFF\x48\x99\x71\xFF\x40\x80\x61\xFF\x37\x66\x4D\xFF\x2F\x48\x38\xFF\x2C\x38\x2C" + "\xFF\x2B\x33\x29\xFF\x2A\x2E\x26\xFF" + "\x27\x29\x23\xFF\x27\x27\x21\xFF\x27\x27\x21\xFF\x27\x26\x20\xFF\x26\x25\x1F\xFF\x26\x25\x1F" + "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF" + "\x27\x26\x20\xFF\x28\x26\x20\xFF\x28\x25\x20\xFF\x28\x27\x20\xFF\x27\x25\x1F\xFF\x27\x25\x20" + "\xFF\x27\x25\x1F\xFF\x28\x26\x20\xFF" + "\x25\x23\x1E\xFF\x26\x24\x1E\xFF\x25\x23\x1D\xFF\x27\x25\x1F\xFF\x26\x24\x1E\xFF\x26\x23\x1E" + "\xFF\x29\x26\x21\xFF\x28\x25\x20\xFF" + "\x26\x25\x1F\xFF\x27\x25\x1F\xFF\x25\x25\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x26\x25\x1F" + "\xFF\x25\x23\x1D\xFF\x27\x24\x1F\xFF" + "\x25\x24\x1F\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x27\x24\x1E\xFF\x26\x26\x1E\xFF\x27\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF" + "\x4E\xBA\x89\xFF\x4D\xB3\x83\xFF\x45\x97\x70\xFF\x3D\x79\x5B\xFF\x35\x58\x44\xFF\x30\x48\x39" + "\xFF\x2D\x3A\x2F\xFF\x29\x31\x27\xFF" + "\x2B\x37\x2C\xFF\x3E\x79\x5B\xFF\x56\xCA\x95\xFF\x56\xCC\x96\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x52\xBC\x8B" + "\xFF\x49\x9D\x76\xFF\x3E\x7A\x5C\xFF" + "\x36\x5C\x47\xFF\x31\x4C\x3B\xFF\x2D\x3D\x2F\xFF\x29\x30\x28\xFF\x27\x2B\x23\xFF\x27\x29\x22" + "\xFF\x27\x28\x21\xFF\x28\x27\x21\xFF" + "\x27\x25\x20\xFF\x27\x24\x1F\xFF\x24\x24\x1E\xFF\x25\x23\x1F\xFF\x28\x25\x20\xFF\x29\x26\x20" + "\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF" + "\x26\x23\x1F\xFF\x27\x24\x1F\xFF\x29\x27\x21\xFF\x2A\x27\x21\xFF\x27\x24\x1F\xFF\x25\x24\x1E" + "\xFF\x24\x24\x1F\xFF\x26\x24\x1F\xFF" + "\x28\x25\x1F\xFF\x27\x25\x1E\xFF\x27\x25\x20\xFF\x25\x25\x1F\xFF\x29\x26\x21\xFF\x27\x27\x20" + "\xFF\x26\x25\x1F\xFF\x25\x23\x1E\xFF" + "\x26\x23\x1E\xFF\x23\x23\x1D\xFF\x26\x25\x1E\xFF\x27\x24\x1E\xFF\x25\x22\x1E\xFF\x27\x24\x1F" + "\xFF\x29\x26\x21\xFF\x26\x26\x1E\xFF" + "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x50\xC0\x8E\xFF\x51\xC2\x8F\xFF\x52\xBC\x8B\xFF\x4A\xA7\x7C" + "\xFF\x44\x90\x6B\xFF\x3E\x78\x5B\xFF" + "\x36\x63\x4B\xFF\x41\x87\x66\xFF\x55\xCB\x96\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x53\xC2\x90\xFF\x4D\xAD\x81\xFF\x47\x96\x70\xFF\x40\x7E\x5F\xFF\x39\x63\x4C\xFF\x31\x48\x39" + "\xFF\x2D\x36\x2B\xFF\x27\x2C\x24\xFF" + "\x25\x27\x21\xFF\x27\x27\x21\xFF\x26\x24\x1E\xFF\x25\x24\x1E\xFF\x28\x25\x1F\xFF\x26\x24\x1E" + "\xFF\x25\x25\x20\xFF\x26\x24\x1F\xFF" + "\x25\x22\x1E\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x29\x26\x21\xFF\x25\x23\x1D\xFF\x25\x23\x1D" + "\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF" + "\x2A\x27\x21\xFF\x29\x26\x21\xFF\x28\x25\x20\xFF\x26\x26\x20\xFF\x29\x26\x21\xFF\x29\x26\x21" + "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF" + "\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x25\x23\x1E\xFF\x24\x22\x1E\xFF\x28\x25\x20\xFF\x27\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x25\x24\x1F\xFF" + "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x50\xC0\x8E\xFF\x51\xC2\x8F\xFF\x53\xC5\x91\xFF\x54\xC7\x92" + "\xFF\x55\xC8\x95\xFF\x53\xC3\x92\xFF" + "\x50\xBA\x89\xFF\x51\xBD\x8C\xFF\x56\xCC\x97\xFF\x57\xCC\x97\xFF\x57\xCD\x99\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x58\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x55\xC8\x95\xFF\x52\xBD\x8D\xFF\x4E\xB0\x82" + "\xFF\x47\x93\x6E\xFF\x38\x65\x4C\xFF" + "\x2D\x3A\x2F\xFF\x2C\x33\x2A\xFF\x26\x2A\x23\xFF\x27\x28\x20\xFF\x28\x27\x21\xFF\x27\x24\x1F" + "\xFF\x26\x23\x1F\xFF\x28\x25\x20\xFF" + "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x24\x23\x1E\xFF\x26\x24\x1D\xFF\x24\x24\x1D\xFF\x24\x22\x1D" + "\xFF\x24\x21\x1D\xFF\x27\x24\x1F\xFF" + "\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF\x28\x25\x20\xFF\x27\x26\x20\xFF\x27\x25\x1E" + "\xFF\x25\x25\x1E\xFF\x26\x23\x1E\xFF" + "\x26\x23\x1E\xFF\x27\x25\x1E\xFF\x26\x24\x1F\xFF\x27\x24\x20\xFF\x27\x26\x20\xFF\x28\x25\x20" + "\xFF\x27\x24\x1F\xFF\x24\x24\x1E\xFF" + "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x52\xC4\x91\xFF\x54\xC7\x92" + "\xFF\x55\xC8\x95\xFF\x54\xC8\x94\xFF" + "\x55\xCB\x96\xFF\x55\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x97\xFF\x57\xCD\x97\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCC\x97" + "\xFF\x57\xCC\x97\xFF\x56\xCC\x97\xFF" + "\x52\xBE\x8D\xFF\x45\x90\x6C\xFF\x38\x62\x4B\xFF\x2F\x42\x34\xFF\x29\x30\x27\xFF\x27\x28\x22" + "\xFF\x28\x27\x21\xFF\x25\x22\x1E\xFF" + "\x23\x23\x1D\xFF\x25\x24\x1F\xFF\x28\x25\x20\xFF\x26\x25\x1F\xFF\x27\x25\x1F\xFF\x27\x25\x1E" + "\xFF\x25\x24\x1E\xFF\x27\x24\x20\xFF" + "\x27\x25\x1F\xFF\x26\x25\x1F\xFF\x25\x24\x1E\xFF\x28\x26\x20\xFF\x25\x24\x1F\xFF\x26\x24\x1F" + "\xFF\x25\x23\x1E\xFF\x27\x23\x1F\xFF" + "\x27\x25\x1E\xFF\x26\x25\x1F\xFF\x2A\x27\x21\xFF\x2C\x29\x24\xFF\x27\x24\x1F\xFF\x27\x24\x1F" + "\xFF\x28\x25\x20\xFF\x29\x26\x20\xFF" + "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x53\xC7\x93" + "\xFF\x54\xC8\x95\xFF\x55\xC9\x95\xFF" + "\x55\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x56\xCD\x97\xFF\x56\xCC\x97" + "\xFF\x56\xCC\x97\xFF\x57\xCC\x97\xFF" + "\x56\xCD\x97\xFF\x56\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x56\xCC\x98\xFF\x52\xC0\x8E\xFF\x49\x9D\x76\xFF\x3A\x6A\x51\xFF\x2E\x3F\x31" + "\xFF\x29\x2E\x24\xFF\x28\x27\x21\xFF" + "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x24\x1E\xFF\x25\x24\x1E\xFF\x25\x25\x1F\xFF\x26\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF" + "\x27\x25\x20\xFF\x27\x24\x1E\xFF\x27\x25\x20\xFF\x27\x27\x21\xFF\x25\x24\x1E\xFF\x26\x25\x1F" + "\xFF\x27\x25\x1F\xFF\x25\x24\x1E\xFF" + "\x25\x25\x1F\xFF\x28\x25\x20\xFF\x28\x26\x20\xFF\x28\x25\x20\xFF\x25\x23\x1D\xFF\x25\x23\x1E" + "\xFF\x26\x25\x1F\xFF\x27\x26\x20\xFF" + "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x52\xC4\x91\xFF\x53\xC7\x93" + "\xFF\x54\xC8\x95\xFF\x55\xC9\x95\xFF" + "\x55\xCB\x95\xFF\x55\xCA\x95\xFF\x56\xCC\x96\xFF\x56\xCB\x96\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x58\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97" + "\xFF\x56\xCC\x96\xFF\x56\xCC\x97\xFF" + "\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x52\xC1\x8E\xFF\x4C\xA8\x7C\xFF\x45\x92\x6D" + "\xFF\x42\x86\x64\xFF\x47\x99\x72\xFF" + "\x51\xB9\x89\xFF\x56\xCB\x96\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x53\xBF\x8E\xFF\x45\x8F\x6B" + "\xFF\x30\x47\x38\xFF\x28\x2E\x25\xFF" + "\x27\x26\x1E\xFF\x27\x25\x1E\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF\x29\x26\x20" + "\xFF\x28\x25\x1F\xFF\x26\x25\x1F\xFF" + "\x25\x24\x1F\xFF\x26\x25\x1F\xFF\x28\x25\x1F\xFF\x26\x24\x1E\xFF\x25\x23\x1E\xFF\x25\x24\x1E" + "\xFF\x29\x26\x21\xFF\x26\x24\x1F\xFF" + "\x27\x25\x1F\xFF\x29\x27\x20\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x26\x24\x1D\xFF\x26\x23\x1E" + "\xFF\x25\x23\x1D\xFF\x26\x24\x1E\xFF" + "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x52\xC4\x91\xFF\x54\xC7\x93" + "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF" + "\x55\xCA\x95\xFF\x55\xCA\x95\xFF\x56\xCC\x96\xFF\x56\xCB\x96\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x99\xFF\x57\xCD\x98" + "\xFF\x56\xCD\x97\xFF\x56\xCD\x98\xFF" + "\x56\xCA\x96\xFF\x50\xB6\x87\xFF\x42\x83\x62\xFF\x30\x48\x39\xFF\x2A\x3B\x2E\xFF\x2B\x39\x2E" + "\xFF\x2D\x3B\x2F\xFF\x2D\x3B\x2F\xFF" + "\x2E\x40\x32\xFF\x35\x58\x45\xFF\x48\x9B\x74\xFF\x54\xC5\x92\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x4C\xA9\x7D\xFF\x34\x52\x3E\xFF" + "\x27\x2B\x22\xFF\x27\x26\x20\xFF\x29\x26\x21\xFF\x2A\x27\x22\xFF\x28\x25\x1E\xFF\x26\x23\x1E" + "\xFF\x29\x26\x21\xFF\x26\x24\x1E\xFF" + "\x25\x24\x1F\xFF\x26\x25\x1F\xFF\x28\x25\x20\xFF\x25\x23\x1E\xFF\x27\x25\x20\xFF\x25\x23\x1E" + "\xFF\x25\x22\x1D\xFF\x25\x23\x1E\xFF" + "\x27\x25\x1F\xFF\x25\x24\x1E\xFF\x27\x24\x1E\xFF\x25\x24\x1F\xFF\x26\x24\x1E\xFF\x25\x22\x1E" + "\xFF\x25\x23\x1D\xFF\x26\x25\x1E\xFF" + "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93" + "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF" + "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x56\xCC\x97\xFF\x56\xCD\x98\xFF" + "\x4C\xA8\x7D\xFF\x37\x5D\x47\xFF\x2D\x3D\x31\xFF\x2E\x3D\x30\xFF\x34\x5F\x49\xFF\x3B\x71\x55" + "\xFF\x3D\x79\x5B\xFF\x3A\x6C\x52\xFF" + "\x33\x50\x3D\xFF\x2F\x3D\x30\xFF\x2F\x47\x37\xFF\x3E\x7A\x5D\xFF\x54\xC5\x92\xFF\x56\xCD\x98" + "\xFF\x56\xCC\x97\xFF\x48\x98\x72\xFF" + "\x2C\x37\x2C\xFF\x29\x2A\x23\xFF\x27\x26\x1F\xFF\x25\x24\x1E\xFF\x27\x25\x20\xFF\x26\x25\x1F" + "\xFF\x29\x27\x21\xFF\x25\x25\x1F\xFF" + "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x28\x26\x21\xFF\x28\x25\x20\xFF\x26\x23\x1E" + "\xFF\x24\x23\x1D\xFF\x25\x22\x1D\xFF" + "\x25\x23\x1E\xFF\x24\x23\x1E\xFF\x29\x26\x21\xFF\x28\x26\x20\xFF\x25\x24\x1F\xFF\x25\x24\x1F" + "\xFF\x25\x24\x1E\xFF\x24\x23\x1D\xFF" + "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93" + "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF" + "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x56\xCC\x97\xFF\x53\xC3\x91\xFF" + "\x35\x59\x45\xFF\x2D\x3B\x2F\xFF\x32\x50\x3E\xFF\x46\x92\x6D\xFF\x51\xBB\x8B\xFF\x54\xC4\x91" + "\xFF\x55\xC7\x93\xFF\x53\xC1\x8F\xFF" + "\x4E\xAE\x81\xFF\x3F\x75\x59\xFF\x2D\x40\x32\xFF\x2D\x3D\x31\xFF\x43\x8C\x68\xFF\x56\xCC\x97" + "\xFF\x56\xCD\x97\xFF\x56\xC8\x94\xFF" + "\x39\x65\x4D\xFF\x2A\x30\x26\xFF\x28\x28\x22\xFF\x27\x25\x1E\xFF\x2A\x27\x22\xFF\x28\x26\x21" + "\xFF\x29\x26\x21\xFF\x26\x25\x1E\xFF" + "\x27\x24\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x26\x24\x1E\xFF\x26\x25\x20" + "\xFF\x27\x25\x1E\xFF\x28\x25\x1F\xFF" + "\x27\x24\x20\xFF\x26\x23\x1E\xFF\x25\x24\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF\x26\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF" + "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93" + "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF" + "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x97" + "\xFF\x55\xC7\x93\xFF\x3B\x70\x56\xFF" + "\x2E\x3B\x2F\xFF\x36\x59\x44\xFF\x4E\xAE\x81\xFF\x57\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCC\x97" + "\xFF\x55\xC9\x94\xFF\x54\xC4\x91\xFF" + "\x56\xCB\x96\xFF\x56\xCA\x96\xFF\x43\x8A\x69\xFF\x2E\x41\x33\xFF\x31\x4C\x3A\xFF\x4A\xA1\x79" + "\xFF\x56\xCC\x97\xFF\x57\xCD\x97\xFF" + "\x4E\xB0\x81\xFF\x2F\x45\x37\xFF\x28\x2B\x24\xFF\x25\x23\x1E\xFF\x27\x25\x20\xFF\x27\x25\x20" + "\xFF\x28\x26\x20\xFF\x27\x24\x20\xFF" + "\x26\x23\x1E\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x25\x1F" + "\xFF\x26\x24\x1E\xFF\x26\x23\x1E\xFF" + "\x28\x26\x21\xFF\x25\x25\x1E\xFF\x26\x23\x1E\xFF\x26\x24\x20\xFF\x27\x24\x1F\xFF\x27\x24\x20" + "\xFF\x26\x23\x1F\xFF\x27\x24\x1F\xFF" + "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93" + "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF" + "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98" + "\xFF\x4E\xAF\x82\xFF\x32\x50\x3E\xFF" + "\x2F\x42\x35\xFF\x45\x90\x6C\xFF\x56\xCA\x96\xFF\x57\xCD\x98\xFF\x56\xCB\x96\xFF\x4E\xB0\x82" + "\xFF\x42\x84\x63\xFF\x3D\x74\x57\xFF" + "\x44\x8B\x69\xFF\x50\xB7\x88\xFF\x53\xC0\x8F\xFF\x36\x64\x4C\xFF\x2D\x3C\x2F\xFF\x3E\x76\x59" + "\xFF\x54\xC7\x92\xFF\x57\xCD\x97\xFF" + "\x54\xC3\x91\xFF\x3D\x74\x58\xFF\x2A\x31\x28\xFF\x27\x26\x1F\xFF\x27\x25\x20\xFF\x26\x25\x1F" + "\xFF\x27\x26\x20\xFF\x27\x26\x20\xFF" + "\x27\x24\x1F\xFF\x27\x25\x1F\xFF\x27\x25\x1F\xFF\x26\x25\x20\xFF\x27\x24\x1F\xFF\x27\x26\x1F" + "\xFF\x27\x26\x1F\xFF\x23\x23\x1D\xFF" + "\x25\x23\x1E\xFF\x27\x26\x1F\xFF\x27\x24\x1E\xFF\x29\x26\x20\xFF\x26\x25\x1F\xFF\x26\x24\x1F" + "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF" + "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93" + "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF" + "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x98" + "\xFF\x49\x98\x72\xFF\x2E\x44\x36\xFF" + "\x33\x51\x3F\xFF\x4F\xB4\x86\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x53\xC0\x8F\xFF\x3D\x73\x57" + "\xFF\x2F\x3E\x32\xFF\x2C\x38\x2C\xFF" + "\x2F\x42\x35\xFF\x41\x83\x62\xFF\x55\xC8\x94\xFF\x43\x8F\x6A\xFF\x2E\x3A\x2F\xFF\x39\x66\x4E" + "\xFF\x52\xBB\x8B\xFF\x56\xCD\x97\xFF" + "\x56\xCB\x97\xFF\x48\x9C\x75\xFF\x2E\x42\x34\xFF\x28\x29\x22\xFF\x28\x27\x21\xFF\x28\x26\x1F" + "\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF" + "\x28\x26\x20\xFF\x2A\x27\x21\xFF\x27\x26\x20\xFF\x25\x23\x1E\xFF\x29\x26\x21\xFF\x29\x28\x22" + "\xFF\x26\x25\x1F\xFF\x25\x22\x1D\xFF" + "\x27\x24\x1F\xFF\x28\x25\x1F\xFF\x27\x24\x1E\xFF\x27\x26\x1F\xFF\x27\x25\x1F\xFF\x26\x24\x1F" + "\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF" + "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93" + "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF" + "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x56\xCD\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCD\x97\xFF\x57\xCD\x98" + "\xFF\x41\x80\x61\xFF\x2B\x39\x2E\xFF" + "\x33\x58\x44\xFF\x54\xC7\x93\xFF\x56\xCD\x98\xFF\x56\xCC\x97\xFF\x55\xC9\x94\xFF\x46\x96\x70" + "\xFF\x34\x5B\x46\xFF\x32\x51\x3E\xFF" + "\x38\x67\x4D\xFF\x49\xA3\x79\xFF\x56\xCD\x97\xFF\x4C\xAC\x80\xFF\x2F\x3D\x31\xFF\x36\x58\x44" + "\xFF\x4E\xAF\x82\xFF\x56\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x52\xBF\x8E\xFF\x39\x69\x50\xFF\x2A\x30\x26\xFF\x26\x25\x1F\xFF\x26\x23\x1E" + "\xFF\x27\x25\x1E\xFF\x28\x25\x20\xFF" + "\x28\x26\x21\xFF\x28\x25\x20\xFF\x26\x25\x1E\xFF\x27\x25\x1F\xFF\x28\x26\x21\xFF\x28\x26\x20" + "\xFF\x28\x26\x1F\xFF\x25\x24\x1D\xFF" + "\x25\x24\x1E\xFF\x25\x25\x1F\xFF\x24\x23\x1D\xFF\x26\x24\x1F\xFF\x25\x22\x1D\xFF\x23\x23\x1D" + "\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF" + "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93" + "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF" + "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x56\xCC\x98\xFF\x56\xCC\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98" + "\xFF\x44\x8C\x69\xFF\x2D\x40\x31\xFF" + "\x33\x55\x41\xFF\x52\xBE\x8D\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x55\xC9\x95" + "\xFF\x50\xBA\x8A\xFF\x4D\xAD\x80\xFF" + "\x52\xC0\x8D\xFF\x56\xCC\x97\xFF\x57\xCD\x97\xFF\x4A\xA5\x7A\xFF\x2E\x3B\x2F\xFF\x36\x5A\x45" + "\xFF\x4F\xB1\x83\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x42\x8A\x68\xFF\x2B\x3B\x2E\xFF\x28\x28\x20\xFF\x27\x24\x1E" + "\xFF\x28\x25\x1F\xFF\x28\x25\x1F\xFF" + "\x24\x23\x1E\xFF\x25\x24\x1E\xFF\x27\x25\x1F\xFF\x28\x26\x20\xFF\x26\x25\x1F\xFF\x27\x26\x1F" + "\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF" + "\x27\x25\x1F\xFF\x26\x24\x1E\xFF\x25\x24\x1D\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x25\x23\x1E" + "\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF" + "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93" + "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF" + "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x97" + "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x99\xFF\x56\xCD\x97\xFF\x56\xCD\x98" + "\xFF\x49\x9E\x77\xFF\x31\x47\x38\xFF" + "\x2F\x4A\x39\xFF\x4B\xA9\x7E\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x58\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF" + "\x57\xCE\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x40\x80\x61\xFF\x2E\x3B\x2F\xFF\x38\x6A\x50" + "\xFF\x52\xBF\x8E\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x4D\xAC\x80\xFF\x2F\x4B\x3A\xFF\x28\x2A\x21\xFF\x27\x26\x1F" + "\xFF\x28\x25\x1F\xFF\x29\x26\x21\xFF" + "\x26\x23\x1F\xFF\x27\x26\x20\xFF\x27\x25\x1F\xFF\x27\x25\x20\xFF\x26\x23\x1E\xFF\x27\x24\x1F" + "\xFF\x28\x25\x1F\xFF\x28\x25\x20\xFF" + "\x27\x25\x20\xFF\x26\x25\x1F\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x26\x24\x1E\xFF\x24\x23\x1D" + "\xFF\x27\x24\x1F\xFF\x24\x22\x1D\xFF" + "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93" + "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF" + "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCA\x96\xFF\x53\xC1\x8F" + "\xFF\x57\xC7\x94\xFF\x57\xCC\x97\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x97\xFF\x56\xCC\x97\xFF\x56\xCD\x97" + "\xFF\x51\xBA\x89\xFF\x37\x5E\x47\xFF" + "\x2D\x39\x2E\xFF\x39\x6B\x52\xFF\x52\xBE\x8D\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCB\x97\xFF\x4C\xA8\x7C\xFF\x31\x4D\x3B\xFF\x2F\x3F\x31\xFF\x41\x86\x65" + "\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF" + "\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xC9\x96\xFF\x39\x63\x4D\xFF\x27\x2D\x26\xFF\x26\x27\x1F" + "\xFF\x27\x26\x20\xFF\x24\x23\x1D\xFF" + "\x24\x22\x1D\xFF\x26\x24\x1D\xFF\x27\x26\x20\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF\x26\x25\x1F" + "\xFF\x26\x23\x1F\xFF\x25\x23\x1E\xFF" + "\x25\x24\x1F\xFF\x26\x23\x1E\xFF\x28\x26\x20\xFF\x28\x26\x21\xFF\x27\x24\x1F\xFF\x27\x25\x1F" + "\xFF\x26\x23\x1E\xFF\x27\x24\x20\xFF" + "\x4F\xBA\x8A\xFF\x50\xBD\x8C\xFF\x51\xC0\x8F\xFF\x52\xC2\x90\xFF\x53\xC4\x92\xFF\x54\xC6\x93" + "\xFF\x54\xC8\x94\xFF\x55\xC8\x95\xFF" + "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x55\xC8\x94\xFF\x49\x9E\x76" + "\xFF\x43\x8B\x68\xFF\x50\xB5\x87\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97" + "\xFF\x56\xCD\x97\xFF\x48\x9A\x72\xFF" + "\x2C\x3B\x2F\xFF\x30\x42\x34\xFF\x3F\x7C\x5D\xFF\x51\xBB\x8B\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF" + "\x56\xC9\x95\xFF\x4C\xA9\x7D\xFF\x37\x5F\x49\xFF\x2C\x3A\x2E\xFF\x36\x5D\x47\xFF\x50\xB4\x86" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCC\x99\xFF\x57\xCC\x98\xFF\x57\xCD\x98\xFF\x44\x8C\x69\xFF\x29\x30\x27\xFF\x27\x27\x20" + "\xFF\x27\x26\x1F\xFF\x25\x23\x1E\xFF" + "\x23\x22\x1C\xFF\x26\x23\x1E\xFF\x27\x25\x20\xFF\x27\x25\x20\xFF\x26\x23\x1E\xFF\x26\x23\x1E" + "\xFF\x27\x25\x1F\xFF\x26\x23\x1D\xFF" + "\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x26\x25\x1F\xFF\x25\x23\x1E\xFF\x28\x25\x20\xFF\x27\x25\x20" + "\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF" + "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC4\x92\xFF\x54\xC6\x93" + "\xFF\x54\xC8\x94\xFF\x55\xC9\x95\xFF" + "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x4C\xAA\x7F" + "\xFF\x35\x5E\x49\xFF\x35\x59\x45\xFF" + "\x46\x99\x72\xFF\x55\xC8\x94\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x55\xC8\x94\xFF" + "\x3B\x6C\x51\xFF\x2C\x3A\x2E\xFF\x2E\x3F\x31\xFF\x38\x5F\x49\xFF\x46\x94\x6F\xFF\x4E\xB1\x83" + "\xFF\x50\xB9\x88\xFF\x4C\xAA\x7F\xFF" + "\x41\x80\x61\xFF\x32\x50\x3E\xFF\x2C\x3A\x2E\xFF\x2B\x40\x32\xFF\x4B\xA7\x7C\xFF\x57\xCC\x97" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x4F\xB6\x86\xFF\x2D\x38\x2D\xFF\x29\x2A\x23" + "\xFF\x27\x25\x1F\xFF\x26\x23\x1E\xFF" + "\x26\x24\x1F\xFF\x27\x26\x20\xFF\x29\x26\x21\xFF\x27\x25\x20\xFF\x28\x26\x1F\xFF\x26\x24\x1F" + "\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF" + "\x25\x24\x1E\xFF\x25\x24\x1E\xFF\x25\x24\x1F\xFF\x24\x23\x1D\xFF\x25\x24\x1E\xFF\x26\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF" + "\x4F\xBB\x8A\xFF\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC7\x93" + "\xFF\x54\xC8\x94\xFF\x55\xC9\x95\xFF" + "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x55\xC7\x94" + "\xFF\x40\x7B\x5D\xFF\x2C\x3B\x2E\xFF" + "\x2B\x37\x2C\xFF\x35\x5B\x46\xFF\x48\x9E\x76\xFF\x52\xBF\x8D\xFF\x56\xCC\x97\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x54\xC4\x91\xFF\x44\x89\x67\xFF\x31\x4D\x3C\xFF\x29\x37\x2C\xFF\x2C\x39\x2D\xFF\x2C\x3A\x2E" + "\xFF\x2D\x3C\x2E\xFF\x2C\x3B\x2E\xFF" + "\x2C\x39\x2D\xFF\x2E\x41\x32\xFF\x38\x60\x4B\xFF\x4B\xA2\x79\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x97\xFF\x57\xCC\x97\xFF\x33\x52\x3F\xFF\x28\x2C\x23" + "\xFF\x28\x27\x1F\xFF\x27\x24\x1F\xFF" + "\x23\x22\x1D\xFF\x26\x25\x1F\xFF\x27\x24\x1F\xFF\x24\x23\x1E\xFF\x27\x24\x1F\xFF\x27\x25\x1F" + "\xFF\x26\x25\x1F\xFF\x26\x24\x1F\xFF" + "\x25\x22\x1D\xFF\x27\x24\x1F\xFF\x27\x26\x21\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF\x26\x23\x1F" + "\xFF\x26\x23\x1F\xFF\x27\x24\x1F\xFF" + "\x4F\xBC\x8B\xFF\x51\xBF\x8D\xFF\x52\xC1\x8F\xFF\x53\xC4\x91\xFF\x53\xC5\x92\xFF\x54\xC7\x93" + "\xFF\x54\xC8\x94\xFF\x55\xC9\x95\xFF" + "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x4A\xA4\x7A\xFF\x30\x4B\x39\xFF" + "\x2A\x30\x28\xFF\x2A\x32\x28\xFF\x2E\x40\x32\xFF\x38\x67\x4E\xFF\x48\x9B\x74\xFF\x53\xC0\x8F" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x55\xC8\x94\xFF\x4A\xA5\x7A\xFF\x3F\x7D\x5E\xFF\x37\x62\x4C\xFF\x31\x4C\x3A" + "\xFF\x31\x49\x39\xFF\x35\x57\x42\xFF" + "\x3A\x6B\x52\xFF\x44\x8D\x6A\xFF\x50\xBB\x8B\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCD\x98" + "\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCA\x96\xFF\x4F\xAC\x80\xFF\x2F\x4A\x39\xFF\x28\x2A\x23" + "\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF" + "\x25\x23\x1E\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF\x29\x26\x21\xFF\x28\x26\x20" + "\xFF\x27\x24\x1F\xFF\x27\x25\x1F\xFF" + "\x28\x25\x20\xFF\x26\x25\x1F\xFF\x25\x24\x1F\xFF\x27\x25\x1F\xFF\x26\x23\x1E\xFF\x28\x25\x20" + "\xFF\x27\x25\x1F\xFF\x26\x25\x20\xFF" + "\x50\xBC\x8C\xFF\x51\xBF\x8D\xFF\x52\xC1\x8F\xFF\x53\xC4\x91\xFF\x53\xC5\x92\xFF\x54\xC7\x93" + "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF" + "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x54\xC6\x93\xFF\x41\x82\x62\xFF" + "\x2D\x3C\x30\xFF\x2B\x34\x29\xFF\x29\x2F\x26\xFF\x27\x30\x27\xFF\x2F\x42\x34\xFF\x38\x63\x4C" + "\xFF\x48\x98\x72\xFF\x55\xC6\x93\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x55\xC7\x94\xFF\x52\xBC\x8C\xFF\x4F\xB2\x84" + "\xFF\x4E\xB0\x83\xFF\x53\xB7\x88\xFF" + "\x53\xC0\x8E\xFF\x55\xC9\x95\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98" + "\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCD\x97\xFF\x52\xBC\x8C\xFF\x40\x81\x62\xFF\x32\x4E\x3D\xFF\x29\x2C\x25\xFF\x29\x27\x21" + "\xFF\x29\x27\x20\xFF\x27\x25\x1F\xFF" + "\x25\x23\x1E\xFF\x26\x24\x1F\xFF\x26\x26\x20\xFF\x26\x25\x20\xFF\x26\x25\x1F\xFF\x28\x26\x20" + "\xFF\x27\x25\x20\xFF\x29\x26\x20\xFF" + "\x27\x25\x1F\xFF\x28\x25\x20\xFF\x25\x23\x1E\xFF\x26\x24\x1E\xFF\x26\x25\x20\xFF\x27\x24\x1F" + "\xFF\x26\x24\x1F\xFF\x27\x25\x1F\xFF" + "\x50\xBD\x8C\xFF\x51\xC0\x8E\xFF\x52\xC2\x90\xFF\x53\xC4\x91\xFF\x54\xC6\x93\xFF\x54\xC7\x93" + "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF" + "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x56\xCD\x97\xFF\x56\xCC\x97\xFF" + "\x52\xBA\x8B\xFF\x42\x85\x64\xFF\x36\x59\x46\xFF\x2F\x40\x33\xFF\x2B\x31\x28\xFF\x2B\x31\x27" + "\xFF\x2C\x36\x2B\xFF\x31\x4B\x3A\xFF" + "\x41\x82\x61\xFF\x4C\xA8\x7E\xFF\x52\xBC\x8C\xFF\x55\xC9\x95\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x97" + "\xFF\x54\xC8\x93\xFF\x51\xB8\x89\xFF" + "\x3F\x82\x62\xFF\x2F\x47\x38\xFF\x2A\x34\x29\xFF\x26\x2B\x22\xFF\x27\x27\x21\xFF\x26\x25\x1F" + "\xFF\x26\x26\x1F\xFF\x26\x25\x1E\xFF" + "\x27\x24\x20\xFF\x27\x24\x20\xFF\x28\x26\x20\xFF\x28\x26\x21\xFF\x26\x25\x1F\xFF\x26\x25\x1F" + "\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF" + "\x26\x23\x1E\xFF\x27\x25\x20\xFF\x26\x25\x1F\xFF\x28\x27\x20\xFF\x27\x24\x1F\xFF\x27\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x28\x26\x1F\xFF" + "\x50\xBE\x8D\xFF\x51\xC0\x8F\xFF\x52\xC3\x90\xFF\x53\xC4\x92\xFF\x54\xC6\x93\xFF\x54\xC8\x94" + "\xFF\x55\xC8\x95\xFF\x55\xC9\x95\xFF" + "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x56\xCB\x96\xFF\x50\xBA\x8A\xFF\x46\x94\x6F\xFF\x38\x64\x4B\xFF\x2D\x3F\x32" + "\xFF\x29\x33\x29\xFF\x29\x32\x27\xFF" + "\x2A\x35\x29\xFF\x30\x43\x34\xFF\x38\x61\x4A\xFF\x40\x81\x61\xFF\x49\x9F\x78\xFF\x4E\xB0\x83" + "\xFF\x52\xC0\x8E\xFF\x56\xCB\x97\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xC9\x95\xFF\x4F\xB7\x86\xFF\x48\x9B\x74" + "\xFF\x3F\x7E\x5F\xFF\x35\x5A\x44\xFF" + "\x2A\x36\x2B\xFF\x2B\x34\x2A\xFF\x2B\x37\x2C\xFF\x2D\x3D\x30\xFF\x28\x29\x21\xFF\x25\x25\x1E" + "\xFF\x26\x25\x20\xFF\x26\x26\x1E\xFF" + "\x27\x24\x1F\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x24\x23\x1D" + "\xFF\x28\x24\x1F\xFF\x25\x22\x1E\xFF" + "\x25\x22\x1E\xFF\x24\x25\x1F\xFF\x27\x25\x20\xFF\x29\x27\x21\xFF\x26\x23\x1E\xFF\x28\x25\x20" + "\xFF\x28\x25\x20\xFF\x26\x25\x1E\xFF" + "\x51\xBF\x8D\xFF\x52\xC1\x8F\xFF\x52\xC3\x90\xFF\x53\xC5\x92\xFF\x54\xC6\x93\xFF\x54\xC8\x94" + "\xFF\x55\xC8\x95\xFF\x55\xCA\x96\xFF" + "\x55\xCA\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x51\xBD\x8C\xFF\x49\xA2\x79" + "\xFF\x3D\x72\x57\xFF\x31\x47\x37\xFF" + "\x2D\x36\x2C\xFF\x2B\x34\x29\xFF\x2A\x33\x28\xFF\x29\x34\x29\xFF\x2F\x43\x35\xFF\x31\x4D\x3C" + "\xFF\x35\x5A\x45\xFF\x3A\x67\x50\xFF" + "\x3D\x73\x57\xFF\x3B\x6B\x52\xFF\x39\x66\x4D\xFF\x37\x61\x4A\xFF\x33\x53\x3F\xFF\x2E\x42\x34" + "\xFF\x29\x35\x2A\xFF\x2A\x34\x2A\xFF" + "\x2B\x3B\x2F\xFF\x37\x5F\x49\xFF\x46\x95\x6F\xFF\x47\x97\x70\xFF\x28\x2E\x25\xFF\x25\x25\x1F" + "\xFF\x27\x24\x1F\xFF\x27\x25\x1F\xFF" + "\x28\x25\x20\xFF\x26\x25\x1F\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x26\x24\x1F" + "\xFF\x28\x25\x20\xFF\x25\x23\x1E\xFF" + "\x26\x23\x1E\xFF\x24\x24\x1E\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x25\x22\x1D\xFF\x28\x25\x1F" + "\xFF\x26\x25\x1F\xFF\x25\x25\x1F\xFF" + "\x51\xC0\x8E\xFF\x52\xC2\x90\xFF\x53\xC4\x91\xFF\x53\xC5\x92\xFF\x54\xC7\x93\xFF\x54\xC8\x94" + "\xFF\x55\xC9\x95\xFF\x55\xCA\x96\xFF" + "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCD\x98\xFF\x57\xCC\x97" + "\xFF\x57\xCD\x98\xFF\x54\xC7\x94\xFF" + "\x4A\x9E\x77\xFF\x3E\x75\x58\xFF\x35\x57\x42\xFF\x2E\x40\x32\xFF\x28\x33\x28\xFF\x29\x33\x28" + "\xFF\x28\x32\x27\xFF\x29\x32\x28\xFF" + "\x29\x31\x27\xFF\x2A\x32\x2A\xFF\x2C\x34\x2A\xFF\x2B\x34\x2A\xFF\x2B\x37\x2C\xFF\x2F\x42\x35" + "\xFF\x35\x55\x42\xFF\x3E\x74\x58\xFF" + "\x4E\xB2\x85\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x45\x90\x6B\xFF\x2B\x32\x28\xFF\x25\x25\x20" + "\xFF\x27\x24\x20\xFF\x29\x25\x20\xFF" + "\x25\x23\x1E\xFF\x26\x24\x1F\xFF\x27\x26\x1F\xFF\x25\x23\x1D\xFF\x25\x25\x1F\xFF\x26\x25\x1F" + "\xFF\x26\x25\x1F\xFF\x27\x24\x1F\xFF" + "\x26\x24\x1F\xFF\x22\x21\x1C\xFF\x25\x21\x1D\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x27\x25\x1F" + "\xFF\x25\x25\x1F\xFF\x27\x26\x20\xFF" + "\x51\xC0\x8F\xFF\x52\xC2\x90\xFF\x53\xC4\x92\xFF\x54\xC6\x93\xFF\x54\xC7\x93\xFF\x55\xC8\x95" + "\xFF\x55\xC9\x95\xFF\x55\xCA\x96\xFF" + "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x56\xCA\x96\xFF\x50\xB8\x89\xFF\x48\x9C\x74\xFF\x41\x83\x62\xFF\x3E\x76\x59" + "\xFF\x38\x66\x4D\xFF\x34\x56\x42\xFF" + "\x31\x4A\x38\xFF\x33\x51\x3F\xFF\x38\x5E\x48\xFF\x3B\x6B\x51\xFF\x41\x83\x62\xFF\x48\x9B\x74" + "\xFF\x4F\xB5\x86\xFF\x55\xC9\x95\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x55\xC5\x92\xFF\x38\x66\x4E\xFF\x2A\x2E\x26\xFF\x28\x26\x21" + "\xFF\x28\x25\x20\xFF\x27\x25\x1F\xFF" + "\x25\x25\x1E\xFF\x26\x25\x1F\xFF\x26\x25\x20\xFF\x22\x23\x1D\xFF\x27\x25\x20\xFF\x27\x25\x20" + "\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF" + "\x25\x23\x1D\xFF\x25\x22\x1D\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF\x27\x26\x20\xFF\x27\x27\x1F" + "\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF" + "\x52\xC1\x8F\xFF\x52\xC3\x90\xFF\x53\xC4\x92\xFF\x54\xC6\x93\xFF\x54\xC8\x94\xFF\x55\xC8\x95" + "\xFF\x55\xC9\x95\xFF\x55\xCA\x96\xFF" + "\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCB\x96\xFF\x54\xC5\x92" + "\xFF\x52\xBE\x8D\xFF\x51\xB7\x88\xFF" + "\x4F\xB2\x84\xFF\x4F\xB5\x87\xFF\x52\xBB\x8B\xFF\x53\xC1\x8F\xFF\x55\xCA\x95\xFF\x56\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x49\x9C\x75\xFF\x2F\x44\x35\xFF\x26\x27\x23\xFF\x26\x25\x1E" + "\xFF\x29\x26\x20\xFF\x26\x26\x1E\xFF" + "\x26\x22\x1D\xFF\x26\x23\x1F\xFF\x27\x25\x20\xFF\x27\x24\x20\xFF\x27\x24\x1F\xFF\x25\x23\x1D" + "\xFF\x26\x25\x1E\xFF\x28\x25\x20\xFF" + "\x26\x24\x1D\xFF\x27\x24\x1F\xFF\x27\x25\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x25\x1E" + "\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF" + "\x52\xC2\x90\xFF\x53\xC4\x91\xFF\x53\xC5\x92\xFF\x54\xC7\x93\xFF\x54\xC8\x94\xFF\x55\xC9\x95" + "\xFF\x55\xCA\x96\xFF\x55\xCA\x96\xFF" + "\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF" + "\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98" + "\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF" + "\x51\xBC\x8B\xFF\x43\x8C\x69\xFF\x30\x48\x39\xFF\x2A\x2D\x27\xFF\x24\x25\x20\xFF\x24\x24\x1E" + "\xFF\x25\x24\x1E\xFF\x25\x22\x1D\xFF" + "\x27\x25\x1F\xFF\x28\x26\x20\xFF\x25\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x25\x1F\xFF\x27\x24\x1F" + "\xFF\x28\x25\x1F\xFF\x25\x24\x1E\xFF" + "\x25\x22\x1D\xFF\x27\x25\x1F\xFF\x27\x26\x20\xFF\x26\x24\x1E\xFF\x27\x25\x20\xFF\x27\x24\x1F" + "\xFF\x29\x26\x21\xFF\x26\x26\x20\xFF" + "\x52\xC3\x90\xFF\x53\xC4\x92\xFF\x54\xC6\x93\xFF\x54\xC8\x94\xFF\x55\xC8\x95\xFF\x55\xC9\x95" + "\xFF\x55\xCA\x96\xFF\x56\xCB\x96\xFF" + "\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97" + "\xFF\x52\xBE\x8D\xFF\x48\x9A\x73\xFF" + "\x38\x63\x4D\xFF\x2C\x39\x2E\xFF\x27\x2B\x24\xFF\x27\x26\x21\xFF\x25\x23\x1E\xFF\x25\x24\x1E" + "\xFF\x25\x24\x1D\xFF\x25\x26\x1D\xFF" + "\x25\x24\x1F\xFF\x27\x25\x1E\xFF\x26\x25\x1E\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x26\x25\x20" + "\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF" + "\x27\x25\x1F\xFF\x26\x25\x1E\xFF\x25\x24\x1D\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x27\x24\x20" + "\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF" + "\x53\xC4\x91\xFF\x53\xC5\x92\xFF\x54\xC7\x93\xFF\x54\xC8\x94\xFF\x55\xC8\x95\xFF\x55\xC9\x95" + "\xFF\x55\xCA\x96\xFF\x56\xCB\x96\xFF" + "\x56\xCB\x96\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x58\xCC\x99\xFF\x57\xCD\x98\xFF\x57\xCD\x99\xFF\x57\xCD\x98\xFF\x52\xBE\x8D\xFF\x43\x8C\x68" + "\xFF\x35\x5D\x47\xFF\x2E\x40\x32\xFF" + "\x27\x2C\x26\xFF\x28\x2A\x23\xFF\x29\x29\x22\xFF\x27\x26\x20\xFF\x23\x22\x1C\xFF\x25\x23\x1D" + "\xFF\x26\x24\x1E\xFF\x27\x25\x20\xFF" + "\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x26\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1D\xFF\x25\x23\x1E" + "\xFF\x26\x23\x1E\xFF\x24\x23\x1E\xFF" + "\x25\x22\x1C\xFF\x25\x23\x1E\xFF\x25\x23\x1D\xFF\x25\x24\x1E\xFF\x26\x23\x1F\xFF\x28\x25\x20" + "\xFF\x27\x24\x1E\xFF\x27\x24\x1E\xFF" + "\x53\xC4\x92\xFF\x54\xC6\x93\xFF\x54\xC7\x92\xFF\x55\xC8\x94\xFF\x55\xC9\x95\xFF\x54\xC9\x95" + "\xFF\x54\xC9\x95\xFF\x55\xCA\x95\xFF" + "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCD\x99\xFF\x56\xCD\x97" + "\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCE\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x99\xFF" + "\x56\xCD\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x58\xCD\x98\xFF\x56\xCD\x97\xFF\x56\xCC\x97" + "\xFF\x56\xCC\x97\xFF\x54\xC7\x93\xFF" + "\x51\xBC\x8B\xFF\x4E\xAF\x83\xFF\x46\x92\x6D\xFF\x39\x67\x4E\xFF\x2D\x3B\x30\xFF\x2B\x32\x29" + "\xFF\x29\x2D\x24\xFF\x28\x28\x22\xFF" + "\x27\x26\x20\xFF\x27\x25\x1F\xFF\x28\x26\x20\xFF\x28\x25\x20\xFF\x26\x25\x1F\xFF\x26\x26\x1F" + "\xFF\x26\x25\x1E\xFF\x28\x25\x20\xFF" + "\x2A\x27\x22\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x26\x23\x1C\xFF\x26\x23\x1E" + "\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF" + "\x26\x24\x1F\xFF\x28\x25\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x25\x23\x1E\xFF\x28\x25\x20" + "\xFF\x29\x26\x21\xFF\x25\x24\x1E\xFF" + "\x53\xC5\x92\xFF\x54\xC7\x93\xFF\x53\xC8\x93\xFF\x54\xC8\x94\xFF\x55\xC8\x94\xFF\x52\xBD\x8C" + "\xFF\x4C\xAD\x80\xFF\x4B\xA7\x7D\xFF" + "\x48\xA1\x77\xFF\x48\xA0\x77\xFF\x4B\xA7\x7D\xFF\x4F\xB0\x83\xFF\x51\xB9\x8A\xFF\x52\xC0\x8E" + "\xFF\x55\xC6\x92\xFF\x56\xCB\x96\xFF" + "\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x56\xCD\x97\xFF" + "\x57\xCD\x98\xFF\x56\xCB\x97\xFF\x54\xC4\x91\xFF\x50\xBA\x8B\xFF\x4E\xAF\x81\xFF\x49\x9F\x77" + "\xFF\x43\x8B\x68\xFF\x3F\x78\x5B\xFF" + "\x37\x61\x4A\xFF\x30\x48\x38\xFF\x2A\x35\x2B\xFF\x2A\x30\x27\xFF\x28\x2B\x22\xFF\x27\x27\x20" + "\xFF\x28\x26\x20\xFF\x28\x25\x20\xFF" + "\x27\x24\x1F\xFF\x25\x24\x1D\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x27\x24\x1F" + "\xFF\x26\x24\x1F\xFF\x27\x26\x20\xFF" + "\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x27\x24\x20\xFF\x28\x27\x20\xFF\x27\x24\x1F\xFF\x26\x23\x1E" + "\xFF\x26\x24\x1F\xFF\x27\x24\x20\xFF" + "\x26\x24\x1E\xFF\x27\x24\x1E\xFF\x25\x22\x1D\xFF\x26\x24\x1E\xFF\x26\x25\x1F\xFF\x27\x25\x1F" + "\xFF\x27\x26\x1F\xFF\x26\x24\x1E\xFF" + "\x54\xC6\x92\xFF\x54\xC8\x94\xFF\x54\xC8\x94\xFF\x54\xC8\x94\xFF\x44\x8C\x69\xFF\x37\x5E\x49" + "\xFF\x32\x4D\x3C\xFF\x31\x49\x38\xFF" + "\x2D\x42\x33\xFF\x2D\x44\x33\xFF\x2E\x47\x37\xFF\x32\x4E\x3C\xFF\x35\x54\x42\xFF\x34\x58\x43" + "\xFF\x34\x5C\x45\xFF\x38\x63\x4C\xFF" + "\x3C\x6F\x55\xFF\x40\x7B\x5C\xFF\x43\x87\x65\xFF\x45\x91\x6D\xFF\x44\x8E\x6B\xFF\x43\x8A\x68" + "\xFF\x43\x86\x65\xFF\x41\x81\x61\xFF" + "\x3D\x75\x59\xFF\x38\x67\x4E\xFF\x36\x5C\x47\xFF\x33\x55\x42\xFF\x33\x4E\x3C\xFF\x2F\x41\x34" + "\xFF\x2A\x34\x2A\xFF\x2B\x31\x27\xFF" + "\x27\x2C\x23\xFF\x29\x2B\x23\xFF\x28\x29\x22\xFF\x26\x25\x20\xFF\x29\x28\x22\xFF\x27\x26\x20" + "\xFF\x27\x25\x20\xFF\x27\x24\x20\xFF" + "\x29\x26\x21\xFF\x27\x25\x1F\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF\x25\x25\x1F" + "\xFF\x26\x25\x1E\xFF\x28\x26\x21\xFF" + "\x27\x25\x20\xFF\x25\x25\x1E\xFF\x25\x24\x1F\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF\x26\x24\x1F" + "\xFF\x26\x24\x20\xFF\x25\x24\x1F\xFF" + "\x26\x25\x1F\xFF\x26\x23\x1E\xFF\x23\x21\x1C\xFF\x25\x23\x1C\xFF\x28\x25\x20\xFF\x26\x25\x1F" + "\xFF\x28\x27\x1F\xFF\x27\x25\x1F\xFF" + "\x53\xC7\x92\xFF\x53\xC8\x94\xFF\x53\xC3\x91\xFF\x3E\x7B\x5D\xFF\x2B\x35\x2D\xFF\x25\x2B\x22" + "\xFF\x23\x26\x1E\xFF\x25\x26\x20\xFF" + "\x24\x24\x1E\xFF\x27\x27\x1F\xFF\x28\x28\x22\xFF\x28\x29\x23\xFF\x27\x29\x23\xFF\x27\x29\x23" + "\xFF\x2A\x2C\x25\xFF\x2A\x2D\x25\xFF" + "\x27\x2D\x25\xFF\x27\x2D\x25\xFF\x27\x2D\x24\xFF\x29\x30\x27\xFF\x29\x2F\x28\xFF\x28\x2F\x26" + "\xFF\x29\x30\x27\xFF\x29\x2F\x26\xFF" + "\x28\x2C\x23\xFF\x2A\x2D\x24\xFF\x29\x2B\x23\xFF\x28\x29\x22\xFF\x27\x27\x21\xFF\x26\x26\x21" + "\xFF\x27\x26\x1F\xFF\x29\x26\x21\xFF" + "\x26\x26\x20\xFF\x26\x24\x1F\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1F" + "\xFF\x26\x24\x1E\xFF\x26\x25\x1F\xFF" + "\x28\x25\x20\xFF\x28\x25\x20\xFF\x29\x27\x21\xFF\x29\x26\x21\xFF\x27\x24\x20\xFF\x25\x24\x1F" + "\xFF\x25\x24\x1F\xFF\x28\x25\x21\xFF" + "\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x29\x26\x20\xFF\x26\x25\x20\xFF\x29\x26\x21\xFF\x26\x23\x1F" + "\xFF\x27\x26\x20\xFF\x27\x24\x1F\xFF" + "\x27\x24\x1F\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x26\x25\x1F\xFF\x25\x22\x1D\xFF\x25\x23\x1F" + "\xFF\x26\x25\x20\xFF\x26\x25\x1F\xFF" + "\x53\xC8\x94\xFF\x54\xC8\x95\xFF\x49\xA4\x7A\xFF\x30\x49\x39\xFF\x26\x29\x23\xFF\x24\x25\x1F" + "\xFF\x23\x22\x1D\xFF\x25\x22\x1D\xFF" + "\x25\x24\x1D\xFF\x28\x25\x1F\xFF\x28\x26\x20\xFF\x26\x25\x1F\xFF\x25\x24\x1F\xFF\x27\x25\x20" + "\xFF\x27\x25\x20\xFF\x25\x24\x1F\xFF" + "\x26\x25\x1E\xFF\x28\x26\x20\xFF\x28\x27\x20\xFF\x26\x25\x1F\xFF\x27\x26\x20\xFF\x28\x26\x21" + "\xFF\x27\x26\x20\xFF\x26\x25\x20\xFF" + "\x27\x25\x20\xFF\x26\x25\x1F\xFF\x26\x26\x20\xFF\x27\x26\x1F\xFF\x25\x25\x20\xFF\x24\x23\x1D" + "\xFF\x25\x24\x1D\xFF\x27\x25\x1F\xFF" + "\x25\x25\x1E\xFF\x26\x23\x1F\xFF\x27\x25\x1F\xFF\x27\x26\x21\xFF\x29\x27\x22\xFF\x27\x25\x20" + "\xFF\x26\x24\x1D\xFF\x26\x25\x1E\xFF" + "\x27\x26\x20\xFF\x2A\x27\x21\xFF\x29\x27\x21\xFF\x27\x25\x1F\xFF\x25\x22\x1F\xFF\x24\x22\x1D" + "\xFF\x25\x23\x1D\xFF\x28\x26\x21\xFF" + "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x26\x24\x1F" + "\xFF\x27\x26\x20\xFF\x26\x25\x1F\xFF" + "\x26\x23\x1F\xFF\x28\x25\x21\xFF\x28\x25\x20\xFF\x26\x25\x1E\xFF\x26\x25\x1F\xFF\x26\x25\x1F" + "\xFF\x26\x26\x1F\xFF\x26\x24\x1F\xFF" + "\x55\xC8\x94\xFF\x54\xC8\x94\xFF\x40\x80\x60\xFF\x29\x36\x2A\xFF\x26\x26\x20\xFF\x24\x23\x1D" + "\xFF\x24\x22\x1D\xFF\x27\x23\x1F\xFF" + "\x25\x23\x1D\xFF\x26\x24\x1F\xFF\x27\x27\x20\xFF\x29\x27\x21\xFF\x26\x24\x1F\xFF\x24\x24\x1E" + "\xFF\x26\x25\x1F\xFF\x26\x24\x1F\xFF" + "\x27\x24\x1F\xFF\x29\x27\x20\xFF\x29\x26\x20\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF\x28\x25\x20" + "\xFF\x28\x25\x20\xFF\x26\x24\x1D\xFF" + "\x28\x25\x20\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x25\x24\x1F\xFF\x28\x25\x1F" + "\xFF\x27\x25\x1E\xFF\x27\x24\x1F\xFF" + "\x27\x25\x1F\xFF\x27\x25\x1F\xFF\x25\x25\x1F\xFF\x25\x24\x1E\xFF\x26\x23\x1F\xFF\x27\x24\x20" + "\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF" + "\x25\x24\x1E\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF\x24\x23\x1E\xFF\x27\x24\x1F\xFF\x25\x25\x1F" + "\xFF\x26\x24\x1F\xFF\x27\x25\x20\xFF" + "\x27\x24\x1F\xFF\x27\x25\x1F\xFF\x27\x27\x20\xFF\x28\x27\x21\xFF\x24\x24\x1E\xFF\x27\x24\x1F" + "\xFF\x27\x26\x20\xFF\x28\x26\x20\xFF" + "\x26\x24\x1F\xFF\x26\x26\x1F\xFF\x29\x27\x21\xFF\x26\x25\x1F\xFF\x25\x25\x1F\xFF\x26\x25\x1F" + "\xFF\x26\x24\x1F\xFF\x26\x26\x1D\xFF" + "\x54\xC9\x94\xFF\x52\xC1\x8E\xFF\x3A\x6C\x52\xFF\x29\x30\x26\xFF\x25\x25\x1F\xFF\x24\x22\x1D" + "\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF" + "\x26\x23\x1E\xFF\x25\x24\x1F\xFF\x26\x25\x1F\xFF\x26\x26\x20\xFF\x27\x24\x1F\xFF\x27\x24\x1F" + "\xFF\x27\x25\x1F\xFF\x28\x26\x20\xFF" + "\x29\x26\x22\xFF\x2A\x27\x21\xFF\x28\x26\x20\xFF\x29\x26\x20\xFF\x27\x24\x1F\xFF\x26\x24\x20" + "\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF" + "\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x28\x26\x20\xFF\x29\x26\x21\xFF\x28\x25\x21\xFF\x27\x25\x20" + "\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF" + "\x27\x24\x1F\xFF\x28\x26\x21\xFF\x25\x23\x1E\xFF\x23\x23\x1D\xFF\x27\x25\x20\xFF\x26\x24\x1F" + "\xFF\x28\x25\x20\xFF\x26\x25\x1E\xFF" + "\x23\x23\x1D\xFF\x25\x24\x1F\xFF\x26\x23\x1E\xFF\x26\x24\x1F\xFF\x28\x25\x1F\xFF\x26\x23\x1E" + "\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF" + "\x26\x25\x1F\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x24\x24\x1E\xFF\x24\x23\x1D" + "\xFF\x26\x24\x1E\xFF\x27\x25\x20\xFF" + "\x27\x26\x20\xFF\x27\x25\x1F\xFF\x28\x25\x1F\xFF\x28\x25\x1E\xFF\x27\x24\x1F\xFF\x27\x25\x20" + "\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF" + "\x55\xC9\x94\xFF\x52\xBE\x8C\xFF\x38\x68\x4F\xFF\x29\x2E\x25\xFF\x24\x24\x1D\xFF\x26\x23\x1F" + "\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF" + "\x25\x23\x1D\xFF\x25\x24\x1E\xFF\x27\x24\x1E\xFF\x27\x24\x1E\xFF\x26\x24\x1F\xFF\x26\x25\x1F" + "\xFF\x27\x25\x20\xFF\x26\x26\x20\xFF" + "\x29\x27\x21\xFF\x27\x25\x20\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E" + "\xFF\x25\x23\x1E\xFF\x27\x24\x20\xFF" + "\x27\x25\x1E\xFF\x26\x25\x1E\xFF\x27\x26\x20\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x27\x25\x1F" + "\xFF\x28\x26\x20\xFF\x28\x26\x20\xFF" + "\x27\x25\x20\xFF\x27\x24\x1F\xFF\x24\x22\x1D\xFF\x25\x23\x1E\xFF\x28\x26\x20\xFF\x27\x25\x20" + "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF" + "\x26\x25\x20\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF\x25\x23\x1E" + "\xFF\x26\x23\x1E\xFF\x26\x25\x1E\xFF" + "\x28\x25\x1F\xFF\x27\x26\x20\xFF\x28\x25\x20\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF\x25\x24\x1E" + "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF" + "\x25\x23\x1E\xFF\x26\x24\x1E\xFF\x26\x23\x1E\xFF\x26\x25\x1E\xFF\x27\x24\x1F\xFF\x28\x25\x20" + "\xFF\x26\x24\x1F\xFF\x24\x24\x1E\xFF" + "\x55\xCA\x95\xFF\x51\xBB\x8A\xFF\x38\x66\x4D\xFF\x28\x2D\x25\xFF\x24\x22\x1C\xFF\x26\x23\x1D" + "\xFF\x25\x23\x1D\xFF\x25\x24\x1E\xFF" + "\x26\x23\x1E\xFF\x25\x24\x1E\xFF\x25\x25\x1F\xFF\x27\x25\x1F\xFF\x29\x27\x20\xFF\x28\x26\x21" + "\xFF\x27\x24\x1F\xFF\x24\x23\x1D\xFF" + "\x27\x24\x1F\xFF\x28\x25\x20\xFF\x28\x27\x21\xFF\x26\x24\x1F\xFF\x23\x22\x1C\xFF\x25\x24\x1E" + "\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF" + "\x25\x25\x1E\xFF\x28\x27\x20\xFF\x27\x25\x1F\xFF\x26\x24\x20\xFF\x29\x26\x21\xFF\x28\x26\x20" + "\xFF\x27\x26\x20\xFF\x2A\x28\x22\xFF" + "\x28\x25\x20\xFF\x28\x25\x20\xFF\x26\x25\x20\xFF\x27\x25\x20\xFF\x26\x23\x1E\xFF\x27\x25\x20" + "\xFF\x27\x26\x1F\xFF\x27\x25\x1F\xFF" + "\x28\x25\x20\xFF\x26\x24\x1E\xFF\x26\x24\x1E\xFF\x24\x21\x1C\xFF\x23\x22\x1C\xFF\x25\x23\x1E" + "\xFF\x25\x25\x1F\xFF\x27\x25\x1E\xFF" + "\x27\x26\x20\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x29\x27\x20\xFF\x27\x24\x20\xFF\x27\x26\x1F" + "\xFF\x27\x25\x1F\xFF\x29\x26\x21\xFF" + "\x25\x24\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1E\xFF\x24\x24\x1E\xFF\x26\x25\x1E\xFF\x28\x25\x20" + "\xFF\x28\x25\x20\xFF\x24\x23\x1E\xFF" + "\x56\xCB\x96\xFF\x53\xC0\x8F\xFF\x39\x6A\x50\xFF\x28\x2D\x25\xFF\x28\x25\x20\xFF\x28\x25\x1F" + "\xFF\x28\x26\x1F\xFF\x27\x24\x1F\xFF" + "\x28\x25\x20\xFF\x27\x25\x20\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF\x2A\x27\x21\xFF\x2A\x27\x22" + "\xFF\x29\x26\x21\xFF\x29\x26\x21\xFF" + "\x27\x24\x1F\xFF\x27\x25\x1F\xFF\x27\x25\x1F\xFF\x26\x25\x1F\xFF\x25\x23\x1E\xFF\x26\x25\x1F" + "\xFF\x27\x25\x20\xFF\x28\x26\x1F\xFF" + "\x28\x26\x21\xFF\x26\x25\x20\xFF\x27\x25\x20\xFF\x28\x26\x21\xFF\x26\x24\x1F\xFF\x27\x26\x20" + "\xFF\x28\x25\x20\xFF\x27\x25\x1F\xFF" + "\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x24\x23\x1D\xFF\x26\x23\x1E" + "\xFF\x26\x25\x1F\xFF\x27\x24\x20\xFF" + "\x25\x25\x1F\xFF\x25\x23\x1E\xFF\x23\x23\x1D\xFF\x25\x22\x1D\xFF\x27\x25\x1D\xFF\x26\x25\x1E" + "\xFF\x28\x27\x20\xFF\x27\x24\x1F\xFF" + "\x27\x24\x1F\xFF\x27\x26\x1F\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x29\x26\x21\xFF\x27\x24\x1F" + "\xFF\x27\x26\x1F\xFF\x27\x25\x1F\xFF" + "\x28\x24\x1F\xFF\x26\x23\x1F\xFF\x26\x23\x1E\xFF\x25\x24\x1E\xFF\x26\x25\x1E\xFF\x25\x23\x1E" + "\xFF\x26\x25\x1F\xFF\x26\x26\x20\xFF" + "\x56\xCB\x96\xFF\x55\xC6\x92\xFF\x3B\x71\x56\xFF\x27\x2F\x26\xFF\x26\x25\x1F\xFF\x24\x23\x1D" + "\xFF\x26\x25\x1F\xFF\x27\x25\x20\xFF" + "\x24\x23\x1D\xFF\x26\x24\x1F\xFF\x27\x25\x20\xFF\x28\x26\x21\xFF\x25\x24\x1E\xFF\x27\x25\x20" + "\xFF\x27\x24\x1F\xFF\x27\x25\x1F\xFF" + "\x29\x26\x20\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x26\x23\x1E\xFF\x27\x25\x1F\xFF\x26\x25\x1E" + "\xFF\x27\x25\x1F\xFF\x27\x24\x1F\xFF" + "\x28\x26\x20\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x28\x26\x21\xFF\x25\x24\x1F\xFF\x28\x26\x21" + "\xFF\x27\x24\x1F\xFF\x26\x25\x1F\xFF" + "\x26\x25\x1F\xFF\x25\x23\x1D\xFF\x26\x24\x1F\xFF\x27\x25\x1F\xFF\x26\x23\x1E\xFF\x25\x23\x1E" + "\xFF\x27\x25\x1F\xFF\x28\x26\x21\xFF" + "\x27\x25\x20\xFF\x25\x24\x1F\xFF\x25\x24\x1E\xFF\x26\x24\x1F\xFF\x28\x26\x20\xFF\x26\x26\x1F" + "\xFF\x26\x25\x1E\xFF\x28\x26\x1F\xFF" + "\x26\x22\x1E\xFF\x26\x23\x1E\xFF\x26\x23\x1E\xFF\x27\x25\x20\xFF\x26\x23\x1E\xFF\x26\x24\x1E" + "\xFF\x27\x25\x1F\xFF\x26\x25\x1E\xFF" + "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x28\x25\x1F\xFF\x27\x25\x1F\xFF\x27\x24\x1E\xFF\x27\x25\x20" + "\xFF\x26\x25\x1F\xFF\x25\x24\x1E\xFF" + "\x56\xCB\x96\xFF\x56\xCB\x96\xFF\x43\x87\x66\xFF\x2C\x39\x2D\xFF\x25\x25\x1D\xFF\x25\x22\x1D" + "\xFF\x27\x24\x1F\xFF\x29\x26\x21\xFF" + "\x27\x25\x1F\xFF\x27\x24\x20\xFF\x28\x24\x20\xFF\x28\x27\x22\xFF\x26\x24\x1F\xFF\x25\x24\x1E" + "\xFF\x27\x24\x1F\xFF\x23\x20\x1B\xFF" + "\x23\x23\x1C\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x28\x26\x21\xFF\x27\x24\x1E\xFF\x26\x24\x1E" + "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF" + "\x28\x25\x20\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x26\x25\x1F\xFF\x2A\x28\x21" + "\xFF\x29\x26\x20\xFF\x27\x26\x20\xFF" + "\x29\x27\x21\xFF\x29\x27\x20\xFF\x29\x26\x21\xFF\x27\x25\x1F\xFF\x28\x25\x20\xFF\x28\x26\x20" + "\xFF\x29\x26\x20\xFF\x27\x25\x1E\xFF" + "\x28\x23\x20\xFF\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x26\x24\x1E\xFF\x25\x24\x1E\xFF\x27\x24\x1F" + "\xFF\x28\x25\x20\xFF\x28\x25\x1E\xFF" + "\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x25\x25\x1D\xFF\x25\x22\x1E\xFF\x27\x24\x1F" + "\xFF\x29\x27\x20\xFF\x26\x24\x1E\xFF" + "\x25\x24\x1F\xFF\x28\x26\x20\xFF\x28\x25\x1E\xFF\x25\x24\x1E\xFF\x28\x26\x1E\xFF\x27\x25\x20" + "\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF" + "\x55\xCC\x96\xFF\x56\xCC\x97\xFF\x4B\xA6\x7A\xFF\x2E\x47\x36\xFF\x25\x25\x20\xFF\x28\x26\x21" + "\xFF\x29\x26\x21\xFF\x28\x25\x20\xFF" + "\x25\x25\x1F\xFF\x25\x22\x1E\xFF\x27\x24\x1F\xFF\x28\x25\x20\xFF\x25\x24\x1E\xFF\x25\x25\x1F" + "\xFF\x27\x25\x20\xFF\x27\x24\x1F\xFF" + "\x23\x23\x1D\xFF\x24\x22\x1D\xFF\x25\x23\x1E\xFF\x26\x26\x20\xFF\x26\x24\x1F\xFF\x26\x23\x1F" + "\xFF\x26\x25\x1F\xFF\x24\x23\x1E\xFF" + "\x25\x24\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF\x25\x23\x1F\xFF\x27\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x24\x23\x1E\xFF" + "\x26\x23\x1F\xFF\x29\x26\x21\xFF\x26\x23\x1E\xFF\x23\x22\x1D\xFF\x27\x24\x1F\xFF\x28\x25\x1F" + "\xFF\x27\x25\x20\xFF\x28\x26\x20\xFF" + "\x26\x24\x1E\xFF\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x26\x23\x1E" + "\xFF\x27\x25\x1F\xFF\x25\x23\x1E\xFF" + "\x28\x26\x1F\xFF\x27\x24\x1F\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF\x29\x26\x21\xFF\x28\x25\x20" + "\xFF\x27\x24\x20\xFF\x24\x22\x1D\xFF" + "\x29\x26\x21\xFF\x28\x25\x20\xFF\x27\x24\x1F\xFF\x27\x25\x20\xFF\x26\x23\x1E\xFF\x28\x25\x20" + "\xFF\x28\x25\x1F\xFF\x25\x23\x1E\xFF" + "\x55\xCC\x97\xFF\x56\xCC\x97\xFF\x51\xBE\x8C\xFF\x32\x52\x3F\xFF\x27\x2A\x22\xFF\x28\x26\x21" + "\xFF\x27\x24\x20\xFF\x27\x25\x20\xFF" + "\x26\x24\x1F\xFF\x29\x26\x21\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF\x26\x24\x1F\xFF\x26\x24\x1F" + "\xFF\x28\x25\x20\xFF\x27\x25\x1F\xFF" + "\x26\x25\x20\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x29\x29\x22\xFF\x25\x25\x1F\xFF\x27\x24\x20" + "\xFF\x29\x26\x21\xFF\x26\x24\x1F\xFF" + "\x26\x24\x1E\xFF\x27\x25\x1F\xFF\x29\x26\x21\xFF\x27\x24\x1F\xFF\x26\x25\x1F\xFF\x28\x25\x20" + "\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF" + "\x25\x22\x1D\xFF\x25\x22\x1D\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x27\x25\x1F" + "\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF" + "\x26\x25\x1E\xFF\x26\x24\x1F\xFF\x28\x26\x20\xFF\x2A\x27\x22\xFF\x29\x26\x21\xFF\x25\x24\x1E" + "\xFF\x24\x22\x1C\xFF\x24\x23\x1D\xFF" + "\x26\x24\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x28\x25\x1F\xFF\x26\x24\x1F\xFF\x26\x25\x1F" + "\xFF\x26\x25\x1E\xFF\x25\x23\x1D\xFF" + "\x26\x24\x1E\xFF\x24\x23\x1E\xFF\x26\x23\x1E\xFF\x27\x26\x20\xFF\x26\x23\x1E\xFF\x27\x25\x1E" + "\xFF\x28\x25\x1F\xFF\x27\x25\x1F\xFF" + "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x55\xCA\x95\xFF\x39\x6C\x51\xFF\x29\x2F\x26\xFF\x29\x28\x22" + "\xFF\x28\x25\x20\xFF\x29\x27\x22\xFF" + "\x28\x25\x20\xFF\x26\x25\x1F\xFF\x26\x24\x1F\xFF\x27\x25\x20\xFF\x26\x25\x1E\xFF\x26\x24\x1F" + "\xFF\x25\x22\x1E\xFF\x27\x24\x1F\xFF" + "\x25\x25\x1F\xFF\x25\x24\x1F\xFF\x26\x25\x1F\xFF\x25\x23\x1E\xFF\x26\x24\x1E\xFF\x27\x24\x1F" + "\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF" + "\x27\x24\x1F\xFF\x26\x25\x1E\xFF\x28\x26\x20\xFF\x27\x25\x20\xFF\x26\x24\x1E\xFF\x27\x26\x1F" + "\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF" + "\x26\x25\x1F\xFF\x27\x24\x20\xFF\x27\x26\x20\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x28\x25\x20" + "\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF" + "\x29\x27\x21\xFF\x28\x25\x20\xFF\x26\x25\x1F\xFF\x29\x26\x21\xFF\x29\x26\x21\xFF\x28\x26\x20" + "\xFF\x27\x25\x1E\xFF\x28\x25\x1E\xFF" + "\x27\x26\x21\xFF\x29\x26\x21\xFF\x27\x24\x1F\xFF\x26\x26\x1E\xFF\x25\x24\x1E\xFF\x26\x24\x1E" + "\xFF\x25\x24\x1E\xFF\x26\x25\x1F\xFF" + "\x24\x23\x1D\xFF\x25\x24\x1D\xFF\x26\x24\x1E\xFF\x27\x24\x1F\xFF\x26\x24\x1E\xFF\x25\x23\x1D" + "\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF" + "\x57\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x4A\xA3\x79\xFF\x29\x35\x2B\xFF\x27\x29\x23" + "\xFF\x27\x25\x1F\xFF\x27\x25\x1E\xFF" + "\x27\x26\x1F\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x29\x27\x20\xFF\x27\x25\x20\xFF\x29\x26\x21" + "\xFF\x25\x23\x1E\xFF\x25\x22\x1D\xFF" + "\x25\x25\x1F\xFF\x25\x24\x1E\xFF\x27\x24\x1F\xFF\x29\x26\x21\xFF\x26\x26\x20\xFF\x26\x24\x1F" + "\xFF\x27\x25\x1F\xFF\x26\x25\x1F\xFF" + "\x28\x25\x20\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x25\x23\x1E\xFF\x29\x26\x21\xFF\x29\x27\x20" + "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF" + "\x25\x22\x1D\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF\x24\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x24\x1F" + "\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF" + "\x26\x23\x1F\xFF\x28\x26\x20\xFF\x27\x26\x20\xFF\x27\x25\x20\xFF\x27\x25\x20\xFF\x26\x24\x1E" + "\xFF\x27\x25\x1F\xFF\x27\x25\x1E\xFF" + "\x27\x24\x1F\xFF\x28\x27\x21\xFF\x27\x25\x20\xFF\x26\x25\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1F" + "\xFF\x26\x24\x1E\xFF\x2A\x26\x21\xFF" + "\x28\x25\x20\xFF\x26\x24\x1E\xFF\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x28\x25\x20\xFF\x26\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x26\x23\x1D\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x55\xC8\x93\xFF\x32\x53\x40\xFF\x28\x2E\x25" + "\xFF\x25\x25\x20\xFF\x24\x23\x1E\xFF" + "\x28\x25\x20\xFF\x28\x26\x21\xFF\x27\x26\x1F\xFF\x27\x26\x20\xFF\x27\x24\x1F\xFF\x27\x26\x1F" + "\xFF\x27\x27\x21\xFF\x26\x25\x20\xFF" + "\x26\x25\x20\xFF\x27\x25\x1F\xFF\x28\x25\x1F\xFF\x26\x24\x1F\xFF\x25\x24\x1E\xFF\x27\x25\x20" + "\xFF\x29\x27\x22\xFF\x27\x25\x20\xFF" + "\x27\x24\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x24\x23\x1D\xFF\x29\x26\x21\xFF\x27\x25\x20" + "\xFF\x26\x24\x1F\xFF\x26\x23\x1E\xFF" + "\x25\x23\x1E\xFF\x28\x25\x20\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x29\x25\x20\xFF\x26\x24\x1F" + "\xFF\x26\x24\x1F\xFF\x26\x25\x1F\xFF" + "\x26\x23\x1E\xFF\x29\x26\x21\xFF\x28\x25\x20\xFF\x25\x25\x1F\xFF\x25\x24\x1E\xFF\x25\x22\x1D" + "\xFF\x27\x25\x1F\xFF\x28\x26\x21\xFF" + "\x26\x24\x1F\xFF\x26\x25\x1F\xFF\x26\x25\x1F\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x25\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x28\x26\x20\xFF" + "\x28\x25\x20\xFF\x28\x26\x20\xFF\x28\x25\x1F\xFF\x24\x23\x1D\xFF\x26\x24\x1F\xFF\x26\x25\x1F" + "\xFF\x25\x24\x1E\xFF\x25\x23\x1C\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x97\xFF\x43\x8E\x6A\xFF\x2B\x3C\x2F" + "\xFF\x29\x32\x28\xFF\x28\x30\x26\xFF" + "\x2A\x2F\x28\xFF\x29\x2F\x26\xFF\x29\x2D\x25\xFF\x2A\x2E\x26\xFF\x28\x2B\x23\xFF\x27\x2A\x24" + "\xFF\x28\x2B\x25\xFF\x27\x2A\x23\xFF" + "\x27\x29\x21\xFF\x27\x29\x22\xFF\x27\x28\x20\xFF\x29\x27\x22\xFF\x26\x25\x1F\xFF\x2A\x28\x23" + "\xFF\x28\x28\x21\xFF\x24\x24\x1D\xFF" + "\x26\x23\x1E\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x25\x23\x1E\xFF\x24\x22\x1E\xFF\x26\x23\x1E" + "\xFF\x26\x24\x1F\xFF\x26\x25\x1F\xFF" + "\x26\x24\x1F\xFF\x28\x25\x20\xFF\x28\x26\x21\xFF\x27\x25\x20\xFF\x28\x25\x20\xFF\x27\x24\x1F" + "\xFF\x25\x24\x1F\xFF\x25\x23\x1E\xFF" + "\x26\x23\x1E\xFF\x27\x24\x1F\xFF\x27\x25\x20\xFF\x23\x23\x1D\xFF\x24\x23\x1E\xFF\x25\x23\x1E" + "\xFF\x25\x25\x1F\xFF\x26\x24\x1F\xFF" + "\x28\x25\x20\xFF\x27\x25\x20\xFF\x27\x25\x1F\xFF\x27\x24\x20\xFF\x26\x25\x20\xFF\x26\x23\x1E" + "\xFF\x28\x25\x20\xFF\x25\x22\x1D\xFF" + "\x27\x24\x1F\xFF\x27\x25\x20\xFF\x28\x25\x1F\xFF\x26\x24\x1E\xFF\x27\x24\x1F\xFF\x27\x24\x1F" + "\xFF\x26\x23\x1E\xFF\x25\x24\x1E\xFF" + "\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x56\xCD\x97\xFF\x54\xC5\x93\xFF\x49\xA2\x78" + "\xFF\x47\x94\x6F\xFF\x44\x8D\x6A\xFF" + "\x42\x87\x64\xFF\x40\x7F\x5F\xFF\x3F\x79\x5B\xFF\x3D\x71\x57\xFF\x37\x68\x4F\xFF\x39\x65\x4D" + "\xFF\x38\x60\x4B\xFF\x36\x5E\x49\xFF" + "\x37\x5A\x45\xFF\x32\x51\x3E\xFF\x30\x48\x38\xFF\x2F\x41\x33\xFF\x2A\x35\x2A\xFF\x2C\x31\x2A" + "\xFF\x2B\x31\x29\xFF\x29\x2E\x26\xFF" + "\x2B\x2D\x27\xFF\x28\x2A\x23\xFF\x28\x2A\x23\xFF\x26\x27\x21\xFF\x26\x26\x20\xFF\x26\x25\x1F" + "\xFF\x27\x24\x1E\xFF\x28\x27\x1F\xFF" + "\x27\x26\x1D\xFF\x25\x24\x1F\xFF\x26\x25\x1F\xFF\x26\x24\x1D\xFF\x24\x23\x1D\xFF\x25\x23\x1E" + "\xFF\x25\x23\x1E\xFF\x26\x23\x1E\xFF" + "\x29\x26\x21\xFF\x27\x24\x20\xFF\x28\x27\x20\xFF\x27\x26\x1E\xFF\x28\x26\x21\xFF\x26\x24\x1E" + "\xFF\x27\x25\x1F\xFF\x26\x24\x1F\xFF" + "\x26\x26\x1E\xFF\x28\x26\x20\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x24\x1F" + "\xFF\x27\x25\x20\xFF\x25\x24\x1E\xFF" + "\x28\x25\x20\xFF\x26\x23\x1E\xFF\x29\x26\x21\xFF\x27\x25\x20\xFF\x27\x26\x20\xFF\x26\x25\x1F" + "\xFF\x27\x26\x1F\xFF\x28\x26\x20\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97" + "\xFF\x55\xC9\x95\xFF\x54\xC6\x93\xFF" + "\x53\xBF\x8D\xFF\x4F\xB4\x85\xFF\x4C\xA8\x7D\xFF\x48\x9C\x74\xFF\x44\x8D\x6A\xFF\x41\x82\x62" + "\xFF\x3D\x76\x59\xFF\x3B\x6A\x51\xFF" + "\x36\x5A\x46\xFF\x30\x4B\x3B\xFF\x2C\x3D\x30\xFF\x2B\x35\x2B\xFF\x2B\x32\x2A\xFF\x2A\x2F\x27" + "\xFF\x2A\x2C\x24\xFF\x2A\x2B\x24\xFF" + "\x28\x29\x22\xFF\x27\x26\x20\xFF\x26\x25\x1F\xFF\x27\x24\x1F\xFF\x28\x25\x1E\xFF\x26\x23\x1E" + "\xFF\x26\x24\x1F\xFF\x27\x26\x1F\xFF" + "\x28\x25\x20\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x26\x25\x20\xFF\x25\x24\x1E\xFF\x25\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x27\x24\x20\xFF" + "\x27\x24\x1F\xFF\x25\x25\x1F\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x27\x24\x1F" + "\xFF\x28\x26\x1F\xFF\x28\x26\x1F\xFF" + "\x28\x26\x20\xFF\x28\x26\x1F\xFF\x27\x25\x20\xFF\x27\x26\x20\xFF\x27\x26\x1F\xFF\x28\x26\x20" + "\xFF\x29\x25\x20\xFF\x27\x25\x20\xFF" + "\x56\xCC\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x97\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCA\x96" + "\xFF\x54\xC5\x92\xFF\x53\xC0\x8F\xFF" + "\x51\xB9\x89\xFF\x50\xB3\x85\xFF\x4D\xAC\x80\xFF\x49\xA1\x78\xFF\x43\x8E\x6A\xFF\x3D\x76\x59" + "\xFF\x34\x56\x42\xFF\x2E\x3D\x30\xFF" + "\x2D\x36\x2C\xFF\x2A\x31\x28\xFF\x26\x2A\x23\xFF\x25\x28\x21\xFF\x28\x29\x22\xFF\x27\x26\x1F" + "\xFF\x28\x27\x20\xFF\x26\x26\x1E\xFF" + "\x28\x25\x20\xFF\x29\x26\x21\xFF\x27\x24\x1F\xFF\x25\x25\x1F\xFF\x26\x24\x1F\xFF\x27\x25\x20" + "\xFF\x27\x26\x20\xFF\x27\x24\x1F\xFF" + "\x24\x25\x1F\xFF\x26\x25\x1F\xFF\x28\x26\x20\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x26\x23\x1E" + "\xFF\x25\x23\x1D\xFF\x29\x26\x20\xFF" + "\x28\x26\x1F\xFF\x29\x26\x21\xFF\x27\x26\x20\xFF\x26\x24\x1E\xFF\x27\x25\x1F\xFF\x27\x24\x1F" + "\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF" + "\x56\xCE\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x97\xFF\x58\xCC\x97\xFF\x56\xCC\x97" + "\xFF\x57\xCC\x97\xFF\x56\xC6\x91\xFF" + "\x4B\xA9\x7D\xFF\x42\x8C\x69\xFF\x3A\x6D\x54\xFF\x36\x5B\x46\xFF\x2F\x49\x38\xFF\x2C\x3D\x2F" + "\xFF\x29\x32\x27\xFF\x28\x2E\x26\xFF" + "\x27\x2B\x23\xFF\x27\x28\x22\xFF\x24\x25\x1E\xFF\x23\x23\x1D\xFF\x26\x24\x1F\xFF\x27\x24\x1E" + "\xFF\x26\x23\x1E\xFF\x28\x26\x20\xFF" + "\x27\x26\x20\xFF\x27\x25\x20\xFF\x26\x24\x1E\xFF\x25\x23\x1E\xFF\x25\x24\x1E\xFF\x24\x23\x1D" + "\xFF\x25\x24\x1E\xFF\x27\x24\x1F\xFF" + "\x27\x26\x1E\xFF\x27\x26\x20\xFF\x28\x26\x21\xFF\x28\x25\x20\xFF\x29\x26\x21\xFF\x25\x24\x1E" + "\xFF\x22\x21\x1C\xFF\x26\x24\x1F\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x57\xCD\x98" + "\xFF\x56\xCD\x97\xFF\x56\xCC\x97\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCB\x96\xFF\x52\xC0\x8E\xFF\x4C\xAB\x7E\xFF\x46\x97\x70" + "\xFF\x41\x82\x62\xFF\x3C\x6E\x53\xFF" + "\x34\x54\x41\xFF\x2C\x3C\x31\xFF\x28\x30\x27\xFF\x27\x2C\x23\xFF\x27\x29\x22\xFF\x28\x27\x21" + "\xFF\x26\x25\x1F\xFF\x27\x27\x20\xFF" + "\x28\x25\x20\xFF\x26\x25\x20\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x25\x23\x1E\xFF\x25\x24\x1E" + "\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF" + "\x28\x26\x21\xFF\x26\x24\x1E\xFF\x25\x25\x1F\xFF\x26\x24\x1F\xFF\x25\x23\x1E\xFF\x27\x24\x1F" + "\xFF\x28\x25\x20\xFF\x26\x24\x1F\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x56\xCD\x97\xFF\x56\xCD\x97\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCD\x97\xFF\x57\xCD\x97" + "\xFF\x55\xCA\x95\xFF\x53\xC2\x90\xFF" + "\x50\xB6\x88\xFF\x4B\xA7\x7C\xFF\x41\x86\x64\xFF\x35\x5A\x45\xFF\x2B\x38\x2D\xFF\x29\x31\x28" + "\xFF\x28\x2C\x25\xFF\x26\x28\x22\xFF" + "\x26\x26\x21\xFF\x27\x25\x20\xFF\x26\x24\x1F\xFF\x26\x24\x1F\xFF\x27\x24\x1F\xFF\x28\x25\x1F" + "\xFF\x29\x26\x20\xFF\x29\x26\x21\xFF" + "\x2B\x29\x22\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x28\x26\x20\xFF\x29\x27\x20\xFF\x28\x25\x20" + "\xFF\x28\x25\x1F\xFF\x28\x26\x20\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x97\xFF\x57\xCD\x97\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCD\x97\xFF\x57\xCD\x97\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF" + "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x97" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97" + "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCC\x97\xFF\x56\xCD\x97\xFF\x57\xCC\x97\xFF\x52\xBB\x89\xFF\x46\x91\x6C" + "\xFF\x38\x68\x4F\xFF\x31\x4F\x3D\xFF" + "\x2C\x3A\x30\xFF\x2A\x30\x28\xFF\x27\x2A\x24\xFF\x26\x28\x21\xFF\x25\x26\x1E\xFF\x27\x25\x1F" + "\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF" + "\x25\x24\x1D\xFF\x24\x24\x1D\xFF\x27\x25\x1F\xFF\x28\x26\x1F\xFF\x23\x23\x1D\xFF\x26\x23\x1E" + "\xFF\x28\x26\x20\xFF\x27\x25\x1F\xFF" + "\x56\xCB\x96\xFF\x55\xCA\x95\xFF\x55\xC9\x94\xFF\x56\xCB\x96\xFF\x58\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x55\xC8\x94\xFF\x4F\xB4\x86\xFF" + "\x46\x93\x6E\xFF\x3F\x76\x59\xFF\x34\x57\x42\xFF\x2B\x3B\x2E\xFF\x29\x30\x27\xFF\x2B\x2D\x24" + "\xFF\x28\x28\x22\xFF\x26\x25\x1F\xFF" + "\x25\x25\x1E\xFF\x28\x25\x20\xFF\x28\x25\x20\xFF\x27\x25\x1E\xFF\x24\x22\x1D\xFF\x26\x23\x1E" + "\xFF\x28\x24\x1F\xFF\x25\x25\x1E\xFF" + "\x39\x64\x4C\xFF\x38\x62\x4B\xFF\x37\x61\x4A\xFF\x37\x62\x4B\xFF\x39\x68\x4F\xFF\x3B\x71\x55" + "\xFF\x3F\x7B\x5B\xFF\x40\x81\x60\xFF" + "\x44\x8C\x69\xFF\x47\x94\x6F\xFF\x49\x9C\x75\xFF\x4A\xA3\x7A\xFF\x4C\xAB\x7F\xFF\x50\xB5\x86" + "\xFF\x53\xC3\x90\xFF\x56\xCC\x97\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x58\xCD\x98\xFF\x57\xCD\x97\xFF\x57\xCD\x97" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x97\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97" + "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF" + "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x58\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCC\x97\xFF\x54\xC5\x92\xFF\x50\xB8\x88\xFF\x4B\xA5\x7A\xFF\x3F\x79\x5B\xFF\x31\x4C\x3C" + "\xFF\x29\x34\x29\xFF\x27\x2E\x25\xFF" + "\x26\x26\x22\xFF\x26\x25\x20\xFF\x26\x25\x20\xFF\x27\x27\x1F\xFF\x23\x21\x1C\xFF\x29\x26\x20" + "\xFF\x27\x26\x21\xFF\x27\x25\x1F\xFF" + "\x2A\x2C\x24\xFF\x29\x2B\x24\xFF\x26\x28\x23\xFF\x2A\x2C\x25\xFF\x28\x2C\x24\xFF\x27\x2B\x23" + "\xFF\x26\x2B\x23\xFF\x28\x2F\x25\xFF" + "\x27\x2E\x25\xFF\x2A\x31\x28\xFF\x29\x33\x2A\xFF\x29\x34\x2A\xFF\x2D\x37\x2C\xFF\x2D\x38\x2D" + "\xFF\x2C\x3B\x2E\xFF\x2F\x43\x35\xFF" + "\x35\x5B\x47\xFF\x3C\x70\x55\xFF\x41\x83\x62\xFF\x47\x95\x6E\xFF\x4B\xA7\x7B\xFF\x4E\xAF\x82" + "\xFF\x50\xB6\x87\xFF\x52\xBC\x8C\xFF" + "\x54\xC5\x92\xFF\x56\xCB\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x97" + "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF" + "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97" + "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF" + "\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97" + "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF" + "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x97\xFF\x56\xCC\x97\xFF\x54\xC6\x92" + "\xFF\x4A\xA1\x78\xFF\x3D\x6E\x54\xFF" + "\x32\x4A\x39\xFF\x2E\x35\x2B\xFF\x27\x29\x22\xFF\x24\x24\x1E\xFF\x28\x27\x1F\xFF\x26\x26\x20" + "\xFF\x26\x24\x1F\xFF\x25\x24\x1E\xFF" + "\x26\x26\x20\xFF\x25\x25\x1F\xFF\x24\x23\x1E\xFF\x24\x23\x1E\xFF\x27\x26\x20\xFF\x26\x25\x1F" + "\xFF\x26\x26\x1F\xFF\x25\x25\x1F\xFF" + "\x26\x25\x1F\xFF\x24\x24\x1F\xFF\x28\x28\x21\xFF\x2A\x2A\x23\xFF\x26\x26\x20\xFF\x26\x28\x21" + "\xFF\x26\x29\x21\xFF\x2A\x2C\x24\xFF" + "\x27\x2B\x24\xFF\x29\x2E\x25\xFF\x29\x30\x27\xFF\x2B\x33\x2A\xFF\x2B\x37\x2D\xFF\x31\x44\x35" + "\xFF\x34\x53\x40\xFF\x38\x61\x4A\xFF" + "\x3C\x74\x58\xFF\x43\x88\x66\xFF\x4A\x9D\x76\xFF\x4E\xB1\x83\xFF\x54\xC4\x91\xFF\x56\xCD\x97" + "\xFF\x57\xCD\x97\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF" + "\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x56\xCC\x97" + "\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF" + "\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x56\xC9\x94\xFF" + "\x4C\xA7\x7C\xFF\x41\x81\x61\xFF\x35\x58\x44\xFF\x2D\x39\x2F\xFF\x2B\x30\x28\xFF\x28\x2A\x22" + "\xFF\x27\x26\x20\xFF\x27\x25\x20\xFF" + "\x27\x25\x1F\xFF\x26\x24\x1D\xFF\x28\x26\x1F\xFF\x28\x26\x20\xFF\x25\x24\x1F\xFF\x25\x24\x1E" + "\xFF\x25\x23\x1E\xFF\x26\x24\x1F\xFF" + "\x26\x23\x1E\xFF\x25\x24\x1E\xFF\x29\x26\x21\xFF\x2A\x28\x20\xFF\x28\x25\x20\xFF\x28\x26\x20" + "\xFF\x27\x25\x1E\xFF\x26\x26\x1F\xFF" + "\x26\x27\x1F\xFF\x27\x25\x1F\xFF\x25\x26\x1E\xFF\x26\x27\x21\xFF\x27\x28\x22\xFF\x29\x28\x22" + "\xFF\x28\x2A\x23\xFF\x29\x2C\x24\xFF" + "\x2A\x31\x27\xFF\x2A\x35\x2A\xFF\x2F\x42\x35\xFF\x34\x50\x3E\xFF\x38\x61\x4A\xFF\x3C\x74\x56" + "\xFF\x46\x92\x6D\xFF\x4D\xAF\x82\xFF" + "\x53\xC9\x94\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCE\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCC\x97\xFF\x55\xC8\x94\xFF\x51\xB8\x89\xFF\x48\x9B\x74\xFF\x38\x5E\x48\xFF\x2C\x39\x2D" + "\xFF\x28\x2F\x26\xFF\x27\x2A\x22\xFF" + "\x27\x24\x1F\xFF\x25\x23\x1E\xFF\x25\x23\x1E\xFF\x27\x24\x1F\xFF\x26\x23\x1E\xFF\x26\x24\x1F" + "\xFF\x28\x25\x20\xFF\x26\x26\x20\xFF" + "\x27\x25\x1F\xFF\x26\x25\x1F\xFF\x25\x25\x1F\xFF\x25\x25\x1F\xFF\x26\x24\x1F\xFF\x27\x24\x1F" + "\xFF\x27\x25\x20\xFF\x25\x24\x1E\xFF" + "\x26\x24\x1F\xFF\x25\x24\x1D\xFF\x25\x22\x1C\xFF\x26\x25\x1F\xFF\x27\x25\x20\xFF\x27\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF" + "\x29\x27\x22\xFF\x27\x27\x21\xFF\x28\x28\x22\xFF\x2B\x2C\x26\xFF\x29\x2B\x23\xFF\x28\x2C\x25" + "\xFF\x29\x31\x27\xFF\x2B\x36\x2C\xFF" + "\x2D\x3F\x31\xFF\x34\x5A\x46\xFF\x3F\x7D\x5E\xFF\x4A\xA0\x77\xFF\x51\xB8\x88\xFF\x55\xC5\x92" + "\xFF\x56\xCC\x97\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCC\x97\xFF\x57\xCC\x97\xFF\x57\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCC\x97" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCD\x98\xFF\x56\xCD\x97\xFF\x56\xCD\x98\xFF\x56\xCC\x97\xFF\x56\xCA\x96\xFF\x4E\xB2\x85" + "\xFF\x3F\x7D\x5E\xFF\x33\x55\x42\xFF" + "\x25\x22\x1E\xFF\x26\x23\x1E\xFF\x27\x25\x1F\xFF\x28\x25\x20\xFF\x27\x25\x20\xFF\x27\x25\x20" + "\xFF\x27\x25\x1F\xFF\x25\x24\x1D\xFF" + "\x27\x24\x1E\xFF\x27\x24\x1F\xFF\x25\x23\x1D\xFF\x27\x24\x1F\xFF\x25\x25\x1F\xFF\x27\x24\x1F" + "\xFF\x27\x24\x1F\xFF\x27\x24\x1F\xFF" + "\x27\x25\x20\xFF\x29\x26\x21\xFF\x28\x26\x21\xFF\x28\x25\x20\xFF\x25\x24\x1F\xFF\x28\x25\x20" + "\xFF\x28\x25\x20\xFF\x26\x25\x1E\xFF" + "\x27\x24\x1F\xFF\x26\x24\x1F\xFF\x28\x25\x20\xFF\x27\x26\x20\xFF\x26\x25\x20\xFF\x28\x26\x20" + "\xFF\x27\x27\x20\xFF\x27\x29\x21\xFF" + "\x28\x2A\x24\xFF\x28\x2C\x25\xFF\x2A\x31\x29\xFF\x2C\x38\x2D\xFF\x35\x57\x43\xFF\x3E\x74\x59" + "\xFF\x46\x91\x6E\xFF\x4E\xAD\x81\xFF" + "\x55\xC7\x93\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x56\xCC\x97\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF" + "\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x56\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98\xFF\x57\xCD\x98" + "\xFF\x56\xCB\x96\xFF\x51\xB7\x88\xFF"; + +static const BYTE TEST_RLE_BITMAP_EXPERIMENTAL_03_RLE[11160] = + "\x30\xF0\x23\x1F\x1E\x1D\x1D\x1E\x1D\x1F\x23\x4A\x78\x71\x64\x58\x4B\xF0\x3E\x30\x29\x26\x24" + "\x22\x21\x20\x20\x20\x1D\x1E\x20\x1E" + "\x1F\x86\x20\x20\x1F\x20\x21\x22\x1E\x1F\xF0\x1E\x1D\x1F\x20\x1F\x1F\x1F\x1E\x1E\x1F\x1F\x20" + "\x1E\x1F\x1F\x50\x1F\x1E\x1D\x1E\x1C" + "\xF0\x38\x1A\x14\x0E\x08\x08\x06\x04\x04\x18\x38\x4A\x66\x74\x82\xF0\x90\x9E\x90\x76\x52\x2C" + "\x16\x12\x0C\x06\x08\x06\x00\x02\x00" + "\xF0\x03\x03\x02\x00\x01\x03\x02\x02\x00\x02\x01\x01\x03\x00\x00\x33\x02\x04\x00\xD0\x02\x00" + "\x00\x03\x01\x02\x00\x02\x01\x00\x04" + "\x02\x06\xF0\x94\xAE\x90\x6E\x46\x2E\x1E\x0C\x0E\x0A\x02\x00\x02\x0C\x18\xF0\x24\x30\x4C\x6C" + "\x94\xBE\xBE\x9A\x6C\x48\x34\x1C\x10" + "\x08\x06\xF0\x06\x06\x00\x01\x03\x01\x02\x00\x02\x00\x02\x02\x08\x04\x02\xF0\x00\x03\x01\x00" + "\x01\x02\x00\x06\x02\x04\x01\x01\x03" + "\x03\x00\x40\x00\x00\x04\x01\xD3\x02\x14\x3C\x68\x8E\x86\x78\x68\x3E\x16\x02\x02\x00\x14\x02" + "\xF0\x1A\x44\x78\x92\x8C\x82\x6E\x52" + "\x2E\x14\x06\x02\x04\x00\x01\xF0\x01\x03\x00\x01\x01\x02\x00\x00\x03\x01\x01\x01\x04\x06\x00" + "\xD0\x02\x00\x02\x00\x02\x02\x02\x00" + "\x00\x04\x00\x03\x02\x04\xAA\x0C\x2C\x54\x6E\x7C\x4C\x02\x00\x02\x00\xF0\x10\x2E\x50\x6C\x82" + "\x92\x86\x50\x1C\x12\x0A\x04\x04\x02" + "\x01\xF0\x02\x02\x01\x05\x07\x00\x00\x01\x02\x03\x05\x03\x00\x01\x05\xA0\x01\x01\x01\x00\x02" + "\x04\x00\x02\x00\x01\xE5\x00\x00\x02" + "\x02\x00\x00\x00\x04\x1A\x14\x00\x00\x01\x00\x35\x01\x01\x00\xF0\x06\x16\x2A\x52\x96\xBC\x84" + "\x50\x28\x0C\x06\x04\x03\x03\x00\xF0" + "\x04\x04\x04\x02\x02\x02\x00\x02\x00\x00\x01\x02\x00\x02\x00\x70\x02\x04\x08\x01\x01\x02\x04" + "\x04\x5C\x02\x02\x00\x02\x00\x14\x01" + "\xF0\x00\x00\x00\x02\x02\x02\x16\x58\x86\x84\x54\x1E\x06\x06\x04\xF0\x00\x03\x01\x00\x02\x02" + "\x01\x02\x01\x04\x02\x01\x00\x02\x01" + "\x80\x02\x02\x01\x07\x03\x01\x01\x00\x04\x53\x01\x00\x00\x00\x01\x16\x00\xF0\x02\x00\x00\x01" + "\x00\x02\x00\x01\x13\x37\x55\x67\x4B" + "\x1D\x03\xF0\x14\x44\x7A\x74\x28\x08\x01\x01\x02\x04\x00\x02\x00\x00\x01\xF0\x02\x01\x05\x00" + "\x01\x04\x02\x00\x00\x00\x01\x00\x00" + "\x03\x03\x41\xF0\x04\x02\x02\x02\x03\x1F\x69\xA9\x9B\x7D\x69\x85\xAD\xA1\x47\xF0\x0B\x14\x5A" + "\x8A\x32\x08\x04\x04\x04\x01\x03\x04" + "\x01\x00\x00\xE0\x02\x00\x04\x00\x07\x01\x00\x03\x03\x00\x02\x00\x00\x00\x04\x53\x02\x00\x00" + "\x00\x02\x17\x00\xF0\x01\x00\x00\x00" + "\x31\x7F\x61\x11\x36\x4E\x58\x46\x16\x29\x79\xF0\x69\x0B\x00\x34\x68\x14\x06\x03\x07\x04\x02" + "\x00\x02\x00\x00\xA3\x00\x06\x00\x00" + "\x00\x01\x01\x00\x06\x02\x10\x01\x71\xF0\x0D\x6F\x2F\x1A\x7A\x84\x78\x70\x7A\x88\x52\x09\x57" + "\x53\x01\xF0\x00\x44\x42\x06\x06\x00" + "\x04\x04\x00\x01\x00\x02\x00\x00\x03\xB0\x04\x02\x04\x04\x00\x05\x00\x00\x00\x02\x02\x41\xF0" + "\x01\x01\x07\x75\x2B\x2A\x86\x54\x1A" + "\x0C\x02\x04\x2A\x7A\x6E\xF0\x04\x5B\x3B\x00\x06\x68\x22\x04\x00\x03\x01\x01\x04\x01\x00\x83" + "\x01\x03\x02\x01\x00\x01\x02\x00\x30" + "\x02\x00\x02\x51\xF0\x02\x21\x2F\x0C\x50\x2A\x02\x03\x29\x61\x73\x59\x1B\x4C\x32\xF0\x15\x3F" + "\x09\x00\x20\x42\x08\x02\x00\x01\x00" + "\x00\x02\x01\x00\xD0\x02\x00\x00\x02\x01\x05\x02\x00\x00\x00\x01\x01\x01\x61\xF0\x1F\x0F\x14" + "\x34\x04\x00\x0D\x55\x61\x55\x67\x4B" + "\x0A\x3C\x00\xF0\x15\x0D\x00\x0C\x3A\x18\x06\x02\x00\x01\x00\x02\x04\x02\x03\xC0\x04\x06\x00" + "\x00\x02\x00\x00\x01\x00\x00\x02\x00" + "\x11\xF0\x01\x01\x01\x00\x00\x21\x0F\x0A\x1A\x00\x01\x0A\x32\x28\x24\xF0\x30\x2E\x06\x2C\x04" + "\x13\x11\x02\x02\x32\x38\x08\x03\x01" + "\x01\xF0\x00\x02\x01\x03\x02\x00\x03\x00\x00\x01\x00\x01\x00\x03\x03\x20\x00\x02\x0E\xF0\x01" + "\x00\x00\x02\x02\x02\x00\x00\x10\x06" + "\x05\x0B\x00\x00\x06\xF0\x4A\x88\x84\x80\x3C\x00\x0B\x03\x02\x02\x00\x00\x12\x30\x10\xF0\x02" + "\x00\x02\x01\x05\x03\x02\x02\x03\x01" + "\x00\x06\x02\x01\x00\x50\x00\x04\x02\x00\x02\x0D\x24\x01\x00\xF0\x02\x00\x00\x1C\x0E\x0F\x1D" + "\x00\x02\x02\x06\x1A\x2E\x16\x02\xF0" + "\x02\x31\x00\x16\x16\x00\x00\x02\x30\x18\x02\x02\x00\x04\x02\xF0\x04\x00\x00\x01\x00\x00\x00" + "\x02\x02\x06\x04\x01\x01\x00\x05\x0C" + "\xF0\x03\x0F\x05\x01\x00\x00\x01\x03\x00\x01\x24\x1E\x15\x57\x15\xF0\x01\x00\x00\x00\x02\x00" + "\x01\x37\x4B\x04\x2A\x12\x00\x00\x00" + "\xF0\x2C\x26\x0A\x00\x02\x07\x03\x05\x02\x00\x02\x00\x00\x03\x01\x70\x01\x00\x00\x02\x04\x01" + "\x06\x33\x00\x01\x00\xF0\x01\x00\x00" + "\x00\x01\x00\x03\x31\x57\x1F\x00\x00\x02\x02\x00\x83\x00\x1C\x56\x02\x3B\x5F\x17\x00\xF0\x05" + "\x33\x65\x19\x2C\x42\x02\x00\x02\x00" + "\x04\x38\x02\x02\x01\xF0\x02\x01\x02\x00\x00\x01\x01\x00\x01\x00\x00\x01\x05\x02\x02\x20\x02" + "\x01\x39\x00\x02\x00\xF0\x06\x12\x3D" + "\x83\x4B\x07\x00\x00\x02\x02\x02\x44\x44\x0B\x57\xF0\x83\x51\x29\x1D\x31\x67\x7D\x35\x08\x6A" + "\x22\x00\x00\x01\x00\xF0\x00\x3A\x0C" + "\x06\x00\x00\x06\x04\x02\x00\x02\x02\x01\x00\x01\x70\x00\x00\x01\x03\x01\x00\x00\x0C\xF0\x02" + "\x2A\x28\x2D\x8B\x9B\x43\x15\x01\x00" + "\x00\x08\x80\x72\x16\xF0\x39\x83\xA9\xB3\xA1\x67\x17\x3A\x8E\x38\x02\x00\x00\x00\x01\xF0\x01" + "\x22\x24\x00\x00\x02\x03\x01\x03\x03" + "\x00\x00\x02\x04\x01\x70\x02\x04\x06\x02\x00\x00\x00\x58\x02\x00\x00\x02\x00\xF0\x08\x3A\x16" + "\x07\x3B\x87\x7D\x45\x11\x00\x00\x0E" + "\x5A\x7C\x64\x94\x3E\x18\x16\x28\x4A\x70\x80\x3C\x00\x83\x02\x01\x2D\x0B\x00\x02\x00\x02\xC0" + "\x04\x02\x00\x00\x06\x00\x03\x01\x01" + "\x02\x00\x02\x24\x02\x00\xF0\x02\x00\x00\x00\x02\x00\x00\x00\x32\x52\x10\x02\x17\x4D\x7F\xF0" + "\x85\x4B\x09\x00\x08\x3A\x6C\x80\x94" + "\x94\x8C\x78\x56\x1A\x02\x13\x00\xF0\x01\x17\x67\x85\x27\x03\x00\x00\x00\x01\x00\x02\x03\x00" + "\x02\x90\x02\x01\x02\x01\x01\x04\x01" + "\x00\x01\x68\x00\x02\x02\x00\x02\x00\xF0\x08\x6A\xB6\x76\x40\x18\x17\x49\x8D\xB1\x6D\x33\x15" + "\x02\x18\xF0\x28\x2A\x20\x12\x04\x01" + "\x00\x00\x01\x09\x1D\x69\xA7\x71\x35\xF0\x07\x03\x01\x01\x04\x02\x00\x02\x00\x01\x00\x03\x01" + "\x00\x02\x50\x04\x01\x00\x00\x00\x77" + "\x02\x02\x00\x02\x00\x02\x00\xF0\x02\x02\x1A\x64\x88\x78\x46\x16\x03\x25\x6F\x93\x83\x67\x3F" + "\xF0\x29\x13\x01\x02\x02\x00\x05\x23" + "\x45\x67\x89\x6D\x1B\x06\x1C\x53\x00\x01\x02\x00\x01\xC0\x00\x03\x01\x00\x00\x01\x02\x02\x01" + "\x02\x02\x01\x07\x28\x02\x00\xF0\x04" + "\x1C\x50\x82\x8E\x5C\x20\x06\x15\x43\x6F\x85\x8D\x91\x8D\xF0\x81\x8B\x93\x95\x8D\x7F\x69\x33" + "\x08\x3E\x86\x80\x08\x02\x01\xF0\x02" + "\x02\x00\x01\x01\x02\x04\x02\x00\x00\x01\x00\x00\x01\x01\x20\x01\x02\x4D\x02\x02\x02\x00\x03" + "\xF0\x18\x3C\x82\xBA\x96\x5E\x34\x12" + "\x19\x27\x3B\x4F\x5F\x4F\x45\xF0\x3F\x25\x02\x30\x5C\xAC\x9C\x50\x09\x06\x02\x02\x02\x03\x00" + "\xE0\x02\x03\x01\x00\x01\x02\x02\x03" + "\x05\x03\x02\x00\x00\x02\x7C\x02\x00\x02\x02\x00\x02\x00\xF0\x02\x00\x02\x00\x08\x42\x7C\x8E" + "\x84\x74\x62\x4C\x34\x22\x2A\xF0\x3C" + "\x4E\x6C\x7E\x88\x7A\x26\x02\x09\x39\x03\x02\x00\x01\x00\xF0\x00\x02\x00\x02\x02\x00\x00\x03" + "\x02\x02\x00\x04\x00\x02\x00\x04\x23" + "\x02\x00\x2E\x02\x00\xF0\x04\x1E\x48\x68\x72\x80\x8C\x98\x90\x86\x7C\x66\x48\x24\x06\xF0\x00" + "\x00\x39\x31\x05\x05\x00\x01\x01\x00" + "\x00\x06\x01\x05\x01\x63\x02\x00\x04\x00\x00\x01\x33\x02\x02\x00\x2F\x02\x00\x05\xF0\x04\x0C" + "\x14\x1E\x28\x22\x1A\x12\x06\x00\x01" + "\x00\x19\x5D\x77\xF0\x1B\x05\x00\x03\x01\x04\x02\x01\x01\x00\x04\x02\x03\x00\x00\x60\x04\x01" + "\x02\x02\x04\x02\x23\x00\x02\x1F\x00" + "\x0F\xF0\x01\x01\x13\x49\x7B\x75\x29\x0B\x03\x00\x01\x00\x00\x03\x01\xD0\x01\x01\x02\x02\x04" + "\x04\x01\x05\x02\x00\x02\x05\x01\x29" + "\x02\x00\x26\x02\x00\x23\x01\x00\x33\x01\x01\x00\xF0\x02\x02\x02\x00\x02\x00\x13\x5D\x8B\x81" + "\x4D\x15\x03\x01\x03\xF0\x01\x02\x06" + "\x00\x02\x00\x04\x01\x03\x03\x03\x05\x00\x00\x01\x40\x01\x00\x00\x01\xF4\x02\x02\x01\x00\x00" + "\x00\x01\x01\x02\x00\x00\x00\x02\x01" + "\x00\xF0\x02\x00\x00\x00\x02\x00\x00\x01\x00\x01\x01\x01\x09\x1B\x29\xF0\x57\x93\xB9\x7D\x45" + "\x1F\x0B\x07\x03\x00\x06\x04\x00\x00" + "\x06\xF0\x02\x04\x00\x01\x00\x02\x00\x06\x02\x06\x04\x01\x00\x06\x00\xF0\x00\x00\x02\x00\x01" + "\x11\x29\x2F\x3F\x3F\x33\x29\x1D\x11" + "\x0B\x26\x03\x00\xF0\x03\x02\x00\x0B\x19\x2B\x3F\x5D\x6F\x81\x95\x83\x4D\x1B\x11\xF0\x07\x03" + "\x01\x03\x03\x01\x00\x00\x02\x00\x05" + "\x03\x00\x00\x06\xB0\x00\x00\x04\x01\x01\x05\x03\x02\x01\x03\x00\xF0\x00\x02\x02\x00\x55\x85" + "\x87\x89\x87\x87\x8B\x8D\x8F\x95\x99" + "\xF0\x93\x85\x77\x65\x55\x59\x5F\x65\x6B\x7D\x91\x93\x91\x89\x85\x73\x7B\x67\x4D\x29\x11\x0D" + "\x00\xF0\x04\x04\x04\x02\x00\x00\x01" + "\x02\x02\x00\x01\x00\x02\x02\x02\x90\x01\x02\x00\x01\x03\x02\x00\x00\x02\xF0\x00\x00\x05\x6D" + "\x77\x4D\x3B\x2F\x29\x27\x29\x31\x3D" + "\x3F\x3F\xF0\x4D\x5F\x6D\x81\x8B\x85\x83\x7B\x75\x6B\x53\x47\x3F\x35\x25\xC3\x15\x0B\x05\x07" + "\x07\x01\x05\x01\x03\x01\x01\x02\x93" + "\x00\x02\x00\x01\x02\x02\x00\x02\x00\x70\x04\x06\x06\x05\x00\x02\x00\xF0\x04\x02\x2D\x47\x13" + "\x05\x01\x05\x01\x00\x03\x07\x07\x05" + "\x09\xF0\x0B\x0D\x09\x07\x0F\x0F\x09\x0D\x0B\x05\x09\x05\x05\x01\x07\xF0\x03\x03\x03\x00\x02" + "\x04\x06\x02\x01\x01\x00\x02\x00\x03" + "\x01\x75\x03\x03\x00\x00\x00\x01\x00\x70\x02\x02\x01\x04\x00\x01\x00\xF0\x00\x01\x33\x1D\x05" + "\x03\x00\x04\x00\x00\x00\x04\x00\x03" + "\x01\xF0\x00\x02\x00\x00\x01\x01\x01\x00\x05\x00\x00\x00\x02\x01\x04\xF0\x02\x00\x02\x00\x00" + "\x05\x05\x00\x04\x02\x03\x01\x05\x01" + "\x00\xF0\x04\x04\x01\x00\x00\x02\x02\x05\x00\x00\x02\x00\x03\x02\x02\x40\x00\x00\x00\x03\xF0" + "\x00\x0B\x1B\x07\x01\x00\x02\x01\x02" + "\x00\x01\x01\x00\x02\x00\xF0\x02\x06\x02\x00\x04\x00\x00\x00\x04\x01\x00\x00\x02\x04\x02\xF0" + "\x04\x02\x00\x04\x01\x01\x02\x01\x02" + "\x01\x01\x01\x00\x02\x00\xF0\x01\x00\x03\x00\x00\x01\x03\x00\x03\x03\x00\x02\x00\x03\x01\x40" + "\x00\x02\x00\x04\xF0\x00\x03\x05\x01" + "\x03\x04\x04\x00\x01\x01\x01\x03\x00\x00\x02\xF0\x00\x01\x01\x00\x01\x00\x03\x03\x02\x01\x01" + "\x00\x01\x01\x01\xF0\x00\x00\x02\x03" + "\x01\x02\x00\x02\x01\x02\x06\x02\x04\x02\x00\xF3\x00\x01\x00\x00\x02\x02\x00\x02\x02\x00\x03" + "\x03\x01\x01\x00\x10\x01\xF0\x02\x03" + "\x03\x00\x01\x03\x05\x00\x02\x00\x02\x02\x02\x04\x01\xF0\x05\x03\x00\x02\x00\x05\x00\x02\x01" + "\x00\x04\x01\x00\x02\x02\x83\x00\x04" + "\x00\x02\x06\x04\x03\x00\xB3\x03\x03\x07\x05\x00\x02\x00\x02\x01\x01\x02\x90\x06\x00\x04\x00" + "\x00\x01\x00\x02\x00\xF0\x02\x0A\x06" + "\x00\x08\x04\x04\x02\x04\x04\x02\x00\x02\x02\x04\xF0\x08\x00\x01\x03\x00\x04\x02\x02\x00\x06" + "\x00\x02\x02\x03\x00\xF0\x00\x05\x01" + "\x03\x01\x03\x01\x03\x00\x02\x01\x00\x01\x02\x02\xF0\x00\x02\x02\x01\x00\x01\x03\x02\x00\x00" + "\x03\x02\x01\x00\x00\x40\x00\x03\x01" + "\x04\xF0\x00\x06\x0C\x02\x01\x03\x00\x02\x05\x01\x00\x04\x05\x03\x03\xB3\x03\x02\x00\x01\x01" + "\x02\x01\x01\x00\x01\x00\xB3\x02\x01" + "\x00\x00\x01\x00\x02\x02\x00\x00\x02\xF0\x04\x06\x02\x03\x00\x01\x01\x00\x04\x05\x01\x00\x01" + "\x00\x00\x60\x02\x02\x00\x04\x00\x03" + "\xF0\x00\x08\x20\x0E\x03\x00\x00\x02\x04\x02\x00\x02\x02\x03\x00\x73\x07\x07\x02\x02\x06\x01" + "\x00\xF0\x03\x01\x01\x00\x00\x02\x02" + "\x04\x06\x04\x00\x04\x04\x02\x05\xF0\x00\x00\x02\x01\x03\x00\x04\x01\x00\x02\x00\x05\x00\x02" + "\x02\x63\x00\x00\x02\x01\x01\x00\xF0" + "\x00\x02\x28\x12\x06\x08\x04\x01\x00\x03\x01\x03\x01\x02\x02\xF0\x08\x02\x05\x01\x01\x02\x02" + "\x00\x01\x01\x04\x02\x01\x00\x03\xF0" + "\x01\x03\x03\x02\x05\x03\x01\x01\x00\x04\x03\x01\x00\x02\x00\xF0\x01\x01\x00\x02\x00\x02\x04" + "\x06\x02\x00\x01\x04\x00\x02\x04\x13" + "\x00\xF0\x02\x00\x24\x12\x04\x00\x01\x00\x00\x06\x00\x03\x02\x00\x00\xF0\x00\x06\x06\x04\x04" + "\x00\x02\x04\x02\x01\x01\x02\x00\x00" + "\x02\xF0\x00\x00\x03\x07\x02\x04\x01\x00\x00\x01\x00\x02\x02\x06\x06\xF0\x00\x05\x01\x00\x02" + "\x02\x00\x03\x01\x03\x00\x05\x03\x01" + "\x00\x40\x00\x03\x00\x02\xF0\x00\x00\x12\x24\x08\x02\x00\x04\x02\x03\x00\x04\x01\x00\x03\xF0" + "\x00\x01\x01\x01\x07\x01\x01\x01\x00" + "\x02\x01\x01\x02\x01\x01\xF0\x02\x02\x04\x06\x02\x00\x02\x02\x00\x00\x06\x02\x01\x01\x00\x63" + "\x04\x04\x02\x04\x02\x01\xA0\x00\x04" + "\x01\x01\x00\x01\x00\x01\x00\x02\xF0\x00\x02\x06\x50\x0A\x02\x01\x07\x01\x02\x04\x00\x04\x04" + "\x00\xF0\x03\x00\x01\x00\x06\x04\x00" + "\x01\x00\x02\x04\x00\x03\x06\x02\xF0\x01\x00\x03\x03\x01\x00\x00\x01\x01\x01\x03\x00\x02\x01" + "\x01\x63\x03\x02\x00\x03\x00\x02\xA0" + "\x00\x04\x06\x02\x02\x01\x04\x04\x00\x05\xF0\x02\x00\x00\x34\x2A\x04\x02\x00\x02\x02\x03\x00" + "\x01\x03\x06\xF0\x06\x02\x02\x00\x03" + "\x03\x02\x06\x02\x01\x00\x00\x01\x00\x00\xF0\x00\x01\x02\x04\x01\x01\x02\x00\x00\x02\x01\x02" + "\x00\x01\x03\xF0\x01\x00\x06\x00\x03" + "\x01\x02\x02\x00\x02\x01\x00\x04\x00\x01\x40\x01\x00\x01\x01\x03\xF0\x08\x54\x14\x10\x10\x10" + "\x0A\x0C\x0C\x08\x0A\x08\x06\x02\x06" + "\xF0\x02\x06\x02\x06\x01\x05\x01\x00\x00\x02\x05\x03\x00\x02\x02\xF0\x00\x06\x04\x00\x00\x00" + "\x01\x00\x03\x00\x03\x00\x02\x00\x03" + "\xF0\x02\x02\x00\x00\x00\x01\x02\x05\x01\x00\x00\x02\x00\x00\x00\x10\x04\x04\xF0\x52\x92\x8E" + "\x88\x78\x72\x6C\x62\x58\x52\x4C\x4C" + "\x48\x38\x30\xF0\x22\x16\x0E\x10\x12\x12\x06\x06\x06\x04\x02\x01\x00\x03\x01\xF0\x03\x05\x05" + "\x01\x01\x00\x06\x02\x00\x02\x06\x00" + "\x00\x00\x03\xF0\x00\x00\x01\x01\x02\x00\x02\x02\x03\x04\x04\x02\x00\x02\x04\x03\xF0\x02\x0A" + "\x40\x52\x5C\x68\x72\x7A\x82\x92\x94" + "\x94\x94\x90\x8E\xF0\x8A\x82\x80\x70\x60\x56\x3E\x30\x1A\x14\x14\x10\x0C\x0A\x0A\xF0\x02\x00" + "\x04\x02\x00\x02\x02\x01\x00\x00\x04" + "\x05\x02\x00\x02\xF0\x02\x01\x00\x00\x02\x00\x01\x02\x00\x02\x01\x00\x01\x02\x02\x10\x00\x39" + "\x00\x01\x00\xF0\x01\x02\x06\x0A\x16" + "\x26\x34\x48\x5A\x68\x72\x7C\x86\x94\xA0\xF0\x9A\x80\x64\x3C\x18\x14\x10\x08\x04\x08\x02\x02" + "\x01\x00\x02\xF0\x01\x01\x02\x02\x02" + "\x01\x00\x00\x02\x00\x01\x01\x03\x02\x01\x70\x04\x00\x03\x00\x01\x01\x01\x04\x5A\x01\x00\x00" + "\x01\x00\xF0\x01\x02\x04\x0C\x10\x1E" + "\x26\x30\x3E\x5A\x7C\xAA\xC2\xA2\x82\xF0\x62\x4A\x2C\x20\x0E\x10\x06\x02\x01\x03\x00\x03\x03" + "\x02\x02\xF0\x02\x03\x01\x01\x01\x02" + "\x01\x01\x01\x02\x04\x04\x01\x05\x00\x33\x00\x02\x00\x24\x01\x00\x29\x02\x00\x53\x02\x00\x00" + "\x00\x02\xF0\x0E\x34\x5E\x84\x90\x8C" + "\x82\x76\x5A\x3C\x1E\x12\x0C\x06\x06\xF0\x02\x00\x00\x00\x02\x00\x00\x02\x02\x00\x06\x03\x03" + "\x01\x05\x30\x02\x08\x00\x03\x3A\x01" + "\x02\x00\x53\x01\x00\x02\x02\x00\x28\x01\x00\xF0\x04\x12\x32\x4E\x66\x7A\x8E\x96\x7A\x44\x16" + "\x0E\x0C\x04\x02\x33\x00\x00\x02\x90" + "\x04\x02\x04\x02\x02\x04\x02\x01\x02\x55\x00\x01\x01\x02\x00\xA8\x01\x01\x01\x00\x00\x01\x00" + "\x01\x01\x00\xF0\x01\x01\x00\x00\x02" + "\x00\x00\x02\x02\x00\x04\x10\x20\x36\x66\xF0\xA4\xB8\x88\x54\x36\x1E\x10\x0A\x04\x01\x00\x01" + "\x05\x09\x05\x60\x01\x01\x05\x03\x02" + "\x01\xF0\x03\x03\x05\x03\x00\x00\x02\x02\x00\x01\x02\x02\x02\x00\x00\x54\x02\x00\x00\x02\x00" + "\x94\x02\x00\x00\x00\x02\x02\x00\x01" + "\x00\xF0\x02\x02\x00\x01\x02\x02\x02\x1E\x58\x8A\x92\x7C\x62\x3C\x1A\xC0\x12\x0A\x06\x02\x02" + "\x06\x02\x01\x00\x00\x01\x01\xF0\x93" + "\x93\x93\x95\x91\x85\x79\x6F\x5D\x4F\x45\x3B\x31\x23\x0F\x8D\x01\x02\x02\x00\x00\x01\x01\x00" + "\x13\x01\xF0\x00\x01\x01\x00\x00\x00" + "\x08\x24\x52\x72\x8C\x98\x68\x30\x0E\x90\x0C\x08\x00\x00\x02\x01\x04\x04\x02\xF0\x4F\x4D\x4D" + "\x4B\x55\x63\x6F\x75\x87\x8D\x95\x9F" + "\xA5\xB1\xC3\xF0\xC3\xA1\x85\x6B\x53\x37\x29\x21\x17\x0B\x01\x00\x00\x00\x01\x49\x01\x02\x01" + "\x00\x14\x01\xF0\x00\x0C\x20\x3A\x78" + "\xAC\x9E\x5E\x2E\x16\x04\x01\x06\x00\x03\x10\x01\xF0\x07\x09\x09\x0D\x07\x07\x07\x0B\x0B\x11" + "\x11\x0D\x17\x17\x19\xF0\x21\x45\x5F" + "\x75\x87\x9B\x99\x8D\x83\x73\x61\x43\x29\x0D\x00\xC6\x00\x00\x02\x00\x00\x00\x02\x02\x00\x00" + "\x02\x00\xF0\x02\x01\x00\x02\x02\x0C" + "\x40\x80\x86\x6C\x44\x22\x12\x04\x02\x10\x04\xF0\x01\x03\x02\x04\x01\x01\x01\x00\x01\x01\x00" + "\x05\x00\x01\x05\xF0\x09\x09\x0B\x11" + "\x11\x15\x25\x39\x4B\x61\x77\x81\x89\x8D\x81\xE4\x53\x2B\x07\x00\x01\x01\x00\x00\x02\x02\x00" + "\x00\x00\x02\x34\x00\x02\x00\x90\x08" + "\x36\x66\x8A\x8A\x40\x16\x0C\x04\xF0\x00\x02\x01\x01\x01\x02\x04\x02\x02\x02\x03\x01\x01\x01" + "\x04\x33\x01\x00\x03\xF0\x05\x07\x09" + "\x09\x11\x25\x2F\x4D\x61\x8B\xAB\xC5\xA3\x71\x3F\x44\x1F\x0B\x01\x00\x66\x01\x01\x00\x00\x01" + "\x00\x80\x02\x06\x1E\x46\x9C\xB0\x70" + "\x40\xF0\x01\x00\x02\x02\x04\x02\x01\x05\x01\x00\x03\x00\x00\x00\x01\xF0\x02\x02\x08\x0A\x02" + "\x01\x02\x02\x01\x05\x03\x03\x0B\x05" + "\x09\xF0\x0D\x15\x19\x41\x69\x93\x89\x71\x51\x2D\x09\x02\x02\x00\x02\x2A\x02\x00\x70\x02\x00" + "\x02\x04\x26\x70\x8C\x34\x2A\x25\x23" + "\xF0\x24\x2B\x60\xA2\x97\x84\x75\x62\x50\x3C\x34\x30\x2C\x29\x28\x93\x26\x25\x25\x23\x23\x25" + "\x24\x24\x25\x84\x26\x27\x25\x24\x25" + "\x25\x27\x24\xC3\x25\x25\x24\x25\x26\x25\x23\x24\x24\x25\x23\x24\x30\x22\x23\x21\xF0\x4E\x24" + "\x1C\x12\x0C\x0A\x08\x06\x08\x26\x4E" + "\x6A\x8E\xA0\xB6\xF0\xCA\xDC\xCA\xA0\x74\x3E\x20\x1A\x12\x08\x08\x08\x02\x02\x02\xF0\x03\x03" + "\x02\x02\x01\x00\x00\x02\x00\x02\x07" + "\x00\x01\x02\x00\xF0\x01\x02\x00\x02\x00\x01\x01\x00\x02\x01\x01\x02\x00\x02\x00\x40\x04\x04" + "\x02\x06\xF0\xD2\xF8\xCC\x9A\x5E\x40" + "\x26\x14\x10\x0C\x02\x00\x04\x10\x20\xF0\x30\x44\x66\x98\xCC\xF7\xF7\xD4\x98\x66\x4A\x2C\x14" + "\x0C\x08\xF0\x0A\x08\x01\x03\x01\x07" + "\x00\x02\x00\x01\x00\x00\x08\x04\x00\xF0\x02\x03\x01\x00\x00\x00\x02\x06\x04\x04\x01\x01\x01" + "\x00\x00\x40\x07\x00\x04\x04\xC4\x02" + "\x16\x52\x92\xC8\xBE\xAC\x8E\x58\x1C\x02\x00\x14\x02\xF0\x22\x60\xA6\xCC\xC2\xB2\x9C\x70\x3E" + "\x1C\x0A\x04\x06\x00\x02\x75\x00\x03" + "\x00\x01\x01\x02\x01\xF0\x04\x02\x00\x02\x00\x01\x01\x02\x02\x02\x03\x03\x06\x00\x03\x10\x03" + "\x04\x8C\x12\x40\x70\x96\xAE\x6C\x02" + "\x00\xF0\x16\x40\x6E\x94\xB4\xD0\xBA\x72\x26\x18\x0C\x08\x04\x00\x03\xF0\x02\x04\x01\x05\x03" + "\x02\x01\x03\x02\x05\x05\x03\x01\x00" + "\x01\xA0\x02\x01\x01\x02\x02\x04\x02\x02\x00\x00\x03\x8D\x02\x01\x00\x00\x0A\x22\x1C\x00\x03" + "\xF0\x0A\x20\x38\x72\xCE\xF7\xBA\x70" + "\x34\x12\x08\x08\x05\x01\x00\xF0\x04\x02\x02\x06\x06\x00\x02\x04\x02\x02\x03\x01\x03\x00\x04" + "\x70\x00\x06\x0A\x03\x01\x02\x04\x04" + "\x5C\x02\x00\x00\x02\x00\x44\x01\x01\x01\x00\xF0\x02\x02\x02\x1E\x78\xBC\xB6\x74\x2E\x0E\x0A" + "\x02\x00\x01\x01\xF0\x00\x01\x00\x00" + "\x00\x01\x02\x02\x00\x02\x04\x02\x00\x00\x01\x50\x07\x01\x01\x00\x00\x04\x23\x01\x00\x47\x01" + "\x00\x01\x00\x23\x01\x00\xF0\x01\x01" + "\x17\x49\x75\x8D\x67\x27\x01\x1A\x60\xAA\xA0\x32\x0E\xF0\x04\x02\x00\x02\x01\x04\x02\x02\x01" + "\x02\x00\x05\x01\x01\x02\x90\x00\x00" + "\x04\x01\x01\x02\x00\x03\x03\x08\x2A\x01\x00\x13\x02\xF0\x05\x2B\x91\xF1\xD9\xB1\x95\xBB\xF1" + "\xE5\x63\x0F\x1C\x7C\xC4\xF0\x48\x0A" + "\x02\x04\x04\x02\x05\x02\x01\x00\x00\x00\x01\x04\x01\xA0\x07\x01\x00\x05\x01\x00\x00\x01\x00" + "\x02\x04\x99\x02\x00\x00\x00\x02\x02" + "\x00\x02\x00\xF0\x01\x00\x43\xB1\x8B\x15\x48\x70\x7C\x62\x20\x35\xA7\x95\x0F\xF0\x00\x46\x8C" + "\x18\x08\x00\x05\x00\x04\x02\x02\x00" + "\x01\x00\x06\xC0\x00\x00\x02\x01\x03\x01\x04\x04\x00\x04\x02\x03\x71\xF0\x13\x9D\x43\x26\xAA" + "\xB8\xA6\x9C\xAA\xBC\x70\x0D\x79\x71" + "\x01\xF0\x02\x60\x5C\x0C\x04\x02\x04\x02\x01\x00\x00\x02\x00\x00\x01\x83\x04\x04\x06\x02\x00" + "\x03\x01\x00\x41\xF0\x01\x00\x09\xA5" + "\x3B\x3C\xBC\x74\x24\x10\x04\x06\x3A\xAA\x94\xF0\x08\x7F\x55\x01\x0A\x96\x2A\x06\x03\x03\x01" + "\x00\x01\x01\x00\xE0\x01\x03\x00\x00" + "\x01\x03\x04\x04\x01\x01\x00\x00\x01\x02\x61\xF0\x2F\x3F\x0E\x6E\x38\x02\x03\x37\x89\x9F\x7F" + "\x25\x6C\x46\x1F\xF0\x55\x09\x00\x26" + "\x5E\x0C\x06\x00\x00\x00\x04\x02\x00\x02\x02\xC0\x00\x02\x04\x00\x05\x02\x02\x04\x02\x00\x00" + "\x01\x41\xF0\x02\x00\x2D\x17\x1E\x48" + "\x06\x00\x15\x79\x8B\x77\x91\x67\x10\xF0\x56\x03\x1F\x17\x00\x10\x50\x22\x06\x04\x02\x03\x01" + "\x04\x04\x93\x02\x03\x04\x04\x01\x01" + "\x02\x01\x00\x20\x02\x00\x21\xF0\x01\x01\x00\x00\x2F\x15\x0E\x26\x00\x01\x12\x46\x3A\x32\x4A" + "\xF0\x40\x0A\x3A\x06\x1B\x17\x00\x04" + "\x46\x4E\x0E\x03\x05\x02\x00\xF0\x00\x03\x01\x04\x00\x03\x02\x04\x00\x00\x01\x03\x05\x01\x00" + "\x10\x02\x11\xF0\x01\x00\x02\x01\x00" + "\x18\x0E\x05\x11\x00\x00\x06\x66\xBE\xB8\xF0\xB2\x52\x00\x0D\x03\x04\x04\x00\x00\x1A\x42\x16" + "\x06\x02\x00\xF0\x00\x05\x01\x00\x02" + "\x01\x00\x03\x02\x02\x01\x02\x00\x04\x00\x20\x00\x02\x11\xF0\x02\x02\x00\x02\x00\x24\x0E\x15" + "\x29\x00\x02\x02\x08\x24\x3E\xF0\x1C" + "\x02\x00\x49\x00\x20\x1C\x00\x00\x02\x44\x20\x04\x04\x00\xF0\x02\x00\x04\x00\x01\x03\x03\x02" + "\x00\x00\x02\x02\x04\x00\x00\x20\x00" + "\x05\x0C\x53\x05\x17\x0B\x01\x00\xF0\x01\x00\x38\x2E\x21\x7B\x1D\x01\x00\x00\x02\x02\x01\x03" + "\x49\xF0\x65\x08\x38\x1A\x00\x00\x00" + "\x3A\x30\x06\x02\x02\x05\x01\x03\xE0\x02\x00\x04\x02\x03\x03\x01\x03\x02\x00\x00\x04\x01\x04" + "\xF0\x01\x01\x00\x01\x01\x01\x00\x01" + "\x01\x00\x01\x00\x03\x45\x77\x24\x2D\x00\x83\x01\x26\x78\x04\x51\x83\x21\x00\xE3\x07\x43\x91" + "\x25\x3C\x5C\x02\x00\x01\x01\x08\x52" + "\x06\x00\xF0\x01\x01\x00\x03\x03\x04\x00\x00\x02\x01\x05\x02\x00\x02\x00\x93\x02\x02\x00\x02" + "\x00\x00\x00\x02\x00\xF0\x08\x18\x59" + "\xB7\x67\x09\x00\x00\x02\x02\x00\x5C\x62\x0F\x79\xF0\xB7\x71\x37\x27\x45\x91\xB1\x49\x0C\x94" + "\x30\x00\x00\x02\x02\xF0\x00\x54\x10" + "\x06\x01\x00\x04\x06\x02\x00\x06\x02\x03\x01\x00\x70\x00\x01\x00\x01\x01\x00\x00\x04\x35\x02" + "\x02\x00\xF0\x02\x3A\x3A\x3B\xC3\xD9" + "\x5D\x1B\x01\x00\x00\x0A\xB0\x9E\x1C\xC4\x4F\xB5\xED\xF9\xDD\x8D\x1D\x4C\xC4\x4C\x02\x00\xF0" + "\x2C\x34\x04\x04\x02\x03\x01\x03\x03" + "\x03\x02\x04\x04\x03\x00\x60\x04\x04\x00\x01\x01\x00\x13\x02\x18\x00\xF0\x0C\x52\x20\x0D\x51" + "\xBB\xAF\x61\x19\x00\x00\x12\x7E\xB0" + "\x8C\x95\x52\x24\x1A\x38\x64\x98\xB6\x54\x00\xF0\x05\x3F\x0F\x03\x03\x02\x02\x00\x02\x04\x04" + "\x02\x01\x02\x06\x70\x02\x03\x00\x01" + "\x04\x04\x02\x0A\xF0\x02\x00\x00\x00\x44\x6E\x18\x04\x21\x6D\xB1\xB9\x69\x0D\x00\xC4\x0A\x4E" + "\x94\xB4\xCC\xCE\xC0\xAA\x78\x24\x02" + "\x00\xF0\x21\x91\xBB\x3B\x05\x04\x00\x00\x01\x02\x00\x01\x00\x02\x02\x80\x00\x00\x01\x01\x04" + "\x01\x01\x00\x68\x02\x02\x02\x00\x02" + "\x00\xF0\x0E\x94\xFC\xA2\x54\x20\x21\x63\xC3\xF5\x95\x49\x1F\x04\x22\xF0\x36\x3A\x2C\x18\x06" + "\x01\x00\x00\x00\x09\x29\x95\xE9\x99" + "\x45\xF0\x09\x03\x01\x00\x02\x00\x00\x02\x00\x01\x00\x05\x03\x00\x04\x50\x06\x01\x00\x00\x02" + "\x78\x02\x00\x02\x00\x00\x02\x00\xF0" + "\x02\x26\x8C\xC2\xA8\x66\x1C\x05\x31\x99\xC9\xB5\x8F\x5B\x39\xF0\x19\x03\x02\x02\x00\x07\x2B" + "\x63\x93\xBB\x97\x25\x06\x24\x04\xF0" + "\x00\x01\x02\x00\x02\x03\x01\x01\x03\x01\x01\x01\x00\x00\x00\x40\x01\x02\x02\x01\x98\x02\x02" + "\x00\x02\x00\x00\x00\x02\x00\xF0\x04" + "\x26\x70\xB2\xC6\x7E\x2A\x02\x1D\x5B\x99\xB7\xC5\xCB\xC7\xF0\xB3\xC3\xCB\xCF\xC7\xB1\x91\x4B" + "\x0A\x56\xBC\xB4\x0A\x00\x01\x64\x01" + "\x02\x00\x00\x01\x02\x70\x01\x00\x01\x01\x00\x00\x00\xAA\x02\x02\x02\x00\x02\x00\x02\x00\x02" + "\x00\xF0\x20\x54\xB6\xFF\xD0\x82\x48" + "\x18\x1F\x33\x4F\x69\x83\x71\x63\xF0\x59\x37\x00\x40\x80\xEE\xDA\x6E\x0D\x08\x00\x00\x00\x03" + "\x01\xE0\x04\x01\x00\x02\x00\x02\x02" + "\x05\x07\x03\x02\x00\x00\x02\x03\x2E\x02\x00\xF0\x02\x00\x02\x00\x0C\x5E\xAA\xC2\xB8\xA0\x86" + "\x68\x48\x32\x3E\xF0\x54\x6E\x98\xB2" + "\xC0\xAA\x36\x02\x0D\x53\x07\x02\x02\x00\x04\x34\x02\x01\x00\x80\x01\x02\x06\x00\x06\x04\x00" + "\x01\x63\x02\x02\x00\x00\x02\x00\x2E" + "\x02\x00\xF0\x06\x2A\x62\x90\x9E\xB0\xC2\xD0\xC8\xBA\xAC\x8E\x64\x30\x08\xF0\x00\x00\x51\x43" + "\x0D\x01\x02\x02\x05\x03\x00\x02\x01" + "\x03\x00\x90\x02\x02\x04\x02\x00\x03\x03\x00\x01\x13\x02\x4F\x00\x02\x02\x00\x05\xF0\x04\x10" + "\x1C\x2A\x36\x30\x24\x18\x06\x00\x01" + "\x00\x21\x81\xA7\xF0\x2D\x03\x01\x03\x07\x06\x06\x01\x00\x02\x02\x00\x01\x03\x02\x60\x02\x00" + "\x02\x01\x02\x04\x9F\x02\x00\x02\x02" + "\x00\x00\x00\x02\x00\x0C\xF0\x01\x01\x1B\x65\xB1\xA5\x39\x0D\x03\x00\x00\x08\x01\x01\x02\xD0" + "\x01\x03\x02\x00\x02\x06\x00\x03\x00" + "\x00\x00\x05\x03\x47\x02\x02\x02\x00\x2B\x02\x00\x33\x01\x01\x00\xF0\x02\x02\x01\x00\x00\x00" + "\x1B\x7F\xC1\xB3\x6D\x1D\x03\x00\x01" + "\xF0\x01\x00\x01\x00\x01\x02\x04\x02\x03\x03\x03\x05\x03\x01\x00\x40\x03\x02\x02\x00\xA6\x00" + "\x02\x00\x00\x02\x00\x01\x01\x02\x00" + "\x26\x02\x00\xF0\x02\x00\x01\x00\x00\x01\x01\x0B\x1F\x3B\x75\xCB\xFA\xB3\x5F\xF0\x2F\x0B\x09" + "\x05\x01\x06\x06\x02\x00\x06\x02\x01" + "\x00\x01\x00\xA0\x02\x00\x04\x04\x04\x02\x00\x00\x04\x00\xF0\x02\x02\x02\x00\x01\x17\x37\x45" + "\x55\x57\x49\x39\x27\x19\x0D\x37\x03" + "\x01\x00\xF0\x01\x0F\x25\x3B\x59\x81\x9D\xB5\xCD\xB9\x6D\x1F\x15\x0D\x05\xF0\x03\x01\x05\x01" + "\x01\x03\x01\x02\x05\x03\x01\x04\x02" + "\x00\x00\x90\x02\x00\x01\x05\x01\x04\x00\x00\x00\xF0\x02\x02\x00\x00\x77\xBD\xBF\xBB\xBD\xB7" + "\xBF\xC3\xC9\xCF\xD3\xF0\xCF\xBB\xA3" + "\x8B\x77\x7D\x85\x8D\x97\xAF\xC7\xCF\xC9\xC1\xBB\xF0\xAD\x8D\x69\x39\x17\x15\x05\x01\x01\x01" + "\x04\x02\x04\x02\x00\xF0\x02\x02\x00" + "\x02\x04\x00\x03\x02\x02\x00\x00\x02\x01\x01\x01\x40\x00\x00\x02\x02\xF0\x02\x00\x09\x99\xAD" + "\x65\x4D\x45\x3B\x39\x3D\x49\x55\x5D" + "\x5F\xF0\x6B\x83\x9B\xB3\xC1\xBD\xB5\xAB\xA3\x91\x73\x61\x57\x4D\x35\xF0\x1B\x15\x0B\x0D\x09" + "\x01\x07\x03\x01\x02\x01\x00\x04\x02" + "\x00\x14\x01\xE0\x04\x00\x02\x01\x04\x00\x01\x04\x06\x04\x05\x03\x03\x00\xF0\x02\x00\x3D\x63" + "\x17\x0B\x07\x07\x00\x03\x03\x07\x09" + "\x07\x0D\xF0\x11\x0F\x0D\x0B\x15\x11\x11\x13\x13\x0D\x0F\x09\x05\x03\x05\xF0\x03\x01\x01\x01" + "\x02\x04\x06\x02\x00\x00\x02\x04\x00" + "\x01\x03\xF0\x03\x01\x02\x00\x00\x03\x00\x00\x02\x00\x02\x01\x00\x02\x00\x40\x06\x04\x02\x01" + "\xF0\x00\x00\x47\x25\x05\x03\x00\x02" + "\x01\x01\x02\x04\x00\x01\x00\xB3\x00\x01\x02\x01\x01\x03\x01\x01\x01\x00\x01\xF0\x04\x02\x01" + "\x00\x04\x00\x03\x07\x01\x02\x01\x03" + "\x03\x07\x03\xF0\x04\x06\x02\x01\x00\x02\x06\x04\x03\x00\x00\x02\x02\x02\x04\x50\x00\x00\x00" + "\x03\x04\xF0\x02\x0D\x27\x0B\x01\x01" + "\x02\x00\x00\x00\x03\x01\x00\x00\x00\xF0\x04\x04\x00\x00\x04\x00\x01\x00\x00\x01\x00\x02\x02" + "\x02\x00\xF0\x00\x02\x01\x02\x03\x01" + "\x04\x00\x00\x02\x01\x01\x00\x02\x02\xF0\x03\x00\x03\x02\x00\x05\x05\x00\x01\x03\x01\x04\x01" + "\x03\x00\x40\x01\x00\x00\x03\xF0\x00" + "\x05\x07\x03\x01\x02\x04\x00\x00\x00\x01\x03\x00\x02\x00\xF0\x00\x02\x03\x01\x01\x00\x01\x03" + "\x00\x02\x02\x00\x01\x00\x00\xF0\x02" + "\x02\x02\x03\x01\x00\x02\x02\x01\x01\x04\x02\x04\x02\x01\xF4\x00\x01\x04\x00\x02\x02\x02\x00" + "\x02\x01\x03\x05\x01\x03\x00\xF0\x02" + "\x05\x03\x01\x03\x00\x03\x02\x00\x00\x02\x02\x06\x02\x01\xF0\x05\x05\x00\x04\x01\x03\x02\x04" + "\x00\x00\x04\x01\x01\x02\x02\xF0\x00" + "\x04\x00\x02\x06\x04\x05\x00\x04\x02\x00\x01\x01\x07\x03\xF0\x00\x04\x00\x02\x03\x01\x04\x00" + "\x04\x04\x06\x02\x02\x02\x01\x40\x02" + "\x00\x02\x01\xF0\x02\x0A\x08\x00\x06\x04\x06\x00\x04\x02\x00\x01\x00\x02\x04\xF0\x06\x00\x00" + "\x03\x02\x02\x02\x00\x04\x02\x03\x00" + "\x04\x03\x00\xF0\x01\x05\x01\x03\x01\x03\x00\x03\x01\x01\x00\x01\x01\x02\x06\xF0\x04\x04\x01" + "\x03\x04\x01\x07\x04\x03\x02\x01\x00" + "\x03\x01\x00\x40\x00\x03\x00\x06\xF0\x00\x0C\x0E\x04\x00\x03\x01\x02\x03\x01\x00\x04\x05\x03" + "\x03\xA5\x01\x04\x01\x01\x03\x04\x00" + "\x00\x03\x00\xF0\x01\x00\x02\x00\x00\x04\x00\x00\x00\x04\x00\x02\x02\x04\x02\xF0\x02\x03\x04" + "\x03\x05\x00\x04\x05\x00\x01\x00\x00" + "\x02\x04\x02\x40\x01\x04\x00\x03\xF0\x00\x0A\x2C\x14\x00\x01\x01\x02\x04\x00\x01\x02\x00\x01" + "\x00\xF0\x09\x05\x02\x00\x06\x01\x01" + "\x01\x00\x01\x03\x01\x01\x02\x04\xF0\x04\x02\x04\x08\x04\x00\x04\x06\x02\x01\x03\x00\x00\x00" + "\x03\xF0\x03\x00\x01\x02\x02\x00\x00" + "\x01\x00\x04\x01\x00\x04\x00\x01\x40\x04\x00\x01\x01\xF0\x02\x02\x3E\x1C\x00\x08\x04\x01\x00" + "\x03\x00\x03\x00\x02\x02\xF0\x08\x00" + "\x05\x01\x00\x00\x01\x02\x01\x01\x04\x02\x01\x03\x07\xF0\x03\x05\x07\x01\x05\x05\x01\x01\x01" + "\x02\x02\x01\x00\x00\x01\xF0\x01\x00" + "\x03\x06\x00\x04\x01\x08\x02\x05\x03\x04\x01\x01\x02\x40\x05\x00\x02\x00\xF0\x00\x00\x30\x16" + "\x0A\x00\x03\x00\x01\x08\x00\x03\x00" + "\x01\x00\x63\x02\x04\x06\x04\x06\x02\xF0\x00\x00\x02\x00\x04\x02\x00\x00\x01\x07\x02\x04\x01" + "\x00\x00\xF0\x03\x02\x02\x04\x06\x06" + "\x02\x05\x00\x03\x02\x00\x02\x03\x00\xA0\x02\x02\x03\x03\x01\x02\x00\x00\x00\x04\xF0\x00\x00" + "\x18\x34\x0A\x04\x02\x04\x02\x01\x00" + "\x04\x02\x00\x05\x93\x01\x00\x01\x00\x0B\x01\x00\x01\x00\xF0\x02\x01\x02\x02\x02\x06\x04\x04" + "\x00\x02\x00\x00\x00\x04\x02\xF0\x01" + "\x01\x00\x04\x06\x04\x04\x02\x01\x02\x00\x01\x01\x04\x01\x70\x02\x02\x03\x02\x03\x01\x00\xF0" + "\x00\x02\x06\x6E\x0C\x02\x00\x03\x02" + "\x00\x04\x04\x00\x04\x02\xF0\x03\x00\x00\x01\x06\x04\x00\x00\x02\x02\x00\x01\x03\x04\x02\xF0" + "\x01\x00\x05\x00\x03\x00\x00\x01\x01" + "\x01\x07\x02\x02\x01\x01\xF0\x03\x00\x00\x03\x02\x02\x01\x00\x00\x00\x02\x04\x00\x00\x00\x40" + "\x02\x02\x00\x03\xF0\x02\x00\x00\x4A" + "\x3C\x0A\x00\x03\x01\x02\x00\x01\x01\x00\x08\xB3\x06\x00\x02\x02\x03\x03\x02\x04\x00\x01\x00" + "\xF0\x03\x00\x01\x02\x02\x01\x01\x02" + "\x00\x00\x04\x00\x00\x01\x00\xA3\x01\x03\x00\x02\x00\x03\x00\x00\x02\x00\x70\x04\x02\x01\x01" + "\x02\x00\x00\x03\xF0\x0A\x76\x1C\x1A" + "\x1A\x14\x12\x0E\x10\x0E\x08\x08\x0A\x08\x08\xF0\x06\x06\x02\x06\x02\x01\x01\x00\x00\x00\x07" + "\x03\x00\x04\x02\xF0\x00\x06\x04\x00" + "\x00\x00\x03\x00\x03\x00\x03\x01\x02\x00\x03\xF0\x02\x00\x00\x01\x00\x01\x02\x07\x01\x01\x00" + "\x02\x00\x01\x01\x10\x02\x04\xF0\x6E" + "\xCC\xC4\xBA\xB0\xA0\x98\x86\x7A\x76\x6A\x68\x62\x50\x40\xF0\x34\x20\x12\x12\x14\x14\x0A\x0A" + "\x08\x08\x04\x00\x04\x04\x01\xF0\x01" + "\x01\x03\x01\x01\x00\x06\x00\x04\x06\x06\x02\x00\x00\x02\xF0\x02\x01\x00\x01\x02\x00\x04\x02" + "\x03\x02\x02\x04\x02\x06\x04\x04\xF0" + "\x10\x56\x72\x80\x8C\x9C\xA8\xB8\xCA\xCE\xD2\xD0\xCA\xC6\xC0\xF0\xB6\xB0\xA2\x8A\x78\x5A\x42" + "\x26\x1C\x18\x14\x10\x08\x06\x04\xF0" + "\x00\x00\x04\x00\x02\x06\x01\x02\x03\x01\x03\x00\x01\x00\x03\xF0\x01\x00\x00\x02\x00\x02\x04" + "\x02\x06\x01\x02\x00\x02\x01\x01\x39" + "\x01\x01\x00\xF0\x01\x02\x08\x0E\x1C\x32\x4A\x62\x7E\x90\x9E\xAC\xBE\xD0\xDE\xF0\xD8\xB8\x8E" + "\x54\x24\x1A\x16\x0A\x08\x08\x06\x06" + "\x00\x00\x02\xF0\x01\x00\x00\x02\x04\x00\x02\x00\x04\x00\x01\x01\x05\x00\x00\x70\x00\x02\x03" + "\x01\x03\x01\x01\x6C\x04\x00\x00\x00" + "\x01\x00\xF0\x01\x01\x02\x06\x10\x1A\x28\x34\x42\x58\x7C\xAC\xEC\xED\xE6\xF0\xB6\x86\x66\x40" + "\x2E\x16\x10\x0C\x04\x02\x03\x00\x01" + "\x05\x04\xF0\x02\x00\x03\x01\x00\x00\x02\x03\x00\x00\x00\x02\x02\x00\x05\x10\x00\x34\x01\x02" + "\x00\x23\x01\x00\x2E\x02\x00\xF0\x02" + "\x02\x02\x0E\x46\x82\xBC\xCA\xC4\xB4\xA0\x80\x52\x28\x16\xF0\x12\x0A\x06\x04\x02\x01\x00\x00" + "\x00\x01\x02\x00\x00\x00\x03\x60\x01" + "\x01\x05\x00\x08\x00\x03\x67\x01\x02\x00\x00\x02\x00\x53\x01\x00\x02\x02\x00\x28\x01\x00\xF0" + "\x04\x18\x44\x6C\x90\xA8\xC4\xD6\xAC" + "\x5C\x1E\x14\x0E\x02\x02\xF0\x00\x00\x02\x02\x02\x04\x04\x06\x02\x00\x04\x08\x02\x00\x04\x03" + "\x25\x02\x00\x25\x01\x00\x34\x01\x01" + "\x00\x26\x02\x00\xF0\x02\x00\x00\x02\x00\x00\x06\x16\x2E\x4A\x8E\xE4\xF9\xC0\x78\xF0\x4E\x28" + "\x16\x0C\x08\x04\x00\x03\x05\x09\x01" + "\x00\x00\x07\x03\x20\x02\x01\x55\x03\x05\x07\x03\x00\x2F\x02\x00\x0D\xF0\x01\x02\x00\x02\x24" + "\x78\xC0\xCA\xB2\x8C\x5A\x26\x14\x10" + "\x08\x90\x04\x02\x02\x00\x01\x01\x00\x03\x00\xF0\xCD\xCF\xCF\xD1\xC9\xB7\xA3\x97\x81\x71\x61" + "\x53\x43\x2F\x13\x5D\x01\x02\x02\x02" + "\x00\x03\x13\x01\xF0\x00\x01\x01\x00\x00\x00\x0A\x32\x72\x9E\xC2\xD4\x92\x3E\x18\x90\x12\x02" + "\x00\x00\x04\x01\x06\x04\x00\xF0\x6F" + "\x6D\x71\x6B\x77\x8B\x9F\xA3\xBB\xC5\xD1\xDD\xE7\xF9\xF0\xC5\xEE\xE3\xB9\x93\x6F\x4B\x3B\x2D" + "\x21\x0F\x03\x00\x29\x01\x00\x14\x01" + "\xF0\x00\x10\x2A\x50\xA6\xF4\xDA\x80\x48\x20\x08\x05\x0C\x00\x03\x10\x01\xF0\x0B\x0B\x09\x11" + "\x0B\x0B\x09\x13\x11\x19\x15\x13\x21" + "\x1F\x23\xF0\x2D\x5F\x83\xA5\xC3\xDF\xD5\xC5\xB5\xA1\x85\x5F\x37\x11\x00\xC6\x00\x00\x02\x00" + "\x00\x00\x02\x02\x00\x00\x02\x00\xF0" + "\x02\x01\x00\x00\x02\x0E\x58\xB6\xBA\x98\x5E\x2A\x12\x08\x04\x10\x02\xF0\x01\x01\x06\x06\x03" + "\x01\x05\x01\x03\x00\x03\x03\x01\x03" + "\x07\xF0\x0B\x07\x11\x13\x17\x1D\x37\x51\x69\x85\xA5\xB5\xC1\xC5\xB1\xE4\x75\x3B\x07\x00\x01" + "\x01\x00\x00\x02\x02\x00\x00\x00\x02" + "\xF0\x00\x02\x00\x00\x02\x00\x00\x08\x4A\x8E\xC0\xC4\x5C\x1E\x12\x10\x0A\xF0\x01\x01\x05\x03" + "\x01\x00\x04\x04\x04\x02\x01\x05\x01" + "\x03\x00\xF0\x03\x05\x01\x07\x03\x05\x07\x0B\x0F\x13\x1B\x33\x47\x6B\x8F\xA4\xC1\xF1\xEC\xE5" + "\x9D\x57\x29\x0F\x01\x00\xF0\x01\x01" + "\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x02\x0A\x2A\x50\x62\xD8\xF2\x9C\x56\xF0\x03\x00\x04" + "\x02\x04\x02\x00\x03\x01\x01\x03\x01" + "\x02\x00\x01\xF0\x00\x02\x04\x08\x00\x01\x02\x02\x02\x05\x05\x05\x0B\x0B\x0B\xF0\x13\x19\x29" + "\x5B\x97\xCF\xC1\xA1\x75\x3F\x0B\x02" + "\x02\x00\x02\x2C\x02\x00\x50\x02\x06\x36\x9C\xC4\x53\x27\x23\x23\x24\x25\xF0\x28\x37\x4A\x47" + "\x41\x3D\x38\x33\x2E\x2A\x28\x27\x26" + "\x27\x27\xF0\x27\x25\x24\x26\x28\x26\x27\x28\x28\x26\x28\x28\x2A\x26\x27\xF0\x27\x28\x27\x27" + "\x25\x26\x24\x25\x27\x28\x27\x28\x27" + "\x27\x25\xB0\x26\x27\x27\x26\x27\x27\x27\x26\x25\x26\x23\xF0\x16\x0C\x0C\x06\x02\x06\x04\x04" + "\x02\x0A\x16\x1E\x2A\x2E\x34\xF0\x3A" + "\x3E\x3C\x30\x20\x12\x0A\x08\x06\x04\x06\x02\x01\x00\x01\xF0\x05\x03\x02\x00\x00\x03\x02\x00" + "\x00\x00\x03\x01\x00\x02\x04\xF0\x02" + "\x04\x00\x01\x01\x03\x00\x00\x00\x03\x00\x01\x00\x02\x00\x40\x00\x04\x02\x08\xF0\x38\x48\x38" + "\x2C\x1E\x10\x0C\x04\x04\x04\x02\x00" + "\x02\x06\x0A\xF0\x0E\x12\x1C\x2C\x3E\x4E\x4C\x3C\x28\x1E\x14\x0C\x04\x02\x02\xF0\x04\x04\x00" + "\x01\x07\x05\x02\x04\x00\x00\x02\x02" + "\x08\x06\x02\xF0\x01\x09\x03\x04\x00\x04\x03\x08\x02\x02\x03\x02\x07\x03\x00\x40\x01\x00\x04" + "\x01\xC4\x02\x06\x16\x28\x3A\x34\x2E" + "\x2A\x16\x06\x01\x00\x13\x02\xF0\x00\x0A\x1C\x32\x3A\x38\x34\x2E\x24\x14\x0C\x01\x03\x00\x04" + "\xF0\x00\x00\x05\x03\x03\x01\x02\x00" + "\x01\x03\x00\x04\x00\x04\x04\xE0\x02\x02\x00\x04\x02\x04\x00\x04\x01\x05\x06\x00\x03\x01\x04" + "\x96\x02\x14\x22\x2A\x34\x20\x02\x02" + "\x00\xF0\x01\x02\x00\x00\x00\x0A\x14\x20\x2A\x32\x3A\x34\x22\x10\x0A\xF0\x00\x04\x00\x02\x02" + "\x04\x04\x03\x09\x05\x01\x01\x03\x02" + "\x05\xF0\x07\x03\x04\x03\x03\x03\x01\x00\x04\x02\x06\x01\x02\x00\x01\xDB\x00\x00\x02\x02\x01" + "\x00\x00\x02\x0A\x08\x00\x01\x00\xF0" + "\x03\x01\x00\x04\x0A\x12\x20\x3C\x4A\x32\x24\x10\x02\x00\x04\xF0\x05\x07\x01\x08\x00\x06\x06" + "\x02\x00\x00\x02\x01\x00\x03\x01\xA0" + "\x00\x02\x02\x01\x08\x0A\x00\x01\x02\x0A\x04\x78\x02\x01\x01\x02\x00\x02\x00\x57\x02\x01\x01" + "\x01\x00\xF0\x02\x0A\x22\x34\x34\x22" + "\x0E\x02\x06\x08\x02\x03\x01\x03\x01\xF3\x04\x00\x00\x02\x04\x01\x00\x00\x04\x03\x03\x04\x03" + "\x07\x03\x04\x23\x01\x00\x28\x01\x00" + "\xF0\x02\x00\x00\x00\x01\x02\x00\x01\x09\x15\x23\x29\x1F\x0B\x00\xF0\x0A\x1C\x32\x2E\x0E\x00" + "\x00\x02\x02\x06\x04\x06\x02\x01\x03" + "\xF0\x01\x02\x01\x00\x01\x04\x02\x04\x02\x01\x03\x02\x02\x01\x01\x05\x3B\x02\x02\x00\xF0\x01" + "\x02\x02\x00\x00\x01\x0B\x27\x43\x43" + "\x33\x29\x33\x45\x41\xE3\x1D\x05\x08\x24\x38\x18\x00\x00\x04\x04\x02\x05\x02\x00\xD0\x01\x04" + "\x00\x07\x01\x00\x07\x00\x01\x00\x01" + "\x00\x00\x04\x7D\x02\x00\x00\x00\x02\x02\x00\xF0\x13\x31\x29\x03\x14\x20\x20\x1A\x0A\x0B\x31" + "\x2B\x05\x01\x14\xF0\x28\x0A\x04\x03" + "\x09\x01\x00\x00\x01\x04\x00\x00\x06\x02\x02\xA0\x01\x00\x03\x01\x04\x06\x01\x00\x00\x03\x71" + "\xF0\x05\x2D\x13\x0A\x30\x3A\x32\x30" + "\x32\x36\x20\x03\x21\x21\x00\xF0\x00\x1C\x1A\x02\x02\x04\x06\x04\x00\x02\x00\x04\x00\x02\x03" + "\xB0\x00\x06\x06\x04\x04\x07\x00\x02" + "\x02\x04\x02\x41\xF0\x01\x00\x01\x2F\x0D\x12\x38\x22\x0C\x04\x00\x02\x10\x2E\x2C\xF0\x02\x23" + "\x17\x00\x02\x2A\x0A\x00\x03\x05\x01" + "\x01\x02\x01\x01\xE0\x01\x05\x00\x00\x01\x03\x02\x01\x02\x03\x02\x02\x01\x04\x61\xF0\x0D\x11" + "\x02\x1E\x10\x00\x01\x0F\x25\x2D\x23" + "\x0B\x20\x10\x07\xF0\x17\x03\x00\x0C\x1C\x04\x04\x00\x01\x01\x00\x02\x00\x00\x00\x93\x02\x02" + "\x02\x05\x05\x04\x02\x06\x01\x41\xF0" + "\x02\x00\x09\x07\x08\x14\x02\x00\x05\x21\x25\x21\x29\x1D\x04\xF0\x1A\x02\x09\x03\x01\x04\x16" + "\x08\x02\x02\x04\x01\x02\x02\x06\xE0" + "\x00\x01\x04\x04\x01\x04\x04\x02\x00\x03\x02\x00\x02\x01\x11\x13\x01\xF0\x00\x0F\x05\x00\x0A" + "\x01\x01\x04\x12\x0A\x0C\x12\x10\x02" + "\x12\xF0\x02\x05\x07\x00\x02\x14\x16\x04\x03\x03\x02\x00\x00\x03\x01\xD0\x04\x01\x01\x04\x00" + "\x03\x05\x05\x01\x03\x05\x00\x04\x31" + "\xF0\x02\x00\x00\x06\x04\x00\x03\x02\x00\x02\x1E\x38\x36\x34\x1A\xF0\x02\x03\x01\x00\x02\x02" + "\x00\x08\x12\x02\x04\x02\x02\x00\x07" + "\xF0\x05\x02\x02\x03\x01\x01\x06\x04\x02\x02\x00\x02\x04\x02\x02\x11\xF0\x02\x02\x00\x00\x01" + "\x0A\x08\x07\x0D\x00\x02\x04\x04\x0C" + "\x12\xF0\x0A\x02\x00\x13\x00\x04\x06\x00\x00\x02\x16\x08\x00\x00\x00\xF0\x02\x04\x04\x00\x01" + "\x00\x00\x02\x00\x00\x00\x06\x06\x00" + "\x01\x20\x00\x07\x0C\x37\x01\x07\x00\xF0\x10\x0C\x03\x23\x09\x01\x01\x00\x02\x02\x00\x00\x15" + "\x1D\x02\xF0\x12\x08\x00\x01\x01\x12" + "\x14\x01\x01\x01\x09\x03\x01\x00\x00\xC0\x02\x01\x03\x05\x03\x00\x00\x01\x02\x06\x01\x06\x06" + "\xF0\x01\x00\x01\x00\x00\x00\x01\x13" + "\x27\x0D\x00\x00\x01\x01\x00\x83\x00\x0A\x22\x01\x11\x25\x09\x00\xF0\x01\x15\x29\x09\x0E\x1E" + "\x02\x00\x02\x02\x02\x16\x04\x02\x00" + "\xF0\x02\x01\x00\x00\x00\x01\x00\x02\x02\x04\x00\x03\x05\x02\x00\x20\x02\x01\x0C\xF0\x02\x06" + "\x1B\x35\x21\x03\x00\x00\x02\x02\x02" + "\x1A\x1E\x07\x21\xC4\x31\x21\x11\x0D\x15\x29\x33\x15\x01\x2A\x0E\x00\xF0\x16\x08\x04\x00\x02" + "\x06\x02\x04\x00\x04\x00\x03\x01\x03" + "\x01\x60\x01\x01\x05\x01\x00\x00\x0C\xF0\x02\x12\x16\x11\x35\x3F\x1B\x07\x01\x00\x00\x04\x32" + "\x30\x06\xB5\x1D\x33\x43\x45\x3F\x29" + "\x07\x18\x40\x18\x00\xF0\x10\x0C\x01\x02\x02\x05\x01\x03\x05\x01\x02\x02\x02\x00\x04\x60\x04" + "\x08\x02\x00\x01\x02\x58\x00\x02\x02" + "\x02\x00\xF0\x04\x14\x08\x01\x15\x33\x33\x1B\x07\x00\x00\x06\x22\x32\x2C\xF0\x16\x0A\x08\x12" + "\x1C\x2C\x30\x16\x00\x01\x01\x00\x01" + "\x00\x01\xF0\x0F\x07\x00\x01\x00\x04\x04\x00\x06\x04\x02\x02\x02\x06\x01\x60\x03\x01\x00\x04" + "\x02\x01\x24\x02\x00\x26\x02\x00\xF0" + "\x14\x22\x06\x02\x09\x21\x31\x35\x1D\x03\x00\x04\x18\x2C\x36\x84\x3C\x3A\x3C\x32\x22\x0E\x02" + "\x00\xF0\x09\x2B\x39\x0B\x02\x04\x00" + "\x00\x03\x01\x01\x05\x00\x00\x04\x80\x01\x04\x00\x01\x00\x01\x01\x02\x04\x28\x02\x00\xF0\x04" + "\x2A\x4A\x2E\x1A\x10\x07\x19\x37\x47" + "\x2B\x15\x07\x00\x0A\xF0\x10\x12\x08\x06\x02\x01\x00\x01\x00\x03\x0B\x2D\x45\x2B\x17\xF0\x03" + "\x05\x05\x01\x04\x02\x04\x04\x00\x03" + "\x02\x05\x01\x01\x02\x50\x04\x02\x00\x02\x02\x0E\xF0\x02\x00\x0A\x28\x34\x2E\x1A\x04\x05\x0F" + "\x2D\x37\x33\x29\x1B\xF0\x11\x09\x01" + "\x02\x02\x00\x01\x0D\x1B\x29\x37\x29\x07\x02\x0E\x33\x02\x01\x00\xE0\x01\x01\x00\x03\x00\x01" + "\x01\x05\x02\x02\x01\x02\x02\x03\x3E" + "\x02\x02\x00\xF0\x02\x0E\x20\x32\x38\x28\x10\x06\x09\x1B\x2D\x33\x39\x39\x37\xF0\x33\x37\x39" + "\x3D\x37\x33\x2B\x15\x02\x18\x36\x34" + "\x00\x00\x02\xF0\x02\x02\x01\x03\x01\x04\x04\x00\x00\x02\x00\x02\x00\x01\x00\x20\x03\x01\x44" + "\x00\x00\x02\x00\x25\x02\x00\x23\x02" + "\x00\xF0\x0A\x1C\x34\x46\x3A\x26\x16\x0A\x0D\x0F\x19\x21\x27\x21\x19\xF0\x17\x0F\x02\x18\x28" + "\x46\x3E\x20\x03\x06\x00\x00\x04\x05" + "\x00\xE0\x04\x01\x05\x00\x03\x04\x00\x03\x05\x05\x02\x01\x01\x04\x03\x4C\x02\x00\x02\x00\xF0" + "\x02\x02\x00\x00\x06\x1A\x30\x36\x34" + "\x32\x2A\x20\x16\x10\x12\xF0\x18\x20\x2C\x32\x34\x2E\x12\x02\x01\x19\x01\x06\x02\x03\x00\xF0" + "\x00\x01\x05\x04\x02\x02\x01\x01\x06" + "\x02\x00\x02\x00\x04\x02\x2F\x02\x00\x08\xF0\x02\x0E\x1C\x2A\x2C\x34\x3A\x3C\x38\x34\x30\x28" + "\x1C\x10\x04\xF0\x00\x00\x17\x11\x07" + "\x03\x02\x01\x02\x00\x02\x0A\x00\x03\x01\x90\x04\x02\x04\x02\x02\x00\x00\x00\x01\x3F\x00\x02" + "\x00\x09\xF0\x02\x02\x06\x08\x0A\x10" + "\x0E\x08\x06\x02\x00\x01\x00\x0B\x27\xF0\x31\x09\x03\x03\x07\x01\x02\x04\x03\x01\x01\x04\x04" + "\x05\x01\x70\x00\x00\x01\x00\x00\x04" + "\x01\x9F\x00\x00\x02\x00\x02\x00\x00\x02\x00\x09\x23\x02\x00\xF0\x07\x1D\x31\x2D\x11\x05\x02" + "\x02\x00\x00\x03\x01\x02\x00\x01\xB0" + "\x01\x00\x04\x04\x01\x03\x00\x02\x00\x07\x00\x29\x02\x00\x2B\x02\x00\x33\x01\x01\x00\xF0\x02" + "\x02\x02\x00\x02\x02\x07\x25\x39\x33" + "\x21\x07\x04\x00\x03\xF0\x00\x02\x04\x04\x00\x02\x04\x02\x01\x03\x05\x03\x01\x00\x01\x40\x03" + "\x02\x04\x02\x93\x00\x02\x00\x02\x00" + "\x01\x01\x01\x00\xA4\x01\x01\x01\x00\x01\x01\x00\x00\x01\x00\xF0\x01\x02\x01\x01\x01\x05\x0D" + "\x11\x21\x3B\x49\x2F\x17\x0B\x00\xF0" + "\x01\x01\x02\x06\x02\x00\x02\x06\x02\x02\x00\x00\x02\x00\x04\x80\x02\x06\x06\x06\x01\x00\x04" + "\x03\xF0\x00\x00\x01\x01\x00\x03\x0F" + "\x13\x1B\x1B\x15\x0F\x09\x07\x01\x24\x01\x00\xF0\x01\x00\x01\x02\x00\x03\x0F\x0F\x19\x25\x29" + "\x33\x3B\x37\x1D\xF0\x09\x07\x01\x00" + "\x00\x03\x03\x01\x00\x02\x00\x01\x05\x05\x01\xD0\x00\x02\x00\x00\x02\x00\x01\x05\x03\x02\x01" + "\x03\x02\xF0\x02\x00\x02\x00\x21\x35" + "\x33\x33\x35\x35\x39\x39\x37\x3B\x41\xF0\x3B\x33\x2B\x27\x23\x23\x25\x27\x29\x33\x3B\x3B\x39" + "\x35\x33\xF0\x31\x27\x1F\x0D\x03\x07" + "\x02\x00\x01\x01\x04\x04\x02\x02\x00\xF0\x03\x00\x02\x00\x00\x03\x01\x02\x00\x00\x03\x00\x01" + "\x03\x01\x40\x04\x01\x02\x02\xF0\x01" + "\x01\x01\x2B\x31\x23\x1D\x17\x11\x0B\x0B\x13\x1B\x19\x13\xF0\x1B\x29\x31\x37\x37\x35\x35\x33" + "\x2F\x29\x1B\x19\x15\x17\x11\x83\x05" + "\x03\x01\x05\x03\x00\x03\x01\xF0\x02\x04\x02\x02\x00\x01\x00\x00\x04\x08\x01\x02\x00\x02\x04" + "\x80\x02\x02\x06\x02\x05\x01\x03\x01" + "\xF0\x00\x02\x13\x1B\x09\x01\x00\x00\x02\x02\x00\x03\x03\x00\x05\xE3\x09\x01\x02\x02\x05\x03" + "\x00\x03\x05\x01\x07\x05\x01\x03\xF0" + "\x01\x00\x02\x02\x04\x02\x00\x00\x01\x04\x00\x03\x03\x01\x00\xF0\x00\x00\x01\x03\x04\x00\x00" + "\x00\x01\x01\x02\x04\x00\x02\x02\x20" + "\x00\x00\xF0\x04\x00\x11\x0D\x00\x00\x02\x04\x00\x03\x01\x06\x02\x05\x01\x13\x02\xF0\x01\x01" + "\x00\x02\x00\x02\x02\x04\x00\x00\x08" + "\x04\x00\x04\x02\xF0\x03\x03\x05\x00\x02\x00\x03\x03\x05\x05\x04\x02\x02\x01\x00\xF0\x02\x00" + "\x00\x09\x02\x00\x04\x00\x03\x02\x00" + "\x01\x00\x00\x00\xF0\x01\x03\x0B\x00\x01\x00\x04\x01\x02\x01\x01\x05\x02\x06\x02\xF0\x04\x04" + "\x02\x01\x08\x02\x03\x01\x02\x01\x00" + "\x00\x04\x06\x01\xF0\x00\x02\x00\x02\x00\x03\x02\x01\x02\x00\x03\x05\x00\x04\x02\xF0\x02\x00" + "\x03\x01\x00\x00\x01\x00\x05\x01\x01" + "\x02\x02\x01\x04\x40\x04\x02\x00\x02\xF0\x02\x00\x03\x00\x01\x04\x04\x00\x01\x00\x02\x02\x01" + "\x01\x00\xF0\x03\x00\x05\x01\x03\x00" + "\x01\x03\x00\x00\x01\x01\x01\x00\x00\xF0\x02\x00\x00\x01\x01\x04\x02\x02\x01\x02\x06\x04\x04" + "\x02\x01\xF0\x01\x00\x02\x04\x00\x02" + "\x00\x04\x02\x01\x01\x03\x01\x03\x03\x40\x00\x02\x00\x05\xF0\x00\x01\x00\x01\x00\x00\x05\x01" + "\x02\x00\x03\x00\x06\x04\x00\xF0\x03" + "\x03\x02\x02\x01\x07\x00\x04\x00\x03\x04\x00\x03\x02\x02\xF0\x01\x04\x02\x02\x04\x04\x03\x00" + "\x00\x00\x04\x01\x03\x05\x07\xF0\x00" + "\x01\x02\x01\x01\x01\x04\x02\x04\x04\x06\x00\x04\x00\x03\x40\x01\x00\x04\x00\x83\x02\x04\x02" + "\x00\x08\x04\x06\x04\xF0\x00\x02\x04" + "\x04\x0A\x00\x01\x01\x00\x04\x02\x00\x02\x06\x03\xF0\x00\x04\x05\x01\x02\x05\x03\x05\x00\x01" + "\x03\x01\x01\x00\x05\xF0\x01\x05\x02" + "\x08\x02\x06\x00\x00\x02\x01\x05\x04\x00\x00\x03\x80\x06\x03\x00\x02\x00\x05\x03\x04\xF0\x00" + "\x04\x04\x01\x03\x07\x03\x00\x07\x01" + "\x00\x02\x09\x05\x03\xF0\x03\x04\x00\x01\x00\x04\x00\x00\x01\x00\x04\x02\x00\x01\x02\xF0\x01" + "\x01\x00\x00\x00\x02\x04\x01\x02\x02" + "\x04\x00\x04\x02\x02\xF0\x00\x03\x02\x01\x01\x00\x02\x05\x01\x00\x01\x01\x00\x04\x04\x40\x02" + "\x04\x00\x01\xF0\x00\x02\x10\x0A\x01" + "\x02\x02\x04\x06\x02\x02\x00\x02\x03\x00\x64\x07\x0B\x00\x00\x04\x00\xF0\x03\x01\x00\x02\x04" + "\x04\x02\x06\x08\x06\x00\x04\x06\x04" + "\x01\xF0\x02\x04\x02\x00\x05\x02\x04\x00\x00\x02\x01\x03\x01\x02\x04\x90\x00\x03\x04\x00\x03" + "\x02\x00\x02\x02\xF0\x01\x00\x10\x04" + "\x00\x06\x04\x01\x03\x03\x01\x00\x01\x00\x00\xF0\x08\x00\x05\x01\x03\x01\x00\x01\x05\x05\x04" + "\x02\x01\x01\x05\xF0\x03\x05\x05\x00" + "\x05\x07\x01\x00\x03\x02\x03\x01\x02\x02\x02\xF0\x01\x01\x05\x04\x00\x04\x02\x08\x02\x03\x03" + "\x08\x00\x01\x04\x40\x03\x02\x02\x01" + "\xF0\x00\x00\x0C\x08\x04\x00\x03\x01\x02\x08\x01\x03\x02\x02\x02\xF0\x00\x06\x08\x06\x06\x01" + "\x02\x06\x04\x02\x01\x02\x00\x02\x02" + "\xF0\x00\x02\x01\x07\x00\x08\x03\x01\x02\x03\x00\x00\x02\x06\x06\xF0\x01\x05\x01\x03\x02\x02" + "\x04\x05\x03\x01\x02\x05\x07\x01\x00" + "\x40\x00\x01\x00\x04\xF0\x02\x00\x08\x0E\x04\x02\x02\x04\x04\x05\x00\x02\x00\x00\x05\xF0\x00" + "\x01\x05\x03\x07\x02\x00\x01\x00\x02" + "\x01\x01\x00\x00\x01\xF0\x00\x02\x02\x04\x02\x00\x04\x02\x00\x00\x06\x04\x03\x01\x00\xF0\x06" + "\x06\x08\x02\x02\x01\x03\x01\x00\x01" + "\x02\x03\x02\x00\x00\x40\x00\x03\x03\x02\xF0\x02\x02\x04\x22\x00\x03\x01\x03\x01\x04\x06\x04" + "\x02\x06\x00\xF0\x03\x00\x00\x02\x08" + "\x00\x01\x01\x00\x02\x04\x01\x03\x06\x04\xF0\x00\x02\x01\x01\x01\x05\x00\x01\x03\x00\x05\x00" + "\x02\x03\x03\xF0\x03\x00\x01\x00\x01" + "\x00\x00\x04\x00\x02\x08\x08\x02\x00\x03\x40\x04\x02\x02\x03\x03\xF0\x16\x12\x02\x03\x05\x02" + "\x00\x03\x03\x00\x03\x04\x02\x02\x04" + "\xF0\x02\x05\x01\x02\x04\x02\x01\x00\x02\x01\x00\x03\x01\x01\x00\xF0\x04\x00\x02\x04\x01\x00" + "\x00\x00\x02\x02\x03\x03\x01\x00\x02" + "\xF0\x01\x03\x01\x04\x02\x01\x02\x03\x00\x04\x04\x01\x03\x00\x03\x10\x01\xF0\x00\x00\x01\x02" + "\x22\x06\x08\x08\x04\x02\x04\x06\x02" + "\x00\x02\xF0\x02\x02\x00\x01\x06\x02\x06\x01\x05\x01\x00\x00\x02\x09\x01\xF0\x00\x00\x02\x00" + "\x04\x04\x01\x02\x01\x01\x00\x03\x01" + "\x03\x01\xF0\x00\x03\x03\x04\x02\x02\x01\x03\x02\x02\x05\x01\x01\x00\x04\x40\x02\x02\x02\x00" + "\xF0\x01\x00\x00\x00\x22\x3C\x3C\x38" + "\x30\x2E\x2C\x26\x1E\x24\x20\xF0\x1E\x20\x16\x12\x0C\x08\x04\x06\x0A\x0A\x00\x00\x02\x04\x00" + "\xF0\x02\x04\x02\x05\x03\x01\x07\x03" + "\x00\x02\x06\x00\x02\x08\x08\xF0\x02\x04\x00\x03\x02\x00\x00\x02\x00\x01\x00\x02\x01\x02\x02" + "\x40\x00\x01\x02\x06\xF0\x02\x00\x02" + "\x02\x06\x1C\x20\x26\x2A\x2E\x30\x34\x40\x3A\x3A\xF0\x3C\x38\x3A\x38\x32\x34\x2A\x24\x24\x16" + "\x10\x08\x0A\x0A\x08\xF0\x06\x04\x02" + "\x04\x00\x02\x08\x02\x02\x02\x01\x02\x00\x01\x05\xF0\x01\x00\x02\x02\x05\x01\x01\x02\x02\x02" + "\x06\x00\x04\x03\x00\x40\x00\x04\x04" + "\x01\x66\x01\x01\x00\x00\x01\x00\xF0\x01\x02\x04\x06\x08\x10\x14\x1E\x24\x2A\x2E\x30\x36\x40" + "\x42\xF0\x3C\x30\x26\x14\x08\x0A\x06" + "\x00\x03\x00\x02\x04\x01\x00\x02\xF0\x01\x01\x02\x04\x00\x00\x05\x02\x04\x00\x01\x01\x05\x02" + "\x00\x70\x02\x00\x01\x00\x01\x05\x01" + "\x31\xF0\x01\x02\x02\x06\x08\x0C\x0E\x12\x1C\x2A\x32\x46\x50\x3C\x30\xF0\x28\x22\x0E\x0A\x02" + "\x04\x01\x03\x05\x03\x00\x00\x01\x02" + "\x06\xF0\x02\x03\x01\x03\x03\x00\x03\x01\x03\x02\x04\x04\x03\x07\x00\x33\x02\x02\x00\x33\x01" + "\x01\x00\x2E\x02\x00\xF0\x01\x02\x00" + "\x02\x16\x2A\x38\x38\x3A\x34\x30\x28\x1A\x0A\x08\xF0\x08\x02\x02\x00\x01\x02\x01\x02\x02\x00" + "\x02\x02\x00\x02\x01\x60\x05\x03\x07" + "\x04\x0C\x00\x04\x2A\x02\x00\x53\x01\x00\x02\x02\x00\x46\x01\x00\x02\x00\xF0\x02\x08\x14\x22" + "\x28\x2E\x38\x3E\x32\x1C\x08\x02\x04" + "\x01\x03\xF0\x02\x01\x00\x04\x06\x06\x04\x06\x04\x04\x04\x08\x02\x00\x04\x06\x74\x02\x02\x00" + "\x00\x01\x01\x00\x37\x01\x01\x00\x23" + "\x01\x00\xF0\x02\x00\x00\x02\x02\x00\x04\x08\x0E\x18\x2A\x44\x4E\x3A\x20\xF0\x16\x0C\x06\x02" + "\x00\x03\x01\x03\x05\x0B\x07\x00\x00" + "\x0B\x03\x20\x00\x01\x64\x01\x03\x03\x01\x02\x00\x3B\x02\x02\x00\x5B\x02\x00\x00\x02\x00\xF0" + "\x01\x00\x02\x00\x0A\x22\x3A\x3C\x34" + "\x2A\x1A\x0A\x08\x08\x02\x90\x00\x00\x08\x02\x01\x02\x00\x00\x03\xF0\x39\x39\x3B\x3D\x3D\x37" + "\x2F\x2D\x25\x1F\x1B\x19\x15\x0D\x07" + "\x23\x01\x02\x1F\x00\x13\x01\xF0\x00\x01\x01\x02\x01\x00\x04\x10\x20\x2A\x38\x40\x2C\x0C\x02" + "\x90\x02\x02\x03\x03\x00\x01\x06\x01" + "\x04\xF0\x1D\x1D\x21\x19\x21\x27\x31\x2F\x39\x39\x3F\x41\x3D\x45\x4D\xC5\x4D\x43\x35\x2B\x21" + "\x17\x11\x0D\x09\x05\x01\x00\x29\x01" + "\x00\xF0\x03\x00\x01\x01\x01\x00\x06\x0E\x16\x2E\x46\x42\x2C\x18\x10\x60\x02\x05\x0A\x05\x01" + "\x03\xF0\x07\x07\x03\x0B\x01\x01\x00" + "\x05\x01\x0B\x01\x02\x0D\x0D\x0B\xF0\x09\x1B\x25\x2F\x37\x3F\x39\x37\x33\x2F\x25\x19\x11\x05" + "\x01\xC6\x00\x00\x02\x00\x00\x00\x02" + "\x02\x00\x00\x02\x00\xF0\x02\x01\x01\x00\x02\x06\x1A\x32\x34\x26\x1C\x12\x06\x04\x02\x10\x04" + "\xF0\x02\x02\x08\x08\x03\x01\x01\x02" + "\x00\x02\x02\x00\x04\x04\x02\xF0\x07\x01\x03\x07\x09\x07\x0F\x17\x1D\x23\x31\x35\x33\x37\x33" + "\xE4\x21\x13\x07\x00\x01\x01\x00\x00" + "\x02\x02\x00\x00\x00\x02\xF0\x00\x02\x02\x02\x00\x00\x00\x02\x14\x28\x38\x36\x1A\x08\x02\x10" + "\x00\xF0\x00\x01\x05\x01\x02\x02\x06" + "\x00\x02\x02\x07\x09\x03\x01\x00\xF0\x01\x00\x03\x00\x00\x00\x03\x01\x03\x01\x05\x0D\x11\x1D" + "\x27\xF0\x39\x43\x4B\x45\x2D\x17\x0B" + "\x03\x01\x00\x00\x02\x02\x00\x01\x84\x01\x00\x00\x01\x00\x00\x01\x00\x70\x02\x0A\x1C\x3C\x44" + "\x2E\x18\xF0\x03\x02\x04\x02\x02\x02" + "\x01\x01\x00\x02\x00\x04\x01\x00\x00\xF0\x04\x02\x08\x06\x04\x03\x02\x02\x01\x03\x01\x00\x07" + "\x05\x00\xF0\x03\x07\x09\x17\x29\x3B" + "\x37\x2D\x1F\x11\x03\x00\x00\x00\x02\x24\x02\x00\x23\x02\x00\x80\x02\x02\x00\x02\x02\x12\x2E" + "\x3C"; + +static const BYTE TEST_64X64_RED_PLANE[4096] = + "\x23\x1F\x1E\x1D\x1D\x1E\x1D\x1F\x23\x4A\x78\x71\x64\x58\x4B\x3E\x30\x29\x26\x24\x22\x21\x20" + "\x20\x20\x1D\x1E\x20\x1E\x1F\x20\x20" + "\x1F\x20\x21\x22\x1E\x1F\x1F\x1F\x1F\x1F\x1F\x1F\x1E\x1D\x1F\x20\x1F\x1F\x1F\x1E\x1E\x1F\x1F" + "\x20\x1E\x1F\x1F\x1F\x1E\x1D\x1E\x1C" + "\x3F\x2C\x28\x24\x21\x22\x20\x21\x25\x56\x94\x96\x97\x92\x8C\x86\x7F\x71\x61\x4D\x38\x2C\x29" + "\x26\x23\x21\x21\x20\x1F\x1F\x1E\x1E" + "\x20\x20\x20\x20\x1F\x20\x1F\x20\x1E\x1E\x1D\x1F\x1E\x1E\x21\x20\x1F\x1F\x1F\x1F\x1E\x1F\x1D" + "\x1F\x1F\x1F\x20\x1E\x1E\x1F\x1F\x1F" + "\x89\x83\x70\x5B\x44\x39\x2F\x27\x2C\x5B\x95\x96\x98\x98\x98\x98\x97\x97\x97\x97\x97\x8B\x76" + "\x5C\x47\x3B\x2F\x28\x23\x22\x21\x21" + "\x20\x1F\x1E\x1F\x20\x20\x20\x20\x1F\x1F\x21\x21\x1F\x1E\x1F\x1F\x1F\x1E\x20\x1F\x21\x20\x1F" + "\x1E\x1E\x1D\x1E\x1E\x1E\x1F\x21\x1E" + "\x8A\x8D\x8E\x8F\x8B\x7C\x6B\x5B\x4B\x66\x96\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98" + "\x98\x90\x81\x70\x5F\x4C\x39\x2B\x24" + "\x21\x21\x1E\x1E\x1F\x1E\x20\x1F\x1E\x20\x21\x21\x1D\x1D\x1E\x1E\x21\x21\x20\x20\x21\x21\x1F" + "\x1F\x1F\x1E\x1E\x1E\x20\x1F\x1F\x1F" + "\x8A\x8D\x8E\x8F\x91\x92\x95\x92\x89\x8C\x97\x97\x99\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98" + "\x98\x98\x98\x98\x95\x8D\x82\x6E\x4C" + "\x2F\x2A\x23\x20\x21\x1F\x1F\x20\x1F\x1F\x1E\x1D\x1D\x1D\x1D\x1F\x1F\x1E\x1E\x20\x20\x1E\x1E" + "\x1E\x1E\x1E\x1F\x20\x20\x20\x1F\x1E" + "\x8A\x8D\x8F\x90\x91\x92\x95\x94\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x97\x97\x98\x98" + "\x98\x98\x98\x98\x98\x98\x97\x97\x97" + "\x8D\x6C\x4B\x34\x27\x22\x21\x1E\x1D\x1F\x20\x1F\x1F\x1E\x1E\x20\x1F\x1F\x1E\x20\x1F\x1F\x1E" + "\x1F\x1E\x1F\x21\x24\x1F\x1F\x20\x20" + "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x97\x97\x97\x97" + "\x97\x97\x97\x98\x98\x98\x98\x98\x98" + "\x98\x98\x8E\x76\x51\x31\x24\x21\x1F\x1F\x1E\x1E\x1F\x1F\x1F\x1F\x20\x1E\x20\x21\x1E\x1F\x1F" + "\x1E\x1F\x20\x20\x20\x1D\x1E\x1F\x20" + "\x8A\x8D\x8F\x90\x91\x93\x95\x95\x95\x95\x96\x96\x98\x98\x98\x98\x98\x98\x98\x98\x97\x97\x96" + "\x97\x98\x97\x97\x8E\x7C\x6D\x64\x72" + "\x89\x96\x98\x98\x8E\x6B\x38\x25\x1E\x1E\x1F\x20\x1F\x20\x1F\x1F\x1F\x1F\x1F\x1E\x1E\x1E\x21" + "\x1F\x1F\x20\x20\x1F\x1D\x1E\x1D\x1E" + "\x8A\x8D\x8F\x90\x91\x93\x95\x95\x95\x95\x96\x96\x98\x98\x98\x98\x98\x98\x98\x98\x99\x98\x97" + "\x98\x96\x87\x62\x39\x2E\x2E\x2F\x2F" + "\x32\x45\x74\x92\x98\x98\x7D\x3E\x22\x20\x21\x22\x1E\x1E\x21\x1E\x1F\x1F\x20\x1E\x20\x1E\x1D" + "\x1E\x1F\x1E\x1E\x1F\x1E\x1E\x1D\x1E" + "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x97" + "\x98\x7D\x47\x31\x30\x49\x55\x5B\x52" + "\x3D\x30\x37\x5D\x92\x98\x97\x72\x2C\x23\x1F\x1E\x20\x1F\x21\x1F\x1F\x1F\x20\x21\x20\x1E\x1D" + "\x1D\x1E\x1E\x21\x20\x1F\x1F\x1E\x1D" + "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x97" + "\x91\x45\x2F\x3E\x6D\x8B\x91\x93\x8F" + "\x81\x59\x32\x31\x68\x97\x97\x94\x4D\x26\x22\x1E\x22\x21\x21\x1E\x1F\x20\x20\x21\x1E\x20\x1E" + "\x1F\x20\x1E\x1E\x20\x1F\x1F\x1F\x1E" + "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x97\x97\x93" + "\x56\x2F\x44\x81\x97\x98\x97\x94\x91" + "\x96\x96\x69\x33\x3A\x79\x97\x97\x81\x37\x24\x1E\x20\x20\x20\x20\x1E\x20\x1F\x1F\x1F\x1F\x1E" + "\x1E\x21\x1E\x1E\x20\x1F\x20\x1F\x1F" + "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x97\x98\x82" + "\x3E\x35\x6C\x96\x98\x96\x82\x63\x57" + "\x69\x88\x8F\x4C\x2F\x59\x92\x97\x91\x58\x28\x1F\x20\x1F\x20\x20\x1F\x1F\x1F\x20\x1F\x1F\x1F" + "\x1D\x1E\x1F\x1E\x20\x1F\x1F\x1E\x1E" + "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x97\x98\x72" + "\x36\x3F\x86\x98\x98\x8F\x57\x32\x2C" + "\x35\x62\x94\x6A\x2F\x4E\x8B\x97\x97\x75\x34\x22\x21\x1F\x1F\x20\x20\x21\x20\x1E\x21\x22\x1F" + "\x1D\x1F\x1F\x1E\x1F\x1F\x1F\x1F\x1E" + "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x97\x97\x97\x97\x98\x61" + "\x2E\x44\x93\x98\x97\x94\x70\x46\x3E" + "\x4D\x79\x97\x80\x31\x44\x82\x98\x98\x8E\x50\x26\x1F\x1E\x1E\x20\x21\x20\x1E\x1F\x21\x20\x1F" + "\x1D\x1E\x1F\x1D\x1F\x1D\x1D\x1F\x1F" + "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x97\x98\x98\x98\x98\x98\x97\x98\x69" + "\x31\x41\x8D\x98\x97\x97\x95\x8A\x80" + "\x8D\x97\x97\x7A\x2F\x45\x83\x98\x98\x97\x68\x2E\x20\x1E\x1F\x1F\x1E\x1E\x1F\x20\x1F\x1F\x1F" + "\x20\x1F\x1E\x1D\x1F\x1F\x1E\x1F\x20" + "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x98\x97\x97\x98\x98\x98\x98\x99\x97\x98\x77" + "\x38\x39\x7E\x98\x98\x98\x98\x97\x97" + "\x98\x98\x98\x61\x2F\x50\x8E\x98\x98\x98\x80\x3A\x21\x1F\x1F\x21\x1F\x20\x1F\x20\x1E\x1F\x1F" + "\x20\x20\x1F\x20\x21\x1E\x1D\x1F\x1D" + "\x8A\x8D\x8F\x90\x92\x93\x95\x95\x96\x96\x97\x97\x96\x8F\x94\x97\x98\x98\x97\x97\x97\x97\x89" + "\x47\x2E\x52\x8D\x97\x98\x98\x97\x98" + "\x98\x97\x7C\x3B\x31\x65\x97\x98\x98\x98\x96\x4D\x26\x1F\x20\x1D\x1D\x1D\x20\x20\x1F\x1F\x1F" + "\x1E\x1F\x1E\x20\x21\x1F\x1F\x1E\x20" + "\x8A\x8C\x8F\x90\x92\x93\x94\x95\x96\x96\x96\x97\x94\x76\x68\x87\x98\x98\x98\x98\x97\x97\x97" + "\x72\x2F\x34\x5D\x8B\x98\x98\x97\x98" + "\x95\x7D\x49\x2E\x47\x86\x98\x98\x99\x98\x98\x69\x27\x20\x1F\x1E\x1C\x1E\x20\x20\x1E\x1E\x1F" + "\x1D\x1F\x1E\x1F\x1E\x20\x20\x1F\x1F" + "\x8A\x8D\x8F\x90\x92\x93\x94\x95\x96\x96\x96\x97\x97\x7F\x49\x45\x72\x94\x98\x98\x98\x98\x98" + "\x94\x51\x2E\x31\x49\x6F\x83\x88\x7F" + "\x61\x3E\x2E\x32\x7C\x97\x98\x98\x98\x98\x98\x86\x2D\x23\x1F\x1E\x1F\x20\x21\x20\x1F\x1F\x1E" + "\x1D\x1E\x1E\x1F\x1D\x1E\x1F\x1F\x1F" + "\x8A\x8D\x8F\x90\x92\x93\x94\x95\x96\x96\x96\x97\x98\x94\x5D\x2E\x2C\x46\x76\x8D\x97\x98\x98" + "\x98\x91\x67\x3C\x2C\x2D\x2E\x2E\x2E" + "\x2D\x32\x4B\x79\x98\x98\x98\x98\x98\x97\x97\x97\x3F\x23\x1F\x1F\x1D\x1F\x1F\x1E\x1F\x1F\x1F" + "\x1F\x1D\x1F\x21\x20\x1F\x1F\x1F\x1F" + "\x8B\x8D\x8F\x91\x92\x93\x94\x95\x96\x96\x96\x97\x98\x98\x7A\x39\x28\x28\x32\x4E\x74\x8F\x98" + "\x98\x98\x94\x7A\x5E\x4C\x3A\x39\x42" + "\x52\x6A\x8B\x97\x98\x98\x98\x98\x98\x98\x96\x80\x39\x23\x20\x1F\x1E\x20\x20\x1F\x21\x20\x1F" + "\x1F\x20\x1F\x1F\x1F\x1E\x20\x1F\x20" + "\x8C\x8D\x8F\x91\x92\x93\x95\x95\x96\x96\x97\x97\x98\x98\x93\x62\x30\x29\x26\x27\x34\x4C\x72" + "\x93\x98\x98\x97\x94\x8C\x84\x83\x88" + "\x8E\x95\x98\x98\x98\x98\x98\x98\x97\x8C\x62\x3D\x25\x21\x20\x1F\x1E\x1F\x20\x20\x1F\x20\x20" + "\x20\x1F\x20\x1E\x1E\x20\x1F\x1F\x1F" + "\x8C\x8E\x90\x91\x93\x93\x95\x95\x96\x96\x97\x97\x98\x98\x97\x97\x8B\x64\x46\x33\x28\x27\x2B" + "\x3A\x61\x7E\x8C\x95\x98\x98\x98\x98" + "\x97\x97\x97\x98\x98\x97\x93\x89\x62\x38\x29\x22\x21\x1F\x1F\x1E\x20\x20\x20\x21\x1F\x1F\x20" + "\x1E\x1E\x20\x1F\x20\x1F\x1F\x1F\x1F" + "\x8D\x8F\x90\x92\x93\x94\x95\x95\x96\x96\x97\x97\x98\x98\x98\x98\x98\x96\x8A\x6F\x4B\x32\x29" + "\x27\x29\x34\x4A\x61\x78\x83\x8E\x97" + "\x98\x98\x97\x95\x86\x74\x5F\x44\x2B\x2A\x2C\x30\x21\x1E\x20\x1E\x1F\x1F\x1F\x20\x1F\x1D\x1F" + "\x1E\x1E\x1F\x20\x21\x1E\x20\x20\x1E" + "\x8D\x8F\x90\x92\x93\x94\x95\x96\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x97\x8C\x79\x57" + "\x37\x2C\x29\x28\x29\x35\x3C\x45\x50" + "\x57\x52\x4D\x4A\x3F\x34\x2A\x2A\x2F\x49\x6F\x70\x25\x1F\x1F\x1F\x20\x1F\x1E\x1F\x20\x1F\x20" + "\x1E\x1E\x1E\x20\x21\x1D\x1F\x1F\x1F" + "\x8E\x90\x91\x92\x93\x94\x95\x96\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x97\x98\x97\x98" + "\x94\x77\x58\x42\x32\x28\x28\x27\x28" + "\x27\x2A\x2A\x2A\x2C\x35\x42\x58\x85\x97\x97\x6B\x28\x20\x20\x20\x1E\x1F\x1F\x1D\x1F\x1F\x1F" + "\x1F\x1F\x1C\x1D\x1F\x1E\x1F\x1F\x20" + "\x8F\x90\x92\x93\x93\x95\x95\x96\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98" + "\x98\x98\x96\x89\x74\x62\x59\x4D\x42" + "\x38\x3F\x48\x51\x62\x74\x86\x95\x98\x98\x92\x4E\x26\x21\x20\x1F\x1E\x1F\x20\x1D\x20\x20\x1F" + "\x1F\x1D\x1D\x1E\x1F\x20\x1F\x20\x20" + "\x8F\x90\x92\x93\x94\x95\x95\x96\x96\x97\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98" + "\x98\x98\x98\x98\x98\x96\x92\x8D\x88" + "\x84\x87\x8B\x8F\x95\x98\x98\x98\x98\x98\x75\x35\x23\x1E\x20\x1E\x1D\x1F\x20\x20\x1F\x1D\x1E" + "\x20\x1D\x1F\x1E\x1F\x1F\x1E\x1F\x1F" + "\x90\x91\x92\x93\x94\x95\x96\x96\x96\x97\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98" + "\x98\x98\x98\x98\x98\x98\x98\x97\x97" + "\x98\x98\x98\x98\x98\x98\x97\x98\x8B\x69\x39\x27\x20\x1E\x1E\x1D\x1F\x20\x1F\x1F\x1F\x1F\x1F" + "\x1E\x1D\x1F\x20\x1E\x20\x1F\x21\x20" + "\x90\x92\x93\x94\x95\x95\x96\x96\x96\x97\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98" + "\x98\x98\x98\x98\x98\x98\x98\x97\x97" + "\x98\x98\x98\x98\x97\x97\x8D\x73\x4D\x2E\x24\x21\x1E\x1E\x1D\x1D\x1F\x1E\x1E\x1E\x1E\x20\x20" + "\x20\x1F\x1E\x1D\x1F\x20\x20\x1E\x1F" + "\x91\x92\x93\x94\x95\x95\x96\x96\x96\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x97\x98\x98\x98" + "\x98\x97\x97\x98\x98\x98\x98\x98\x98" + "\x99\x98\x99\x98\x8D\x68\x47\x32\x26\x23\x22\x20\x1C\x1D\x1E\x20\x1F\x1F\x1E\x20\x1D\x1E\x1E" + "\x1E\x1C\x1E\x1D\x1E\x1F\x20\x1E\x1E" + "\x92\x93\x92\x94\x95\x95\x95\x95\x97\x97\x97\x98\x99\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98" + "\x99\x97\x97\x97\x98\x97\x97\x97\x93" + "\x8B\x83\x6D\x4E\x30\x29\x24\x22\x20\x1F\x20\x20\x1F\x1F\x1E\x20\x22\x20\x20\x20\x1C\x1E\x1F" + "\x1E\x1F\x1F\x20\x20\x1E\x20\x21\x1E" + "\x92\x93\x93\x94\x94\x8C\x80\x7D\x77\x77\x7D\x83\x8A\x8E\x92\x96\x98\x98\x98\x98\x98\x98\x98" + "\x97\x98\x97\x91\x8B\x81\x77\x68\x5B" + "\x4A\x38\x2B\x27\x22\x20\x20\x20\x1F\x1D\x1E\x1F\x1F\x1F\x1F\x20\x1F\x1E\x20\x20\x1F\x1E\x1F" + "\x20\x1E\x1E\x1D\x1E\x1F\x1F\x1F\x1E" + "\x92\x94\x94\x94\x69\x49\x3C\x38\x33\x33\x37\x3C\x42\x43\x45\x4C\x55\x5C\x65\x6D\x6B\x68\x65" + "\x61\x59\x4E\x47\x42\x3C\x34\x2A\x27" + "\x23\x23\x22\x20\x22\x20\x20\x20\x21\x1F\x20\x20\x1F\x1F\x1E\x21\x20\x1E\x1F\x20\x20\x1F\x20" + "\x1F\x1F\x1E\x1C\x1C\x20\x1F\x1F\x1F" + "\x92\x94\x91\x5D\x2D\x22\x1E\x20\x1E\x1F\x22\x23\x23\x23\x25\x25\x25\x25\x24\x27\x28\x26\x27" + "\x26\x23\x24\x23\x22\x21\x21\x1F\x21" + "\x20\x1F\x1E\x1F\x1F\x1F\x1E\x1F\x20\x20\x21\x21\x20\x1F\x1F\x21\x1F\x1F\x20\x20\x21\x1F\x20" + "\x1F\x1F\x20\x1F\x1F\x1D\x1F\x20\x1F" + "\x94\x95\x7A\x39\x23\x1F\x1D\x1D\x1D\x1F\x20\x1F\x1F\x20\x20\x1F\x1E\x20\x20\x1F\x20\x21\x20" + "\x20\x20\x1F\x20\x1F\x20\x1D\x1D\x1F" + "\x1E\x1F\x1F\x21\x22\x20\x1D\x1E\x20\x21\x21\x1F\x1F\x1D\x1D\x21\x1F\x1F\x1F\x20\x21\x1F\x20" + "\x1F\x1F\x21\x20\x1E\x1F\x1F\x1F\x1F" + "\x94\x94\x60\x2A\x20\x1D\x1D\x1F\x1D\x1F\x20\x21\x1F\x1E\x1F\x1F\x1F\x20\x20\x1E\x1F\x20\x20" + "\x1D\x20\x1F\x20\x20\x1F\x1F\x1E\x1F" + "\x1F\x1F\x1F\x1E\x1F\x20\x1F\x1F\x1E\x20\x1E\x1E\x1F\x1F\x1F\x20\x1F\x1F\x20\x21\x1E\x1F\x20" + "\x20\x1F\x1F\x21\x1F\x1F\x1F\x1F\x1D" + "\x94\x8E\x52\x26\x1F\x1D\x1E\x1E\x1E\x1F\x1F\x20\x1F\x1F\x1F\x20\x22\x21\x20\x20\x1F\x20\x20" + "\x1F\x1F\x1F\x20\x21\x21\x20\x20\x20" + "\x1F\x21\x1E\x1D\x20\x1F\x20\x1E\x1D\x1F\x1E\x1F\x1F\x1E\x1F\x1E\x1F\x1F\x1F\x1F\x1E\x1D\x1E" + "\x20\x20\x1F\x1F\x1E\x1F\x20\x1F\x1F" + "\x94\x8C\x4F\x25\x1D\x1F\x20\x1E\x1D\x1E\x1E\x1E\x1F\x1F\x20\x20\x21\x20\x20\x1F\x1F\x1E\x1E" + "\x20\x1E\x1E\x20\x20\x20\x1F\x20\x20" + "\x20\x1F\x1D\x1E\x20\x20\x1F\x1F\x20\x20\x20\x20\x1F\x1E\x1E\x1E\x1F\x20\x20\x1F\x1F\x1E\x1E" + "\x1E\x1E\x1E\x1E\x1E\x1F\x20\x1F\x1E" + "\x95\x8A\x4D\x25\x1C\x1D\x1D\x1E\x1E\x1E\x1F\x1F\x20\x21\x1F\x1D\x1F\x20\x21\x1F\x1C\x1E\x1F" + "\x1F\x1E\x20\x1F\x20\x21\x20\x20\x22" + "\x20\x20\x20\x20\x1E\x20\x1F\x1F\x20\x1E\x1E\x1C\x1C\x1E\x1F\x1E\x20\x1F\x1F\x20\x20\x1F\x1F" + "\x21\x1E\x20\x1E\x1E\x1E\x20\x20\x1E" + "\x96\x8F\x50\x25\x20\x1F\x1F\x1F\x20\x20\x20\x1F\x21\x22\x21\x21\x1F\x1F\x1F\x1F\x1E\x1F\x20" + "\x1F\x21\x20\x20\x21\x1F\x20\x20\x1F" + "\x1F\x1E\x1F\x1E\x1D\x1E\x1F\x20\x1F\x1E\x1D\x1D\x1D\x1E\x20\x1F\x1F\x1F\x1E\x1E\x21\x1F\x1F" + "\x1F\x1F\x1F\x1E\x1E\x1E\x1E\x1F\x20" + "\x96\x92\x56\x26\x1F\x1D\x1F\x20\x1D\x1F\x20\x21\x1E\x20\x1F\x1F\x20\x1F\x1E\x1E\x1F\x1E\x1F" + "\x1F\x20\x20\x20\x21\x1F\x21\x1F\x1F" + "\x1F\x1D\x1F\x1F\x1E\x1E\x1F\x21\x20\x1F\x1E\x1F\x20\x1F\x1E\x1F\x1E\x1E\x1E\x20\x1E\x1E\x1F" + "\x1E\x1F\x1F\x1F\x1F\x1E\x20\x1F\x1E" + "\x96\x96\x66\x2D\x1D\x1D\x1F\x21\x1F\x20\x20\x22\x1F\x1E\x1F\x1B\x1C\x20\x1F\x21\x1E\x1E\x1F" + "\x1F\x20\x1E\x1F\x20\x1F\x21\x20\x20" + "\x21\x20\x21\x1F\x20\x20\x20\x1E\x20\x1F\x1F\x1E\x1E\x1F\x20\x1E\x1E\x1F\x1E\x1D\x1E\x1F\x20" + "\x1E\x1F\x20\x1E\x1E\x1E\x20\x1F\x1E" + "\x96\x97\x7A\x36\x20\x21\x21\x20\x1F\x1E\x1F\x20\x1E\x1F\x20\x1F\x1D\x1D\x1E\x20\x1F\x1F\x1F" + "\x1E\x1F\x20\x20\x1F\x1F\x1F\x1F\x1E" + "\x1F\x21\x1E\x1D\x1F\x1F\x20\x20\x1E\x1E\x1F\x1F\x1E\x1E\x1F\x1E\x1F\x1F\x1F\x1F\x21\x20\x20" + "\x1D\x21\x20\x1F\x20\x1E\x20\x1F\x1E" + "\x97\x97\x8C\x3F\x22\x21\x20\x20\x1F\x21\x1F\x1E\x1F\x1F\x20\x1F\x20\x20\x20\x22\x1F\x20\x21" + "\x1F\x1E\x1F\x21\x1F\x1F\x20\x1F\x1E" + "\x1D\x1D\x1F\x1F\x1E\x1F\x20\x1F\x1E\x1F\x20\x22\x21\x1E\x1C\x1D\x1F\x20\x20\x1F\x1F\x1F\x1E" + "\x1D\x1E\x1E\x1E\x20\x1E\x1E\x1F\x1F" + "\x97\x97\x95\x51\x26\x22\x20\x22\x20\x1F\x1F\x20\x1E\x1F\x1E\x1F\x1F\x1F\x1F\x1E\x1E\x1F\x20" + "\x1F\x1F\x1E\x20\x20\x1E\x1F\x20\x1F" + "\x1F\x20\x20\x1F\x1F\x20\x20\x1F\x21\x20\x1F\x21\x21\x20\x1E\x1E\x21\x21\x1F\x1E\x1E\x1E\x1E" + "\x1F\x1D\x1D\x1E\x1F\x1E\x1D\x1F\x20" + "\x97\x98\x98\x79\x2B\x23\x1F\x1E\x1F\x20\x21\x20\x20\x21\x1E\x1D\x1F\x1E\x1F\x21\x20\x1F\x1F" + "\x1F\x20\x20\x20\x1E\x21\x20\x1F\x1F" + "\x1D\x1E\x1F\x1F\x1F\x1F\x1F\x1E\x1F\x20\x20\x20\x20\x1E\x1F\x1E\x1F\x21\x20\x1F\x1F\x1F\x1E" + "\x21\x20\x1E\x1F\x1E\x20\x1F\x1F\x1D" + "\x98\x98\x98\x93\x40\x25\x20\x1E\x20\x21\x1F\x20\x1F\x1F\x21\x20\x20\x1F\x1F\x1F\x1E\x20\x22" + "\x20\x1F\x20\x20\x1D\x21\x20\x1F\x1E" + "\x1E\x20\x1E\x1E\x20\x1F\x1F\x1F\x1E\x21\x20\x1F\x1E\x1D\x1F\x21\x1F\x1F\x1F\x20\x20\x1F\x1F" + "\x20\x20\x20\x1F\x1D\x1F\x1F\x1E\x1C" + "\x98\x98\x98\x97\x6A\x2F\x28\x26\x28\x26\x25\x26\x23\x24\x25\x23\x21\x22\x20\x22\x1F\x23\x21" + "\x1D\x1E\x20\x20\x1E\x1E\x1E\x1F\x1F" + "\x1F\x20\x21\x20\x20\x1F\x1F\x1E\x1E\x1F\x20\x1D\x1E\x1E\x1F\x1F\x20\x20\x1F\x20\x20\x1E\x20" + "\x1D\x1F\x20\x1F\x1E\x1F\x1F\x1E\x1E" + "\x98\x98\x98\x97\x93\x78\x6F\x6A\x64\x5F\x5B\x57\x4F\x4D\x4B\x49\x45\x3E\x38\x33\x2A\x2A\x29" + "\x26\x27\x23\x23\x21\x20\x1F\x1E\x1F" + "\x1D\x1F\x1F\x1D\x1D\x1E\x1E\x1E\x21\x20\x20\x1E\x21\x1E\x1F\x1F\x1E\x20\x1F\x1F\x1F\x1F\x20" + "\x1E\x20\x1E\x21\x20\x20\x1F\x1F\x20" + "\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x97\x95\x93\x8D\x85\x7D\x74\x6A\x62\x59" + "\x51\x46\x3B\x30\x2B\x2A\x27\x24\x24" + "\x22\x20\x1F\x1F\x1E\x1E\x1F\x1F\x20\x20\x20\x20\x1E\x1F\x1F\x20\x1F\x1F\x1F\x1F\x20\x1F\x1F" + "\x1F\x20\x1F\x20\x20\x1F\x20\x20\x20" + "\x98\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x97\x98\x98\x98\x98\x98\x97\x98\x97\x96\x92" + "\x8F\x89\x85\x80\x78\x6A\x59\x42\x30" + "\x2C\x28\x23\x21\x22\x1F\x20\x1E\x20\x21\x1F\x1F\x1F\x20\x20\x1F\x1F\x1F\x20\x1F\x1F\x1E\x1D" + "\x20\x1F\x21\x20\x1E\x1F\x1F\x1F\x1F" + "\x98\x97\x98\x98\x97\x98\x98\x97\x98\x98\x98\x98\x97\x98\x98\x98\x98\x98\x97\x97\x98\x98\x98" + "\x97\x98\x98\x98\x97\x97\x97\x97\x91" + "\x7D\x69\x54\x46\x38\x2F\x27\x26\x23\x22\x1E\x1D\x1F\x1E\x1E\x20\x20\x20\x1E\x1E\x1E\x1D\x1E" + "\x1F\x1E\x20\x21\x20\x21\x1E\x1C\x1F" + "\x98\x98\x98\x98\x97\x98\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x97\x97\x98\x98\x98" + "\x98\x98\x98\x98\x98\x98\x98\x98\x98" + "\x97\x98\x96\x8E\x7E\x70\x62\x53\x41\x31\x27\x23\x22\x21\x1F\x20\x20\x20\x1F\x1E\x1E\x1E\x1F" + "\x1F\x21\x1E\x1F\x1F\x1E\x1F\x20\x1F" + "\x98\x98\x98\x97\x98\x98\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x97\x98\x98\x98\x98\x98\x98" + "\x98\x97\x98\x98\x98\x98\x98\x98\x98" + "\x97\x98\x98\x97\x97\x97\x95\x90\x88\x7C\x64\x45\x2D\x28\x25\x22\x21\x20\x1F\x1F\x1F\x1F\x20" + "\x21\x22\x20\x20\x20\x20\x20\x1F\x20" + "\x98\x97\x97\x98\x98\x98\x97\x97\x98\x98\x97\x97\x97\x98\x98\x97\x97\x97\x97\x98\x98\x98\x98" + "\x98\x97\x98\x98\x98\x97\x97\x98\x98" + "\x98\x98\x98\x98\x98\x97\x97\x98\x98\x97\x97\x97\x89\x6C\x4F\x3D\x30\x28\x24\x21\x1E\x1F\x1F" + "\x1E\x1D\x1D\x1F\x1F\x1D\x1E\x20\x1F" + "\x96\x95\x94\x96\x98\x98\x98\x98\x98\x97\x98\x98\x98\x98\x98\x98\x97\x97\x98\x98\x98\x98\x98" + "\x98\x98\x98\x98\x98\x98\x98\x98\x97" + "\x98\x98\x98\x98\x98\x98\x98\x98\x97\x98\x98\x98\x98\x98\x94\x86\x6E\x59\x42\x2E\x27\x24\x22" + "\x1F\x1E\x20\x20\x1E\x1D\x1E\x1F\x1E" + "\x4C\x4B\x4A\x4B\x4F\x55\x5B\x60\x69\x6F\x75\x7A\x7F\x86\x90\x97\x98\x98\x98\x98\x97\x97\x98" + "\x98\x98\x98\x98\x98\x98\x98\x98\x97" + "\x98\x98\x98\x98\x97\x97\x97\x97\x97\x97\x97\x98\x98\x98\x98\x98\x97\x92\x88\x7A\x5B\x3C\x29" + "\x25\x22\x20\x20\x1F\x1C\x20\x21\x1F" + "\x24\x24\x23\x25\x24\x23\x23\x25\x25\x28\x2A\x2A\x2C\x2D\x2E\x35\x47\x55\x62\x6E\x7B\x82\x87" + "\x8C\x92\x97\x98\x98\x98\x97\x97\x98" + "\x97\x98\x98\x98\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x97\x98\x98\x97\x97\x92\x78" + "\x54\x39\x2B\x22\x1E\x1F\x20\x1F\x1E" + "\x20\x1F\x1E\x1E\x20\x1F\x1F\x1F\x1F\x1F\x21\x23\x20\x21\x21\x24\x24\x25\x27\x2A\x2D\x35\x40" + "\x4A\x58\x66\x76\x83\x91\x97\x97\x98" + "\x98\x98\x98\x98\x98\x98\x97\x97\x98\x97\x97\x97\x97\x97\x97\x97\x98\x97\x98\x98\x98\x98\x98" + "\x94\x7C\x61\x44\x2F\x28\x22\x20\x20" + "\x1F\x1D\x1F\x20\x1F\x1E\x1E\x1F\x1E\x1E\x21\x20\x20\x20\x1E\x1F\x1F\x1F\x1E\x21\x22\x22\x23" + "\x24\x27\x2A\x35\x3E\x4A\x56\x6D\x82" + "\x94\x98\x97\x97\x98\x98\x98\x98\x98\x97\x97\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98\x98" + "\x98\x97\x94\x89\x74\x48\x2D\x26\x22" + "\x1F\x1E\x1E\x1F\x1E\x1F\x20\x20\x1F\x1F\x1F\x1F\x1F\x1F\x20\x1E\x1F\x1D\x1C\x1F\x20\x1F\x1F" + "\x1F\x22\x21\x22\x26\x23\x25\x27\x2C" + "\x31\x46\x5E\x77\x88\x92\x97\x98\x98\x97\x97\x98\x97\x97\x98\x98\x97\x98\x98\x98\x98\x98\x98" + "\x98\x98\x97\x98\x97\x96\x85\x5E\x42" + "\x1E\x1E\x1F\x20\x20\x20\x1F\x1D\x1E\x1F\x1D\x1F\x1F\x1F\x1F\x1F\x20\x21\x21\x20\x1F\x20\x20" + "\x1E\x1F\x1F\x20\x20\x20\x20\x20\x21" + "\x24\x25\x29\x2D\x43\x59\x6E\x81\x93\x98\x98\x98\x98\x98\x98\x98\x97\x98\x98\x98\x98\x98\x98" + "\x98\x98\x98\x98\x98\x98\x98\x96\x88"; + +static const BYTE TEST_64X64_RED_PLANE_RLE[3739] = + "\xF0\x23\x1F\x1E\x1D\x1D\x1E\x1D\x1F\x23\x4A\x78\x71\x64\x58\x4B\xF0\x3E\x30\x29\x26\x24\x22" + "\x21\x20\x20\x20\x1D\x1E\x20\x1E\x1F" + "\x86\x20\x20\x1F\x20\x21\x22\x1E\x1F\xF0\x1E\x1D\x1F\x20\x1F\x1F\x1F\x1E\x1E\x1F\x1F\x20\x1E" + "\x1F\x1F\x50\x1F\x1E\x1D\x1E\x1C\xF0" + "\x38\x1A\x14\x0E\x08\x08\x06\x04\x04\x18\x38\x4A\x66\x74\x82\xF0\x90\x9E\x90\x76\x52\x2C\x16" + "\x12\x0C\x06\x08\x06\x00\x02\x00\xF0" + "\x03\x03\x02\x00\x01\x03\x02\x02\x00\x02\x01\x01\x03\x00\x00\x33\x02\x04\x00\xD0\x02\x00\x00" + "\x03\x01\x02\x00\x02\x01\x00\x04\x02" + "\x06\xF0\x94\xAE\x90\x6E\x46\x2E\x1E\x0C\x0E\x0A\x02\x00\x02\x0C\x18\xF0\x24\x30\x4C\x6C\x94" + "\xBE\xBE\x9A\x6C\x48\x34\x1C\x10\x08" + "\x06\xF0\x06\x06\x00\x01\x03\x01\x02\x00\x02\x00\x02\x02\x08\x04\x02\xF0\x00\x03\x01\x00\x01" + "\x02\x00\x06\x02\x04\x01\x01\x03\x03" + "\x00\x40\x00\x00\x04\x01\xD3\x02\x14\x3C\x68\x8E\x86\x78\x68\x3E\x16\x02\x02\x00\x14\x02\xF0" + "\x1A\x44\x78\x92\x8C\x82\x6E\x52\x2E" + "\x14\x06\x02\x04\x00\x01\xF0\x01\x03\x00\x01\x01\x02\x00\x00\x03\x01\x01\x01\x04\x06\x00\xD0" + "\x02\x00\x02\x00\x02\x02\x02\x00\x00" + "\x04\x00\x03\x02\x04\xAA\x0C\x2C\x54\x6E\x7C\x4C\x02\x00\x02\x00\xF0\x10\x2E\x50\x6C\x82\x92" + "\x86\x50\x1C\x12\x0A\x04\x04\x02\x01" + "\xF0\x02\x02\x01\x05\x07\x00\x00\x01\x02\x03\x05\x03\x00\x01\x05\xA0\x01\x01\x01\x00\x02\x04" + "\x00\x02\x00\x01\xE5\x00\x00\x02\x02" + "\x00\x00\x00\x04\x1A\x14\x00\x00\x01\x00\x35\x01\x01\x00\xF0\x06\x16\x2A\x52\x96\xBC\x84\x50" + "\x28\x0C\x06\x04\x03\x03\x00\xF0\x04" + "\x04\x04\x02\x02\x02\x00\x02\x00\x00\x01\x02\x00\x02\x00\x70\x02\x04\x08\x01\x01\x02\x04\x04" + "\x5C\x02\x02\x00\x02\x00\x14\x01\xF0" + "\x00\x00\x00\x02\x02\x02\x16\x58\x86\x84\x54\x1E\x06\x06\x04\xF0\x00\x03\x01\x00\x02\x02\x01" + "\x02\x01\x04\x02\x01\x00\x02\x01\x80" + "\x02\x02\x01\x07\x03\x01\x01\x00\x04\x53\x01\x00\x00\x00\x01\x16\x00\xF0\x02\x00\x00\x01\x00" + "\x02\x00\x01\x13\x37\x55\x67\x4B\x1D" + "\x03\xF0\x14\x44\x7A\x74\x28\x08\x01\x01\x02\x04\x00\x02\x00\x00\x01\xF0\x02\x01\x05\x00\x01" + "\x04\x02\x00\x00\x00\x01\x00\x00\x03" + "\x03\x41\xF0\x04\x02\x02\x02\x03\x1F\x69\xA9\x9B\x7D\x69\x85\xAD\xA1\x47\xF0\x0B\x14\x5A\x8A" + "\x32\x08\x04\x04\x04\x01\x03\x04\x01" + "\x00\x00\xE0\x02\x00\x04\x00\x07\x01\x00\x03\x03\x00\x02\x00\x00\x00\x04\x53\x02\x00\x00\x00" + "\x02\x17\x00\xF0\x01\x00\x00\x00\x31" + "\x7F\x61\x11\x36\x4E\x58\x46\x16\x29\x79\xF0\x69\x0B\x00\x34\x68\x14\x06\x03\x07\x04\x02\x00" + "\x02\x00\x00\xA3\x00\x06\x00\x00\x00" + "\x01\x01\x00\x06\x02\x10\x01\x71\xF0\x0D\x6F\x2F\x1A\x7A\x84\x78\x70\x7A\x88\x52\x09\x57\x53" + "\x01\xF0\x00\x44\x42\x06\x06\x00\x04" + "\x04\x00\x01\x00\x02\x00\x00\x03\xB0\x04\x02\x04\x04\x00\x05\x00\x00\x00\x02\x02\x41\xF0\x01" + "\x01\x07\x75\x2B\x2A\x86\x54\x1A\x0C" + "\x02\x04\x2A\x7A\x6E\xF0\x04\x5B\x3B\x00\x06\x68\x22\x04\x00\x03\x01\x01\x04\x01\x00\x83\x01" + "\x03\x02\x01\x00\x01\x02\x00\x30\x02" + "\x00\x02\x51\xF0\x02\x21\x2F\x0C\x50\x2A\x02\x03\x29\x61\x73\x59\x1B\x4C\x32\xF0\x15\x3F\x09" + "\x00\x20\x42\x08\x02\x00\x01\x00\x00" + "\x02\x01\x00\xD0\x02\x00\x00\x02\x01\x05\x02\x00\x00\x00\x01\x01\x01\x61\xF0\x1F\x0F\x14\x34" + "\x04\x00\x0D\x55\x61\x55\x67\x4B\x0A" + "\x3C\x00\xF0\x15\x0D\x00\x0C\x3A\x18\x06\x02\x00\x01\x00\x02\x04\x02\x03\xC0\x04\x06\x00\x00" + "\x02\x00\x00\x01\x00\x00\x02\x00\x11" + "\xF0\x01\x01\x01\x00\x00\x21\x0F\x0A\x1A\x00\x01\x0A\x32\x28\x24\xF0\x30\x2E\x06\x2C\x04\x13" + "\x11\x02\x02\x32\x38\x08\x03\x01\x01" + "\xF0\x00\x02\x01\x03\x02\x00\x03\x00\x00\x01\x00\x01\x00\x03\x03\x20\x00\x02\x0E\xF0\x01\x00" + "\x00\x02\x02\x02\x00\x00\x10\x06\x05" + "\x0B\x00\x00\x06\xF0\x4A\x88\x84\x80\x3C\x00\x0B\x03\x02\x02\x00\x00\x12\x30\x10\xF0\x02\x00" + "\x02\x01\x05\x03\x02\x02\x03\x01\x00" + "\x06\x02\x01\x00\x50\x00\x04\x02\x00\x02\x0D\x24\x01\x00\xF0\x02\x00\x00\x1C\x0E\x0F\x1D\x00" + "\x02\x02\x06\x1A\x2E\x16\x02\xF0\x02" + "\x31\x00\x16\x16\x00\x00\x02\x30\x18\x02\x02\x00\x04\x02\xF0\x04\x00\x00\x01\x00\x00\x00\x02" + "\x02\x06\x04\x01\x01\x00\x05\x0C\xF0" + "\x03\x0F\x05\x01\x00\x00\x01\x03\x00\x01\x24\x1E\x15\x57\x15\xF0\x01\x00\x00\x00\x02\x00\x01" + "\x37\x4B\x04\x2A\x12\x00\x00\x00\xF0" + "\x2C\x26\x0A\x00\x02\x07\x03\x05\x02\x00\x02\x00\x00\x03\x01\x70\x01\x00\x00\x02\x04\x01\x06" + "\x33\x00\x01\x00\xF0\x01\x00\x00\x00" + "\x01\x00\x03\x31\x57\x1F\x00\x00\x02\x02\x00\x83\x00\x1C\x56\x02\x3B\x5F\x17\x00\xF0\x05\x33" + "\x65\x19\x2C\x42\x02\x00\x02\x00\x04" + "\x38\x02\x02\x01\xF0\x02\x01\x02\x00\x00\x01\x01\x00\x01\x00\x00\x01\x05\x02\x02\x20\x02\x01" + "\x39\x00\x02\x00\xF0\x06\x12\x3D\x83" + "\x4B\x07\x00\x00\x02\x02\x02\x44\x44\x0B\x57\xF0\x83\x51\x29\x1D\x31\x67\x7D\x35\x08\x6A\x22" + "\x00\x00\x01\x00\xF0\x00\x3A\x0C\x06" + "\x00\x00\x06\x04\x02\x00\x02\x02\x01\x00\x01\x70\x00\x00\x01\x03\x01\x00\x00\x0C\xF0\x02\x2A" + "\x28\x2D\x8B\x9B\x43\x15\x01\x00\x00" + "\x08\x80\x72\x16\xF0\x39\x83\xA9\xB3\xA1\x67\x17\x3A\x8E\x38\x02\x00\x00\x00\x01\xF0\x01\x22" + "\x24\x00\x00\x02\x03\x01\x03\x03\x00" + "\x00\x02\x04\x01\x70\x02\x04\x06\x02\x00\x00\x00\x58\x02\x00\x00\x02\x00\xF0\x08\x3A\x16\x07" + "\x3B\x87\x7D\x45\x11\x00\x00\x0E\x5A" + "\x7C\x64\x94\x3E\x18\x16\x28\x4A\x70\x80\x3C\x00\x83\x02\x01\x2D\x0B\x00\x02\x00\x02\xC0\x04" + "\x02\x00\x00\x06\x00\x03\x01\x01\x02" + "\x00\x02\x24\x02\x00\xF0\x02\x00\x00\x00\x02\x00\x00\x00\x32\x52\x10\x02\x17\x4D\x7F\xF0\x85" + "\x4B\x09\x00\x08\x3A\x6C\x80\x94\x94" + "\x8C\x78\x56\x1A\x02\x13\x00\xF0\x01\x17\x67\x85\x27\x03\x00\x00\x00\x01\x00\x02\x03\x00\x02" + "\x90\x02\x01\x02\x01\x01\x04\x01\x00" + "\x01\x68\x00\x02\x02\x00\x02\x00\xF0\x08\x6A\xB6\x76\x40\x18\x17\x49\x8D\xB1\x6D\x33\x15\x02" + "\x18\xF0\x28\x2A\x20\x12\x04\x01\x00" + "\x00\x01\x09\x1D\x69\xA7\x71\x35\xF0\x07\x03\x01\x01\x04\x02\x00\x02\x00\x01\x00\x03\x01\x00" + "\x02\x50\x04\x01\x00\x00\x00\x77\x02" + "\x02\x00\x02\x00\x02\x00\xF0\x02\x02\x1A\x64\x88\x78\x46\x16\x03\x25\x6F\x93\x83\x67\x3F\xF0" + "\x29\x13\x01\x02\x02\x00\x05\x23\x45" + "\x67\x89\x6D\x1B\x06\x1C\x53\x00\x01\x02\x00\x01\xC0\x00\x03\x01\x00\x00\x01\x02\x02\x01\x02" + "\x02\x01\x07\x28\x02\x00\xF0\x04\x1C" + "\x50\x82\x8E\x5C\x20\x06\x15\x43\x6F\x85\x8D\x91\x8D\xF0\x81\x8B\x93\x95\x8D\x7F\x69\x33\x08" + "\x3E\x86\x80\x08\x02\x01\xF0\x02\x02" + "\x00\x01\x01\x02\x04\x02\x00\x00\x01\x00\x00\x01\x01\x20\x01\x02\x4D\x02\x02\x02\x00\x03\xF0" + "\x18\x3C\x82\xBA\x96\x5E\x34\x12\x19" + "\x27\x3B\x4F\x5F\x4F\x45\xF0\x3F\x25\x02\x30\x5C\xAC\x9C\x50\x09\x06\x02\x02\x02\x03\x00\xE0" + "\x02\x03\x01\x00\x01\x02\x02\x03\x05" + "\x03\x02\x00\x00\x02\x7C\x02\x00\x02\x02\x00\x02\x00\xF0\x02\x00\x02\x00\x08\x42\x7C\x8E\x84" + "\x74\x62\x4C\x34\x22\x2A\xF0\x3C\x4E" + "\x6C\x7E\x88\x7A\x26\x02\x09\x39\x03\x02\x00\x01\x00\xF0\x00\x02\x00\x02\x02\x00\x00\x03\x02" + "\x02\x00\x04\x00\x02\x00\x04\x23\x02" + "\x00\x2E\x02\x00\xF0\x04\x1E\x48\x68\x72\x80\x8C\x98\x90\x86\x7C\x66\x48\x24\x06\xF0\x00\x00" + "\x39\x31\x05\x05\x00\x01\x01\x00\x00" + "\x06\x01\x05\x01\x63\x02\x00\x04\x00\x00\x01\x33\x02\x02\x00\x2F\x02\x00\x05\xF0\x04\x0C\x14" + "\x1E\x28\x22\x1A\x12\x06\x00\x01\x00" + "\x19\x5D\x77\xF0\x1B\x05\x00\x03\x01\x04\x02\x01\x01\x00\x04\x02\x03\x00\x00\x60\x04\x01\x02" + "\x02\x04\x02\x23\x00\x02\x1F\x00\x0F" + "\xF0\x01\x01\x13\x49\x7B\x75\x29\x0B\x03\x00\x01\x00\x00\x03\x01\xD0\x01\x01\x02\x02\x04\x04" + "\x01\x05\x02\x00\x02\x05\x01\x29\x02" + "\x00\x26\x02\x00\x23\x01\x00\x33\x01\x01\x00\xF0\x02\x02\x02\x00\x02\x00\x13\x5D\x8B\x81\x4D" + "\x15\x03\x01\x03\xF0\x01\x02\x06\x00" + "\x02\x00\x04\x01\x03\x03\x03\x05\x00\x00\x01\x40\x01\x00\x00\x01\xF4\x02\x02\x01\x00\x00\x00" + "\x01\x01\x02\x00\x00\x00\x02\x01\x00" + "\xF0\x02\x00\x00\x00\x02\x00\x00\x01\x00\x01\x01\x01\x09\x1B\x29\xF0\x57\x93\xB9\x7D\x45\x1F" + "\x0B\x07\x03\x00\x06\x04\x00\x00\x06" + "\xF0\x02\x04\x00\x01\x00\x02\x00\x06\x02\x06\x04\x01\x00\x06\x00\xF0\x00\x00\x02\x00\x01\x11" + "\x29\x2F\x3F\x3F\x33\x29\x1D\x11\x0B" + "\x26\x03\x00\xF0\x03\x02\x00\x0B\x19\x2B\x3F\x5D\x6F\x81\x95\x83\x4D\x1B\x11\xF0\x07\x03\x01" + "\x03\x03\x01\x00\x00\x02\x00\x05\x03" + "\x00\x00\x06\xB0\x00\x00\x04\x01\x01\x05\x03\x02\x01\x03\x00\xF0\x00\x02\x02\x00\x55\x85\x87" + "\x89\x87\x87\x8B\x8D\x8F\x95\x99\xF0" + "\x93\x85\x77\x65\x55\x59\x5F\x65\x6B\x7D\x91\x93\x91\x89\x85\x73\x7B\x67\x4D\x29\x11\x0D\x00" + "\xF0\x04\x04\x04\x02\x00\x00\x01\x02" + "\x02\x00\x01\x00\x02\x02\x02\x90\x01\x02\x00\x01\x03\x02\x00\x00\x02\xF0\x00\x00\x05\x6D\x77" + "\x4D\x3B\x2F\x29\x27\x29\x31\x3D\x3F" + "\x3F\xF0\x4D\x5F\x6D\x81\x8B\x85\x83\x7B\x75\x6B\x53\x47\x3F\x35\x25\xC3\x15\x0B\x05\x07\x07" + "\x01\x05\x01\x03\x01\x01\x02\x93\x00" + "\x02\x00\x01\x02\x02\x00\x02\x00\x70\x04\x06\x06\x05\x00\x02\x00\xF0\x04\x02\x2D\x47\x13\x05" + "\x01\x05\x01\x00\x03\x07\x07\x05\x09" + "\xF0\x0B\x0D\x09\x07\x0F\x0F\x09\x0D\x0B\x05\x09\x05\x05\x01\x07\xF0\x03\x03\x03\x00\x02\x04" + "\x06\x02\x01\x01\x00\x02\x00\x03\x01" + "\x75\x03\x03\x00\x00\x00\x01\x00\x70\x02\x02\x01\x04\x00\x01\x00\xF0\x00\x01\x33\x1D\x05\x03" + "\x00\x04\x00\x00\x00\x04\x00\x03\x01" + "\xF0\x00\x02\x00\x00\x01\x01\x01\x00\x05\x00\x00\x00\x02\x01\x04\xF0\x02\x00\x02\x00\x00\x05" + "\x05\x00\x04\x02\x03\x01\x05\x01\x00" + "\xF0\x04\x04\x01\x00\x00\x02\x02\x05\x00\x00\x02\x00\x03\x02\x02\x40\x00\x00\x00\x03\xF0\x00" + "\x0B\x1B\x07\x01\x00\x02\x01\x02\x00" + "\x01\x01\x00\x02\x00\xF0\x02\x06\x02\x00\x04\x00\x00\x00\x04\x01\x00\x00\x02\x04\x02\xF0\x04" + "\x02\x00\x04\x01\x01\x02\x01\x02\x01" + "\x01\x01\x00\x02\x00\xF0\x01\x00\x03\x00\x00\x01\x03\x00\x03\x03\x00\x02\x00\x03\x01\x40\x00" + "\x02\x00\x04\xF0\x00\x03\x05\x01\x03" + "\x04\x04\x00\x01\x01\x01\x03\x00\x00\x02\xF0\x00\x01\x01\x00\x01\x00\x03\x03\x02\x01\x01\x00" + "\x01\x01\x01\xF0\x00\x00\x02\x03\x01" + "\x02\x00\x02\x01\x02\x06\x02\x04\x02\x00\xF3\x00\x01\x00\x00\x02\x02\x00\x02\x02\x00\x03\x03" + "\x01\x01\x00\x10\x01\xF0\x02\x03\x03" + "\x00\x01\x03\x05\x00\x02\x00\x02\x02\x02\x04\x01\xF0\x05\x03\x00\x02\x00\x05\x00\x02\x01\x00" + "\x04\x01\x00\x02\x02\x83\x00\x04\x00" + "\x02\x06\x04\x03\x00\xB3\x03\x03\x07\x05\x00\x02\x00\x02\x01\x01\x02\x90\x06\x00\x04\x00\x00" + "\x01\x00\x02\x00\xF0\x02\x0A\x06\x00" + "\x08\x04\x04\x02\x04\x04\x02\x00\x02\x02\x04\xF0\x08\x00\x01\x03\x00\x04\x02\x02\x00\x06\x00" + "\x02\x02\x03\x00\xF0\x00\x05\x01\x03" + "\x01\x03\x01\x03\x00\x02\x01\x00\x01\x02\x02\xF0\x00\x02\x02\x01\x00\x01\x03\x02\x00\x00\x03" + "\x02\x01\x00\x00\x40\x00\x03\x01\x04" + "\xF0\x00\x06\x0C\x02\x01\x03\x00\x02\x05\x01\x00\x04\x05\x03\x03\xB3\x03\x02\x00\x01\x01\x02" + "\x01\x01\x00\x01\x00\xB3\x02\x01\x00" + "\x00\x01\x00\x02\x02\x00\x00\x02\xF0\x04\x06\x02\x03\x00\x01\x01\x00\x04\x05\x01\x00\x01\x00" + "\x00\x60\x02\x02\x00\x04\x00\x03\xF0" + "\x00\x08\x20\x0E\x03\x00\x00\x02\x04\x02\x00\x02\x02\x03\x00\x73\x07\x07\x02\x02\x06\x01\x00" + "\xF0\x03\x01\x01\x00\x00\x02\x02\x04" + "\x06\x04\x00\x04\x04\x02\x05\xF0\x00\x00\x02\x01\x03\x00\x04\x01\x00\x02\x00\x05\x00\x02\x02" + "\x63\x00\x00\x02\x01\x01\x00\xF0\x00" + "\x02\x28\x12\x06\x08\x04\x01\x00\x03\x01\x03\x01\x02\x02\xF0\x08\x02\x05\x01\x01\x02\x02\x00" + "\x01\x01\x04\x02\x01\x00\x03\xF0\x01" + "\x03\x03\x02\x05\x03\x01\x01\x00\x04\x03\x01\x00\x02\x00\xF0\x01\x01\x00\x02\x00\x02\x04\x06" + "\x02\x00\x01\x04\x00\x02\x04\x13\x00" + "\xF0\x02\x00\x24\x12\x04\x00\x01\x00\x00\x06\x00\x03\x02\x00\x00\xF0\x00\x06\x06\x04\x04\x00" + "\x02\x04\x02\x01\x01\x02\x00\x00\x02" + "\xF0\x00\x00\x03\x07\x02\x04\x01\x00\x00\x01\x00\x02\x02\x06\x06\xF0\x00\x05\x01\x00\x02\x02" + "\x00\x03\x01\x03\x00\x05\x03\x01\x00" + "\x40\x00\x03\x00\x02\xF0\x00\x00\x12\x24\x08\x02\x00\x04\x02\x03\x00\x04\x01\x00\x03\xF0\x00" + "\x01\x01\x01\x07\x01\x01\x01\x00\x02" + "\x01\x01\x02\x01\x01\xF0\x02\x02\x04\x06\x02\x00\x02\x02\x00\x00\x06\x02\x01\x01\x00\x63\x04" + "\x04\x02\x04\x02\x01\xA0\x00\x04\x01" + "\x01\x00\x01\x00\x01\x00\x02\xF0\x00\x02\x06\x50\x0A\x02\x01\x07\x01\x02\x04\x00\x04\x04\x00" + "\xF0\x03\x00\x01\x00\x06\x04\x00\x01" + "\x00\x02\x04\x00\x03\x06\x02\xF0\x01\x00\x03\x03\x01\x00\x00\x01\x01\x01\x03\x00\x02\x01\x01" + "\x63\x03\x02\x00\x03\x00\x02\xA0\x00" + "\x04\x06\x02\x02\x01\x04\x04\x00\x05\xF0\x02\x00\x00\x34\x2A\x04\x02\x00\x02\x02\x03\x00\x01" + "\x03\x06\xF0\x06\x02\x02\x00\x03\x03" + "\x02\x06\x02\x01\x00\x00\x01\x00\x00\xF0\x00\x01\x02\x04\x01\x01\x02\x00\x00\x02\x01\x02\x00" + "\x01\x03\xF0\x01\x00\x06\x00\x03\x01" + "\x02\x02\x00\x02\x01\x00\x04\x00\x01\x40\x01\x00\x01\x01\x03\xF0\x08\x54\x14\x10\x10\x10\x0A" + "\x0C\x0C\x08\x0A\x08\x06\x02\x06\xF0" + "\x02\x06\x02\x06\x01\x05\x01\x00\x00\x02\x05\x03\x00\x02\x02\xF0\x00\x06\x04\x00\x00\x00\x01" + "\x00\x03\x00\x03\x00\x02\x00\x03\xF0" + "\x02\x02\x00\x00\x00\x01\x02\x05\x01\x00\x00\x02\x00\x00\x00\x10\x04\x04\xF0\x52\x92\x8E\x88" + "\x78\x72\x6C\x62\x58\x52\x4C\x4C\x48" + "\x38\x30\xF0\x22\x16\x0E\x10\x12\x12\x06\x06\x06\x04\x02\x01\x00\x03\x01\xF0\x03\x05\x05\x01" + "\x01\x00\x06\x02\x00\x02\x06\x00\x00" + "\x00\x03\xF0\x00\x00\x01\x01\x02\x00\x02\x02\x03\x04\x04\x02\x00\x02\x04\x03\xF0\x02\x0A\x40" + "\x52\x5C\x68\x72\x7A\x82\x92\x94\x94" + "\x94\x90\x8E\xF0\x8A\x82\x80\x70\x60\x56\x3E\x30\x1A\x14\x14\x10\x0C\x0A\x0A\xF0\x02\x00\x04" + "\x02\x00\x02\x02\x01\x00\x00\x04\x05" + "\x02\x00\x02\xF0\x02\x01\x00\x00\x02\x00\x01\x02\x00\x02\x01\x00\x01\x02\x02\x10\x00\x39\x00" + "\x01\x00\xF0\x01\x02\x06\x0A\x16\x26" + "\x34\x48\x5A\x68\x72\x7C\x86\x94\xA0\xF0\x9A\x80\x64\x3C\x18\x14\x10\x08\x04\x08\x02\x02\x01" + "\x00\x02\xF0\x01\x01\x02\x02\x02\x01" + "\x00\x00\x02\x00\x01\x01\x03\x02\x01\x70\x04\x00\x03\x00\x01\x01\x01\x04\x5A\x01\x00\x00\x01" + "\x00\xF0\x01\x02\x04\x0C\x10\x1E\x26" + "\x30\x3E\x5A\x7C\xAA\xC2\xA2\x82\xF0\x62\x4A\x2C\x20\x0E\x10\x06\x02\x01\x03\x00\x03\x03\x02" + "\x02\xF0\x02\x03\x01\x01\x01\x02\x01" + "\x01\x01\x02\x04\x04\x01\x05\x00\x33\x00\x02\x00\x24\x01\x00\x29\x02\x00\x53\x02\x00\x00\x00" + "\x02\xF0\x0E\x34\x5E\x84\x90\x8C\x82" + "\x76\x5A\x3C\x1E\x12\x0C\x06\x06\xF0\x02\x00\x00\x00\x02\x00\x00\x02\x02\x00\x06\x03\x03\x01" + "\x05\x30\x02\x08\x00\x03\x3A\x01\x02" + "\x00\x53\x01\x00\x02\x02\x00\x28\x01\x00\xF0\x04\x12\x32\x4E\x66\x7A\x8E\x96\x7A\x44\x16\x0E" + "\x0C\x04\x02\x33\x00\x00\x02\x90\x04" + "\x02\x04\x02\x02\x04\x02\x01\x02\x55\x00\x01\x01\x02\x00\xA8\x01\x01\x01\x00\x00\x01\x00\x01" + "\x01\x00\xF0\x01\x01\x00\x00\x02\x00" + "\x00\x02\x02\x00\x04\x10\x20\x36\x66\xF0\xA4\xB8\x88\x54\x36\x1E\x10\x0A\x04\x01\x00\x01\x05" + "\x09\x05\x60\x01\x01\x05\x03\x02\x01" + "\xF0\x03\x03\x05\x03\x00\x00\x02\x02\x00\x01\x02\x02\x02\x00\x00\x54\x02\x00\x00\x02\x00\x94" + "\x02\x00\x00\x00\x02\x02\x00\x01\x00" + "\xF0\x02\x02\x00\x01\x02\x02\x02\x1E\x58\x8A\x92\x7C\x62\x3C\x1A\xC0\x12\x0A\x06\x02\x02\x06" + "\x02\x01\x00\x00\x01\x01\xF0\x93\x93" + "\x93\x95\x91\x85\x79\x6F\x5D\x4F\x45\x3B\x31\x23\x0F\x8D\x01\x02\x02\x00\x00\x01\x01\x00\x13" + "\x01\xF0\x00\x01\x01\x00\x00\x00\x08" + "\x24\x52\x72\x8C\x98\x68\x30\x0E\x90\x0C\x08\x00\x00\x02\x01\x04\x04\x02\xF0\x4F\x4D\x4D\x4B" + "\x55\x63\x6F\x75\x87\x8D\x95\x9F\xA5" + "\xB1\xC3\xF0\xC3\xA1\x85\x6B\x53\x37\x29\x21\x17\x0B\x01\x00\x00\x00\x01\x49\x01\x02\x01\x00" + "\x14\x01\xF0\x00\x0C\x20\x3A\x78\xAC" + "\x9E\x5E\x2E\x16\x04\x01\x06\x00\x03\x10\x01\xF0\x07\x09\x09\x0D\x07\x07\x07\x0B\x0B\x11\x11" + "\x0D\x17\x17\x19\xF0\x21\x45\x5F\x75" + "\x87\x9B\x99\x8D\x83\x73\x61\x43\x29\x0D\x00\xC6\x00\x00\x02\x00\x00\x00\x02\x02\x00\x00\x02" + "\x00\xF0\x02\x01\x00\x02\x02\x0C\x40" + "\x80\x86\x6C\x44\x22\x12\x04\x02\x10\x04\xF0\x01\x03\x02\x04\x01\x01\x01\x00\x01\x01\x00\x05" + "\x00\x01\x05\xF0\x09\x09\x0B\x11\x11" + "\x15\x25\x39\x4B\x61\x77\x81\x89\x8D\x81\xE4\x53\x2B\x07\x00\x01\x01\x00\x00\x02\x02\x00\x00" + "\x00\x02\x34\x00\x02\x00\x90\x08\x36" + "\x66\x8A\x8A\x40\x16\x0C\x04\xF0\x00\x02\x01\x01\x01\x02\x04\x02\x02\x02\x03\x01\x01\x01\x04" + "\x33\x01\x00\x03\xF0\x05\x07\x09\x09" + "\x11\x25\x2F\x4D\x61\x8B\xAB\xC5\xA3\x71\x3F\x44\x1F\x0B\x01\x00\x66\x01\x01\x00\x00\x01\x00" + "\x80\x02\x06\x1E\x46\x9C\xB0\x70\x40" + "\xF0\x01\x00\x02\x02\x04\x02\x01\x05\x01\x00\x03\x00\x00\x00\x01\xF0\x02\x02\x08\x0A\x02\x01" + "\x02\x02\x01\x05\x03\x03\x0B\x05\x09" + "\xF0\x0D\x15\x19\x41\x69\x93\x89\x71\x51\x2D\x09\x02\x02\x00\x02\x2A\x02\x00\x70\x02\x00\x02" + "\x04\x26\x70\x8C"; + +static const BYTE TEST_64X64_GREEN_PLANE[4096] = + "\x2A\x25\x23\x23\x23\x23\x23\x24\x2B\x60\xA2\x97\x84\x75\x62\x50\x3C\x34\x30\x2C\x29\x28\x26" + "\x25\x25\x23\x23\x25\x24\x24\x25\x25" + "\x25\x25\x26\x27\x25\x24\x25\x25\x27\x24\x24\x24\x24\x24\x25\x25\x24\x25\x26\x25\x23\x24\x24" + "\x25\x23\x24\x24\x24\x24\x22\x23\x21" + "\x51\x37\x31\x2C\x29\x28\x27\x27\x2F\x73\xC9\xCC\xCB\xC5\xBD\xB5\xAA\x99\x80\x66\x48\x38\x33" + "\x2E\x29\x27\x27\x26\x25\x25\x23\x23" + "\x26\x26\x25\x27\x25\x25\x25\x26\x23\x24\x23\x25\x24\x23\x26\x25\x25\x25\x25\x24\x23\x25\x23" + "\x24\x24\x24\x25\x24\x26\x24\x24\x24" + "\xBA\xB3\x97\x79\x58\x48\x3A\x31\x37\x79\xCA\xCC\xCD\xCD\xCD\xCD\xCC\xCC\xCC\xCC\xCC\xBC\x9D" + "\x7A\x5C\x4C\x3D\x30\x2B\x29\x28\x27" + "\x25\x24\x24\x23\x25\x26\x25\x25\x23\x24\x27\x27\x24\x24\x24\x24\x25\x25\x25\x25\x26\x27\x25" + "\x23\x23\x23\x25\x24\x22\x24\x26\x26" + "\xBB\xBE\xC0\xC2\xBC\xA7\x90\x78\x63\x87\xCB\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xC2\xAD\x96\x7E\x63\x48\x36\x2C" + "\x27\x27\x24\x24\x25\x24\x25\x24\x22\x25\x26\x26\x23\x23\x23\x23\x27\x26\x25\x26\x26\x26\x24" + "\x24\x24\x24\x23\x22\x25\x24\x24\x24" + "\xBB\xBE\xC0\xC2\xC5\xC7\xC8\xC3\xBA\xBD\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCD\xCD\xCD\xC8\xBD\xB0\x93\x65" + "\x3A\x33\x2A\x28\x27\x24\x23\x25\x24\x24\x23\x24\x24\x22\x21\x24\x24\x23\x23\x25\x26\x25\x25" + "\x23\x23\x25\x24\x24\x26\x25\x24\x24" + "\xBB\xBE\xC0\xC3\xC4\xC7\xC8\xC8\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCC" + "\xBE\x90\x62\x42\x30\x28\x27\x22\x23\x24\x25\x25\x25\x25\x24\x24\x25\x25\x24\x26\x24\x24\x23" + "\x23\x25\x25\x27\x29\x24\x24\x25\x26" + "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC" + "\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCC\xC0\x9D\x6A\x3F\x2E\x27\x24\x24\x24\x24\x25\x24\x24\x24\x25\x24\x25\x27\x24\x25\x25" + "\x24\x25\x25\x26\x25\x23\x23\x25\x26" + "\xBB\xBE\xC0\xC3\xC4\xC7\xC8\xC9\xCB\xCA\xCC\xCB\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCC" + "\xCC\xCD\xCC\xCC\xC1\xA8\x92\x86\x99" + "\xB9\xCB\xCD\xCD\xBF\x8F\x47\x2E\x26\x25\x24\x25\x24\x26\x25\x25\x24\x25\x25\x24\x23\x24\x26" + "\x24\x25\x27\x25\x24\x24\x23\x23\x24" + "\xBB\xBE\xC0\xC3\xC4\xC7\xC8\xC9\xCA\xCA\xCC\xCB\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCA\xB6\x83\x48\x3B\x39\x3B\x3B" + "\x40\x58\x9B\xC5\xCD\xCD\xA9\x52\x2B\x26\x26\x27\x25\x23\x26\x24\x24\x25\x25\x23\x25\x23\x22" + "\x23\x25\x24\x24\x24\x24\x22\x23\x25" + "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC" + "\xCD\xA8\x5D\x3D\x3D\x5F\x71\x79\x6C" + "\x50\x3D\x47\x7A\xC5\xCD\xCC\x98\x37\x2A\x26\x24\x25\x25\x27\x25\x24\x24\x25\x26\x25\x23\x23" + "\x22\x23\x23\x26\x26\x24\x24\x24\x23" + "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC" + "\xC3\x59\x3B\x50\x92\xBB\xC4\xC7\xC1" + "\xAE\x75\x40\x3D\x8C\xCC\xCD\xC8\x65\x30\x28\x25\x27\x26\x26\x25\x24\x25\x25\x26\x24\x25\x25" + "\x25\x24\x23\x24\x25\x24\x24\x24\x23" + "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xC7" + "\x70\x3B\x59\xAE\xCC\xCD\xCC\xC9\xC4" + "\xCB\xCA\x8A\x41\x4C\xA1\xCC\xCD\xB0\x45\x2B\x23\x25\x25\x26\x24\x23\x25\x24\x24\x24\x25\x24" + "\x23\x26\x25\x23\x24\x24\x24\x23\x24" + "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xAF" + "\x50\x42\x90\xCA\xCD\xCB\xB0\x84\x74" + "\x8B\xB7\xC0\x64\x3C\x76\xC7\xCD\xC3\x74\x31\x26\x25\x25\x26\x26\x24\x25\x25\x25\x24\x26\x26" + "\x23\x23\x26\x24\x26\x25\x24\x23\x23" + "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\x98" + "\x44\x51\xB4\xCD\xCD\xC0\x73\x3E\x38" + "\x42\x83\xC8\x8F\x3A\x66\xBB\xCD\xCB\x9C\x42\x29\x27\x26\x24\x25\x26\x27\x26\x23\x26\x28\x25" + "\x22\x24\x25\x24\x26\x25\x24\x24\x23" + "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCD\xCD\x80" + "\x39\x58\xC7\xCD\xCC\xC9\x96\x5B\x51" + "\x67\xA3\xCD\xAC\x3D\x58\xAF\xCD\xCD\xBF\x69\x30\x25\x23\x25\x25\x26\x25\x25\x25\x26\x26\x26" + "\x24\x24\x25\x23\x24\x22\x23\x24\x24" + "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCD\xCC\xCD\x8C" + "\x40\x55\xBE\xCD\xCC\xCC\xC9\xBA\xAD" + "\xC0\xCC\xCD\xA5\x3B\x5A\xB1\xCD\xCD\xCC\x8A\x3B\x28\x24\x25\x25\x23\x24\x25\x26\x25\x26\x24" + "\x25\x25\x24\x24\x24\x24\x23\x24\x25" + "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\x9E" + "\x47\x4A\xA9\xCD\xCD\xCD\xCD\xCC\xCC" + "\xCE\xCD\xCD\x80\x3B\x6A\xBF\xCD\xCD\xCD\xAC\x4B\x2A\x26\x25\x26\x23\x26\x25\x25\x23\x24\x25" + "\x25\x25\x25\x25\x26\x24\x23\x24\x22" + "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCB\xCB\xCC\xCC\xCA\xC1\xC7\xCC\xCD\xCD\xCD\xCD\xCC\xCD\xBA" + "\x5E\x39\x6B\xBE\xCC\xCD\xCD\xCD\xCD" + "\xCD\xCB\xA8\x4D\x3F\x86\xCC\xCD\xCD\xCD\xC9\x63\x2D\x27\x26\x23\x22\x24\x26\x25\x25\x25\x23" + "\x23\x24\x23\x26\x26\x24\x25\x23\x24" + "\xBA\xBD\xC0\xC2\xC4\xC6\xC8\xC8\xCA\xCB\xCB\xCC\xC8\x9E\x8B\xB5\xCD\xCD\xCD\xCD\xCC\xCC\xCD" + "\x9A\x3B\x42\x7C\xBB\xCD\xCD\xCD\xCD" + "\xC9\xA9\x5F\x3A\x5D\xB4\xCD\xCD\xCC\xCC\xCD\x8C\x30\x27\x26\x23\x22\x23\x25\x25\x23\x23\x25" + "\x23\x24\x24\x25\x23\x25\x25\x24\x24" + "\xBB\xBE\xC0\xC3\xC4\xC6\xC8\xC9\xCA\xCB\xCB\xCC\xCC\xAA\x5E\x59\x99\xC8\xCD\xCD\xCD\xCD\xCD" + "\xC8\x6C\x3A\x3F\x5F\x94\xB1\xB9\xAA" + "\x80\x50\x3A\x40\xA7\xCC\xCD\xCD\xCD\xCD\xCD\xB6\x38\x2A\x25\x23\x24\x26\x26\x25\x26\x24\x23" + "\x22\x24\x24\x24\x23\x24\x24\x24\x24" + "\xBB\xBE\xC0\xC3\xC5\xC7\xC8\xC9\xCA\xCB\xCB\xCC\xCD\xC7\x7B\x3B\x37\x5B\x9E\xBF\xCC\xCD\xCD" + "\xCD\xC4\x89\x4D\x37\x39\x3A\x3C\x3B" + "\x39\x41\x60\xA2\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\x52\x2C\x27\x24\x22\x25\x24\x23\x24\x25\x25" + "\x24\x22\x24\x26\x25\x24\x23\x23\x24" + "\xBC\xBF\xC1\xC4\xC5\xC7\xC8\xC9\xCA\xCB\xCB\xCC\xCD\xCD\xA4\x4B\x30\x32\x40\x67\x9B\xC0\xCD" + "\xCD\xCD\xC8\xA5\x7D\x62\x4C\x49\x57" + "\x6B\x8D\xBB\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCA\xAC\x4A\x2A\x25\x25\x23\x25\x25\x25\x26\x26\x24" + "\x25\x25\x25\x24\x25\x23\x25\x25\x25" + "\xBC\xBF\xC1\xC4\xC5\xC7\xC8\xC9\xCA\xCB\xCC\xCC\xCD\xCD\xC6\x82\x3C\x34\x2F\x30\x42\x63\x98" + "\xC6\xCD\xCD\xCC\xC7\xBC\xB2\xB0\xB7" + "\xC0\xC9\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xBC\x81\x4E\x2C\x27\x27\x25\x23\x24\x26\x25\x25\x26\x25" + "\x26\x25\x25\x23\x24\x25\x24\x24\x25" + "\xBD\xC0\xC2\xC4\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCC\xCD\xCD\xCD\xCC\xBA\x85\x59\x40\x31\x31\x36" + "\x4B\x82\xA8\xBC\xC9\xCD\xCD\xCD\xCD" + "\xCC\xCC\xCC\xCD\xCD\xCD\xC8\xB8\x82\x47\x34\x2B\x27\x25\x26\x25\x24\x24\x26\x26\x25\x25\x25" + "\x23\x23\x25\x25\x27\x24\x24\x24\x26" + "\xBE\xC0\xC3\xC4\xC6\xC8\xC8\xC9\xCA\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCB\xBA\x94\x64\x3F\x33" + "\x32\x35\x43\x61\x81\x9F\xB0\xC0\xCB" + "\xCD\xCD\xCC\xC9\xB7\x9B\x7E\x5A\x36\x34\x37\x3D\x29\x25\x25\x26\x24\x25\x24\x25\x24\x23\x24" + "\x22\x22\x25\x25\x27\x23\x25\x25\x25" + "\xBF\xC1\xC3\xC5\xC6\xC8\xC8\xCA\xCA\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xBD\xA2\x72" + "\x47\x36\x34\x33\x34\x43\x4D\x5A\x67" + "\x73\x6B\x66\x61\x53\x42\x35\x34\x3B\x5F\x95\x97\x2E\x25\x24\x25\x25\x25\x24\x24\x25\x24\x25" + "\x23\x23\x24\x25\x26\x22\x25\x25\x25" + "\xC0\xC2\xC4\xC5\xC7\xC8\xC9\xCA\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCC\xCD" + "\xC7\x9E\x75\x57\x40\x33\x33\x32\x32" + "\x31\x32\x34\x34\x37\x42\x55\x74\xB2\xCC\xCC\x90\x32\x25\x24\x25\x23\x24\x26\x23\x25\x25\x25" + "\x24\x24\x21\x21\x24\x23\x25\x25\x26" + "\xC0\xC2\xC4\xC6\xC7\xC8\xC9\xCA\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCD\xCA\xB8\x9C\x83\x76\x66\x56" + "\x4A\x51\x5E\x6B\x83\x9B\xB5\xC9\xCD\xCD\xC5\x66\x2E\x26\x25\x25\x25\x25\x25\x23\x25\x25\x25" + "\x24\x23\x22\x24\x24\x26\x27\x25\x25" + "\xC1\xC3\xC4\xC6\xC8\xC8\xC9\xCA\xCB\xCC\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCD\xCD\xCD\xCD\xCB\xC5\xBE\xB7" + "\xB2\xB5\xBB\xC1\xCA\xCD\xCD\xCD\xCD\xCD\x9C\x44\x27\x25\x26\x26\x22\x23\x25\x24\x24\x23\x25" + "\x25\x24\x24\x25\x24\x24\x25\x25\x24" + "\xC2\xC4\xC5\xC7\xC8\xC9\xCA\xCA\xCB\xCC\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC" + "\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xBC\x8C\x48\x2D\x25\x24\x24\x22\x25\x26\x24\x24\x25\x24\x25" + "\x24\x22\x25\x26\x24\x25\x24\x26\x26" + "\xC3\xC4\xC6\xC8\xC8\xC9\xCA\xCB\xCB\xCC\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC" + "\xCD\xCD\xCD\xCD\xCC\xCC\xBE\x9A\x63\x39\x2B\x26\x23\x24\x24\x26\x24\x25\x25\x23\x23\x25\x25" + "\x25\x25\x25\x24\x24\x25\x24\x23\x24" + "\xC4\xC5\xC7\xC8\xC8\xC9\xCA\xCB\xCB\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCC\xCD\xCD\xCD\xBE\x8C\x5D\x40\x2C\x2A\x29\x26\x22\x23\x24\x25\x24\x24\x26\x25\x24\x23\x23" + "\x23\x22\x23\x23\x24\x23\x25\x24\x24" + "\xC4\xC6\xC7\xC8\xC9\xC9\xC9\xCA\xCC\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCE\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCD\xCC\xCC\xCD\xCD\xCC\xCC\xC7" + "\xBC\xAF\x92\x67\x3B\x32\x2D\x28\x26\x25\x26\x25\x25\x26\x25\x25\x27\x25\x25\x25\x23\x23\x24" + "\x23\x24\x25\x25\x25\x23\x25\x26\x24" + "\xC5\xC7\xC8\xC8\xC8\xBD\xAD\xA7\xA1\xA0\xA7\xB0\xB9\xC0\xC6\xCB\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCD\xCB\xC4\xBA\xAF\x9F\x8B\x78" + "\x61\x48\x35\x30\x2B\x27\x26\x25\x24\x24\x23\x24\x24\x24\x24\x26\x24\x23\x24\x27\x24\x23\x24" + "\x24\x24\x24\x22\x24\x25\x25\x26\x24" + "\xC6\xC8\xC8\xC8\x8C\x5E\x4D\x49\x42\x44\x47\x4E\x54\x58\x5C\x63\x6F\x7B\x87\x91\x8E\x8A\x86" + "\x81\x75\x67\x5C\x55\x4E\x41\x34\x31" + "\x2C\x2B\x29\x25\x28\x26\x25\x24\x26\x25\x25\x25\x24\x25\x25\x26\x25\x25\x24\x25\x25\x24\x24" + "\x24\x25\x23\x21\x23\x25\x25\x27\x25" + "\xC7\xC8\xC3\x7B\x35\x2B\x26\x26\x24\x27\x28\x29\x29\x29\x2C\x2D\x2D\x2D\x2D\x30\x2F\x2F\x30" + "\x2F\x2C\x2D\x2B\x29\x27\x26\x26\x26" + "\x26\x24\x24\x24\x24\x24\x24\x25\x25\x25\x27\x26\x24\x24\x24\x25\x24\x24\x26\x25\x26\x23\x26" + "\x24\x24\x25\x24\x25\x22\x23\x25\x25" + "\xC8\xC8\xA4\x49\x29\x25\x22\x22\x24\x25\x26\x25\x24\x25\x25\x24\x25\x26\x27\x25\x26\x26\x26" + "\x25\x25\x25\x26\x26\x25\x23\x24\x25" + "\x25\x23\x25\x26\x27\x25\x24\x25\x26\x27\x27\x25\x22\x22\x23\x26\x24\x24\x24\x25\x26\x24\x26" + "\x25\x23\x25\x25\x25\x25\x25\x26\x24" + "\xC8\xC8\x80\x36\x26\x23\x22\x23\x23\x24\x27\x27\x24\x24\x25\x24\x24\x27\x26\x24\x24\x25\x25" + "\x24\x25\x24\x25\x25\x24\x25\x25\x24" + "\x25\x25\x25\x24\x23\x24\x25\x24\x24\x25\x23\x23\x24\x25\x24\x25\x24\x25\x27\x27\x24\x24\x26" + "\x26\x24\x26\x27\x25\x25\x25\x24\x26" + "\xC9\xC1\x6C\x30\x25\x22\x23\x23\x23\x24\x25\x26\x24\x24\x25\x26\x26\x27\x26\x26\x24\x24\x25" + "\x24\x24\x24\x26\x26\x25\x25\x25\x25" + "\x24\x26\x23\x23\x25\x24\x25\x25\x23\x24\x23\x24\x25\x23\x24\x23\x25\x25\x24\x24\x24\x23\x24" + "\x25\x26\x25\x25\x25\x24\x25\x24\x24" + "\xC9\xBE\x68\x2E\x24\x23\x25\x23\x23\x24\x24\x24\x24\x25\x25\x26\x27\x25\x25\x25\x24\x23\x23" + "\x24\x25\x25\x26\x25\x25\x25\x26\x26" + "\x25\x24\x22\x23\x26\x25\x24\x24\x25\x25\x25\x25\x24\x23\x23\x25\x25\x26\x25\x25\x24\x24\x23" + "\x23\x23\x24\x23\x25\x24\x25\x24\x24" + "\xCA\xBB\x66\x2D\x22\x23\x23\x24\x23\x24\x25\x25\x27\x26\x24\x23\x24\x25\x27\x24\x22\x24\x25" + "\x24\x25\x27\x25\x24\x26\x26\x26\x28" + "\x25\x25\x25\x25\x23\x25\x26\x25\x25\x24\x24\x21\x22\x23\x25\x25\x26\x24\x24\x27\x24\x26\x25" + "\x26\x24\x25\x24\x24\x25\x25\x25\x23" + "\xCB\xC0\x6A\x2D\x25\x25\x26\x24\x25\x25\x25\x24\x27\x27\x26\x26\x24\x25\x25\x25\x23\x25\x25" + "\x26\x26\x25\x25\x26\x24\x26\x25\x25" + "\x24\x23\x24\x23\x23\x23\x25\x24\x25\x23\x23\x22\x25\x25\x27\x24\x24\x26\x23\x23\x26\x24\x26" + "\x25\x24\x23\x23\x24\x25\x23\x25\x26" + "\xCB\xC6\x71\x2F\x25\x23\x25\x25\x23\x24\x25\x26\x24\x25\x24\x25\x26\x24\x24\x23\x25\x25\x25" + "\x24\x26\x25\x25\x26\x24\x26\x24\x25" + "\x25\x23\x24\x25\x23\x23\x25\x26\x25\x24\x24\x24\x26\x26\x25\x26\x22\x23\x23\x25\x23\x24\x25" + "\x25\x24\x24\x25\x25\x24\x25\x25\x24" + "\xCB\xCB\x87\x39\x25\x22\x24\x26\x25\x24\x24\x27\x24\x24\x24\x20\x23\x25\x24\x26\x24\x24\x24" + "\x24\x25\x23\x24\x25\x25\x28\x26\x26" + "\x27\x27\x26\x25\x25\x26\x26\x25\x23\x24\x24\x24\x24\x24\x25\x25\x23\x24\x23\x25\x22\x24\x27" + "\x24\x24\x26\x25\x24\x26\x25\x24\x23" + "\xCC\xCC\xA6\x47\x25\x26\x26\x25\x25\x22\x24\x25\x24\x25\x25\x24\x23\x22\x23\x26\x24\x23\x25" + "\x23\x24\x25\x25\x24\x23\x24\x24\x23" + "\x23\x26\x23\x22\x24\x25\x25\x26\x24\x23\x24\x24\x23\x23\x25\x23\x26\x24\x25\x24\x26\x25\x24" + "\x22\x26\x25\x24\x25\x23\x25\x25\x23" + "\xCC\xCC\xBE\x52\x2A\x26\x24\x25\x24\x26\x24\x23\x24\x24\x25\x25\x25\x25\x25\x29\x25\x24\x26" + "\x24\x24\x25\x26\x24\x25\x25\x24\x23" + "\x22\x22\x24\x24\x23\x25\x25\x24\x25\x24\x26\x27\x26\x24\x22\x23\x24\x25\x25\x25\x24\x25\x25" + "\x23\x24\x23\x23\x26\x23\x25\x25\x25" + "\xCC\xCC\xCA\x6C\x2F\x28\x25\x27\x25\x25\x24\x25\x25\x24\x22\x24\x25\x24\x25\x23\x24\x24\x25" + "\x24\x24\x25\x26\x25\x24\x26\x25\x24" + "\x25\x24\x26\x24\x24\x25\x25\x24\x27\x25\x25\x26\x26\x26\x25\x25\x26\x26\x24\x26\x24\x24\x24" + "\x25\x23\x24\x24\x24\x24\x23\x24\x25" + "\xCC\xCD\xCD\xA3\x35\x29\x25\x25\x26\x25\x26\x27\x25\x26\x23\x22\x25\x24\x24\x26\x26\x24\x25" + "\x25\x25\x25\x25\x23\x26\x27\x24\x24" + "\x22\x24\x24\x24\x24\x24\x24\x23\x23\x26\x26\x25\x25\x24\x25\x25\x24\x27\x25\x25\x24\x24\x24" + "\x26\x25\x24\x24\x24\x25\x24\x24\x23" + "\xCD\xCD\xCD\xC8\x53\x2E\x25\x23\x25\x26\x26\x26\x24\x26\x27\x25\x25\x25\x25\x24\x24\x25\x27" + "\x25\x24\x25\x25\x23\x26\x25\x24\x23" + "\x23\x25\x23\x23\x25\x24\x24\x25\x23\x26\x25\x25\x24\x22\x25\x26\x24\x25\x25\x25\x25\x24\x24" + "\x26\x25\x26\x25\x23\x24\x25\x24\x23" + "\xCD\xCD\xCD\xCD\x8E\x3C\x32\x30\x2F\x2F\x2D\x2E\x2B\x2A\x2B\x2A\x29\x29\x28\x27\x25\x28\x28" + "\x24\x23\x25\x25\x23\x22\x23\x24\x25" + "\x24\x25\x26\x25\x25\x24\x24\x23\x23\x24\x25\x23\x23\x23\x25\x24\x25\x25\x25\x24\x25\x23\x25" + "\x22\x24\x25\x25\x24\x24\x24\x23\x24" + "\xCD\xCD\xCD\xCD\xC5\xA2\x94\x8D\x87\x7F\x79\x71\x68\x65\x60\x5E\x5A\x51\x48\x41\x35\x31\x31" + "\x2E\x2D\x2A\x2A\x27\x26\x25\x24\x27" + "\x26\x24\x25\x24\x23\x23\x23\x23\x26\x24\x27\x26\x26\x24\x25\x24\x26\x26\x24\x24\x24\x24\x25" + "\x24\x25\x23\x26\x25\x26\x25\x26\x26" + "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xC9\xC6\xBF\xB4\xA8\x9C\x8D\x82\x76" + "\x6A\x5A\x4B\x3D\x35\x32\x2F\x2C\x2B" + "\x29\x26\x25\x24\x25\x23\x24\x26\x25\x25\x25\x25\x24\x24\x24\x24\x24\x25\x24\x24\x25\x24\x26" + "\x26\x26\x26\x25\x26\x26\x26\x25\x25" + "\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCA\xC5" + "\xC0\xB9\xB3\xAC\xA1\x8E\x76\x56\x3D" + "\x36\x31\x2A\x28\x29\x26\x27\x26\x25\x26\x24\x25\x24\x25\x26\x24\x25\x25\x26\x24\x24\x23\x23" + "\x26\x26\x26\x26\x24\x25\x24\x24\x24" + "\xCE\xCC\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCD\xCD\xCD" + "\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCC\xC6" + "\xA9\x8C\x6D\x5B\x49\x3D\x32\x2E\x2B\x28\x25\x23\x24\x24\x23\x26\x26\x25\x24\x23\x24\x23\x24" + "\x24\x26\x26\x26\x25\x26\x24\x21\x24" + "\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCD\xCD\xCD" + "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCC\xCD\xCB\xC0\xAB\x97\x82\x6E\x54\x3C\x30\x2C\x29\x27\x25\x27\x25\x25\x24\x23\x23\x24\x24" + "\x24\x26\x24\x25\x24\x23\x24\x25\x24" + "\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCC\xCD\xCD\xCC\xCD\xCD\xCA\xC2\xB6\xA7\x86\x5A\x38\x31\x2C\x28\x26\x25\x24\x24\x24\x25\x26" + "\x26\x29\x25\x25\x26\x27\x25\x25\x26" + "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCC\xCD\xCD\xCD\xCD" + "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCC\xBB\x91\x68\x4F\x3A\x30\x2A\x28\x26\x25\x24" + "\x23\x24\x24\x25\x26\x23\x23\x26\x25" + "\xCB\xCA\xC9\xCB\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCC\xCD\xCD\xCD\xCD" + "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xC8\xB4\x93\x76\x57\x3B\x30\x2D\x28" + "\x25\x25\x25\x25\x25\x22\x23\x24\x25" + "\x64\x62\x61\x62\x68\x71\x7B\x81\x8C\x94\x9C\xA3\xAB\xB5\xC3\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCD\xCD\xCD\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCC\xC5\xB8\xA5\x79\x4C\x34" + "\x2E\x26\x25\x25\x27\x21\x26\x26\x25" + "\x2C\x2B\x28\x2C\x2C\x2B\x2B\x2F\x2E\x31\x33\x34\x37\x38\x3B\x43\x5B\x70\x83\x95\xA7\xAF\xB6" + "\xBC\xC5\xCB\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCC\xCD\xCD\xCD\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCD\xCD\xCD\xCC\xC6\xA1" + "\x6E\x4A\x35\x29\x24\x27\x26\x24\x24" + "\x26\x25\x23\x23\x26\x25\x26\x25\x25\x24\x28\x2A\x26\x28\x29\x2C\x2B\x2E\x30\x33\x37\x44\x53" + "\x61\x74\x88\x9D\xB1\xC4\xCD\xCD\xCD" + "\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCD\xCC\xCC\xCC\xCC\xCC\xCC\xCC\xCD\xCC\xCD\xCD\xCD\xCD\xCD" + "\xC9\xA7\x81\x58\x39\x30\x2A\x26\x25" + "\x25\x24\x26\x26\x24\x24\x23\x24\x23\x24\x26\x28\x25\x26\x25\x26\x27\x25\x26\x27\x28\x28\x2A" + "\x2C\x31\x35\x42\x50\x61\x74\x92\xAF" + "\xC9\xCD\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCC\xCC\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCE\xCD\xCD" + "\xCD\xCC\xC8\xB8\x9B\x5E\x39\x2F\x2A" + "\x24\x23\x23\x24\x23\x24\x25\x26\x25\x25\x25\x25\x24\x24\x25\x24\x24\x24\x22\x25\x25\x24\x24" + "\x24\x27\x27\x28\x2C\x2B\x2C\x31\x36" + "\x3F\x5A\x7D\xA0\xB8\xC5\xCC\xCD\xCD\xCC\xCC\xCD\xCC\xCC\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCD\xCD\xCD\xCC\xCA\xB2\x7D\x55" + "\x22\x23\x25\x25\x25\x25\x25\x24\x24\x24\x23\x24\x25\x24\x24\x24\x25\x26\x26\x25\x24\x25\x25" + "\x25\x24\x24\x25\x26\x25\x26\x27\x29" + "\x2A\x2C\x31\x38\x57\x74\x91\xAD\xC7\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCC\xCD\xCD\xCD\xCD\xCD\xCD" + "\xCD\xCD\xCD\xCD\xCD\xCD\xCD\xCB\xB7"; + +static const BYTE TEST_64X64_GREEN_PLANE_RLE[3696] = + "\x34\x2A\x25\x23\xF0\x24\x2B\x60\xA2\x97\x84\x75\x62\x50\x3C\x34\x30\x2C\x29\x28\x93\x26\x25" + "\x25\x23\x23\x25\x24\x24\x25\x84\x26" + "\x27\x25\x24\x25\x25\x27\x24\xC3\x25\x25\x24\x25\x26\x25\x23\x24\x24\x25\x23\x24\x30\x22\x23" + "\x21\xF0\x4E\x24\x1C\x12\x0C\x0A\x08" + "\x06\x08\x26\x4E\x6A\x8E\xA0\xB6\xF0\xCA\xDC\xCA\xA0\x74\x3E\x20\x1A\x12\x08\x08\x08\x02\x02" + "\x02\xF0\x03\x03\x02\x02\x01\x00\x00" + "\x02\x00\x02\x07\x00\x01\x02\x00\xF0\x01\x02\x00\x02\x00\x01\x01\x00\x02\x01\x01\x02\x00\x02" + "\x00\x40\x04\x04\x02\x06\xF0\xD2\xF8" + "\xCC\x9A\x5E\x40\x26\x14\x10\x0C\x02\x00\x04\x10\x20\xF0\x30\x44\x66\x98\xCC\xF7\xF7\xD4\x98" + "\x66\x4A\x2C\x14\x0C\x08\xF0\x0A\x08" + "\x01\x03\x01\x07\x00\x02\x00\x01\x00\x00\x08\x04\x00\xF0\x02\x03\x01\x00\x00\x00\x02\x06\x04" + "\x04\x01\x01\x01\x00\x00\x40\x07\x00" + "\x04\x04\xC4\x02\x16\x52\x92\xC8\xBE\xAC\x8E\x58\x1C\x02\x00\x14\x02\xF0\x22\x60\xA6\xCC\xC2" + "\xB2\x9C\x70\x3E\x1C\x0A\x04\x06\x00" + "\x02\x75\x00\x03\x00\x01\x01\x02\x01\xF0\x04\x02\x00\x02\x00\x01\x01\x02\x02\x02\x03\x03\x06" + "\x00\x03\x10\x03\x04\x8C\x12\x40\x70" + "\x96\xAE\x6C\x02\x00\xF0\x16\x40\x6E\x94\xB4\xD0\xBA\x72\x26\x18\x0C\x08\x04\x00\x03\xF0\x02" + "\x04\x01\x05\x03\x02\x01\x03\x02\x05" + "\x05\x03\x01\x00\x01\xA0\x02\x01\x01\x02\x02\x04\x02\x02\x00\x00\x03\x8D\x02\x01\x00\x00\x0A" + "\x22\x1C\x00\x03\xF0\x0A\x20\x38\x72" + "\xCE\xF7\xBA\x70\x34\x12\x08\x08\x05\x01\x00\xF0\x04\x02\x02\x06\x06\x00\x02\x04\x02\x02\x03" + "\x01\x03\x00\x04\x70\x00\x06\x0A\x03" + "\x01\x02\x04\x04\x5C\x02\x00\x00\x02\x00\x44\x01\x01\x01\x00\xF0\x02\x02\x02\x1E\x78\xBC\xB6" + "\x74\x2E\x0E\x0A\x02\x00\x01\x01\xF0" + "\x00\x01\x00\x00\x00\x01\x02\x02\x00\x02\x04\x02\x00\x00\x01\x50\x07\x01\x01\x00\x00\x04\x23" + "\x01\x00\x47\x01\x00\x01\x00\x23\x01" + "\x00\xF0\x01\x01\x17\x49\x75\x8D\x67\x27\x01\x1A\x60\xAA\xA0\x32\x0E\xF0\x04\x02\x00\x02\x01" + "\x04\x02\x02\x01\x02\x00\x05\x01\x01" + "\x02\x90\x00\x00\x04\x01\x01\x02\x00\x03\x03\x08\x2A\x01\x00\x13\x02\xF0\x05\x2B\x91\xF1\xD9" + "\xB1\x95\xBB\xF1\xE5\x63\x0F\x1C\x7C" + "\xC4\xF0\x48\x0A\x02\x04\x04\x02\x05\x02\x01\x00\x00\x00\x01\x04\x01\xA0\x07\x01\x00\x05\x01" + "\x00\x00\x01\x00\x02\x04\x99\x02\x00" + "\x00\x00\x02\x02\x00\x02\x00\xF0\x01\x00\x43\xB1\x8B\x15\x48\x70\x7C\x62\x20\x35\xA7\x95\x0F" + "\xF0\x00\x46\x8C\x18\x08\x00\x05\x00" + "\x04\x02\x02\x00\x01\x00\x06\xC0\x00\x00\x02\x01\x03\x01\x04\x04\x00\x04\x02\x03\x71\xF0\x13" + "\x9D\x43\x26\xAA\xB8\xA6\x9C\xAA\xBC" + "\x70\x0D\x79\x71\x01\xF0\x02\x60\x5C\x0C\x04\x02\x04\x02\x01\x00\x00\x02\x00\x00\x01\x83\x04" + "\x04\x06\x02\x00\x03\x01\x00\x41\xF0" + "\x01\x00\x09\xA5\x3B\x3C\xBC\x74\x24\x10\x04\x06\x3A\xAA\x94\xF0\x08\x7F\x55\x01\x0A\x96\x2A" + "\x06\x03\x03\x01\x00\x01\x01\x00\xE0" + "\x01\x03\x00\x00\x01\x03\x04\x04\x01\x01\x00\x00\x01\x02\x61\xF0\x2F\x3F\x0E\x6E\x38\x02\x03" + "\x37\x89\x9F\x7F\x25\x6C\x46\x1F\xF0" + "\x55\x09\x00\x26\x5E\x0C\x06\x00\x00\x00\x04\x02\x00\x02\x02\xC0\x00\x02\x04\x00\x05\x02\x02" + "\x04\x02\x00\x00\x01\x41\xF0\x02\x00" + "\x2D\x17\x1E\x48\x06\x00\x15\x79\x8B\x77\x91\x67\x10\xF0\x56\x03\x1F\x17\x00\x10\x50\x22\x06" + "\x04\x02\x03\x01\x04\x04\x93\x02\x03" + "\x04\x04\x01\x01\x02\x01\x00\x20\x02\x00\x21\xF0\x01\x01\x00\x00\x2F\x15\x0E\x26\x00\x01\x12" + "\x46\x3A\x32\x4A\xF0\x40\x0A\x3A\x06" + "\x1B\x17\x00\x04\x46\x4E\x0E\x03\x05\x02\x00\xF0\x00\x03\x01\x04\x00\x03\x02\x04\x00\x00\x01" + "\x03\x05\x01\x00\x10\x02\x11\xF0\x01" + "\x00\x02\x01\x00\x18\x0E\x05\x11\x00\x00\x06\x66\xBE\xB8\xF0\xB2\x52\x00\x0D\x03\x04\x04\x00" + "\x00\x1A\x42\x16\x06\x02\x00\xF0\x00" + "\x05\x01\x00\x02\x01\x00\x03\x02\x02\x01\x02\x00\x04\x00\x20\x00\x02\x11\xF0\x02\x02\x00\x02" + "\x00\x24\x0E\x15\x29\x00\x02\x02\x08" + "\x24\x3E\xF0\x1C\x02\x00\x49\x00\x20\x1C\x00\x00\x02\x44\x20\x04\x04\x00\xF0\x02\x00\x04\x00" + "\x01\x03\x03\x02\x00\x00\x02\x02\x04" + "\x00\x00\x20\x00\x05\x0C\x53\x05\x17\x0B\x01\x00\xF0\x01\x00\x38\x2E\x21\x7B\x1D\x01\x00\x00" + "\x02\x02\x01\x03\x49\xF0\x65\x08\x38" + "\x1A\x00\x00\x00\x3A\x30\x06\x02\x02\x05\x01\x03\xE0\x02\x00\x04\x02\x03\x03\x01\x03\x02\x00" + "\x00\x04\x01\x04\xF0\x01\x01\x00\x01" + "\x01\x01\x00\x01\x01\x00\x01\x00\x03\x45\x77\x24\x2D\x00\x83\x01\x26\x78\x04\x51\x83\x21\x00" + "\xE3\x07\x43\x91\x25\x3C\x5C\x02\x00" + "\x01\x01\x08\x52\x06\x00\xF0\x01\x01\x00\x03\x03\x04\x00\x00\x02\x01\x05\x02\x00\x02\x00\x93" + "\x02\x02\x00\x02\x00\x00\x00\x02\x00" + "\xF0\x08\x18\x59\xB7\x67\x09\x00\x00\x02\x02\x00\x5C\x62\x0F\x79\xF0\xB7\x71\x37\x27\x45\x91" + "\xB1\x49\x0C\x94\x30\x00\x00\x02\x02" + "\xF0\x00\x54\x10\x06\x01\x00\x04\x06\x02\x00\x06\x02\x03\x01\x00\x70\x00\x01\x00\x01\x01\x00" + "\x00\x04\x35\x02\x02\x00\xF0\x02\x3A" + "\x3A\x3B\xC3\xD9\x5D\x1B\x01\x00\x00\x0A\xB0\x9E\x1C\xC4\x4F\xB5\xED\xF9\xDD\x8D\x1D\x4C\xC4" + "\x4C\x02\x00\xF0\x2C\x34\x04\x04\x02" + "\x03\x01\x03\x03\x03\x02\x04\x04\x03\x00\x60\x04\x04\x00\x01\x01\x00\x13\x02\x18\x00\xF0\x0C" + "\x52\x20\x0D\x51\xBB\xAF\x61\x19\x00" + "\x00\x12\x7E\xB0\x8C\x95\x52\x24\x1A\x38\x64\x98\xB6\x54\x00\xF0\x05\x3F\x0F\x03\x03\x02\x02" + "\x00\x02\x04\x04\x02\x01\x02\x06\x70" + "\x02\x03\x00\x01\x04\x04\x02\x0A\xF0\x02\x00\x00\x00\x44\x6E\x18\x04\x21\x6D\xB1\xB9\x69\x0D" + "\x00\xC4\x0A\x4E\x94\xB4\xCC\xCE\xC0" + "\xAA\x78\x24\x02\x00\xF0\x21\x91\xBB\x3B\x05\x04\x00\x00\x01\x02\x00\x01\x00\x02\x02\x80\x00" + "\x00\x01\x01\x04\x01\x01\x00\x68\x02" + "\x02\x02\x00\x02\x00\xF0\x0E\x94\xFC\xA2\x54\x20\x21\x63\xC3\xF5\x95\x49\x1F\x04\x22\xF0\x36" + "\x3A\x2C\x18\x06\x01\x00\x00\x00\x09" + "\x29\x95\xE9\x99\x45\xF0\x09\x03\x01\x00\x02\x00\x00\x02\x00\x01\x00\x05\x03\x00\x04\x50\x06" + "\x01\x00\x00\x02\x78\x02\x00\x02\x00" + "\x00\x02\x00\xF0\x02\x26\x8C\xC2\xA8\x66\x1C\x05\x31\x99\xC9\xB5\x8F\x5B\x39\xF0\x19\x03\x02" + "\x02\x00\x07\x2B\x63\x93\xBB\x97\x25" + "\x06\x24\x04\xF0\x00\x01\x02\x00\x02\x03\x01\x01\x03\x01\x01\x01\x00\x00\x00\x40\x01\x02\x02" + "\x01\x98\x02\x02\x00\x02\x00\x00\x00" + "\x02\x00\xF0\x04\x26\x70\xB2\xC6\x7E\x2A\x02\x1D\x5B\x99\xB7\xC5\xCB\xC7\xF0\xB3\xC3\xCB\xCF" + "\xC7\xB1\x91\x4B\x0A\x56\xBC\xB4\x0A" + "\x00\x01\x64\x01\x02\x00\x00\x01\x02\x70\x01\x00\x01\x01\x00\x00\x00\xAA\x02\x02\x02\x00\x02" + "\x00\x02\x00\x02\x00\xF0\x20\x54\xB6" + "\xFF\xD0\x82\x48\x18\x1F\x33\x4F\x69\x83\x71\x63\xF0\x59\x37\x00\x40\x80\xEE\xDA\x6E\x0D\x08" + "\x00\x00\x00\x03\x01\xE0\x04\x01\x00" + "\x02\x00\x02\x02\x05\x07\x03\x02\x00\x00\x02\x03\x2E\x02\x00\xF0\x02\x00\x02\x00\x0C\x5E\xAA" + "\xC2\xB8\xA0\x86\x68\x48\x32\x3E\xF0" + "\x54\x6E\x98\xB2\xC0\xAA\x36\x02\x0D\x53\x07\x02\x02\x00\x04\x34\x02\x01\x00\x80\x01\x02\x06" + "\x00\x06\x04\x00\x01\x63\x02\x02\x00" + "\x00\x02\x00\x2E\x02\x00\xF0\x06\x2A\x62\x90\x9E\xB0\xC2\xD0\xC8\xBA\xAC\x8E\x64\x30\x08\xF0" + "\x00\x00\x51\x43\x0D\x01\x02\x02\x05" + "\x03\x00\x02\x01\x03\x00\x90\x02\x02\x04\x02\x00\x03\x03\x00\x01\x13\x02\x4F\x00\x02\x02\x00" + "\x05\xF0\x04\x10\x1C\x2A\x36\x30\x24" + "\x18\x06\x00\x01\x00\x21\x81\xA7\xF0\x2D\x03\x01\x03\x07\x06\x06\x01\x00\x02\x02\x00\x01\x03" + "\x02\x60\x02\x00\x02\x01\x02\x04\x9F" + "\x02\x00\x02\x02\x00\x00\x00\x02\x00\x0C\xF0\x01\x01\x1B\x65\xB1\xA5\x39\x0D\x03\x00\x00\x08" + "\x01\x01\x02\xD0\x01\x03\x02\x00\x02" + "\x06\x00\x03\x00\x00\x00\x05\x03\x47\x02\x02\x02\x00\x2B\x02\x00\x33\x01\x01\x00\xF0\x02\x02" + "\x01\x00\x00\x00\x1B\x7F\xC1\xB3\x6D" + "\x1D\x03\x00\x01\xF0\x01\x00\x01\x00\x01\x02\x04\x02\x03\x03\x03\x05\x03\x01\x00\x40\x03\x02" + "\x02\x00\xA6\x00\x02\x00\x00\x02\x00" + "\x01\x01\x02\x00\x26\x02\x00\xF0\x02\x00\x01\x00\x00\x01\x01\x0B\x1F\x3B\x75\xCB\xFA\xB3\x5F" + "\xF0\x2F\x0B\x09\x05\x01\x06\x06\x02" + "\x00\x06\x02\x01\x00\x01\x00\xA0\x02\x00\x04\x04\x04\x02\x00\x00\x04\x00\xF0\x02\x02\x02\x00" + "\x01\x17\x37\x45\x55\x57\x49\x39\x27" + "\x19\x0D\x37\x03\x01\x00\xF0\x01\x0F\x25\x3B\x59\x81\x9D\xB5\xCD\xB9\x6D\x1F\x15\x0D\x05\xF0" + "\x03\x01\x05\x01\x01\x03\x01\x02\x05" + "\x03\x01\x04\x02\x00\x00\x90\x02\x00\x01\x05\x01\x04\x00\x00\x00\xF0\x02\x02\x00\x00\x77\xBD" + "\xBF\xBB\xBD\xB7\xBF\xC3\xC9\xCF\xD3" + "\xF0\xCF\xBB\xA3\x8B\x77\x7D\x85\x8D\x97\xAF\xC7\xCF\xC9\xC1\xBB\xF0\xAD\x8D\x69\x39\x17\x15" + "\x05\x01\x01\x01\x04\x02\x04\x02\x00" + "\xF0\x02\x02\x00\x02\x04\x00\x03\x02\x02\x00\x00\x02\x01\x01\x01\x40\x00\x00\x02\x02\xF0\x02" + "\x00\x09\x99\xAD\x65\x4D\x45\x3B\x39" + "\x3D\x49\x55\x5D\x5F\xF0\x6B\x83\x9B\xB3\xC1\xBD\xB5\xAB\xA3\x91\x73\x61\x57\x4D\x35\xF0\x1B" + "\x15\x0B\x0D\x09\x01\x07\x03\x01\x02" + "\x01\x00\x04\x02\x00\x14\x01\xE0\x04\x00\x02\x01\x04\x00\x01\x04\x06\x04\x05\x03\x03\x00\xF0" + "\x02\x00\x3D\x63\x17\x0B\x07\x07\x00" + "\x03\x03\x07\x09\x07\x0D\xF0\x11\x0F\x0D\x0B\x15\x11\x11\x13\x13\x0D\x0F\x09\x05\x03\x05\xF0" + "\x03\x01\x01\x01\x02\x04\x06\x02\x00" + "\x00\x02\x04\x00\x01\x03\xF0\x03\x01\x02\x00\x00\x03\x00\x00\x02\x00\x02\x01\x00\x02\x00\x40" + "\x06\x04\x02\x01\xF0\x00\x00\x47\x25" + "\x05\x03\x00\x02\x01\x01\x02\x04\x00\x01\x00\xB3\x00\x01\x02\x01\x01\x03\x01\x01\x01\x00\x01" + "\xF0\x04\x02\x01\x00\x04\x00\x03\x07" + "\x01\x02\x01\x03\x03\x07\x03\xF0\x04\x06\x02\x01\x00\x02\x06\x04\x03\x00\x00\x02\x02\x02\x04" + "\x50\x00\x00\x00\x03\x04\xF0\x02\x0D" + "\x27\x0B\x01\x01\x02\x00\x00\x00\x03\x01\x00\x00\x00\xF0\x04\x04\x00\x00\x04\x00\x01\x00\x00" + "\x01\x00\x02\x02\x02\x00\xF0\x00\x02" + "\x01\x02\x03\x01\x04\x00\x00\x02\x01\x01\x00\x02\x02\xF0\x03\x00\x03\x02\x00\x05\x05\x00\x01" + "\x03\x01\x04\x01\x03\x00\x40\x01\x00" + "\x00\x03\xF0\x00\x05\x07\x03\x01\x02\x04\x00\x00\x00\x01\x03\x00\x02\x00\xF0\x00\x02\x03\x01" + "\x01\x00\x01\x03\x00\x02\x02\x00\x01" + "\x00\x00\xF0\x02\x02\x02\x03\x01\x00\x02\x02\x01\x01\x04\x02\x04\x02\x01\xF4\x00\x01\x04\x00" + "\x02\x02\x02\x00\x02\x01\x03\x05\x01" + "\x03\x00\xF0\x02\x05\x03\x01\x03\x00\x03\x02\x00\x00\x02\x02\x06\x02\x01\xF0\x05\x05\x00\x04" + "\x01\x03\x02\x04\x00\x00\x04\x01\x01" + "\x02\x02\xF0\x00\x04\x00\x02\x06\x04\x05\x00\x04\x02\x00\x01\x01\x07\x03\xF0\x00\x04\x00\x02" + "\x03\x01\x04\x00\x04\x04\x06\x02\x02" + "\x02\x01\x40\x02\x00\x02\x01\xF0\x02\x0A\x08\x00\x06\x04\x06\x00\x04\x02\x00\x01\x00\x02\x04" + "\xF0\x06\x00\x00\x03\x02\x02\x02\x00" + "\x04\x02\x03\x00\x04\x03\x00\xF0\x01\x05\x01\x03\x01\x03\x00\x03\x01\x01\x00\x01\x01\x02\x06" + "\xF0\x04\x04\x01\x03\x04\x01\x07\x04" + "\x03\x02\x01\x00\x03\x01\x00\x40\x00\x03\x00\x06\xF0\x00\x0C\x0E\x04\x00\x03\x01\x02\x03\x01" + "\x00\x04\x05\x03\x03\xA5\x01\x04\x01" + "\x01\x03\x04\x00\x00\x03\x00\xF0\x01\x00\x02\x00\x00\x04\x00\x00\x00\x04\x00\x02\x02\x04\x02" + "\xF0\x02\x03\x04\x03\x05\x00\x04\x05" + "\x00\x01\x00\x00\x02\x04\x02\x40\x01\x04\x00\x03\xF0\x00\x0A\x2C\x14\x00\x01\x01\x02\x04\x00" + "\x01\x02\x00\x01\x00\xF0\x09\x05\x02" + "\x00\x06\x01\x01\x01\x00\x01\x03\x01\x01\x02\x04\xF0\x04\x02\x04\x08\x04\x00\x04\x06\x02\x01" + "\x03\x00\x00\x00\x03\xF0\x03\x00\x01" + "\x02\x02\x00\x00\x01\x00\x04\x01\x00\x04\x00\x01\x40\x04\x00\x01\x01\xF0\x02\x02\x3E\x1C\x00" + "\x08\x04\x01\x00\x03\x00\x03\x00\x02" + "\x02\xF0\x08\x00\x05\x01\x00\x00\x01\x02\x01\x01\x04\x02\x01\x03\x07\xF0\x03\x05\x07\x01\x05" + "\x05\x01\x01\x01\x02\x02\x01\x00\x00" + "\x01\xF0\x01\x00\x03\x06\x00\x04\x01\x08\x02\x05\x03\x04\x01\x01\x02\x40\x05\x00\x02\x00\xF0" + "\x00\x00\x30\x16\x0A\x00\x03\x00\x01" + "\x08\x00\x03\x00\x01\x00\x63\x02\x04\x06\x04\x06\x02\xF0\x00\x00\x02\x00\x04\x02\x00\x00\x01" + "\x07\x02\x04\x01\x00\x00\xF0\x03\x02" + "\x02\x04\x06\x06\x02\x05\x00\x03\x02\x00\x02\x03\x00\xA0\x02\x02\x03\x03\x01\x02\x00\x00\x00" + "\x04\xF0\x00\x00\x18\x34\x0A\x04\x02" + "\x04\x02\x01\x00\x04\x02\x00\x05\x93\x01\x00\x01\x00\x0B\x01\x00\x01\x00\xF0\x02\x01\x02\x02" + "\x02\x06\x04\x04\x00\x02\x00\x00\x00" + "\x04\x02\xF0\x01\x01\x00\x04\x06\x04\x04\x02\x01\x02\x00\x01\x01\x04\x01\x70\x02\x02\x03\x02" + "\x03\x01\x00\xF0\x00\x02\x06\x6E\x0C" + "\x02\x00\x03\x02\x00\x04\x04\x00\x04\x02\xF0\x03\x00\x00\x01\x06\x04\x00\x00\x02\x02\x00\x01" + "\x03\x04\x02\xF0\x01\x00\x05\x00\x03" + "\x00\x00\x01\x01\x01\x07\x02\x02\x01\x01\xF0\x03\x00\x00\x03\x02\x02\x01\x00\x00\x00\x02\x04" + "\x00\x00\x00\x40\x02\x02\x00\x03\xF0" + "\x02\x00\x00\x4A\x3C\x0A\x00\x03\x01\x02\x00\x01\x01\x00\x08\xB3\x06\x00\x02\x02\x03\x03\x02" + "\x04\x00\x01\x00\xF0\x03\x00\x01\x02" + "\x02\x01\x01\x02\x00\x00\x04\x00\x00\x01\x00\xA3\x01\x03\x00\x02\x00\x03\x00\x00\x02\x00\x70" + "\x04\x02\x01\x01\x02\x00\x00\x03\xF0" + "\x0A\x76\x1C\x1A\x1A\x14\x12\x0E\x10\x0E\x08\x08\x0A\x08\x08\xF0\x06\x06\x02\x06\x02\x01\x01" + "\x00\x00\x00\x07\x03\x00\x04\x02\xF0" + "\x00\x06\x04\x00\x00\x00\x03\x00\x03\x00\x03\x01\x02\x00\x03\xF0\x02\x00\x00\x01\x00\x01\x02" + "\x07\x01\x01\x00\x02\x00\x01\x01\x10" + "\x02\x04\xF0\x6E\xCC\xC4\xBA\xB0\xA0\x98\x86\x7A\x76\x6A\x68\x62\x50\x40\xF0\x34\x20\x12\x12" + "\x14\x14\x0A\x0A\x08\x08\x04\x00\x04" + "\x04\x01\xF0\x01\x01\x03\x01\x01\x00\x06\x00\x04\x06\x06\x02\x00\x00\x02\xF0\x02\x01\x00\x01" + "\x02\x00\x04\x02\x03\x02\x02\x04\x02" + "\x06\x04\x04\xF0\x10\x56\x72\x80\x8C\x9C\xA8\xB8\xCA\xCE\xD2\xD0\xCA\xC6\xC0\xF0\xB6\xB0\xA2" + "\x8A\x78\x5A\x42\x26\x1C\x18\x14\x10" + "\x08\x06\x04\xF0\x00\x00\x04\x00\x02\x06\x01\x02\x03\x01\x03\x00\x01\x00\x03\xF0\x01\x00\x00" + "\x02\x00\x02\x04\x02\x06\x01\x02\x00" + "\x02\x01\x01\x39\x01\x01\x00\xF0\x01\x02\x08\x0E\x1C\x32\x4A\x62\x7E\x90\x9E\xAC\xBE\xD0\xDE" + "\xF0\xD8\xB8\x8E\x54\x24\x1A\x16\x0A" + "\x08\x08\x06\x06\x00\x00\x02\xF0\x01\x00\x00\x02\x04\x00\x02\x00\x04\x00\x01\x01\x05\x00\x00" + "\x70\x00\x02\x03\x01\x03\x01\x01\x6C" + "\x04\x00\x00\x00\x01\x00\xF0\x01\x01\x02\x06\x10\x1A\x28\x34\x42\x58\x7C\xAC\xEC\xED\xE6\xF0" + "\xB6\x86\x66\x40\x2E\x16\x10\x0C\x04" + "\x02\x03\x00\x01\x05\x04\xF0\x02\x00\x03\x01\x00\x00\x02\x03\x00\x00\x00\x02\x02\x00\x05\x10" + "\x00\x34\x01\x02\x00\x23\x01\x00\x2E" + "\x02\x00\xF0\x02\x02\x02\x0E\x46\x82\xBC\xCA\xC4\xB4\xA0\x80\x52\x28\x16\xF0\x12\x0A\x06\x04" + "\x02\x01\x00\x00\x00\x01\x02\x00\x00" + "\x00\x03\x60\x01\x01\x05\x00\x08\x00\x03\x67\x01\x02\x00\x00\x02\x00\x53\x01\x00\x02\x02\x00" + "\x28\x01\x00\xF0\x04\x18\x44\x6C\x90" + "\xA8\xC4\xD6\xAC\x5C\x1E\x14\x0E\x02\x02\xF0\x00\x00\x02\x02\x02\x04\x04\x06\x02\x00\x04\x08" + "\x02\x00\x04\x03\x25\x02\x00\x25\x01" + "\x00\x34\x01\x01\x00\x26\x02\x00\xF0\x02\x00\x00\x02\x00\x00\x06\x16\x2E\x4A\x8E\xE4\xF9\xC0" + "\x78\xF0\x4E\x28\x16\x0C\x08\x04\x00" + "\x03\x05\x09\x01\x00\x00\x07\x03\x20\x02\x01\x55\x03\x05\x07\x03\x00\x2F\x02\x00\x0D\xF0\x01" + "\x02\x00\x02\x24\x78\xC0\xCA\xB2\x8C" + "\x5A\x26\x14\x10\x08\x90\x04\x02\x02\x00\x01\x01\x00\x03\x00\xF0\xCD\xCF\xCF\xD1\xC9\xB7\xA3" + "\x97\x81\x71\x61\x53\x43\x2F\x13\x5D" + "\x01\x02\x02\x02\x00\x03\x13\x01\xF0\x00\x01\x01\x00\x00\x00\x0A\x32\x72\x9E\xC2\xD4\x92\x3E" + "\x18\x90\x12\x02\x00\x00\x04\x01\x06" + "\x04\x00\xF0\x6F\x6D\x71\x6B\x77\x8B\x9F\xA3\xBB\xC5\xD1\xDD\xE7\xF9\xF0\xC5\xEE\xE3\xB9\x93" + "\x6F\x4B\x3B\x2D\x21\x0F\x03\x00\x29" + "\x01\x00\x14\x01\xF0\x00\x10\x2A\x50\xA6\xF4\xDA\x80\x48\x20\x08\x05\x0C\x00\x03\x10\x01\xF0" + "\x0B\x0B\x09\x11\x0B\x0B\x09\x13\x11" + "\x19\x15\x13\x21\x1F\x23\xF0\x2D\x5F\x83\xA5\xC3\xDF\xD5\xC5\xB5\xA1\x85\x5F\x37\x11\x00\xC6" + "\x00\x00\x02\x00\x00\x00\x02\x02\x00" + "\x00\x02\x00\xF0\x02\x01\x00\x00\x02\x0E\x58\xB6\xBA\x98\x5E\x2A\x12\x08\x04\x10\x02\xF0\x01" + "\x01\x06\x06\x03\x01\x05\x01\x03\x00" + "\x03\x03\x01\x03\x07\xF0\x0B\x07\x11\x13\x17\x1D\x37\x51\x69\x85\xA5\xB5\xC1\xC5\xB1\xE4\x75" + "\x3B\x07\x00\x01\x01\x00\x00\x02\x02" + "\x00\x00\x00\x02\xF0\x00\x02\x00\x00\x02\x00\x00\x08\x4A\x8E\xC0\xC4\x5C\x1E\x12\x10\x0A\xF0" + "\x01\x01\x05\x03\x01\x00\x04\x04\x04" + "\x02\x01\x05\x01\x03\x00\xF0\x03\x05\x01\x07\x03\x05\x07\x0B\x0F\x13\x1B\x33\x47\x6B\x8F\xA4" + "\xC1\xF1\xEC\xE5\x9D\x57\x29\x0F\x01" + "\x00\xF0\x01\x01\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x02\x0A\x2A\x50\x62\xD8\xF2\x9C\x56" + "\xF0\x03\x00\x04\x02\x04\x02\x00\x03" + "\x01\x01\x03\x01\x02\x00\x01\xF0\x00\x02\x04\x08\x00\x01\x02\x02\x02\x05\x05\x05\x0B\x0B\x0B" + "\xF0\x13\x19\x29\x5B\x97\xCF\xC1\xA1" + "\x75\x3F\x0B\x02\x02\x00\x02\x2C\x02\x00\x50\x02\x06\x36\x9C\xC4"; + +static const BYTE TEST_64X64_BLUE_PLANE[4096] = + "\x27\x23\x23\x24\x25\x25\x25\x25\x28\x37\x4A\x47\x41\x3D\x38\x33\x2E\x2A\x28\x27\x26\x27\x27" + "\x27\x25\x24\x26\x28\x26\x27\x28\x28" + "\x26\x28\x28\x2A\x26\x27\x27\x28\x27\x27\x25\x26\x24\x25\x27\x28\x27\x28\x27\x27\x25\x26\x27" + "\x27\x26\x27\x27\x27\x26\x25\x26\x23" + "\x32\x29\x29\x27\x26\x28\x27\x27\x29\x3C\x55\x56\x56\x54\x52\x50\x4D\x48\x40\x37\x2F\x2C\x2B" + "\x2A\x27\x27\x27\x27\x26\x26\x25\x26" + "\x27\x28\x28\x28\x27\x27\x27\x28\x25\x26\x25\x27\x26\x26\x29\x28\x26\x27\x25\x27\x25\x26\x25" + "\x27\x25\x27\x28\x27\x26\x27\x27\x27" + "\x4E\x4D\x45\x3D\x35\x30\x2D\x29\x2B\x3E\x56\x56\x57\x57\x57\x57\x56\x56\x56\x56\x56\x52\x49" + "\x3E\x36\x31\x2D\x29\x27\x27\x27\x28" + "\x27\x27\x24\x25\x28\x29\x27\x28\x26\x27\x29\x2A\x27\x25\x24\x26\x28\x27\x27\x25\x29\x27\x26" + "\x25\x26\x23\x26\x27\x25\x27\x29\x26" + "\x4F\x50\x50\x51\x52\x4A\x44\x3E\x36\x41\x55\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57" + "\x57\x53\x4D\x47\x40\x39\x31\x2D\x27" + "\x25\x27\x26\x25\x28\x26\x25\x26\x25\x28\x29\x29\x25\x25\x26\x26\x2A\x29\x28\x26\x29\x29\x27" + "\x27\x26\x25\x25\x24\x28\x27\x27\x25" + "\x4F\x50\x50\x51\x53\x54\x55\x53\x50\x51\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57" + "\x57\x58\x57\x57\x55\x52\x4E\x47\x38" + "\x2D\x2C\x26\x27\x28\x27\x26\x28\x27\x26\x24\x26\x24\x24\x24\x27\x27\x25\x26\x28\x27\x27\x25" + "\x26\x26\x27\x26\x27\x27\x28\x27\x24" + "\x4F\x50\x51\x52\x52\x54\x55\x54\x55\x55\x56\x56\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57" + "\x57\x56\x56\x57\x57\x57\x57\x57\x56" + "\x52\x45\x38\x2F\x29\x27\x28\x25\x23\x25\x28\x26\x27\x27\x25\x27\x27\x26\x25\x28\x25\x26\x25" + "\x27\x27\x26\x2A\x2C\x27\x27\x28\x29" + "\x4F\x50\x51\x52\x53\x53\x54\x55\x55\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x56\x56" + "\x57\x56\x56\x57\x57\x57\x57\x57\x57" + "\x57\x56\x52\x49\x3A\x2E\x29\x28\x27\x26\x26\x25\x25\x26\x27\x27\x27\x27\x27\x27\x25\x26\x27" + "\x25\x25\x28\x28\x28\x25\x25\x26\x27" + "\x4F\x50\x51\x52\x52\x53\x54\x55\x55\x55\x56\x56\x57\x57\x57\x57\x57\x57\x57\x58\x56\x56\x56" + "\x56\x57\x56\x56\x52\x4C\x45\x42\x47" + "\x51\x56\x57\x57\x53\x45\x30\x28\x27\x27\x27\x28\x27\x29\x28\x26\x25\x26\x28\x26\x25\x25\x29" + "\x26\x27\x29\x27\x26\x26\x26\x25\x26" + "\x4F\x50\x51\x52\x52\x54\x55\x55\x55\x55\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56" + "\x56\x56\x50\x42\x30\x2A\x2B\x2D\x2D" + "\x2E\x35\x48\x54\x57\x57\x4C\x34\x27\x27\x29\x2A\x28\x26\x29\x26\x25\x26\x28\x25\x27\x25\x25" + "\x25\x27\x25\x27\x25\x26\x25\x25\x26" + "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56" + "\x56\x4C\x37\x2D\x2E\x34\x3B\x3D\x3A" + "\x33\x2F\x2F\x3E\x54\x56\x56\x48\x2C\x29\x27\x25\x27\x26\x29\x25\x27\x26\x28\x28\x28\x26\x24" + "\x25\x25\x24\x29\x28\x25\x25\x25\x24" + "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56" + "\x53\x35\x2D\x32\x46\x51\x54\x55\x53" + "\x4E\x3F\x2D\x2D\x43\x56\x56\x56\x39\x2A\x28\x27\x2A\x28\x29\x26\x27\x28\x28\x29\x26\x26\x27" + "\x28\x27\x26\x25\x28\x26\x26\x27\x25" + "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x55" + "\x3B\x2E\x36\x4E\x57\x57\x56\x55\x54" + "\x56\x56\x43\x2E\x31\x4A\x56\x57\x4E\x2F\x28\x25\x27\x27\x28\x27\x26\x27\x27\x26\x26\x26\x26" + "\x26\x28\x25\x26\x26\x27\x27\x26\x27" + "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x4E" + "\x32\x2F\x45\x56\x57\x56\x4E\x42\x3D" + "\x44\x50\x53\x36\x2D\x3E\x54\x57\x54\x3D\x2A\x27\x27\x26\x27\x27\x27\x27\x27\x26\x27\x27\x27" + "\x23\x25\x27\x27\x29\x26\x26\x25\x26" + "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x49" + "\x2E\x33\x4F\x57\x57\x53\x3D\x2F\x2C" + "\x2F\x41\x55\x43\x2E\x39\x52\x56\x56\x48\x2E\x28\x28\x28\x26\x28\x28\x2A\x27\x25\x29\x29\x26" + "\x25\x27\x28\x27\x27\x27\x26\x26\x25" + "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x56\x56\x56\x56\x57\x41" + "\x2B\x33\x54\x56\x56\x55\x46\x34\x32" + "\x38\x49\x56\x4C\x2F\x36\x4E\x56\x57\x52\x39\x2A\x26\x26\x27\x28\x28\x28\x26\x27\x28\x28\x28" + "\x25\x25\x25\x24\x26\x25\x23\x26\x27" + "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x56\x56\x57\x56\x57\x44" + "\x2D\x33\x52\x57\x56\x56\x55\x50\x4D" + "\x52\x56\x57\x4A\x2E\x36\x4F\x57\x57\x56\x42\x2B\x28\x27\x28\x28\x24\x25\x27\x28\x26\x27\x27" + "\x28\x27\x26\x25\x26\x26\x25\x27\x28" + "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x56\x49" + "\x31\x2F\x4B\x57\x57\x58\x57\x56\x56" + "\x57\x57\x57\x40\x2E\x38\x52\x57\x57\x57\x4D\x2F\x28\x27\x28\x29\x26\x27\x27\x27\x26\x27\x28" + "\x28\x27\x26\x28\x29\x26\x24\x27\x24" + "\x4F\x50\x51\x52\x53\x54\x55\x55\x56\x56\x56\x56\x56\x53\x57\x57\x57\x57\x57\x57\x56\x56\x51" + "\x37\x2D\x39\x52\x56\x57\x57\x57\x57" + "\x57\x57\x4C\x31\x2F\x41\x56\x57\x56\x56\x56\x39\x27\x26\x27\x24\x24\x26\x27\x27\x27\x26\x26" + "\x25\x25\x26\x28\x28\x27\x27\x26\x27" + "\x4F\x50\x51\x52\x53\x54\x54\x55\x55\x56\x56\x56\x55\x49\x43\x50\x57\x57\x56\x56\x56\x56\x56" + "\x48\x2C\x30\x3F\x51\x57\x57\x57\x57" + "\x56\x4C\x37\x2C\x36\x50\x57\x57\x57\x57\x57\x44\x29\x27\x27\x25\x23\x26\x27\x27\x26\x26\x27" + "\x26\x27\x26\x26\x25\x28\x27\x27\x26" + "\x4F\x50\x51\x52\x53\x54\x54\x55\x55\x56\x56\x56\x56\x4C\x35\x35\x46\x55\x56\x56\x57\x57\x57" + "\x55\x3B\x2C\x2E\x38\x46\x4E\x50\x4C" + "\x41\x32\x2C\x2B\x4B\x57\x57\x57\x57\x57\x57\x4F\x2D\x29\x27\x26\x26\x27\x29\x27\x28\x26\x25" + "\x25\x25\x25\x25\x24\x25\x26\x27\x26" + "\x4F\x50\x51\x52\x53\x54\x54\x55\x55\x56\x56\x56\x57\x55\x40\x2C\x2B\x35\x48\x52\x56\x57\x57" + "\x57\x54\x44\x31\x29\x2C\x2C\x2D\x2C" + "\x2C\x2E\x38\x4B\x57\x57\x57\x57\x57\x57\x57\x57\x33\x28\x28\x27\x23\x26\x27\x24\x27\x27\x26" + "\x26\x25\x27\x27\x28\x26\x26\x26\x27" + "\x4F\x51\x52\x53\x53\x54\x54\x55\x55\x56\x56\x56\x57\x57\x4A\x30\x2A\x2A\x2E\x38\x48\x53\x57" + "\x57\x57\x55\x4A\x3F\x37\x31\x31\x35" + "\x3A\x44\x50\x56\x57\x56\x56\x57\x56\x57\x56\x4F\x2F\x28\x27\x27\x25\x28\x27\x27\x29\x28\x27" + "\x27\x28\x26\x25\x27\x26\x28\x27\x26" + "\x50\x51\x52\x53\x53\x54\x55\x55\x55\x56\x56\x56\x57\x57\x54\x41\x2D\x2B\x29\x27\x2F\x38\x48" + "\x55\x57\x57\x56\x55\x52\x4F\x4E\x53" + "\x53\x55\x57\x57\x57\x56\x56\x57\x56\x52\x40\x32\x29\x29\x29\x27\x25\x26\x26\x26\x26\x28\x27" + "\x29\x27\x28\x25\x26\x26\x27\x26\x27" + "\x50\x51\x52\x53\x54\x54\x55\x55\x55\x56\x56\x56\x57\x57\x56\x56\x52\x42\x36\x2F\x2B\x2B\x2C" + "\x31\x41\x4C\x52\x55\x57\x57\x57\x57" + "\x56\x56\x56\x57\x56\x56\x54\x51\x3F\x2F\x2A\x26\x27\x26\x26\x26\x27\x27\x28\x28\x26\x26\x28" + "\x26\x26\x27\x26\x28\x27\x27\x27\x28" + "\x50\x51\x52\x53\x54\x54\x55\x55\x55\x56\x56\x56\x57\x57\x57\x56\x57\x56\x50\x46\x38\x2D\x29" + "\x29\x2A\x30\x38\x40\x49\x4E\x52\x56" + "\x57\x57\x56\x56\x4F\x48\x3F\x35\x2A\x2B\x2B\x2D\x28\x25\x26\x26\x27\x27\x27\x27\x26\x24\x28" + "\x25\x25\x24\x27\x29\x26\x28\x28\x26" + "\x51\x52\x52\x53\x54\x54\x55\x55\x55\x56\x56\x56\x57\x57\x57\x56\x57\x57\x57\x56\x51\x49\x3D" + "\x31\x2D\x2B\x2A\x29\x2F\x31\x35\x3A" + "\x3D\x3B\x39\x37\x33\x2E\x29\x2A\x2B\x37\x46\x47\x28\x25\x27\x27\x28\x26\x25\x26\x28\x26\x28" + "\x25\x26\x24\x28\x29\x25\x28\x26\x25" + "\x51\x52\x53\x53\x54\x54\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x56\x56\x57\x57" + "\x54\x4A\x3E\x35\x2E\x28\x29\x28\x29" + "\x29\x2A\x2C\x2B\x2B\x2F\x35\x3E\x4E\x56\x56\x45\x2B\x25\x27\x29\x25\x26\x27\x25\x25\x26\x26" + "\x27\x26\x22\x25\x26\x26\x27\x25\x27" + "\x51\x52\x53\x54\x54\x55\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57" + "\x57\x57\x56\x50\x48\x41\x3E\x38\x34" + "\x31\x33\x38\x3B\x41\x48\x4F\x55\x57\x57\x55\x38\x2A\x28\x28\x27\x25\x26\x26\x22\x27\x27\x27" + "\x26\x25\x25\x26\x26\x27\x27\x27\x28" + "\x52\x52\x53\x54\x54\x55\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57" + "\x57\x57\x57\x57\x56\x56\x54\x52\x51" + "\x4F\x4F\x52\x53\x55\x56\x57\x57\x57\x57\x49\x2F\x26\x26\x29\x26\x26\x26\x27\x27\x27\x25\x26" + "\x28\x26\x27\x27\x27\x27\x27\x27\x27" + "\x52\x53\x53\x54\x54\x55\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57" + "\x57\x57\x57\x57\x57\x57\x57\x56\x56" + "\x57\x56\x56\x56\x56\x56\x56\x57\x51\x43\x30\x2A\x24\x24\x25\x25\x27\x28\x25\x26\x26\x27\x28" + "\x25\x25\x27\x27\x26\x27\x27\x29\x26" + "\x52\x53\x54\x54\x55\x55\x55\x56\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57" + "\x57\x57\x57\x57\x57\x57\x57\x56\x56" + "\x57\x57\x56\x56\x56\x56\x52\x48\x38\x2C\x27\x27\x25\x25\x25\x25\x25\x27\x26\x26\x25\x26\x28" + "\x27\x27\x26\x25\x26\x28\x27\x25\x26" + "\x53\x53\x54\x54\x55\x55\x55\x56\x56\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57" + "\x57\x56\x56\x57\x57\x57\x57\x57\x57" + "\x58\x57\x57\x57\x52\x43\x35\x2E\x27\x28\x29\x27\x23\x25\x26\x27\x27\x27\x27\x28\x26\x25\x26" + "\x24\x25\x25\x25\x25\x26\x28\x27\x27" + "\x53\x54\x54\x55\x55\x54\x54\x55\x56\x56\x56\x57\x56\x56\x56\x57\x56\x56\x57\x57\x56\x57\x57" + "\x57\x56\x56\x56\x58\x56\x56\x56\x54" + "\x51\x4E\x46\x39\x2D\x2B\x29\x28\x27\x27\x28\x28\x26\x26\x26\x28\x2A\x28\x28\x28\x26\x26\x26" + "\x26\x26\x28\x28\x28\x25\x28\x29\x25" + "\x53\x54\x53\x54\x55\x52\x4C\x4B\x48\x48\x4B\x4F\x51\x52\x55\x56\x56\x56\x57\x57\x56\x56\x57" + "\x56\x57\x56\x54\x50\x4E\x49\x43\x3F" + "\x37\x30\x2A\x2A\x28\x27\x28\x28\x27\x25\x26\x27\x26\x27\x26\x27\x27\x25\x27\x28\x27\x26\x26" + "\x27\x26\x27\x25\x26\x26\x27\x27\x26" + "\x54\x54\x54\x54\x44\x37\x32\x31\x2D\x2D\x2E\x32\x35\x34\x34\x38\x3C\x40\x43\x45\x44\x43\x43" + "\x41\x3D\x38\x36\x33\x33\x2F\x2A\x2B" + "\x27\x29\x28\x26\x29\x27\x27\x27\x29\x27\x27\x28\x26\x25\x26\x28\x27\x25\x25\x27\x28\x26\x26" + "\x25\x26\x26\x23\x25\x28\x26\x28\x27" + "\x53\x53\x53\x3E\x2B\x25\x23\x25\x24\x27\x28\x28\x27\x27\x2A\x2A\x27\x27\x27\x29\x29\x28\x29" + "\x29\x28\x2A\x29\x28\x27\x26\x27\x29" + "\x26\x26\x26\x26\x27\x26\x26\x26\x28\x28\x29\x29\x27\x25\x25\x28\x27\x27\x29\x26\x29\x26\x27" + "\x27\x27\x27\x26\x26\x25\x25\x26\x26" + "\x53\x54\x49\x30\x26\x24\x23\x25\x25\x28\x28\x26\x25\x27\x27\x25\x26\x28\x28\x26\x27\x28\x27" + "\x26\x27\x26\x26\x27\x25\x24\x25\x27" + "\x25\x26\x27\x27\x29\x27\x26\x26\x27\x2A\x29\x27\x25\x24\x25\x28\x27\x26\x27\x28\x29\x26\x27" + "\x26\x26\x28\x28\x26\x26\x26\x26\x26" + "\x55\x54\x40\x29\x26\x24\x24\x27\x25\x26\x27\x29\x26\x24\x26\x26\x27\x29\x29\x25\x26\x28\x28" + "\x26\x28\x27\x28\x27\x25\x28\x27\x27" + "\x27\x27\x25\x25\x26\x27\x27\x26\x25\x28\x26\x24\x27\x25\x26\x27\x27\x27\x27\x28\x24\x27\x27" + "\x28\x26\x26\x29\x26\x25\x26\x26\x26" + "\x54\x52\x3A\x29\x25\x24\x26\x26\x26\x25\x26\x26\x27\x27\x27\x28\x29\x2A\x28\x29\x27\x26\x27" + "\x27\x27\x27\x28\x29\x28\x27\x27\x28" + "\x27\x28\x25\x23\x27\x26\x28\x26\x23\x25\x26\x26\x28\x26\x26\x25\x26\x27\x27\x27\x24\x24\x26" + "\x27\x27\x27\x28\x28\x27\x27\x26\x27" + "\x55\x52\x38\x29\x24\x26\x28\x26\x25\x25\x27\x27\x26\x26\x27\x26\x29\x27\x27\x27\x27\x25\x25" + "\x27\x27\x26\x27\x28\x28\x27\x28\x28" + "\x27\x27\x24\x25\x28\x27\x27\x27\x26\x27\x28\x27\x27\x25\x26\x26\x28\x27\x28\x27\x26\x25\x25" + "\x26\x25\x26\x26\x26\x27\x28\x26\x24" + "\x55\x51\x38\x28\x24\x26\x25\x25\x26\x25\x25\x27\x29\x28\x27\x24\x27\x28\x28\x26\x23\x25\x27" + "\x27\x25\x28\x27\x26\x29\x28\x27\x2A" + "\x28\x28\x26\x27\x26\x27\x27\x27\x28\x26\x26\x24\x23\x25\x25\x27\x27\x26\x27\x29\x27\x27\x27" + "\x29\x25\x28\x26\x24\x26\x28\x28\x24" + "\x56\x53\x39\x28\x28\x28\x28\x27\x28\x27\x27\x27\x2A\x2A\x29\x29\x27\x27\x27\x26\x25\x26\x27" + "\x28\x28\x26\x27\x28\x26\x27\x28\x27" + "\x26\x25\x26\x26\x24\x26\x26\x27\x25\x25\x23\x25\x27\x26\x28\x27\x27\x27\x26\x26\x29\x27\x27" + "\x27\x28\x26\x26\x25\x26\x25\x26\x26" + "\x56\x55\x3B\x27\x26\x24\x26\x27\x24\x26\x27\x28\x25\x27\x27\x27\x29\x27\x26\x26\x27\x26\x27" + "\x27\x28\x28\x28\x28\x25\x28\x27\x26" + "\x26\x25\x26\x27\x26\x25\x27\x28\x27\x25\x25\x26\x28\x26\x26\x28\x26\x26\x26\x27\x26\x26\x27" + "\x26\x27\x26\x28\x27\x27\x27\x26\x25" + "\x56\x56\x43\x2C\x25\x25\x27\x29\x27\x27\x28\x28\x26\x25\x27\x23\x23\x27\x26\x28\x27\x26\x27" + "\x27\x28\x26\x27\x28\x26\x2A\x29\x27" + "\x29\x29\x29\x27\x28\x28\x29\x27\x28\x27\x26\x26\x25\x27\x28\x28\x26\x27\x25\x25\x25\x27\x29" + "\x26\x25\x28\x28\x25\x28\x27\x27\x26" + "\x55\x56\x4B\x2E\x25\x28\x29\x28\x25\x25\x27\x28\x25\x25\x27\x27\x23\x24\x25\x26\x26\x26\x26" + "\x24\x25\x28\x28\x27\x25\x27\x27\x24" + "\x26\x29\x26\x23\x27\x28\x27\x28\x26\x26\x27\x27\x26\x26\x27\x25\x28\x27\x27\x26\x29\x28\x27" + "\x24\x29\x28\x27\x27\x26\x28\x28\x25" + "\x55\x56\x51\x32\x27\x28\x27\x27\x26\x29\x26\x26\x26\x26\x28\x27\x26\x28\x28\x29\x25\x27\x29" + "\x26\x26\x27\x29\x27\x26\x28\x27\x25" + "\x25\x25\x26\x27\x25\x27\x28\x26\x26\x26\x28\x2A\x29\x25\x24\x24\x26\x28\x28\x28\x26\x26\x26" + "\x25\x26\x24\x26\x27\x26\x27\x28\x27" + "\x56\x56\x55\x39\x29\x29\x28\x29\x28\x26\x26\x27\x26\x26\x25\x27\x25\x25\x26\x25\x26\x27\x28" + "\x26\x27\x26\x28\x27\x26\x27\x27\x26" + "\x26\x27\x27\x27\x27\x28\x28\x26\x29\x28\x26\x29\x29\x28\x27\x28\x27\x29\x27\x26\x25\x26\x25" + "\x26\x24\x25\x26\x27\x26\x25\x26\x28" + "\x57\x57\x57\x4A\x29\x27\x27\x27\x27\x28\x29\x29\x27\x29\x25\x25\x25\x25\x27\x29\x26\x26\x27" + "\x26\x28\x28\x27\x25\x29\x29\x27\x27" + "\x25\x26\x26\x24\x27\x27\x26\x26\x26\x28\x27\x27\x27\x26\x27\x27\x27\x28\x27\x26\x27\x26\x26" + "\x2A\x28\x26\x26\x25\x28\x26\x27\x26" + "\x57\x57\x57\x55\x32\x28\x25\x24\x28\x28\x27\x27\x27\x27\x27\x26\x26\x27\x28\x26\x25\x27\x29" + "\x27\x27\x28\x28\x24\x29\x27\x26\x26" + "\x25\x28\x26\x25\x29\x26\x26\x26\x26\x29\x28\x25\x25\x25\x27\x28\x26\x26\x26\x28\x28\x25\x27" + "\x28\x28\x28\x28\x24\x26\x26\x25\x25" + "\x57\x57\x56\x56\x43\x2B\x29\x28\x2A\x29\x29\x2A\x28\x27\x28\x27\x27\x27\x27\x29\x26\x2A\x28" + "\x24\x26\x28\x28\x25\x24\x26\x26\x26" + "\x26\x28\x28\x27\x28\x27\x25\x25\x26\x27\x27\x23\x24\x25\x25\x26\x28\x27\x27\x27\x26\x26\x28" + "\x25\x27\x27\x28\x26\x27\x27\x26\x25" + "\x56\x57\x56\x56\x54\x49\x47\x44\x42\x40\x3F\x3D\x37\x39\x38\x36\x37\x32\x30\x2F\x2A\x2C\x2B" + "\x29\x2B\x28\x28\x26\x26\x26\x27\x28" + "\x27\x25\x26\x26\x24\x25\x25\x26\x29\x27\x28\x27\x28\x26\x27\x26\x26\x28\x27\x27\x27\x26\x27" + "\x25\x28\x26\x29\x27\x27\x26\x27\x28" + "\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56\x55\x54\x53\x4F\x4C\x48\x44\x41\x3D" + "\x3B\x36\x30\x2C\x2B\x2B\x2A\x2A\x2A" + "\x28\x27\x26\x27\x28\x26\x26\x27\x28\x28\x28\x26\x25\x25\x27\x27\x27\x25\x26\x26\x28\x27\x28" + "\x28\x28\x28\x27\x27\x27\x28\x29\x27" + "\x56\x56\x57\x57\x56\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57\x57\x57\x56\x57\x56\x56\x54" + "\x53\x51\x50\x4D\x49\x43\x3D\x34\x2E" + "\x2D\x2A\x26\x25\x28\x27\x28\x26\x28\x29\x27\x25\x26\x27\x27\x27\x24\x26\x28\x26\x27\x26\x25" + "\x29\x28\x29\x27\x26\x27\x27\x26\x26" + "\x56\x56\x57\x57\x56\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57\x57\x57\x56\x56\x57\x57\x57" + "\x57\x57\x57\x56\x57\x58\x56\x57\x56" + "\x4B\x42\x3A\x36\x2F\x2C\x29\x28\x27\x27\x24\x23\x26\x27\x26\x28\x27\x27\x26\x25\x25\x24\x25" + "\x27\x27\x27\x28\x28\x29\x25\x22\x26" + "\x57\x57\x57\x57\x56\x57\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56\x56\x57\x57\x57" + "\x57\x57\x57\x56\x57\x57\x57\x57\x57" + "\x56\x57\x56\x52\x4C\x46\x41\x3C\x34\x2C\x28\x27\x27\x28\x26\x27\x28\x26\x27\x26\x25\x25\x26" + "\x27\x28\x26\x25\x26\x25\x27\x28\x26" + "\x57\x57\x57\x57\x57\x57\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57\x57\x57\x57" + "\x57\x56\x57\x57\x57\x57\x57\x57\x57" + "\x56\x57\x57\x56\x56\x57\x55\x53\x50\x4B\x41\x35\x2B\x29\x28\x26\x26\x27\x26\x26\x27\x28\x29" + "\x29\x2B\x28\x27\x28\x29\x28\x28\x28" + "\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56\x56\x57\x57\x57\x57\x56\x56\x56\x57\x57\x57\x57" + "\x57\x56\x57\x57\x56\x57\x57\x57\x57" + "\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x52\x46\x38\x31\x2C\x2A\x27\x26\x25\x27\x27" + "\x26\x25\x24\x27\x28\x23\x26\x28\x27" + "\x56\x55\x55\x56\x58\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x56\x56\x56\x57\x57\x57\x57" + "\x57\x57\x57\x57\x57\x57\x57\x57\x57" + "\x57\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57\x57\x57\x55\x4F\x46\x3F\x34\x2B\x29\x2B\x28" + "\x26\x25\x28\x28\x27\x24\x26\x28\x25" + "\x39\x38\x37\x37\x39\x3B\x3F\x40\x44\x47\x49\x4A\x4C\x50\x53\x56\x57\x57\x57\x58\x57\x57\x57" + "\x57\x57\x57\x57\x57\x57\x57\x57\x57" + "\x57\x57\x57\x57\x56\x56\x56\x56\x56\x56\x56\x58\x56\x57\x57\x57\x56\x54\x50\x4B\x3F\x31\x29" + "\x27\x26\x26\x26\x27\x23\x29\x27\x27" + "\x2A\x29\x26\x2A\x28\x27\x26\x28\x27\x2A\x29\x29\x2D\x2D\x2C\x2F\x35\x3C\x41\x47\x4B\x4E\x50" + "\x52\x54\x56\x57\x57\x57\x57\x57\x57" + "\x56\x57\x57\x57\x56\x56\x56\x56\x56\x56\x56\x56\x56\x56\x56\x56\x56\x57\x57\x56\x56\x54\x4A" + "\x3D\x32\x2E\x27\x24\x28\x26\x26\x25" + "\x26\x25\x24\x24\x27\x26\x26\x25\x26\x24\x28\x2A\x26\x26\x26\x2A\x27\x29\x29\x2B\x2B\x31\x34" + "\x38\x3C\x43\x4A\x4E\x54\x56\x57\x57" + "\x57\x57\x57\x57\x57\x57\x56\x56\x57\x56\x56\x56\x56\x56\x56\x56\x57\x56\x56\x56\x57\x57\x57" + "\x56\x4C\x41\x35\x2D\x2B\x28\x27\x27" + "\x27\x26\x28\x28\x25\x25\x25\x26\x26\x25\x29\x2A\x28\x28\x27\x26\x26\x27\x25\x26\x27\x29\x28" + "\x29\x2A\x2A\x2F\x34\x38\x3C\x46\x4D" + "\x53\x57\x56\x56\x57\x57\x57\x57\x57\x56\x56\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57\x57" + "\x57\x56\x55\x51\x48\x38\x2C\x28\x27" + "\x27\x25\x25\x27\x26\x26\x28\x26\x27\x26\x25\x25\x26\x27\x27\x25\x26\x25\x25\x26\x27\x27\x27" + "\x27\x29\x27\x28\x2B\x29\x28\x29\x2B" + "\x2D\x34\x3F\x4A\x51\x55\x56\x57\x57\x57\x57\x57\x56\x56\x57\x57\x56\x57\x57\x56\x57\x57\x57" + "\x57\x56\x56\x56\x56\x56\x4E\x3F\x33" + "\x25\x26\x27\x28\x27\x27\x27\x25\x27\x27\x25\x27\x25\x27\x27\x27\x27\x29\x28\x28\x25\x28\x28" + "\x26\x27\x26\x28\x27\x26\x28\x27\x27" + "\x28\x28\x2A\x2C\x35\x3E\x46\x4E\x55\x57\x57\x57\x57\x57\x57\x57\x56\x57\x57\x57\x57\x57\x57" + "\x57\x57\x57\x56\x57\x57\x57\x56\x51"; + +static const BYTE TEST_64X64_BLUE_PLANE_RLE[3724] = + "\x53\x27\x23\x23\x24\x25\xF0\x28\x37\x4A\x47\x41\x3D\x38\x33\x2E\x2A\x28\x27\x26\x27\x27\xF0" + "\x27\x25\x24\x26\x28\x26\x27\x28\x28" + "\x26\x28\x28\x2A\x26\x27\xF0\x27\x28\x27\x27\x25\x26\x24\x25\x27\x28\x27\x28\x27\x27\x25\xB0" + "\x26\x27\x27\x26\x27\x27\x27\x26\x25" + "\x26\x23\xF0\x16\x0C\x0C\x06\x02\x06\x04\x04\x02\x0A\x16\x1E\x2A\x2E\x34\xF0\x3A\x3E\x3C\x30" + "\x20\x12\x0A\x08\x06\x04\x06\x02\x01" + "\x00\x01\xF0\x05\x03\x02\x00\x00\x03\x02\x00\x00\x00\x03\x01\x00\x02\x04\xF0\x02\x04\x00\x01" + "\x01\x03\x00\x00\x00\x03\x00\x01\x00" + "\x02\x00\x40\x00\x04\x02\x08\xF0\x38\x48\x38\x2C\x1E\x10\x0C\x04\x04\x04\x02\x00\x02\x06\x0A" + "\xF0\x0E\x12\x1C\x2C\x3E\x4E\x4C\x3C" + "\x28\x1E\x14\x0C\x04\x02\x02\xF0\x04\x04\x00\x01\x07\x05\x02\x04\x00\x00\x02\x02\x08\x06\x02" + "\xF0\x01\x09\x03\x04\x00\x04\x03\x08" + "\x02\x02\x03\x02\x07\x03\x00\x40\x01\x00\x04\x01\xC4\x02\x06\x16\x28\x3A\x34\x2E\x2A\x16\x06" + "\x01\x00\x13\x02\xF0\x00\x0A\x1C\x32" + "\x3A\x38\x34\x2E\x24\x14\x0C\x01\x03\x00\x04\xF0\x00\x00\x05\x03\x03\x01\x02\x00\x01\x03\x00" + "\x04\x00\x04\x04\xE0\x02\x02\x00\x04" + "\x02\x04\x00\x04\x01\x05\x06\x00\x03\x01\x04\x96\x02\x14\x22\x2A\x34\x20\x02\x02\x00\xF0\x01" + "\x02\x00\x00\x00\x0A\x14\x20\x2A\x32" + "\x3A\x34\x22\x10\x0A\xF0\x00\x04\x00\x02\x02\x04\x04\x03\x09\x05\x01\x01\x03\x02\x05\xF0\x07" + "\x03\x04\x03\x03\x03\x01\x00\x04\x02" + "\x06\x01\x02\x00\x01\xDB\x00\x00\x02\x02\x01\x00\x00\x02\x0A\x08\x00\x01\x00\xF0\x03\x01\x00" + "\x04\x0A\x12\x20\x3C\x4A\x32\x24\x10" + "\x02\x00\x04\xF0\x05\x07\x01\x08\x00\x06\x06\x02\x00\x00\x02\x01\x00\x03\x01\xA0\x00\x02\x02" + "\x01\x08\x0A\x00\x01\x02\x0A\x04\x78" + "\x02\x01\x01\x02\x00\x02\x00\x57\x02\x01\x01\x01\x00\xF0\x02\x0A\x22\x34\x34\x22\x0E\x02\x06" + "\x08\x02\x03\x01\x03\x01\xF3\x04\x00" + "\x00\x02\x04\x01\x00\x00\x04\x03\x03\x04\x03\x07\x03\x04\x23\x01\x00\x28\x01\x00\xF0\x02\x00" + "\x00\x00\x01\x02\x00\x01\x09\x15\x23" + "\x29\x1F\x0B\x00\xF0\x0A\x1C\x32\x2E\x0E\x00\x00\x02\x02\x06\x04\x06\x02\x01\x03\xF0\x01\x02" + "\x01\x00\x01\x04\x02\x04\x02\x01\x03" + "\x02\x02\x01\x01\x05\x3B\x02\x02\x00\xF0\x01\x02\x02\x00\x00\x01\x0B\x27\x43\x43\x33\x29\x33" + "\x45\x41\xE3\x1D\x05\x08\x24\x38\x18" + "\x00\x00\x04\x04\x02\x05\x02\x00\xD0\x01\x04\x00\x07\x01\x00\x07\x00\x01\x00\x01\x00\x00\x04" + "\x7D\x02\x00\x00\x00\x02\x02\x00\xF0" + "\x13\x31\x29\x03\x14\x20\x20\x1A\x0A\x0B\x31\x2B\x05\x01\x14\xF0\x28\x0A\x04\x03\x09\x01\x00" + "\x00\x01\x04\x00\x00\x06\x02\x02\xA0" + "\x01\x00\x03\x01\x04\x06\x01\x00\x00\x03\x71\xF0\x05\x2D\x13\x0A\x30\x3A\x32\x30\x32\x36\x20" + "\x03\x21\x21\x00\xF0\x00\x1C\x1A\x02" + "\x02\x04\x06\x04\x00\x02\x00\x04\x00\x02\x03\xB0\x00\x06\x06\x04\x04\x07\x00\x02\x02\x04\x02" + "\x41\xF0\x01\x00\x01\x2F\x0D\x12\x38" + "\x22\x0C\x04\x00\x02\x10\x2E\x2C\xF0\x02\x23\x17\x00\x02\x2A\x0A\x00\x03\x05\x01\x01\x02\x01" + "\x01\xE0\x01\x05\x00\x00\x01\x03\x02" + "\x01\x02\x03\x02\x02\x01\x04\x61\xF0\x0D\x11\x02\x1E\x10\x00\x01\x0F\x25\x2D\x23\x0B\x20\x10" + "\x07\xF0\x17\x03\x00\x0C\x1C\x04\x04" + "\x00\x01\x01\x00\x02\x00\x00\x00\x93\x02\x02\x02\x05\x05\x04\x02\x06\x01\x41\xF0\x02\x00\x09" + "\x07\x08\x14\x02\x00\x05\x21\x25\x21" + "\x29\x1D\x04\xF0\x1A\x02\x09\x03\x01\x04\x16\x08\x02\x02\x04\x01\x02\x02\x06\xE0\x00\x01\x04" + "\x04\x01\x04\x04\x02\x00\x03\x02\x00" + "\x02\x01\x11\x13\x01\xF0\x00\x0F\x05\x00\x0A\x01\x01\x04\x12\x0A\x0C\x12\x10\x02\x12\xF0\x02" + "\x05\x07\x00\x02\x14\x16\x04\x03\x03" + "\x02\x00\x00\x03\x01\xD0\x04\x01\x01\x04\x00\x03\x05\x05\x01\x03\x05\x00\x04\x31\xF0\x02\x00" + "\x00\x06\x04\x00\x03\x02\x00\x02\x1E" + "\x38\x36\x34\x1A\xF0\x02\x03\x01\x00\x02\x02\x00\x08\x12\x02\x04\x02\x02\x00\x07\xF0\x05\x02" + "\x02\x03\x01\x01\x06\x04\x02\x02\x00" + "\x02\x04\x02\x02\x11\xF0\x02\x02\x00\x00\x01\x0A\x08\x07\x0D\x00\x02\x04\x04\x0C\x12\xF0\x0A" + "\x02\x00\x13\x00\x04\x06\x00\x00\x02" + "\x16\x08\x00\x00\x00\xF0\x02\x04\x04\x00\x01\x00\x00\x02\x00\x00\x00\x06\x06\x00\x01\x20\x00" + "\x07\x0C\x37\x01\x07\x00\xF0\x10\x0C" + "\x03\x23\x09\x01\x01\x00\x02\x02\x00\x00\x15\x1D\x02\xF0\x12\x08\x00\x01\x01\x12\x14\x01\x01" + "\x01\x09\x03\x01\x00\x00\xC0\x02\x01" + "\x03\x05\x03\x00\x00\x01\x02\x06\x01\x06\x06\xF0\x01\x00\x01\x00\x00\x00\x01\x13\x27\x0D\x00" + "\x00\x01\x01\x00\x83\x00\x0A\x22\x01" + "\x11\x25\x09\x00\xF0\x01\x15\x29\x09\x0E\x1E\x02\x00\x02\x02\x02\x16\x04\x02\x00\xF0\x02\x01" + "\x00\x00\x00\x01\x00\x02\x02\x04\x00" + "\x03\x05\x02\x00\x20\x02\x01\x0C\xF0\x02\x06\x1B\x35\x21\x03\x00\x00\x02\x02\x02\x1A\x1E\x07" + "\x21\xC4\x31\x21\x11\x0D\x15\x29\x33" + "\x15\x01\x2A\x0E\x00\xF0\x16\x08\x04\x00\x02\x06\x02\x04\x00\x04\x00\x03\x01\x03\x01\x60\x01" + "\x01\x05\x01\x00\x00\x0C\xF0\x02\x12" + "\x16\x11\x35\x3F\x1B\x07\x01\x00\x00\x04\x32\x30\x06\xB5\x1D\x33\x43\x45\x3F\x29\x07\x18\x40" + "\x18\x00\xF0\x10\x0C\x01\x02\x02\x05" + "\x01\x03\x05\x01\x02\x02\x02\x00\x04\x60\x04\x08\x02\x00\x01\x02\x58\x00\x02\x02\x02\x00\xF0" + "\x04\x14\x08\x01\x15\x33\x33\x1B\x07" + "\x00\x00\x06\x22\x32\x2C\xF0\x16\x0A\x08\x12\x1C\x2C\x30\x16\x00\x01\x01\x00\x01\x00\x01\xF0" + "\x0F\x07\x00\x01\x00\x04\x04\x00\x06" + "\x04\x02\x02\x02\x06\x01\x60\x03\x01\x00\x04\x02\x01\x24\x02\x00\x26\x02\x00\xF0\x14\x22\x06" + "\x02\x09\x21\x31\x35\x1D\x03\x00\x04" + "\x18\x2C\x36\x84\x3C\x3A\x3C\x32\x22\x0E\x02\x00\xF0\x09\x2B\x39\x0B\x02\x04\x00\x00\x03\x01" + "\x01\x05\x00\x00\x04\x80\x01\x04\x00" + "\x01\x00\x01\x01\x02\x04\x28\x02\x00\xF0\x04\x2A\x4A\x2E\x1A\x10\x07\x19\x37\x47\x2B\x15\x07" + "\x00\x0A\xF0\x10\x12\x08\x06\x02\x01" + "\x00\x01\x00\x03\x0B\x2D\x45\x2B\x17\xF0\x03\x05\x05\x01\x04\x02\x04\x04\x00\x03\x02\x05\x01" + "\x01\x02\x50\x04\x02\x00\x02\x02\x0E" + "\xF0\x02\x00\x0A\x28\x34\x2E\x1A\x04\x05\x0F\x2D\x37\x33\x29\x1B\xF0\x11\x09\x01\x02\x02\x00" + "\x01\x0D\x1B\x29\x37\x29\x07\x02\x0E" + "\x33\x02\x01\x00\xE0\x01\x01\x00\x03\x00\x01\x01\x05\x02\x02\x01\x02\x02\x03\x3E\x02\x02\x00" + "\xF0\x02\x0E\x20\x32\x38\x28\x10\x06" + "\x09\x1B\x2D\x33\x39\x39\x37\xF0\x33\x37\x39\x3D\x37\x33\x2B\x15\x02\x18\x36\x34\x00\x00\x02" + "\xF0\x02\x02\x01\x03\x01\x04\x04\x00" + "\x00\x02\x00\x02\x00\x01\x00\x20\x03\x01\x44\x00\x00\x02\x00\x25\x02\x00\x23\x02\x00\xF0\x0A" + "\x1C\x34\x46\x3A\x26\x16\x0A\x0D\x0F" + "\x19\x21\x27\x21\x19\xF0\x17\x0F\x02\x18\x28\x46\x3E\x20\x03\x06\x00\x00\x04\x05\x00\xE0\x04" + "\x01\x05\x00\x03\x04\x00\x03\x05\x05" + "\x02\x01\x01\x04\x03\x4C\x02\x00\x02\x00\xF0\x02\x02\x00\x00\x06\x1A\x30\x36\x34\x32\x2A\x20" + "\x16\x10\x12\xF0\x18\x20\x2C\x32\x34" + "\x2E\x12\x02\x01\x19\x01\x06\x02\x03\x00\xF0\x00\x01\x05\x04\x02\x02\x01\x01\x06\x02\x00\x02" + "\x00\x04\x02\x2F\x02\x00\x08\xF0\x02" + "\x0E\x1C\x2A\x2C\x34\x3A\x3C\x38\x34\x30\x28\x1C\x10\x04\xF0\x00\x00\x17\x11\x07\x03\x02\x01" + "\x02\x00\x02\x0A\x00\x03\x01\x90\x04" + "\x02\x04\x02\x02\x00\x00\x00\x01\x3F\x00\x02\x00\x09\xF0\x02\x02\x06\x08\x0A\x10\x0E\x08\x06" + "\x02\x00\x01\x00\x0B\x27\xF0\x31\x09" + "\x03\x03\x07\x01\x02\x04\x03\x01\x01\x04\x04\x05\x01\x70\x00\x00\x01\x00\x00\x04\x01\x9F\x00" + "\x00\x02\x00\x02\x00\x00\x02\x00\x09" + "\x23\x02\x00\xF0\x07\x1D\x31\x2D\x11\x05\x02\x02\x00\x00\x03\x01\x02\x00\x01\xB0\x01\x00\x04" + "\x04\x01\x03\x00\x02\x00\x07\x00\x29" + "\x02\x00\x2B\x02\x00\x33\x01\x01\x00\xF0\x02\x02\x02\x00\x02\x02\x07\x25\x39\x33\x21\x07\x04" + "\x00\x03\xF0\x00\x02\x04\x04\x00\x02" + "\x04\x02\x01\x03\x05\x03\x01\x00\x01\x40\x03\x02\x04\x02\x93\x00\x02\x00\x02\x00\x01\x01\x01" + "\x00\xA4\x01\x01\x01\x00\x01\x01\x00" + "\x00\x01\x00\xF0\x01\x02\x01\x01\x01\x05\x0D\x11\x21\x3B\x49\x2F\x17\x0B\x00\xF0\x01\x01\x02" + "\x06\x02\x00\x02\x06\x02\x02\x00\x00" + "\x02\x00\x04\x80\x02\x06\x06\x06\x01\x00\x04\x03\xF0\x00\x00\x01\x01\x00\x03\x0F\x13\x1B\x1B" + "\x15\x0F\x09\x07\x01\x24\x01\x00\xF0" + "\x01\x00\x01\x02\x00\x03\x0F\x0F\x19\x25\x29\x33\x3B\x37\x1D\xF0\x09\x07\x01\x00\x00\x03\x03" + "\x01\x00\x02\x00\x01\x05\x05\x01\xD0" + "\x00\x02\x00\x00\x02\x00\x01\x05\x03\x02\x01\x03\x02\xF0\x02\x00\x02\x00\x21\x35\x33\x33\x35" + "\x35\x39\x39\x37\x3B\x41\xF0\x3B\x33" + "\x2B\x27\x23\x23\x25\x27\x29\x33\x3B\x3B\x39\x35\x33\xF0\x31\x27\x1F\x0D\x03\x07\x02\x00\x01" + "\x01\x04\x04\x02\x02\x00\xF0\x03\x00" + "\x02\x00\x00\x03\x01\x02\x00\x00\x03\x00\x01\x03\x01\x40\x04\x01\x02\x02\xF0\x01\x01\x01\x2B" + "\x31\x23\x1D\x17\x11\x0B\x0B\x13\x1B" + "\x19\x13\xF0\x1B\x29\x31\x37\x37\x35\x35\x33\x2F\x29\x1B\x19\x15\x17\x11\x83\x05\x03\x01\x05" + "\x03\x00\x03\x01\xF0\x02\x04\x02\x02" + "\x00\x01\x00\x00\x04\x08\x01\x02\x00\x02\x04\x80\x02\x02\x06\x02\x05\x01\x03\x01\xF0\x00\x02" + "\x13\x1B\x09\x01\x00\x00\x02\x02\x00" + "\x03\x03\x00\x05\xE3\x09\x01\x02\x02\x05\x03\x00\x03\x05\x01\x07\x05\x01\x03\xF0\x01\x00\x02" + "\x02\x04\x02\x00\x00\x01\x04\x00\x03" + "\x03\x01\x00\xF0\x00\x00\x01\x03\x04\x00\x00\x00\x01\x01\x02\x04\x00\x02\x02\x20\x00\x00\xF0" + "\x04\x00\x11\x0D\x00\x00\x02\x04\x00" + "\x03\x01\x06\x02\x05\x01\x13\x02\xF0\x01\x01\x00\x02\x00\x02\x02\x04\x00\x00\x08\x04\x00\x04" + "\x02\xF0\x03\x03\x05\x00\x02\x00\x03" + "\x03\x05\x05\x04\x02\x02\x01\x00\xF0\x02\x00\x00\x09\x02\x00\x04\x00\x03\x02\x00\x01\x00\x00" + "\x00\xF0\x01\x03\x0B\x00\x01\x00\x04" + "\x01\x02\x01\x01\x05\x02\x06\x02\xF0\x04\x04\x02\x01\x08\x02\x03\x01\x02\x01\x00\x00\x04\x06" + "\x01\xF0\x00\x02\x00\x02\x00\x03\x02" + "\x01\x02\x00\x03\x05\x00\x04\x02\xF0\x02\x00\x03\x01\x00\x00\x01\x00\x05\x01\x01\x02\x02\x01" + "\x04\x40\x04\x02\x00\x02\xF0\x02\x00" + "\x03\x00\x01\x04\x04\x00\x01\x00\x02\x02\x01\x01\x00\xF0\x03\x00\x05\x01\x03\x00\x01\x03\x00" + "\x00\x01\x01\x01\x00\x00\xF0\x02\x00" + "\x00\x01\x01\x04\x02\x02\x01\x02\x06\x04\x04\x02\x01\xF0\x01\x00\x02\x04\x00\x02\x00\x04\x02" + "\x01\x01\x03\x01\x03\x03\x40\x00\x02" + "\x00\x05\xF0\x00\x01\x00\x01\x00\x00\x05\x01\x02\x00\x03\x00\x06\x04\x00\xF0\x03\x03\x02\x02" + "\x01\x07\x00\x04\x00\x03\x04\x00\x03" + "\x02\x02\xF0\x01\x04\x02\x02\x04\x04\x03\x00\x00\x00\x04\x01\x03\x05\x07\xF0\x00\x01\x02\x01" + "\x01\x01\x04\x02\x04\x04\x06\x00\x04" + "\x00\x03\x40\x01\x00\x04\x00\x83\x02\x04\x02\x00\x08\x04\x06\x04\xF0\x00\x02\x04\x04\x0A\x00" + "\x01\x01\x00\x04\x02\x00\x02\x06\x03" + "\xF0\x00\x04\x05\x01\x02\x05\x03\x05\x00\x01\x03\x01\x01\x00\x05\xF0\x01\x05\x02\x08\x02\x06" + "\x00\x00\x02\x01\x05\x04\x00\x00\x03" + "\x80\x06\x03\x00\x02\x00\x05\x03\x04\xF0\x00\x04\x04\x01\x03\x07\x03\x00\x07\x01\x00\x02\x09" + "\x05\x03\xF0\x03\x04\x00\x01\x00\x04" + "\x00\x00\x01\x00\x04\x02\x00\x01\x02\xF0\x01\x01\x00\x00\x00\x02\x04\x01\x02\x02\x04\x00\x04" + "\x02\x02\xF0\x00\x03\x02\x01\x01\x00" + "\x02\x05\x01\x00\x01\x01\x00\x04\x04\x40\x02\x04\x00\x01\xF0\x00\x02\x10\x0A\x01\x02\x02\x04" + "\x06\x02\x02\x00\x02\x03\x00\x64\x07" + "\x0B\x00\x00\x04\x00\xF0\x03\x01\x00\x02\x04\x04\x02\x06\x08\x06\x00\x04\x06\x04\x01\xF0\x02" + "\x04\x02\x00\x05\x02\x04\x00\x00\x02" + "\x01\x03\x01\x02\x04\x90\x00\x03\x04\x00\x03\x02\x00\x02\x02\xF0\x01\x00\x10\x04\x00\x06\x04" + "\x01\x03\x03\x01\x00\x01\x00\x00\xF0" + "\x08\x00\x05\x01\x03\x01\x00\x01\x05\x05\x04\x02\x01\x01\x05\xF0\x03\x05\x05\x00\x05\x07\x01" + "\x00\x03\x02\x03\x01\x02\x02\x02\xF0" + "\x01\x01\x05\x04\x00\x04\x02\x08\x02\x03\x03\x08\x00\x01\x04\x40\x03\x02\x02\x01\xF0\x00\x00" + "\x0C\x08\x04\x00\x03\x01\x02\x08\x01" + "\x03\x02\x02\x02\xF0\x00\x06\x08\x06\x06\x01\x02\x06\x04\x02\x01\x02\x00\x02\x02\xF0\x00\x02" + "\x01\x07\x00\x08\x03\x01\x02\x03\x00" + "\x00\x02\x06\x06\xF0\x01\x05\x01\x03\x02\x02\x04\x05\x03\x01\x02\x05\x07\x01\x00\x40\x00\x01" + "\x00\x04\xF0\x02\x00\x08\x0E\x04\x02" + "\x02\x04\x04\x05\x00\x02\x00\x00\x05\xF0\x00\x01\x05\x03\x07\x02\x00\x01\x00\x02\x01\x01\x00" + "\x00\x01\xF0\x00\x02\x02\x04\x02\x00" + "\x04\x02\x00\x00\x06\x04\x03\x01\x00\xF0\x06\x06\x08\x02\x02\x01\x03\x01\x00\x01\x02\x03\x02" + "\x00\x00\x40\x00\x03\x03\x02\xF0\x02" + "\x02\x04\x22\x00\x03\x01\x03\x01\x04\x06\x04\x02\x06\x00\xF0\x03\x00\x00\x02\x08\x00\x01\x01" + "\x00\x02\x04\x01\x03\x06\x04\xF0\x00" + "\x02\x01\x01\x01\x05\x00\x01\x03\x00\x05\x00\x02\x03\x03\xF0\x03\x00\x01\x00\x01\x00\x00\x04" + "\x00\x02\x08\x08\x02\x00\x03\x40\x04" + "\x02\x02\x03\x03\xF0\x16\x12\x02\x03\x05\x02\x00\x03\x03\x00\x03\x04\x02\x02\x04\xF0\x02\x05" + "\x01\x02\x04\x02\x01\x00\x02\x01\x00" + "\x03\x01\x01\x00\xF0\x04\x00\x02\x04\x01\x00\x00\x00\x02\x02\x03\x03\x01\x00\x02\xF0\x01\x03" + "\x01\x04\x02\x01\x02\x03\x00\x04\x04" + "\x01\x03\x00\x03\x10\x01\xF0\x00\x00\x01\x02\x22\x06\x08\x08\x04\x02\x04\x06\x02\x00\x02\xF0" + "\x02\x02\x00\x01\x06\x02\x06\x01\x05" + "\x01\x00\x00\x02\x09\x01\xF0\x00\x00\x02\x00\x04\x04\x01\x02\x01\x01\x00\x03\x01\x03\x01\xF0" + "\x00\x03\x03\x04\x02\x02\x01\x03\x02" + "\x02\x05\x01\x01\x00\x04\x40\x02\x02\x02\x00\xF0\x01\x00\x00\x00\x22\x3C\x3C\x38\x30\x2E\x2C" + "\x26\x1E\x24\x20\xF0\x1E\x20\x16\x12" + "\x0C\x08\x04\x06\x0A\x0A\x00\x00\x02\x04\x00\xF0\x02\x04\x02\x05\x03\x01\x07\x03\x00\x02\x06" + "\x00\x02\x08\x08\xF0\x02\x04\x00\x03" + "\x02\x00\x00\x02\x00\x01\x00\x02\x01\x02\x02\x40\x00\x01\x02\x06\xF0\x02\x00\x02\x02\x06\x1C" + "\x20\x26\x2A\x2E\x30\x34\x40\x3A\x3A" + "\xF0\x3C\x38\x3A\x38\x32\x34\x2A\x24\x24\x16\x10\x08\x0A\x0A\x08\xF0\x06\x04\x02\x04\x00\x02" + "\x08\x02\x02\x02\x01\x02\x00\x01\x05" + "\xF0\x01\x00\x02\x02\x05\x01\x01\x02\x02\x02\x06\x00\x04\x03\x00\x40\x00\x04\x04\x01\x66\x01" + "\x01\x00\x00\x01\x00\xF0\x01\x02\x04" + "\x06\x08\x10\x14\x1E\x24\x2A\x2E\x30\x36\x40\x42\xF0\x3C\x30\x26\x14\x08\x0A\x06\x00\x03\x00" + "\x02\x04\x01\x00\x02\xF0\x01\x01\x02" + "\x04\x00\x00\x05\x02\x04\x00\x01\x01\x05\x02\x00\x70\x02\x00\x01\x00\x01\x05\x01\x31\xF0\x01" + "\x02\x02\x06\x08\x0C\x0E\x12\x1C\x2A" + "\x32\x46\x50\x3C\x30\xF0\x28\x22\x0E\x0A\x02\x04\x01\x03\x05\x03\x00\x00\x01\x02\x06\xF0\x02" + "\x03\x01\x03\x03\x00\x03\x01\x03\x02" + "\x04\x04\x03\x07\x00\x33\x02\x02\x00\x33\x01\x01\x00\x2E\x02\x00\xF0\x01\x02\x00\x02\x16\x2A" + "\x38\x38\x3A\x34\x30\x28\x1A\x0A\x08" + "\xF0\x08\x02\x02\x00\x01\x02\x01\x02\x02\x00\x02\x02\x00\x02\x01\x60\x05\x03\x07\x04\x0C\x00" + "\x04\x2A\x02\x00\x53\x01\x00\x02\x02" + "\x00\x46\x01\x00\x02\x00\xF0\x02\x08\x14\x22\x28\x2E\x38\x3E\x32\x1C\x08\x02\x04\x01\x03\xF0" + "\x02\x01\x00\x04\x06\x06\x04\x06\x04" + "\x04\x04\x08\x02\x00\x04\x06\x74\x02\x02\x00\x00\x01\x01\x00\x37\x01\x01\x00\x23\x01\x00\xF0" + "\x02\x00\x00\x02\x02\x00\x04\x08\x0E" + "\x18\x2A\x44\x4E\x3A\x20\xF0\x16\x0C\x06\x02\x00\x03\x01\x03\x05\x0B\x07\x00\x00\x0B\x03\x20" + "\x00\x01\x64\x01\x03\x03\x01\x02\x00" + "\x3B\x02\x02\x00\x5B\x02\x00\x00\x02\x00\xF0\x01\x00\x02\x00\x0A\x22\x3A\x3C\x34\x2A\x1A\x0A" + "\x08\x08\x02\x90\x00\x00\x08\x02\x01" + "\x02\x00\x00\x03\xF0\x39\x39\x3B\x3D\x3D\x37\x2F\x2D\x25\x1F\x1B\x19\x15\x0D\x07\x23\x01\x02" + "\x1F\x00\x13\x01\xF0\x00\x01\x01\x02" + "\x01\x00\x04\x10\x20\x2A\x38\x40\x2C\x0C\x02\x90\x02\x02\x03\x03\x00\x01\x06\x01\x04\xF0\x1D" + "\x1D\x21\x19\x21\x27\x31\x2F\x39\x39" + "\x3F\x41\x3D\x45\x4D\xC5\x4D\x43\x35\x2B\x21\x17\x11\x0D\x09\x05\x01\x00\x29\x01\x00\xF0\x03" + "\x00\x01\x01\x01\x00\x06\x0E\x16\x2E" + "\x46\x42\x2C\x18\x10\x60\x02\x05\x0A\x05\x01\x03\xF0\x07\x07\x03\x0B\x01\x01\x00\x05\x01\x0B" + "\x01\x02\x0D\x0D\x0B\xF0\x09\x1B\x25" + "\x2F\x37\x3F\x39\x37\x33\x2F\x25\x19\x11\x05\x01\xC6\x00\x00\x02\x00\x00\x00\x02\x02\x00\x00" + "\x02\x00\xF0\x02\x01\x01\x00\x02\x06" + "\x1A\x32\x34\x26\x1C\x12\x06\x04\x02\x10\x04\xF0\x02\x02\x08\x08\x03\x01\x01\x02\x00\x02\x02" + "\x00\x04\x04\x02\xF0\x07\x01\x03\x07" + "\x09\x07\x0F\x17\x1D\x23\x31\x35\x33\x37\x33\xE4\x21\x13\x07\x00\x01\x01\x00\x00\x02\x02\x00" + "\x00\x00\x02\xF0\x00\x02\x02\x02\x00" + "\x00\x00\x02\x14\x28\x38\x36\x1A\x08\x02\x10\x00\xF0\x00\x01\x05\x01\x02\x02\x06\x00\x02\x02" + "\x07\x09\x03\x01\x00\xF0\x01\x00\x03" + "\x00\x00\x00\x03\x01\x03\x01\x05\x0D\x11\x1D\x27\xF0\x39\x43\x4B\x45\x2D\x17\x0B\x03\x01\x00" + "\x00\x02\x02\x00\x01\x84\x01\x00\x00" + "\x01\x00\x00\x01\x00\x70\x02\x0A\x1C\x3C\x44\x2E\x18\xF0\x03\x02\x04\x02\x02\x02\x01\x01\x00" + "\x02\x00\x04\x01\x00\x00\xF0\x04\x02" + "\x08\x06\x04\x03\x02\x02\x01\x03\x01\x00\x07\x05\x00\xF0\x03\x07\x09\x17\x29\x3B\x37\x2D\x1F" + "\x11\x03\x00\x00\x00\x02\x24\x02\x00" + "\x23\x02\x00\x80\x02\x02\x00\x02\x02\x12\x2E\x3C"; + +/** + * [MS-RDPEGDI] Test Bitmap 32x32 (16bpp) + */ + +static const BYTE TEST_RLE_UNCOMPRESSED_BITMAP_16BPP[2048] = + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84" + "\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x99\xD6\xFF\xFF" + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x08\x42" + "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42" + "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\xFF\xFF" + "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42" + "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42" + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" + "\xFF\xFF\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84" + "\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x08\x42" + "\x08\x42\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84" + "\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x99\xD6\xFF\xFF" + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x08\x42" + "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42" + "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\xFF\xFF" + "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42" + "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42" + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" + "\xFF\xFF\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84" + "\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x08\x42" + "\x08\x42\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00" + "\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6" + "\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00" + "\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00" + "\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00" + "\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00" + "\x00\x00\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00" + "\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00" + "\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6" + "\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x00\x00\x00\x00\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\xFF\xFF" + "\xFF\xFF\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6" + "\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x99\xD6\x10\x84\x08\x42" + "\x08\x42\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84" + "\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x10\x84\x99\xD6\xFF\xFF" + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF" + "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x08\x42" + "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42" + "\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\x08\x42\xFF\xFF"; + +static const UINT32 colorFormatList[] = { + PIXEL_FORMAT_RGB15, PIXEL_FORMAT_BGR15, PIXEL_FORMAT_RGB16, PIXEL_FORMAT_BGR16, + PIXEL_FORMAT_RGB24, PIXEL_FORMAT_BGR24, PIXEL_FORMAT_ARGB32, PIXEL_FORMAT_ABGR32, + PIXEL_FORMAT_XRGB32, PIXEL_FORMAT_XBGR32, PIXEL_FORMAT_RGBX32, PIXEL_FORMAT_BGRX32 + +}; +static const UINT32 colorFormatCount = sizeof(colorFormatList) / sizeof(colorFormatList[0]); + +static BOOL CompareBitmap(const BYTE* srcA, UINT32 srcAFormat, const BYTE* srcB, UINT32 srcBFormat, + UINT32 width, UINT32 height) +{ + double maxDiff = NAN; + const UINT32 srcABits = FreeRDPGetBitsPerPixel(srcAFormat); + const UINT32 srcBBits = FreeRDPGetBitsPerPixel(srcBFormat); + UINT32 diff = fabs((double)srcABits - srcBBits); + + /* No support for 8bpp */ + if ((srcABits < 15) || (srcBBits < 15)) + return FALSE; + + /* Compare with folliwing granularity: + * 32 --> 24 bpp: Each color channel has 8bpp, no difference expected + * 24/32 --> 15/16 bpp: 8bit per channel against 5/6bit per channel, +/- 3bit + * 16 --> 15bpp: 5/6bit per channel against 5 bit per channel, +/- 1bit + */ + switch (diff) + { + case 1: + maxDiff = 2 * 2.0; + break; + + case 8: + case 9: + case 16: + case 17: + maxDiff = 2 * 8.0; + break; + + default: + maxDiff = 0.0; + break; + } + + if ((srcABits == 32) || (srcBBits == 32)) + { + if (diff == 8) + maxDiff = 0.0; + } + + for (UINT32 y = 0; y < height; y++) + { + const BYTE* lineA = &srcA[width * FreeRDPGetBytesPerPixel(srcAFormat) * y]; + const BYTE* lineB = &srcB[width * FreeRDPGetBytesPerPixel(srcBFormat) * y]; + + for (UINT32 x = 0; x < width; x++) + { + BYTE sR = 0; + BYTE sG = 0; + BYTE sB = 0; + BYTE sA = 0; + BYTE dR = 0; + BYTE dG = 0; + BYTE dB = 0; + BYTE dA = 0; + const BYTE* a = &lineA[x * FreeRDPGetBytesPerPixel(srcAFormat)]; + const BYTE* b = &lineB[x * FreeRDPGetBytesPerPixel(srcBFormat)]; + UINT32 colorA = FreeRDPReadColor(a, srcAFormat); + UINT32 colorB = FreeRDPReadColor(b, srcBFormat); + FreeRDPSplitColor(colorA, srcAFormat, &sR, &sG, &sB, &sA, NULL); + FreeRDPSplitColor(colorB, srcBFormat, &dR, &dG, &dB, &dA, NULL); + + if (fabs((double)sR - dR) > maxDiff) + return FALSE; + + if (fabs((double)sG - dG) > maxDiff) + return FALSE; + + if (fabs((double)sB - dB) > maxDiff) + return FALSE; + + if (fabs((double)sA - dA) > maxDiff) + return FALSE; + } + } + + return TRUE; +} + +static BOOL RunTestPlanar(BITMAP_PLANAR_CONTEXT* planar, const BYTE* srcBitmap, + const UINT32 srcFormat, const UINT32 dstFormat, const UINT32 width, + const UINT32 height) +{ + BOOL rc = FALSE; + UINT32 dstSize = 0; + BYTE* compressedBitmap = freerdp_bitmap_compress_planar(planar, srcBitmap, srcFormat, width, + height, 0, NULL, &dstSize); + BYTE* decompressedBitmap = (BYTE*)calloc(height, width * FreeRDPGetBytesPerPixel(dstFormat)); + printf("%s [%s] --> [%s]: ", __func__, FreeRDPGetColorFormatName(srcFormat), + FreeRDPGetColorFormatName(dstFormat)); + fflush(stdout); + printf("TODO: Skipping unfinished test!"); + rc = TRUE; + goto fail; + + if (!compressedBitmap || !decompressedBitmap) + goto fail; + + if (!planar_decompress(planar, compressedBitmap, dstSize, width, height, decompressedBitmap, + dstFormat, 0, 0, 0, width, height, FALSE)) + { + printf("failed to decompress experimental bitmap 01: width: %" PRIu32 " height: %" PRIu32 + "\n", + width, height); + goto fail; + } + + if (!CompareBitmap(decompressedBitmap, dstFormat, srcBitmap, srcFormat, width, height)) + { + printf("FAIL"); + goto fail; + } + + printf("SUCCESS"); + rc = TRUE; +fail: + free(compressedBitmap); + free(decompressedBitmap); + printf("\n"); + fflush(stdout); + return rc; +} + +static BOOL RunTestPlanarSingleColor(BITMAP_PLANAR_CONTEXT* planar, const UINT32 srcFormat, + const UINT32 dstFormat) +{ + BOOL rc = FALSE; + printf("%s: [%s] --> [%s]: ", __func__, FreeRDPGetColorFormatName(srcFormat), + FreeRDPGetColorFormatName(dstFormat)); + fflush(stdout); + + for (UINT32 j = 0; j < 32; j += 8) + { + for (UINT32 i = 4; i < 32; i += 8) + { + UINT32 compressedSize = 0; + const UINT32 fill = j; + const UINT32 color = + FreeRDPGetColor(srcFormat, (fill >> 8) & 0xF, (fill >> 4) & 0xF, (fill)&0xF, 0xFF); + const UINT32 width = i; + const UINT32 height = i; + BOOL failed = TRUE; + const UINT32 srcSize = width * height * FreeRDPGetBytesPerPixel(srcFormat); + const UINT32 dstSize = width * height * FreeRDPGetBytesPerPixel(dstFormat); + BYTE* compressedBitmap = NULL; + BYTE* bmp = malloc(srcSize); + BYTE* decompressedBitmap = (BYTE*)malloc(dstSize); + + if (!bmp || !decompressedBitmap) + goto fail_loop; + + for (UINT32 y = 0; y < height; y++) + { + BYTE* line = &bmp[width * FreeRDPGetBytesPerPixel(srcFormat) * y]; + + for (UINT32 x = 0; x < width; x++) + { + FreeRDPWriteColor(line, srcFormat, color); + line += FreeRDPGetBytesPerPixel(srcFormat); + } + } + + compressedBitmap = freerdp_bitmap_compress_planar(planar, bmp, srcFormat, width, height, + 0, NULL, &compressedSize); + + if (!compressedBitmap) + goto fail_loop; + + if (!planar_decompress(planar, compressedBitmap, compressedSize, width, height, + decompressedBitmap, dstFormat, 0, 0, 0, width, height, FALSE)) + goto fail_loop; + + if (!CompareBitmap(decompressedBitmap, dstFormat, bmp, srcFormat, width, height)) + goto fail_loop; + + failed = FALSE; + fail_loop: + free(bmp); + free(compressedBitmap); + free(decompressedBitmap); + + if (failed) + { + printf("FAIL"); + goto fail; + } + } + } + + printf("SUCCESS"); + rc = TRUE; +fail: + printf("\n"); + fflush(stdout); + return rc; +} + +static BOOL TestPlanar(const UINT32 format) +{ + BOOL rc = FALSE; + const DWORD planarFlags = PLANAR_FORMAT_HEADER_NA | PLANAR_FORMAT_HEADER_RLE; + BITMAP_PLANAR_CONTEXT* planar = freerdp_bitmap_planar_context_new(planarFlags, 64, 64); + + if (!planar) + goto fail; + + if (!RunTestPlanar(planar, TEST_RLE_BITMAP_EXPERIMENTAL_01, PIXEL_FORMAT_RGBX32, format, 64, + 64)) + goto fail; + + if (!RunTestPlanar(planar, TEST_RLE_BITMAP_EXPERIMENTAL_02, PIXEL_FORMAT_RGBX32, format, 64, + 64)) + goto fail; + + if (!RunTestPlanar(planar, TEST_RLE_BITMAP_EXPERIMENTAL_03, PIXEL_FORMAT_RGBX32, format, 64, + 64)) + goto fail; + + if (!RunTestPlanar(planar, TEST_RLE_UNCOMPRESSED_BITMAP_16BPP, PIXEL_FORMAT_RGB16, format, 32, + 32)) + goto fail; + + for (UINT32 x = 0; x < colorFormatCount; x++) + { + if (!RunTestPlanarSingleColor(planar, format, colorFormatList[x])) + goto fail; + } + + rc = TRUE; +fail: + freerdp_bitmap_planar_context_free(planar); + return rc; +} + +static UINT32 prand(UINT32 max) +{ + UINT32 tmp = 0; + if (max <= 1) + return 1; + winpr_RAND(&tmp, sizeof(tmp)); + return tmp % (max - 1) + 1; +} + +static BOOL FuzzPlanar(void) +{ + BOOL rc = FALSE; + const DWORD planarFlags = PLANAR_FORMAT_HEADER_NA | PLANAR_FORMAT_HEADER_RLE; + BITMAP_PLANAR_CONTEXT* planar = freerdp_bitmap_planar_context_new(planarFlags, 64, 64); + + if (!planar) + goto fail; + + for (UINT32 x = 0; x < 100; x++) + { + BYTE data[0x10000] = { 0 }; + size_t dataSize = 0x10000; + BYTE dstData[0x10000] = { 0 }; + + UINT32 DstFormat = 0; + UINT32 nDstStep = 0; + UINT32 nXDst = 0; + UINT32 nYDst = 0; + UINT32 nDstWidth = 0; + UINT32 nDstHeight = 0; + BOOL invalid = TRUE; + do + { + switch (prand(17) - 1) + { + case 0: + DstFormat = PIXEL_FORMAT_RGB8; + break; + case 1: + DstFormat = PIXEL_FORMAT_BGR15; + break; + case 2: + DstFormat = PIXEL_FORMAT_RGB15; + break; + case 3: + DstFormat = PIXEL_FORMAT_ABGR15; + break; + case 4: + DstFormat = PIXEL_FORMAT_ABGR15; + break; + case 5: + DstFormat = PIXEL_FORMAT_BGR16; + break; + case 6: + DstFormat = PIXEL_FORMAT_RGB16; + break; + case 7: + DstFormat = PIXEL_FORMAT_BGR24; + break; + case 8: + DstFormat = PIXEL_FORMAT_RGB24; + break; + case 9: + DstFormat = PIXEL_FORMAT_BGRA32; + break; + case 10: + DstFormat = PIXEL_FORMAT_BGRX32; + break; + case 11: + DstFormat = PIXEL_FORMAT_RGBA32; + break; + case 12: + DstFormat = PIXEL_FORMAT_RGBX32; + break; + case 13: + DstFormat = PIXEL_FORMAT_ABGR32; + break; + case 14: + DstFormat = PIXEL_FORMAT_XBGR32; + break; + case 15: + DstFormat = PIXEL_FORMAT_ARGB32; + break; + case 16: + DstFormat = PIXEL_FORMAT_XRGB32; + break; + default: + break; + } + nDstStep = prand(sizeof(dstData)); + nXDst = prand(nDstStep); + nYDst = prand(sizeof(dstData) / nDstStep); + nDstWidth = prand(nDstStep / FreeRDPGetBytesPerPixel(DstFormat)); + nDstHeight = prand(sizeof(dstData) / nDstStep); + invalid = nXDst * FreeRDPGetBytesPerPixel(DstFormat) + (nYDst + nDstHeight) * nDstStep > + sizeof(dstData); + } while (invalid); + printf("DstFormat=%s, nXDst=%" PRIu32 ", nYDst=%" PRIu32 ", nDstWidth=%" PRIu32 + ", nDstHeight=%" PRIu32 ", nDstStep=%" PRIu32 ", total size=%" PRIuz "\n", + FreeRDPGetColorFormatName(DstFormat), nXDst, nYDst, nDstWidth, nDstHeight, nDstStep, + sizeof(dstData)); + freerdp_planar_switch_bgr(planar, rand() % 2); + planar_decompress(planar, data, dataSize, prand(4096), prand(4096), dstData, DstFormat, + nDstStep, nXDst, nYDst, nDstWidth, nDstHeight, prand(2)); + } + + rc = TRUE; +fail: + freerdp_bitmap_planar_context_free(planar); + return rc; +} + +int TestFreeRDPCodecPlanar(int argc, char* argv[]) +{ + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + if (!FuzzPlanar()) + return -2; + + for (UINT32 x = 0; x < colorFormatCount; x++) + { + if (!TestPlanar(colorFormatList[x])) + return -1; + } + + return 0; +} diff --git a/libfreerdp/codec/test/TestFreeRDPCodecProgressive.c b/libfreerdp/codec/test/TestFreeRDPCodecProgressive.c new file mode 100644 index 0000000..fb36595 --- /dev/null +++ b/libfreerdp/codec/test/TestFreeRDPCodecProgressive.c @@ -0,0 +1,1146 @@ +#include <winpr/wtypes.h> +#include <winpr/crt.h> +#include <winpr/path.h> +#include <winpr/image.h> +#include <winpr/print.h> +#include <winpr/wlog.h> +#include <winpr/sysinfo.h> +#include <winpr/file.h> + +#include <freerdp/codec/region.h> + +#include <freerdp/codec/progressive.h> + +#include "../progressive.h" + +/** + * Microsoft Progressive Codec Sample Data + * (available under NDA only) + * + * <enc/dec>_<image#>_<quarter#>_<prog%>_<bitmap>.<type> + * + * readme.pdf + * + * bitmaps/ + * 1920by1080-SampleImage1.bmp + * 1920by1080-SampleImage2.bmp + * 1920by1080-SampleImage3.bmp + * + * compress/ + * enc_0_0_025_sampleimage1.bin + * enc_0_0_050_sampleimage1.bin + * enc_0_0_075_sampleimage1.bin + * enc_0_0_100_sampleimage1.bin + * enc_0_1_025_sampleimage1.bin + * enc_0_1_050_sampleimage1.bin + * enc_0_1_075_sampleimage1.bin + * enc_0_1_100_sampleimage1.bin + * enc_0_2_025_sampleimage1.bin + * enc_0_2_050_sampleimage1.bin + * enc_0_2_075_sampleimage1.bin + * enc_0_2_100_sampleimage1.bin + * enc_0_3_025_sampleimage1.bin + * enc_0_3_050_sampleimage1.bin + * enc_0_3_075_sampleimage1.bin + * enc_0_3_100_sampleimage1.bin + * enc_1_0_025_sampleimage2.bin + * enc_1_0_050_sampleimage2.bin + * enc_1_0_075_sampleimage2.bin + * enc_1_0_100_sampleimage2.bin + * enc_1_1_025_sampleimage2.bin + * enc_1_1_050_sampleimage2.bin + * enc_1_1_075_sampleimage2.bin + * enc_1_1_100_sampleimage2.bin + * enc_1_2_025_sampleimage2.bin + * enc_1_2_050_sampleimage2.bin + * enc_1_2_075_sampleimage2.bin + * enc_1_2_100_sampleimage2.bin + * enc_1_3_025_sampleimage2.bin + * enc_1_3_050_sampleimage2.bin + * enc_1_3_075_sampleimage2.bin + * enc_1_3_100_sampleimage2.bin + * enc_2_0_025_sampleimage3.bin + * enc_2_0_050_sampleimage3.bin + * enc_2_0_075_sampleimage3.bin + * enc_2_0_100_sampleimage3.bin + * enc_2_1_025_sampleimage3.bin + * enc_2_1_050_sampleimage3.bin + * enc_2_1_075_sampleimage3.bin + * enc_2_1_100_sampleimage3.bin + * enc_2_2_025_sampleimage3.bin + * enc_2_2_050_sampleimage3.bin + * enc_2_2_075_sampleimage3.bin + * enc_2_2_100_sampleimage3.bin + * enc_2_3_025_sampleimage3.bin + * enc_2_3_050_sampleimage3.bin + * enc_2_3_075_sampleimage3.bin + * enc_2_3_100_sampleimage3.bin + * + * decompress/ + * dec_0_0_025_sampleimage1.bmp + * dec_0_0_050_sampleimage1.bmp + * dec_0_0_075_sampleimage1.bmp + * dec_0_0_100_sampleimage1.bmp + * dec_0_1_025_sampleimage1.bmp + * dec_0_1_050_sampleimage1.bmp + * dec_0_1_075_sampleimage1.bmp + * dec_0_1_100_sampleimage1.bmp + * dec_0_2_025_sampleimage1.bmp + * dec_0_2_050_sampleimage1.bmp + * dec_0_2_075_sampleimage1.bmp + * dec_0_2_100_sampleimage1.bmp + * dec_0_3_025_sampleimage1.bmp + * dec_0_3_050_sampleimage1.bmp + * dec_0_3_075_sampleimage1.bmp + * dec_0_3_100_sampleimage1.bmp + * dec_1_0_025_sampleimage2.bmp + * dec_1_0_050_sampleimage2.bmp + * dec_1_0_075_sampleimage2.bmp + * dec_1_0_100_sampleimage2.bmp + * dec_1_1_025_sampleimage2.bmp + * dec_1_1_050_sampleimage2.bmp + * dec_1_1_075_sampleimage2.bmp + * dec_1_1_100_sampleimage2.bmp + * dec_1_2_025_sampleimage2.bmp + * dec_1_2_050_sampleimage2.bmp + * dec_1_2_075_sampleimage2.bmp + * dec_1_2_100_sampleimage2.bmp + * dec_1_3_025_sampleimage2.bmp + * dec_1_3_050_sampleimage2.bmp + * dec_1_3_075_sampleimage2.bmp + * dec_1_3_100_sampleimage2.bmp + * dec_2_0_025_sampleimage3.bmp + * dec_2_0_050_sampleimage3.bmp + * dec_2_0_075_sampleimage3.bmp + * dec_2_0_100_sampleimage3.bmp + * dec_2_1_025_sampleimage3.bmp + * dec_2_1_050_sampleimage3.bmp + * dec_2_1_075_sampleimage3.bmp + * dec_2_1_100_sampleimage3.bmp + * dec_2_2_025_sampleimage3.bmp + * dec_2_2_050_sampleimage3.bmp + * dec_2_2_075_sampleimage3.bmp + * dec_2_2_100_sampleimage3.bmp + * dec_2_3_025_sampleimage3.bmp + * dec_2_3_050_sampleimage3.bmp + * dec_2_3_075_sampleimage3.bmp + * dec_2_3_100_sampleimage3.bmp + */ + +typedef struct +{ + BYTE* buffer; + size_t size; +} EGFX_SAMPLE_FILE; + +static int g_Width = 0; +static int g_Height = 0; +static int g_DstStep = 0; +static BYTE* g_DstData = NULL; + +static void sample_file_free(EGFX_SAMPLE_FILE* file) +{ + if (!file) + return; + + free(file->buffer); + file->buffer = NULL; + file->size = 0; +} + +static void test_fill_image_alpha_channel(BYTE* data, int width, int height, BYTE value) +{ + UINT32* pixel = NULL; + + for (int i = 0; i < height; i++) + { + for (int j = 0; j < width; j++) + { + pixel = (UINT32*)&data[((i * width) + j) * 4]; + *pixel = ((*pixel & 0x00FFFFFF) | (value << 24)); + } + } +} + +static void* test_image_memset32(UINT32* ptr, UINT32 fill, size_t length) +{ + while (length--) + { + *ptr++ = fill; + } + + return (void*)ptr; +} + +static int test_image_fill(BYTE* pDstData, int nDstStep, int nXDst, int nYDst, int nWidth, + int nHeight, UINT32 color) +{ + UINT32* pDstPixel = NULL; + + if (nDstStep < 0) + nDstStep = 4 * nWidth; + + for (int y = 0; y < nHeight; y++) + { + pDstPixel = (UINT32*)&pDstData[((nYDst + y) * nDstStep) + (nXDst * 4)]; + test_image_memset32(pDstPixel, color, nWidth); + } + + return 1; +} + +static int test_image_fill_quarter(BYTE* pDstData, int nDstStep, int nWidth, int nHeight, + UINT32 color, int quarter) +{ + int x = 0; + int y = 0; + int width = 0; + int height = 0; + + switch (quarter) + { + case 0: + x = 0; + y = 0; + width = nWidth / 2; + height = nHeight / 2; + break; + + case 1: + x = nWidth / 2; + y = nHeight / 2; + width = nWidth / 2; + height = nHeight / 2; + break; + + case 2: + x = 0; + y = nHeight / 2; + width = nWidth / 2; + height = nHeight / 2; + break; + + case 3: + x = nWidth / 2; + y = 0; + width = nWidth / 2; + height = nHeight / 2; + break; + } + + test_image_fill(pDstData, nDstStep, x, y, width, height, 0xFF000000); + return 1; +} + +static int test_image_fill_unused_quarters(BYTE* pDstData, int nDstStep, int nWidth, int nHeight, + UINT32 color, int quarter) +{ + return 1; + + if (quarter == 0) + { + test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 1); + test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 2); + test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 3); + } + else if (quarter == 1) + { + test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 0); + test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 2); + test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 3); + } + else if (quarter == 2) + { + test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 0); + test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 1); + test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 3); + } + else if (quarter == 3) + { + test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 0); + test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 1); + test_image_fill_quarter(pDstData, nDstStep, nWidth, nHeight, color, 2); + } + + return 1; +} + +static BYTE* test_progressive_load_file(const char* path, const char* file, size_t* size) +{ + char* filename = GetCombinedPath(path, file); + + if (!filename) + return NULL; + + FILE* fp = winpr_fopen(filename, "r"); + free(filename); + + if (!fp) + return NULL; + + _fseeki64(fp, 0, SEEK_END); + const INT64 pos = _ftelli64(fp); + WINPR_ASSERT(pos >= 0); + WINPR_ASSERT(pos <= SIZE_MAX); + *size = (size_t)pos; + _fseeki64(fp, 0, SEEK_SET); + BYTE* buffer = (BYTE*)malloc(*size); + + if (!buffer) + { + fclose(fp); + return NULL; + } + + if (fread(buffer, *size, 1, fp) != 1) + { + free(buffer); + fclose(fp); + return NULL; + } + + fclose(fp); + return buffer; +} + +static int test_progressive_load_files(char* ms_sample_path, EGFX_SAMPLE_FILE files[3][4][4]) +{ + int imageNo = 0; + int quarterNo = 0; + int passNo = 0; + /* image 1 */ + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_0_0_025_sampleimage1.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_0_0_050_sampleimage1.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_0_0_075_sampleimage1.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_0_0_100_sampleimage1.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + quarterNo = (quarterNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_0_1_025_sampleimage1.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_0_1_050_sampleimage1.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_0_1_075_sampleimage1.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_0_1_100_sampleimage1.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + quarterNo = (quarterNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_0_2_025_sampleimage1.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_0_2_050_sampleimage1.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_0_2_075_sampleimage1.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_0_2_100_sampleimage1.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + quarterNo = (quarterNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_0_3_025_sampleimage1.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_0_3_050_sampleimage1.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_0_3_075_sampleimage1.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_0_3_100_sampleimage1.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + imageNo++; + /* image 2 */ + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_1_0_025_sampleimage2.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_1_0_050_sampleimage2.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_1_0_075_sampleimage2.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_1_0_100_sampleimage2.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + quarterNo = (quarterNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_1_1_025_sampleimage2.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_1_1_050_sampleimage2.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_1_1_075_sampleimage2.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_1_1_100_sampleimage2.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + quarterNo = (quarterNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_1_2_025_sampleimage2.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_1_2_050_sampleimage2.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_1_2_075_sampleimage2.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_1_2_100_sampleimage2.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + quarterNo = (quarterNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_1_3_025_sampleimage2.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_1_3_050_sampleimage2.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_1_3_075_sampleimage2.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_1_3_100_sampleimage2.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + imageNo++; + /* image 3 */ + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_2_0_025_sampleimage3.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_2_0_050_sampleimage3.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_2_0_075_sampleimage3.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_2_0_100_sampleimage3.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + quarterNo = (quarterNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_2_1_025_sampleimage3.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_2_1_050_sampleimage3.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_2_1_075_sampleimage3.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_2_1_100_sampleimage3.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + quarterNo = (quarterNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_2_2_025_sampleimage3.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_2_2_050_sampleimage3.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_2_2_075_sampleimage3.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_2_2_100_sampleimage3.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + quarterNo = (quarterNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_2_3_025_sampleimage3.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_2_3_050_sampleimage3.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_2_3_075_sampleimage3.bin", + &(files[imageNo][quarterNo][passNo].size)); + passNo = (passNo + 1) % 4; + files[imageNo][quarterNo][passNo].buffer = + test_progressive_load_file(ms_sample_path, "compress/enc_2_3_100_sampleimage3.bin", + &(files[imageNo][quarterNo][passNo].size)); + + /* check if all test data has been loaded */ + + for (imageNo = 0; imageNo < 3; imageNo++) + { + for (quarterNo = 0; quarterNo < 4; quarterNo++) + { + for (passNo = 0; passNo < 4; passNo++) + { + if (!files[imageNo][quarterNo][passNo].buffer) + return -1; + } + } + } + + return 1; +} + +static BYTE* test_progressive_load_bitmap(char* path, char* file, size_t* size, int quarter) +{ + int status = 0; + BYTE* buffer = NULL; + wImage* image = NULL; + char* filename = NULL; + filename = GetCombinedPath(path, file); + + if (!filename) + return NULL; + + image = winpr_image_new(); + + if (!image) + return NULL; + + status = winpr_image_read(image, filename); + + if (status < 0) + return NULL; + + buffer = image->data; + *size = image->height * image->scanline; + test_fill_image_alpha_channel(image->data, image->width, image->height, 0xFF); + test_image_fill_unused_quarters(image->data, image->scanline, image->width, image->height, + quarter, 0xFF000000); + winpr_image_free(image, FALSE); + free(filename); + return buffer; +} + +static int test_progressive_load_bitmaps(char* ms_sample_path, EGFX_SAMPLE_FILE bitmaps[3][4][4]) +{ + int imageNo = 0; + int quarterNo = 0; + int passNo = 0; + /* image 1 */ + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_0_025_sampleimage1.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_0_050_sampleimage1.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_0_075_sampleimage1.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_0_100_sampleimage1.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + quarterNo = (quarterNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_1_025_sampleimage1.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_1_050_sampleimage1.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_1_075_sampleimage1.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_1_100_sampleimage1.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + quarterNo = (quarterNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_2_025_sampleimage1.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_2_050_sampleimage1.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_2_075_sampleimage1.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_2_100_sampleimage1.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + quarterNo = (quarterNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_3_025_sampleimage1.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_3_050_sampleimage1.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_3_075_sampleimage1.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_0_3_100_sampleimage1.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + imageNo++; + /* image 2 */ + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_0_025_sampleimage2.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_0_050_sampleimage2.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_0_075_sampleimage2.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_0_100_sampleimage2.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + quarterNo = (quarterNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_1_025_sampleimage2.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_1_050_sampleimage2.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_1_075_sampleimage2.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_1_100_sampleimage2.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + quarterNo = (quarterNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_2_025_sampleimage2.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_2_050_sampleimage2.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_2_075_sampleimage2.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_2_100_sampleimage2.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + quarterNo = (quarterNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_3_025_sampleimage2.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_3_050_sampleimage2.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_3_075_sampleimage2.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_1_3_100_sampleimage2.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + imageNo++; + /* image 3 */ + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_0_025_sampleimage3.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_0_050_sampleimage3.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_0_075_sampleimage3.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_0_100_sampleimage3.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + quarterNo = (quarterNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_1_025_sampleimage3.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_1_050_sampleimage3.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_1_075_sampleimage3.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_1_100_sampleimage3.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + quarterNo = (quarterNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_2_025_sampleimage3.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_2_050_sampleimage3.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_2_075_sampleimage3.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_2_100_sampleimage3.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + quarterNo = (quarterNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_3_025_sampleimage3.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_3_050_sampleimage3.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_3_075_sampleimage3.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + passNo = (passNo + 1) % 4; + bitmaps[imageNo][quarterNo][passNo].buffer = + test_progressive_load_bitmap(ms_sample_path, "decompress/dec_2_3_100_sampleimage3.bmp", + &(bitmaps[imageNo][quarterNo][passNo].size), quarterNo); + + /* check if all test data has been loaded */ + + for (imageNo = 0; imageNo < 3; imageNo++) + { + for (quarterNo = 0; quarterNo < 4; quarterNo++) + { + for (passNo = 0; passNo < 4; passNo++) + { + if (!bitmaps[imageNo][quarterNo][passNo].buffer) + return -1; + } + } + } + + return 1; +} + +static size_t test_memcmp_count(const BYTE* mem1, const BYTE* mem2, size_t size, int margin) +{ + size_t count = 0; + + for (size_t index = 0; index < size; index++) + { + if (*mem1 != *mem2) + { + const int error = (*mem1 > *mem2) ? *mem1 - *mem2 : *mem2 - *mem1; + + if (error > margin) + count++; + } + + mem1++; + mem2++; + } + + return count; +} + +static int test_progressive_decode(PROGRESSIVE_CONTEXT* progressive, EGFX_SAMPLE_FILE files[4], + EGFX_SAMPLE_FILE bitmaps[4], int quarter, int count) +{ + int nXSrc = 0; + int nYSrc = 0; + + RECTANGLE_16 clippingRect = { 0 }; + clippingRect.right = g_Width; + clippingRect.bottom = g_Height; + + for (int pass = 0; pass < count; pass++) + { + const int status = + progressive_decompress(progressive, files[pass].buffer, files[pass].size, g_DstData, + PIXEL_FORMAT_XRGB32, g_DstStep, 0, 0, NULL, 0, 0); + printf("ProgressiveDecompress: status: %d pass: %d\n", status, pass + 1); + PROGRESSIVE_BLOCK_REGION* region = &(progressive->region); + + switch (quarter) + { + case 0: + clippingRect.left = 0; + clippingRect.top = 0; + clippingRect.right = g_Width / 2; + clippingRect.bottom = g_Height / 2; + break; + + case 1: + clippingRect.left = g_Width / 2; + clippingRect.top = g_Height / 2; + clippingRect.right = g_Width; + clippingRect.bottom = g_Height; + break; + + case 2: + clippingRect.left = 0; + clippingRect.top = g_Height / 2; + clippingRect.right = g_Width / 2; + clippingRect.bottom = g_Height; + break; + + case 3: + clippingRect.left = g_Width / 2; + clippingRect.top = 0; + clippingRect.right = g_Width; + clippingRect.bottom = g_Height / 2; + break; + } + + for (UINT16 index = 0; index < region->numTiles; index++) + { + RFX_PROGRESSIVE_TILE* tile = region->tiles[index]; + + const RECTANGLE_16 tileRect = { tile->x, tile->y, tile->x + tile->width, + tile->y + tile->height }; + RECTANGLE_16 updateRect = { 0 }; + rectangles_intersection(&tileRect, &clippingRect, &updateRect); + const UINT16 nXDst = updateRect.left; + const UINT16 nYDst = updateRect.top; + const UINT16 nWidth = updateRect.right - updateRect.left; + const UINT16 nHeight = updateRect.bottom - updateRect.top; + + if ((nWidth <= 0) || (nHeight <= 0)) + continue; + + nXSrc = nXDst - tile->x; + nYSrc = nYDst - tile->y; + freerdp_image_copy(g_DstData, PIXEL_FORMAT_XRGB32, g_DstStep, nXDst, nYDst, nWidth, + nHeight, tile->data, PIXEL_FORMAT_XRGB32, 64 * 4, nXSrc, nYSrc, NULL, + FREERDP_FLIP_NONE); + } + + const size_t size = bitmaps[pass].size; + const size_t cnt = test_memcmp_count(g_DstData, bitmaps[pass].buffer, size, 1); + + if (cnt) + { + const float rate = ((float)cnt) / ((float)size) * 100.0f; + printf("Progressive RemoteFX decompression failure\n"); + printf("Actual, Expected (%" PRIuz "/%" PRIuz " = %.3f%%):\n", cnt, size, rate); + } + + // WLog_Image(progressive->log, WLOG_TRACE, g_DstData, g_Width, g_Height, 32); + } + + return 1; +} + +static int test_progressive_ms_sample(char* ms_sample_path) +{ + int count = 0; + int status = 0; + EGFX_SAMPLE_FILE files[3][4][4] = { 0 }; + EGFX_SAMPLE_FILE bitmaps[3][4][4] = { 0 }; + PROGRESSIVE_CONTEXT* progressive = NULL; + g_Width = 1920; + g_Height = 1080; + g_DstStep = g_Width * 4; + status = test_progressive_load_files(ms_sample_path, files); + + if (status < 0) + { + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 4; j++) + { + for (int k = 0; k < 4; k++) + sample_file_free(&files[i][j][k]); + } + } + + return -1; + } + + status = test_progressive_load_bitmaps(ms_sample_path, bitmaps); + + if (status < 0) + { + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 4; j++) + { + for (int k = 0; k < 4; k++) + sample_file_free(&files[i][j][k]); + } + } + + return -1; + } + + count = 4; + progressive = progressive_context_new(FALSE); + g_DstData = winpr_aligned_malloc(g_DstStep * g_Height, 16); + progressive_create_surface_context(progressive, 0, g_Width, g_Height); + + /* image 1 */ + + if (1) + { + printf("\nSample Image 1\n"); + test_image_fill(g_DstData, g_DstStep, 0, 0, g_Width, g_Height, 0xFF000000); + test_progressive_decode(progressive, files[0][0], bitmaps[0][0], 0, count); + test_progressive_decode(progressive, files[0][1], bitmaps[0][1], 1, count); + test_progressive_decode(progressive, files[0][2], bitmaps[0][2], 2, count); + test_progressive_decode(progressive, files[0][3], bitmaps[0][3], 3, count); + } + + /* image 2 */ + + if (0) + { + printf("\nSample Image 2\n"); /* sample data is in incorrect order */ + test_image_fill(g_DstData, g_DstStep, 0, 0, g_Width, g_Height, 0xFF000000); + test_progressive_decode(progressive, files[1][0], bitmaps[1][0], 0, count); + test_progressive_decode(progressive, files[1][1], bitmaps[1][1], 1, count); + test_progressive_decode(progressive, files[1][2], bitmaps[1][2], 2, count); + test_progressive_decode(progressive, files[1][3], bitmaps[1][3], 3, count); + } + + /* image 3 */ + + if (0) + { + printf("\nSample Image 3\n"); /* sample data is in incorrect order */ + test_image_fill(g_DstData, g_DstStep, 0, 0, g_Width, g_Height, 0xFF000000); + test_progressive_decode(progressive, files[2][0], bitmaps[2][0], 0, count); + test_progressive_decode(progressive, files[2][1], bitmaps[2][1], 1, count); + test_progressive_decode(progressive, files[2][2], bitmaps[2][2], 2, count); + test_progressive_decode(progressive, files[2][3], bitmaps[2][3], 3, count); + } + + progressive_context_free(progressive); + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 4; j++) + { + for (int k = 0; k < 4; k++) + { + sample_file_free(&bitmaps[i][j][k]); + sample_file_free(&files[i][j][k]); + } + } + } + + winpr_aligned_free(g_DstData); + return 0; +} + +static BOOL diff(BYTE a, BYTE b) +{ + BYTE big = MAX(a, b); + BYTE little = MIN(a, b); + if (big - little <= 0x25) + return TRUE; + return FALSE; +} + +static BOOL colordiff(UINT32 format, UINT32 a, UINT32 b) +{ + BYTE ar = 0; + BYTE ag = 0; + BYTE ab = 0; + BYTE aa = 0; + BYTE br = 0; + BYTE bg = 0; + BYTE bb = 0; + BYTE ba = 0; + FreeRDPSplitColor(a, format, &ar, &ag, &ab, &aa, NULL); + FreeRDPSplitColor(b, format, &br, &bg, &bb, &ba, NULL); + if (!diff(aa, ba) || !diff(ar, br) || !diff(ag, bg) || !diff(ab, bb)) + return FALSE; + return TRUE; +} + +static BOOL test_encode_decode(const char* path) +{ + BOOL res = FALSE; + int rc = 0; + BYTE* resultData = NULL; + BYTE* dstData = NULL; + UINT32 dstSize = 0; + UINT32 ColorFormat = PIXEL_FORMAT_BGRX32; + REGION16 invalidRegion = { 0 }; + wImage* image = winpr_image_new(); + wImage* dstImage = winpr_image_new(); + char* name = GetCombinedPath(path, "progressive.bmp"); + PROGRESSIVE_CONTEXT* progressiveEnc = progressive_context_new(TRUE); + PROGRESSIVE_CONTEXT* progressiveDec = progressive_context_new(FALSE); + + region16_init(&invalidRegion); + if (!image || !dstImage || !name || !progressiveEnc || !progressiveDec) + goto fail; + + rc = winpr_image_read(image, name); + if (rc <= 0) + goto fail; + + resultData = calloc(image->scanline, image->height); + if (!resultData) + goto fail; + + // Progressive encode + rc = progressive_compress(progressiveEnc, image->data, image->scanline * image->height, + ColorFormat, image->width, image->height, image->scanline, NULL, + &dstData, &dstSize); + + // Progressive decode + rc = progressive_create_surface_context(progressiveDec, 0, image->width, image->height); + if (rc <= 0) + goto fail; + + rc = progressive_decompress(progressiveDec, dstData, dstSize, resultData, ColorFormat, + image->scanline, 0, 0, &invalidRegion, 0, 0); + if (rc < 0) + goto fail; + + // Compare result + if (0) // Dump result image for manual inspection + { + *dstImage = *image; + dstImage->data = resultData; + winpr_image_write(dstImage, "/tmp/test.bmp"); + } + for (UINT32 y = 0; y < image->height; y++) + { + const BYTE* orig = &image->data[y * image->scanline]; + const BYTE* dec = &resultData[y * image->scanline]; + for (UINT32 x = 0; x < image->width; x++) + { + const BYTE* po = &orig[x * 4]; + const BYTE* pd = &dec[x * 4]; + + const DWORD a = FreeRDPReadColor(po, ColorFormat); + const DWORD b = FreeRDPReadColor(pd, ColorFormat); + if (!colordiff(ColorFormat, a, b)) + { + printf("xxxxxxx [%u:%u] [%s] %08X != %08X\n", x, y, + FreeRDPGetColorFormatName(ColorFormat), a, b); + goto fail; + } + } + } + res = TRUE; +fail: + region16_uninit(&invalidRegion); + progressive_context_free(progressiveEnc); + progressive_context_free(progressiveDec); + winpr_image_free(image, TRUE); + winpr_image_free(dstImage, FALSE); + free(resultData); + free(name); + return res; +} + +int TestFreeRDPCodecProgressive(int argc, char* argv[]) +{ + int rc = -1; + char* ms_sample_path = NULL; + char name[8192]; + SYSTEMTIME systemTime; + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + GetSystemTime(&systemTime); + sprintf_s(name, sizeof(name), + "EGFX_PROGRESSIVE_MS_SAMPLE-%04" PRIu16 "%02" PRIu16 "%02" PRIu16 "%02" PRIu16 + "%02" PRIu16 "%02" PRIu16 "%04" PRIu16, + systemTime.wYear, systemTime.wMonth, systemTime.wDay, systemTime.wHour, + systemTime.wMinute, systemTime.wSecond, systemTime.wMilliseconds); + ms_sample_path = _strdup(CMAKE_CURRENT_SOURCE_DIR); + + if (!ms_sample_path) + { + printf("Memory allocation failed\n"); + goto fail; + } + + if (winpr_PathFileExists(ms_sample_path)) + { + /* + if (test_progressive_ms_sample(ms_sample_path) < 0) + goto fail; + */ + if (!test_encode_decode(ms_sample_path)) + goto fail; + rc = 0; + } + +fail: + free(ms_sample_path); + return rc; +} diff --git a/libfreerdp/codec/test/TestFreeRDPCodecRemoteFX.c b/libfreerdp/codec/test/TestFreeRDPCodecRemoteFX.c new file mode 100644 index 0000000..be67cea --- /dev/null +++ b/libfreerdp/codec/test/TestFreeRDPCodecRemoteFX.c @@ -0,0 +1,894 @@ +#include <winpr/crt.h> +#include <winpr/print.h> + +#include <freerdp/freerdp.h> +#include <freerdp/codec/rfx.h> + +static BYTE encodeHeaderSample[] = { + /* as in 4.2.2 */ + 0xc0, 0xcc, 0x0c, 0x00, 0x00, 0x00, 0xca, 0xac, 0xcc, 0xca, 0x00, 0x01, 0xc3, 0xcc, 0x0d, 0x00, + 0x00, 0x00, 0x01, 0xff, 0x00, 0x40, 0x00, 0x28, 0xa8, 0xc1, 0xcc, 0x0a, 0x00, 0x00, 0x00, 0x01, + 0x01, 0x00, 0x01, 0xc2, 0xcc, 0x0c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x40, 0x00, 0x40, 0x00 +}; + +static BYTE encodeDataSample[] = { + /* FRAME_BEGIN as in 4.2.3 */ + 0xc4, 0xcc, 0x0e, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + + /* REGION as in 4.2.3 */ + 0xc6, 0xcc, 0x17, 0x00, 0x00, 0x00, 0x01, 0x00, 0xcd, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, + 0x00, 0x40, 0x00, 0xc1, 0xca, 0x01, 0x00, + + /* TILESET as in 4.2.4.1 */ + 0xc7, 0xcc, 0x3e, 0x0b, 0x00, 0x00, 0x01, 0x00, 0xc2, 0xca, 0x00, 0x00, 0x51, 0x50, 0x01, 0x40, + 0x01, 0x00, 0x23, 0x0b, 0x00, 0x00, 0x66, 0x66, 0x77, 0x88, 0x98, 0xc3, 0xca, 0x23, 0x0b, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xae, 0x03, 0xcf, 0x03, 0x93, 0x03, 0xc0, 0x01, + 0x01, 0x15, 0x48, 0x99, 0xc7, 0x41, 0xa1, 0x12, 0x68, 0x11, 0xdc, 0x22, 0x29, 0x74, 0xef, 0xfd, + 0x20, 0x92, 0xe0, 0x4e, 0xa8, 0x69, 0x3b, 0xfd, 0x41, 0x83, 0xbf, 0x28, 0x53, 0x0c, 0x1f, 0xe2, + 0x54, 0x0c, 0x77, 0x7c, 0xa3, 0x05, 0x7c, 0x30, 0xd0, 0x9c, 0xe8, 0x09, 0x39, 0x1a, 0x5d, 0xff, + 0xe2, 0x01, 0x22, 0x13, 0x80, 0x90, 0x87, 0xd2, 0x9f, 0xfd, 0xfd, 0x50, 0x09, 0x0d, 0x24, 0xa0, + 0x8f, 0xab, 0xfe, 0x3c, 0x04, 0x84, 0xc6, 0x9c, 0xde, 0xf8, 0x80, 0xc3, 0x22, 0x50, 0xaf, 0x4c, + 0x2a, 0x7f, 0xfe, 0xe0, 0x5c, 0xa9, 0x52, 0x8a, 0x06, 0x7d, 0x3d, 0x09, 0x03, 0x65, 0xa3, 0xaf, + 0xd2, 0x61, 0x1f, 0x72, 0x04, 0x50, 0x8d, 0x3e, 0x16, 0x4a, 0x3f, 0xff, 0xfd, 0x41, 0x42, 0x87, + 0x24, 0x37, 0x06, 0x17, 0x2e, 0x56, 0x05, 0x9c, 0x1c, 0xb3, 0x84, 0x6a, 0xff, 0xfb, 0x43, 0x8b, + 0xa3, 0x7a, 0x32, 0x43, 0x28, 0xe1, 0x1f, 0x50, 0x54, 0xfc, 0xca, 0xa5, 0xdf, 0xff, 0x08, 0x04, + 0x48, 0x15, 0x61, 0xd9, 0x76, 0x43, 0xf8, 0x2a, 0x07, 0xe9, 0x65, 0xf7, 0xc6, 0x89, 0x2d, 0x40, + 0xa1, 0xc3, 0x35, 0x8d, 0xf5, 0xed, 0xf5, 0x91, 0xae, 0x2f, 0xcc, 0x01, 0xce, 0x03, 0x48, 0xc0, + 0x8d, 0x63, 0xf4, 0xfd, 0x50, 0x20, 0x2d, 0x0c, 0x9b, 0xb0, 0x8d, 0x13, 0xc0, 0x8a, 0x09, 0x52, + 0x1b, 0x02, 0x6e, 0x42, 0x3b, 0xd0, 0x13, 0x4e, 0x84, 0x01, 0x26, 0x88, 0x6a, 0x04, 0x84, 0x34, + 0x2a, 0xa5, 0x00, 0xba, 0x54, 0x48, 0x58, 0xea, 0x54, 0x02, 0xb4, 0x1d, 0xa7, 0xfa, 0x47, 0x82, + 0xec, 0x7a, 0x77, 0xfd, 0x00, 0x92, 0x66, 0x62, 0x04, 0xa6, 0x9b, 0xff, 0xf6, 0x80, 0xc0, 0x69, + 0x01, 0xc2, 0x3e, 0x90, 0x14, 0x20, 0x2f, 0xfc, 0x40, 0x96, 0x59, 0x58, 0x0c, 0xb1, 0x13, 0x68, + 0x20, 0x2e, 0xb5, 0xf5, 0xdf, 0xff, 0xf8, 0xfc, 0x56, 0x88, 0x60, 0x24, 0x53, 0xb5, 0x41, 0x46, + 0x5f, 0xf8, 0xf1, 0x7e, 0xde, 0x4a, 0x08, 0x97, 0xe0, 0x55, 0x03, 0x8f, 0xe5, 0x75, 0x61, 0x03, + 0xf2, 0xe1, 0x90, 0x01, 0xa2, 0x8e, 0x88, 0x04, 0x98, 0x05, 0x93, 0x6b, 0xff, 0xea, 0xc0, 0x60, + 0xa1, 0x88, 0x04, 0x49, 0xbf, 0xf7, 0xff, 0x8c, 0xb4, 0x59, 0x90, 0x80, 0x30, 0x64, 0x53, 0xff, + 0xf5, 0xc4, 0x48, 0xda, 0xda, 0xcb, 0x80, 0x38, 0x61, 0x57, 0xb2, 0xaf, 0x00, 0xe8, 0x7b, 0x46, + 0xe6, 0xd8, 0x02, 0x03, 0x8a, 0x06, 0x18, 0x14, 0x32, 0x83, 0xd0, 0x8a, 0xee, 0xbc, 0x81, 0xb4, + 0x28, 0xc4, 0x7f, 0xf9, 0xa1, 0x69, 0x00, 0x91, 0xc5, 0x51, 0xff, 0xfe, 0x3f, 0xe9, 0xf1, 0x70, + 0x30, 0x24, 0x10, 0xa7, 0xcb, 0x1f, 0x8a, 0x24, 0x93, 0xed, 0x83, 0x00, 0x36, 0x20, 0xd1, 0x50, + 0xe7, 0xd8, 0xad, 0x58, 0x20, 0x09, 0x22, 0x80, 0xd0, 0xca, 0x5d, 0x1a, 0xd7, 0xf1, 0x60, 0x75, + 0x2a, 0xf2, 0xd7, 0xf8, 0xc0, 0x32, 0x45, 0x86, 0x00, 0x43, 0x01, 0xfe, 0x80, 0xf7, 0x42, 0x81, + 0x74, 0x84, 0x4c, 0xa1, 0x60, 0x4c, 0xcb, 0x14, 0x58, 0x01, 0x4d, 0x18, 0xa1, 0xaa, 0x47, 0x0e, + 0x11, 0x1a, 0x40, 0x7d, 0x41, 0x02, 0xe3, 0x30, 0xcd, 0x33, 0x81, 0x34, 0x06, 0x46, 0x83, 0xa2, + 0x47, 0x1c, 0x04, 0xaa, 0x20, 0x12, 0xa2, 0x8b, 0x81, 0xc4, 0x9c, 0xa0, 0x2e, 0x06, 0x32, 0xf8, + 0x86, 0x85, 0x01, 0xe8, 0x70, 0xf9, 0x46, 0x09, 0x6a, 0xbf, 0xe0, 0xf5, 0xa4, 0xc8, 0x78, 0xe7, + 0xd2, 0x97, 0x0b, 0xbc, 0x3c, 0x97, 0xff, 0xd5, 0x40, 0x94, 0xb2, 0xc1, 0x18, 0x18, 0x11, 0x1f, + 0x43, 0xc1, 0x18, 0xc3, 0x83, 0x7f, 0x9a, 0x31, 0xc4, 0x8e, 0x70, 0x56, 0xda, 0xf6, 0x17, 0xde, + 0xd1, 0x02, 0x0d, 0x42, 0x21, 0x13, 0xdc, 0x3a, 0x3c, 0x40, 0x9e, 0xf4, 0x01, 0x43, 0xea, 0x0c, + 0x46, 0x73, 0xa2, 0x7b, 0x0c, 0x80, 0xff, 0xe4, 0xad, 0x2e, 0x09, 0xb4, 0x63, 0xb0, 0x8c, 0x54, + 0x59, 0xfa, 0xac, 0x76, 0x36, 0x10, 0x05, 0xf0, 0x98, 0x88, 0x83, 0x42, 0x00, 0x20, 0x71, 0xcc, + 0xc1, 0xa9, 0x97, 0x3e, 0x5a, 0x0d, 0x04, 0x50, 0x92, 0x23, 0x20, 0x0d, 0x0a, 0x1c, 0x57, 0xd7, + 0xff, 0x10, 0xf2, 0x03, 0x0f, 0x58, 0x1b, 0xa5, 0x11, 0xf8, 0xf1, 0xb4, 0x12, 0xdb, 0x1a, 0x48, + 0x56, 0x1f, 0xe3, 0xc7, 0x50, 0xe9, 0x16, 0xb4, 0xbc, 0xb0, 0x40, 0x93, 0xea, 0xb5, 0x5b, 0x2f, + 0xfc, 0x50, 0x0a, 0x6f, 0xcc, 0x25, 0xe0, 0x06, 0xab, 0x5f, 0x24, 0xfe, 0x8b, 0xcb, 0x42, 0x43, + 0x7e, 0x69, 0x02, 0x25, 0xc7, 0x38, 0x00, 0x6e, 0xe5, 0x80, 0xa8, 0xa4, 0x30, 0x44, 0x15, 0x8f, + 0xe9, 0x0c, 0xd3, 0xa6, 0xc2, 0x14, 0x34, 0x4a, 0xfe, 0x03, 0x7f, 0x06, 0xa5, 0x91, 0x02, 0x54, + 0xf1, 0xa1, 0xa1, 0x53, 0xbf, 0x11, 0xf2, 0x8f, 0x83, 0x67, 0x80, 0x09, 0x08, 0x12, 0x3f, 0xfd, + 0x44, 0x91, 0xc2, 0x83, 0x30, 0x50, 0x07, 0x02, 0x82, 0x4d, 0x31, 0x34, 0x06, 0x41, 0x79, 0x6f, + 0xf0, 0xcc, 0x03, 0x79, 0x00, 0x2c, 0x05, 0x24, 0xec, 0x8d, 0x29, 0x15, 0xaf, 0x44, 0xc8, 0xeb, + 0x4f, 0xe1, 0xfd, 0xf1, 0x41, 0x48, 0x81, 0x08, 0xaf, 0xfe, 0x51, 0x48, 0xce, 0xe7, 0xf9, 0xb6, + 0x0a, 0x30, 0x83, 0x11, 0xf0, 0x0c, 0x3b, 0xd2, 0xa6, 0x24, 0x24, 0xef, 0x25, 0xfa, 0x5a, 0x3e, + 0x92, 0x3e, 0x79, 0x0e, 0x35, 0x61, 0xc8, 0xaa, 0x1c, 0x2e, 0x9a, 0x27, 0x7f, 0xff, 0xf0, 0x7d, + 0x30, 0x5b, 0xbc, 0x91, 0xff, 0xfe, 0x43, 0x24, 0x28, 0x66, 0xa7, 0x70, 0x99, 0x28, 0x6e, 0x2b, + 0x18, 0x2b, 0xd4, 0xa1, 0x77, 0x3b, 0x96, 0x9f, 0xf7, 0xeb, 0xbe, 0x1f, 0x04, 0x34, 0x75, 0x84, + 0x31, 0x42, 0x4c, 0x65, 0xaa, 0x09, 0x50, 0xa0, 0xc4, 0x51, 0x31, 0xd3, 0x26, 0x3a, 0x1b, 0xf4, + 0x6e, 0x4a, 0x4e, 0x17, 0x25, 0x84, 0x78, 0x7d, 0x2c, 0x3f, 0x46, 0x18, 0xca, 0x5f, 0xf9, 0xe5, + 0x38, 0x2f, 0xd8, 0x71, 0x94, 0x94, 0xe2, 0xcc, 0xa3, 0x15, 0xb0, 0xda, 0xa9, 0xcb, 0x58, 0xe4, + 0x18, 0x77, 0x93, 0x8a, 0x51, 0xc6, 0x23, 0xc4, 0x4e, 0x6d, 0xd9, 0x14, 0x1e, 0x9b, 0x8d, 0xbc, + 0xcb, 0x9d, 0xc4, 0x18, 0x05, 0xf5, 0xa9, 0x29, 0xf8, 0x6d, 0x29, 0x38, 0xc7, 0x44, 0xe5, 0x3a, + 0xcd, 0xba, 0x61, 0x98, 0x4a, 0x57, 0x02, 0x96, 0x42, 0x02, 0xd9, 0x37, 0x11, 0xde, 0x2d, 0xd4, + 0x3f, 0xfe, 0x61, 0xe7, 0x33, 0xd7, 0x89, 0x4a, 0xdd, 0xb0, 0x34, 0x47, 0xf4, 0xdc, 0xad, 0xaa, + 0xc9, 0x9d, 0x7e, 0x6d, 0x4b, 0xcc, 0xdc, 0x17, 0x89, 0x57, 0xfd, 0xbb, 0x37, 0x75, 0x47, 0x5a, + 0xec, 0x2c, 0x6e, 0x3c, 0x15, 0x92, 0x54, 0x64, 0x2c, 0xab, 0x9e, 0xab, 0x2b, 0xdd, 0x3c, 0x66, + 0xa0, 0x8f, 0x47, 0x5e, 0x93, 0x1a, 0x37, 0x16, 0xf4, 0x89, 0x23, 0x00, 0x00, 0xb0, 0x33, 0x56, + 0xfa, 0x14, 0x1e, 0xff, 0x48, 0x7a, 0x7e, 0x0f, 0x10, 0x1f, 0xf4, 0x91, 0xc8, 0x10, 0x56, 0x84, + 0xff, 0x08, 0xec, 0xb4, 0xac, 0x0e, 0x0f, 0xff, 0xad, 0xc5, 0xe0, 0x1a, 0x2f, 0x82, 0x04, 0x9f, + 0x91, 0xc2, 0x0e, 0xfe, 0x48, 0x36, 0x79, 0x01, 0x42, 0x14, 0xff, 0xfe, 0x30, 0xf0, 0x08, 0x18, + 0xf1, 0x81, 0x45, 0x9a, 0x60, 0xc1, 0x79, 0xf0, 0x14, 0x12, 0x10, 0xce, 0xea, 0x31, 0x5a, 0xff, + 0xfc, 0x20, 0x13, 0x82, 0x2f, 0xc9, 0x02, 0x1f, 0x81, 0xcb, 0x00, 0xe1, 0x10, 0xd2, 0xb4, 0xbe, + 0x87, 0xff, 0xb0, 0x1e, 0x27, 0x81, 0xb7, 0x04, 0x06, 0x3c, 0xc2, 0x04, 0xf6, 0x06, 0x0e, 0x28, + 0xbc, 0x40, 0xbf, 0x12, 0x1e, 0x86, 0xd4, 0x6a, 0x7f, 0x18, 0x1b, 0x96, 0x85, 0x4c, 0x16, 0x80, + 0xdf, 0x2c, 0xa5, 0x8d, 0x86, 0xa3, 0x4a, 0x8a, 0xb4, 0x1b, 0xa1, 0x38, 0xa9, 0xd5, 0xff, 0xff, + 0xea, 0x06, 0x20, 0xd2, 0x95, 0x1e, 0xf4, 0x2f, 0xb2, 0x12, 0x0e, 0x61, 0x78, 0x4a, 0x17, 0x52, + 0x5d, 0xe4, 0x25, 0x1f, 0xfe, 0xc0, 0xb3, 0x1f, 0xff, 0xff, 0xec, 0x02, 0x82, 0x80, 0x90, 0x41, + 0x88, 0xde, 0x48, 0x2c, 0x42, 0x52, 0x0b, 0x2f, 0x43, 0x7e, 0x50, 0x78, 0xf2, 0x67, 0x78, 0x41, + 0x34, 0x3d, 0xc8, 0x0f, 0x67, 0xa1, 0xeb, 0x21, 0xfe, 0xc0, 0x1f, 0x22, 0x60, 0x41, 0x6c, 0x00, + 0x92, 0x4b, 0x60, 0x10, 0xd0, 0x0d, 0x01, 0x35, 0x05, 0x0e, 0x87, 0xa2, 0xa0, 0x5d, 0x1f, 0xa3, + 0xaf, 0x7f, 0xf1, 0xbe, 0x8f, 0xcd, 0xa5, 0x00, 0x1c, 0x10, 0x40, 0x15, 0x76, 0x81, 0x05, 0xef, + 0xee, 0x00, 0x60, 0x84, 0x00, 0x99, 0x40, 0x4a, 0x82, 0x17, 0xe9, 0xfc, 0xc4, 0x7f, 0xff, 0xfd, + 0x04, 0x80, 0x06, 0x06, 0xdc, 0xaf, 0xa7, 0x7e, 0x94, 0x75, 0x74, 0x01, 0x00, 0xe0, 0x91, 0x00, + 0x85, 0x7f, 0x8e, 0xd6, 0x0b, 0x20, 0x21, 0x30, 0xca, 0x62, 0x8e, 0x07, 0x04, 0xe9, 0x45, 0x40, + 0x5f, 0x47, 0x4a, 0x30, 0x15, 0x41, 0xcb, 0xdf, 0xff, 0xfc, 0xbf, 0xc3, 0xb4, 0x46, 0x6a, 0x01, + 0x40, 0xd0, 0xa7, 0x34, 0x18, 0x24, 0x1c, 0x2a, 0x45, 0xfe, 0xa8, 0x05, 0x08, 0x61, 0xfd, 0xa8, + 0x80, 0x71, 0x01, 0x25, 0x9c, 0xc1, 0x47, 0x17, 0x37, 0x02, 0x7a, 0x15, 0xff, 0xf3, 0x01, 0x45, + 0x7f, 0xd6, 0x80, 0x60, 0x83, 0x67, 0xf8, 0x9d, 0x2f, 0xf4, 0xdd, 0x8c, 0x30, 0x01, 0x51, 0x42, + 0xbc, 0x43, 0x7a, 0x6b, 0x9f, 0x84, 0x1e, 0x00, 0x48, 0xc1, 0xe0, 0xb7, 0xe0, 0x7e, 0x99, 0xf2, + 0x4a, 0xe9, 0x40, 0x02, 0x81, 0xc3, 0x00, 0x24, 0x3a, 0xc5, 0x52, 0x0f, 0x91, 0xc8, 0x68, 0x25, + 0x40, 0x99, 0xa4, 0x25, 0x1a, 0x04, 0xd0, 0xa2, 0x91, 0xdd, 0xeb, 0x93, 0x00, 0x21, 0x49, 0x24, + 0x8b, 0x40, 0x75, 0x38, 0x14, 0xa1, 0xfd, 0x3f, 0x88, 0x25, 0xbf, 0x32, 0x00, 0xe3, 0x19, 0xfc, + 0xb9, 0xf8, 0x6f, 0x81, 0xc0, 0x01, 0xb3, 0x93, 0x20, 0x09, 0x08, 0x25, 0x84, 0xe1, 0x34, 0xd4, + 0x1b, 0x48, 0x88, 0x11, 0xa0, 0x15, 0x59, 0xd7, 0x07, 0x81, 0x81, 0x3b, 0xa1, 0x40, 0x2e, 0x2f, + 0x48, 0x70, 0x09, 0xc4, 0x76, 0x49, 0x0f, 0x2e, 0x50, 0x2e, 0x46, 0x19, 0xa4, 0x16, 0xa2, 0x1b, + 0x84, 0xa2, 0x89, 0x58, 0xfc, 0x4f, 0x3f, 0x40, 0x90, 0x4c, 0xa3, 0x01, 0x32, 0x09, 0x02, 0x80, + 0x9c, 0x91, 0x13, 0x2c, 0xba, 0xde, 0x5d, 0x99, 0xf2, 0xff, 0xff, 0x3d, 0x5a, 0x1f, 0xa9, 0x02, + 0x90, 0x8f, 0xf3, 0x08, 0xbd, 0x01, 0xf8, 0xd0, 0x2a, 0x95, 0x41, 0x0c, 0x40, 0x0a, 0x20, 0xc4, + 0xd4, 0xcc, 0x6b, 0x0f, 0xf0, 0x80, 0xb1, 0x5d, 0x28, 0x3d, 0x08, 0xc2, 0xf8, 0x31, 0x02, 0x49, + 0x88, 0x14, 0x28, 0xed, 0xe8, 0x86, 0x3b, 0x00, 0x9f, 0x95, 0x06, 0x37, 0x15, 0xa4, 0x59, 0xc8, + 0x80, 0xb6, 0x10, 0xf0, 0xe5, 0xb8, 0x18, 0x00, 0x56, 0x1c, 0xff, 0x95, 0x21, 0x0e, 0x7f, 0x2b, + 0xc5, 0x08, 0x59, 0x10, 0xe1, 0x46, 0x31, 0x8d, 0xec, 0xe0, 0xa1, 0x99, 0xbb, 0x21, 0xff, 0xfe, + 0x30, 0x10, 0xd0, 0x05, 0xe3, 0x08, 0x50, 0xfc, 0xf3, 0x0e, 0x00, 0x8d, 0x68, 0x8e, 0x07, 0xa6, + 0x80, 0x34, 0x42, 0xed, 0x1f, 0x88, 0x00, 0xf0, 0x8a, 0x21, 0xae, 0xf7, 0xfb, 0x80, 0x28, 0x86, + 0x0f, 0xff, 0xff, 0x82, 0xea, 0x47, 0x95, 0x91, 0xe0, 0x04, 0x01, 0x44, 0x0c, 0x29, 0xff, 0x0e, + 0x33, 0xe8, 0xc0, 0x54, 0x04, 0x23, 0xfc, 0x81, 0x5b, 0xf0, 0x3c, 0x07, 0x10, 0x70, 0x30, 0xd8, + 0x21, 0x6f, 0xef, 0xde, 0x46, 0x09, 0x43, 0xfa, 0x5f, 0xff, 0x0d, 0x72, 0x30, 0xdd, 0x00, 0xdb, + 0xe4, 0x48, 0x24, 0x97, 0x08, 0x46, 0xb1, 0x49, 0xc4, 0x4d, 0x80, 0x12, 0x60, 0xff, 0xa4, 0xa6, + 0xff, 0xf6, 0x8c, 0x00, 0x40, 0x05, 0x02, 0xb4, 0x0f, 0xf0, 0x3e, 0xfc, 0x84, 0x38, 0x81, 0x94, + 0x8b, 0xfe, 0x49, 0xef, 0xc0, 0x10, 0x49, 0x88, 0x28, 0xa2, 0x1c, 0x2a, 0x8b, 0x64, 0xd4, 0x86, + 0xd7, 0xff, 0xff, 0xff, 0xeb, 0x91, 0x6b, 0x11, 0x10, 0x00, 0x69, 0x4c, 0xbf, 0xb4, 0x1c, 0xd8, + 0x00, 0x07, 0x16, 0x80, 0x60, 0x0a, 0x1c, 0x82, 0x42, 0x27, 0x82, 0x43, 0xc9, 0x0a, 0x64, 0x20, + 0x5a, 0x5f, 0x4e, 0xbf, 0x8c, 0x38, 0x82, 0x36, 0x02, 0x07, 0x72, 0x79, 0x07, 0x23, 0xb4, 0xbb, + 0x57, 0x5f, 0xe8, 0x04, 0xdd, 0x39, 0xe9, 0x07, 0x95, 0xbe, 0x04, 0x2b, 0xdd, 0x8e, 0x22, 0xdc, + 0x14, 0x2c, 0x61, 0xa3, 0xa9, 0xcd, 0x4f, 0x82, 0x5d, 0xa0, 0x44, 0xdf, 0xf4, 0x96, 0xff, 0xf5, + 0x2b, 0xff, 0xfe, 0x01, 0x19, 0xd2, 0xa2, 0x9e, 0x43, 0xa5, 0x7f, 0xf0, 0x4c, 0x4c, 0x2b, 0x3c, + 0x33, 0xe2, 0x55, 0xff, 0x04, 0x06, 0x29, 0x2c, 0x0d, 0x22, 0x5d, 0x7c, 0x93, 0xba, 0x18, 0xaf, + 0xf9, 0x32, 0xa6, 0xc3, 0x99, 0x46, 0x79, 0xe3, 0x06, 0xa6, 0x38, 0x8b, 0x92, 0x22, 0x4b, 0xdb, + 0x1b, 0x36, 0x20, 0xb0, 0x6c, 0x20, 0xce, 0x37, 0x42, 0xe1, 0x66, 0xd4, 0x49, 0x34, 0x42, 0x8b, + 0xfa, 0x9c, 0x12, 0x99, 0xdc, 0x06, 0x87, 0xfa, 0x46, 0xf8, 0x2f, 0x04, 0xa9, 0xd8, 0x82, 0x07, + 0xa6, 0x30, 0x0f, 0xc0, 0xdf, 0x35, 0xe8, 0x90, 0xf0, 0xff, 0xff, 0xa8, 0xe0, 0xd7, 0x02, 0x60, + 0x1a, 0xc3, 0x20, 0x28, 0xa2, 0x31, 0x29, 0x3c, 0xeb, 0x04, 0xa5, 0xdd, 0x48, 0x0e, 0x82, 0xa4, + 0xb6, 0x56, 0x22, 0x06, 0x57, 0xe0, 0xda, 0x10, 0x27, 0x31, 0x0e, 0x11, 0x77, 0xfe, 0x02, 0x60, + 0x16, 0x48, 0x81, 0x8c, 0x0d, 0x05, 0x17, 0x7f, 0xcb, 0xbb, 0x7e, 0x25, 0x2a, 0x41, 0xfd, 0x8a, + 0x7f, 0xc9, 0x36, 0x7c, 0xe0, 0x98, 0x7e, 0x92, 0xef, 0x7e, 0x06, 0x03, 0x13, 0x3e, 0x20, 0x3a, + 0xbf, 0x4c, 0xc3, 0x0f, 0x2e, 0x80, 0x74, 0xbf, 0x39, 0x3c, 0xf0, 0xa6, 0xb2, 0xe9, 0x3f, 0x41, + 0x55, 0x1f, 0x2c, 0xf5, 0xd2, 0x7e, 0x8c, 0xae, 0x4e, 0xaa, 0x61, 0x3c, 0xbc, 0x3f, 0xc4, 0xc7, + 0x36, 0xdc, 0x23, 0xc8, 0xb8, 0x52, 0xe2, 0x8a, 0x80, 0x18, 0x00, 0x00, 0xb2, 0x46, 0xa2, 0x56, + 0x0d, 0x12, 0x94, 0xaa, 0xbd, 0x01, 0x07, 0xff, 0xfa, 0x34, 0x0c, 0x5f, 0xf8, 0x0c, 0x12, 0x50, + 0xaf, 0xd6, 0xd1, 0x89, 0x40, 0xa4, 0xff, 0xe0, 0xce, 0xc4, 0x49, 0x25, 0x9d, 0xc1, 0xff, 0x7e, + 0x60, 0x24, 0x5d, 0xcc, 0x10, 0xc0, 0xbe, 0x5a, 0x12, 0xd3, 0xc3, 0xfe, 0x2d, 0x40, 0x7c, 0x28, + 0x9e, 0x71, 0x01, 0xd2, 0x6e, 0x86, 0x0b, 0xc8, 0xf2, 0x9b, 0x45, 0x08, 0x4c, 0x04, 0x52, 0x7e, + 0xf2, 0x7e, 0xd9, 0xcc, 0x0b, 0x1c, 0x20, 0x80, 0xae, 0xaf, 0xfe, 0xb0, 0x6d, 0x23, 0xf2, 0x41, + 0xe3, 0x2e, 0x20, 0x11, 0x4b, 0x74, 0x89, 0xdd, 0xff, 0xa8, 0x38, 0xa3, 0x95, 0x82, 0x15, 0xf0, + 0xd0, 0xd5, 0xf1, 0x92, 0x8e, 0xee, 0xc0, 0x26, 0x81, 0xe9, 0x47, 0xff, 0xee, 0x0d, 0x20, 0x34, + 0x31, 0x3a, 0xef, 0x40, 0xb2, 0x29, 0x47, 0x19, 0x7f, 0x04, 0x27, 0xf1, 0x90, 0x85, 0x09, 0x86, + 0x7d, 0x42, 0xe2, 0x54, 0x5d, 0x5f, 0xe8, 0x0e, 0xd0, 0x2c, 0xaa, 0x16, 0xbf, 0x04, 0xa7, 0xf8, + 0xa2, 0x46, 0x0b, 0x08, 0x7a, 0x79, 0xe9, 0x28, 0x62, 0x7c, 0x33, 0xf4, 0x0b, 0x14, 0x82, 0xfa, + 0x61, 0xeb, 0xc1, 0xff, 0x4c, 0xa4, 0x11, 0x7f, 0x03, 0x68, 0x44, 0xc1, 0x1f, 0x81, 0x3a, 0x6c, + 0x77, 0x95, 0x02, 0x2b, 0x53, 0x80, 0xe5, 0x10, 0x1e, 0x90, 0xe8, 0xfd, 0x1f, 0xa6, 0x40, 0x0b, + 0x13, 0xff, 0x4e, 0x4d, 0x7f, 0x52, 0xe8, 0xaf, 0x9a, 0xc1, 0x80, 0x0f, 0x0a, 0x14, 0x02, 0x3c, + 0xc0, 0x09, 0x13, 0xe7, 0xdc, 0xc0, 0x1a, 0x28, 0xa0, 0xe4, 0x83, 0x8e, 0x03, 0x88, 0xd5, 0xaf, + 0x1a, 0xbd, 0x91, 0x00, 0xb7, 0x4e, 0xba, 0xdf, 0xf8, 0xdb, 0xcc, 0x02, 0x43, 0xc4, 0x14, 0x2a, + 0x3f, 0xc8, 0x0d, 0x09, 0x1c, 0x44, 0xf4, 0x01, 0x3c, 0xca, 0x28, 0x56, 0x80, 0xa6, 0x85, 0x00, + 0xea, 0x3e, 0x8f, 0xeb, 0x9f, 0xfc, 0x6e, 0x07, 0xc4, 0xe0, 0x30, 0x78, 0xa0, 0x1e, 0x6f, 0x54, + 0x78, 0x51, 0xff, 0x56, 0x4a, 0x01, 0x47, 0x02, 0x4c, 0x21, 0x3b, 0xfb, 0x90, 0x0a, 0xcc, 0x1d, + 0xd2, 0x47, 0xff, 0xfc, 0x70, 0x18, 0x22, 0xc0, 0xb9, 0x2f, 0xe9, 0x7f, 0x91, 0xd3, 0x66, 0x2f, + 0x80, 0x2c, 0x24, 0xa7, 0xfa, 0x84, 0x51, 0xab, 0x6b, 0x72, 0x00, 0xab, 0x33, 0x04, 0xcf, 0x43, + 0xff, 0x17, 0x51, 0x84, 0x0c, 0x01, 0x50, 0x10, 0x8f, 0x90, 0x34, 0x41, 0x44, 0x84, 0x8e, 0x08, + 0x19, 0x04, 0x48, 0x50, 0x84, 0x38, 0x3d, 0x02, 0x52, 0xf9, 0x7c, 0xd2, 0xd0, 0x1f, 0x13, 0x42, + 0xa0, 0x21, 0x41, 0xc4, 0x02, 0x02, 0x3d, 0x09, 0xc8, 0xfd, 0x60, 0x7d, 0x35, 0x4f, 0x7f, 0xff, + 0xf9, 0x97, 0x6a, 0xd8, 0x00, 0xc3, 0x83, 0x00, 0x09, 0x50, 0x4b, 0x90, 0x8a, 0xc7, 0x94, 0x4d, + 0x47, 0xc1, 0x62, 0x32, 0x28, 0x24, 0x09, 0x52, 0x2e, 0x2e, 0x1c, 0x96, 0x44, 0xa0, 0x09, 0xc8, + 0xce, 0x64, 0xa9, 0x1c, 0x19, 0x0e, 0x52, 0x3e, 0x3e, 0x19, 0x93, 0xa0, 0x36, 0x26, 0x22, 0x08, + 0x9a, 0x00, 0xdd, 0x66, 0x3a, 0x93, 0xd5, 0x89, 0xd1, 0x40, 0x06, 0xd4, 0xa8, 0x22, 0x73, 0x7b, + 0x3d, 0x3f, 0xe3, 0x04, 0x94, 0xff, 0xff, 0xff, 0xff, 0x0c, 0x56, 0x77, 0xac, 0xe0, 0xc4, 0x06, + 0x1f, 0xb8, 0xa5, 0x80, 0xfd, 0x68, 0x1c, 0x32, 0x16, 0x03, 0xde, 0x71, 0x2a, 0x3d, 0x14, 0x19, + 0xbe, 0xc2, 0x88, 0xd9, 0x24, 0x92, 0x5f, 0xc5, 0x90, 0x0a, 0x85, 0xc2, 0x3f, 0x87, 0x03, 0xa8, + 0x26, 0x17, 0xc4, 0x06, 0x86, 0x12, 0x87, 0x76, 0x0a, 0x48, 0x16, 0xed, 0x96, 0x93, 0xec, 0x1b, + 0x30, 0x73, 0xe8, 0x1a, 0x3f, 0xff, 0x4d, 0xce, 0x40, 0xf3, 0x0c, 0x51, 0x4b, 0x84, 0x9e, 0x67, + 0x2b, 0x15, 0x40, 0x1a, 0xa0, 0xfc, 0x10, 0x0f, 0xd8, 0x81, 0x35, 0x87, 0xff, 0x98, 0x0f, 0x40, + 0x00, 0xba, 0xc0, 0x71, 0xe2, 0x00, 0x18, 0x28, 0xb3, 0x82, 0xcc, 0x80, 0x6a, 0xa0, 0x43, 0xff, + 0x2d, 0xd6, 0x04, 0x8a, 0x68, 0xff, 0xff, 0xff, 0xfc, 0x1a, 0xf3, 0x1a, 0x2a, 0x06, 0xc0, 0x01, + 0x40, 0x0c, 0x30, 0xc1, 0xd0, 0xd7, 0x4f, 0xcb, 0x74, 0x1f, 0x07, 0xd3, 0xb4, 0x0d, 0x88, 0x98, + 0xea, 0xda, 0x9f, 0xce, 0x2b, 0x3c, 0x55, 0xb3, 0x40, 0x14, 0xff, 0xff, 0xff, 0xea, 0xdb, 0x9b, + 0x92, 0xd8, 0x68, 0x08, 0x0b, 0x41, 0x09, 0x26, 0x40, 0x8c, 0xf1, 0xb0, 0x9a, 0x98, 0xc0, 0x80, + 0x8b, 0xf0, 0x3d, 0xe7, 0xec, 0x19, 0x68, 0x21, 0x03, 0x29, 0x7f, 0xe1, 0x6d, 0x4c, 0x0f, 0x01, + 0xd1, 0x51, 0x01, 0x1a, 0x50, 0x2a, 0x59, 0x27, 0x80, 0xc1, 0x6e, 0x33, 0xf1, 0x80, 0xe1, 0x49, + 0x08, 0xe9, 0x17, 0xff, 0xff, 0xff, 0x80, 0x5a, 0x10, 0x10, 0x36, 0x5e, 0xca, 0xf8, 0x3a, 0x00, + 0x1e, 0xb0, 0x06, 0x84, 0x01, 0xf3, 0x07, 0x1b, 0x4a, 0xc0, 0x1e, 0x21, 0x43, 0x8e, 0xa5, 0x55, + 0x77, 0xc7, 0x65, 0x7c, 0xc2, 0xdf, 0x5e, 0x0c, 0x42, 0x20, 0xd2, 0x48, 0x61, 0xc8, 0x1c, 0x65, + 0xf8, 0xfe, 0x4c, 0x88, 0x71, 0x1f, 0x82, 0x50, 0x81, 0xa3, 0x54, 0x09, 0x13, 0x28, 0x52, 0xf5, + 0xe0, 0x82, 0xc3, 0x06, 0x7f, 0xfa, 0x2c, 0xcf, 0xf8, 0xf4, 0x7f, 0xff, 0xfd, 0x01, 0x49, 0xa4, + 0xb8, 0xde, 0x62, 0x84, 0xfe, 0xed, 0x65, 0x1f, 0x3c, 0x3c, 0xb2, 0x50, 0x76, 0x30, 0x5b, 0x03, + 0xc0, 0x08, 0xa6, 0x64, 0x90, 0xc8, 0xcd, 0x14, 0x6e, 0x69, 0x46, 0x7a, 0xc6, 0x1c, 0x87, 0xd7, + 0x48, 0x7b, 0x49, 0x05, 0x2d, 0x5e, 0x7f, 0xcb, 0x67, 0xf0, 0xd9, 0x0d, 0x1e, 0x9e, 0x53, 0xb7, + 0x64, 0xa5, 0xa5, 0x10, 0x39, 0x06, 0x11, 0x3f, 0xb1, 0xa9, 0xa6, 0xe8, 0x4d, 0x47, 0x77, 0xda, + 0x43, 0x76, 0x89, 0x45, 0x09, 0x70, 0xc2, 0x38, 0x0f, 0x09, 0x6f, 0xe7, 0x2d, 0x82, 0x35, 0x07, + 0xfe, 0x64, 0x18, 0x2e, 0xb8, 0x04, 0x42, 0x54, 0x80, 0x43, 0x12, 0x6c, 0x9a, 0x55, 0xc9, 0x0a, + 0xa0, 0x79, 0x47, 0x52, 0x65, 0x2a, 0xff, 0x50, 0x11, 0xc9, 0x4e, 0xfe, 0x5b, 0x30, 0xa4, 0xe8, + 0x30, 0x63, 0xff, 0x21, 0x12, 0x1b, 0xdc, 0x1c, 0x01, 0x41, 0x51, 0x1f, 0xff, 0xfa, 0xc3, 0xe3, + 0x55, 0xf1, 0x66, 0xe2, 0xd5, 0x78, 0x5e, 0xfa, 0x4d, 0xf2, 0x61, 0x01, 0x26, 0x15, 0xa9, 0xf9, + 0xd9, 0x32, 0x41, 0x90, 0x36, 0x4e, 0xae, 0xe3, 0x0b, 0x16, 0x56, 0x8c, 0x6e, 0x42, 0x5d, 0xd8, + 0x1e, 0xfe, 0x1d, 0x40, 0x3a, 0x50, 0x9f, 0x09, 0x14, 0xeb, 0x6e, 0x48, 0x7a, 0x91, 0x88, 0x7b, + 0x7d, 0x8f, 0x72, 0x42, 0x39, 0xb0, 0x1c, 0x65, 0x18, 0x23, 0x8b, 0x60, 0x30, 0x00, + + /* FRAME_END as in 4.2.3 */ + 0xc5, 0xcc, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00 +}; + +static UINT32 srefImage + [] = { /* 4.2.4.4 Inverse Color Conversion */ + 0x00229cdf, 0x00249de0, 0x00259fe2, 0x002ca5e8, 0x00229cdf, 0x00229ce0, 0x00239de0, + 0x00229ce0, 0x00229cdf, 0x00229cdf, 0x00239ce0, 0x00249ce0, 0x00249ce0, 0x00219ce3, + 0x001e9ce6, 0x00209ae2, 0x002299dd, 0x002199de, 0x00209adf, 0x00209ae0, 0x001f9be0, + 0x001e9ae0, 0x001d99e0, 0x001c98e0, 0x001b97df, 0x001e96dc, 0x002194d9, 0x001f93dd, + 0x001d93e0, 0x001b94dc, 0x001895d8, 0x001c92db, 0x00208fde, 0x001b91de, 0x001693df, + 0x001793df, 0x001992df, 0x001891df, 0x00178fdf, 0x00178edf, 0x00168dde, 0x00158cdd, + 0x00148cdc, 0x00128cda, 0x00118cd9, 0x00118bd9, 0x00128ada, 0x001289da, 0x001288db, + 0x001187da, 0x001186da, 0x001085da, 0x000f85d9, 0x000f84d9, 0x000e83d9, 0x000d82d8, + 0x000d82d8, 0x000d81d8, 0x000d80d7, 0x000d7fd7, 0x000d7ed6, 0x000d7ed6, 0x000d7ed6, + 0x000d7ed6, 0x00259fe1, 0x0027a1e2, 0x0029a2e3, 0x002ba4e6, 0x00249fe1, 0x00249fe1, + 0x00249fe1, 0x00249ee1, 0x00239ee1, 0x00249ee1, 0x00249ee1, 0x00259de1, 0x00259de2, + 0x00249de2, 0x00229de2, 0x00229ce1, 0x00229bdf, 0x00219ce0, 0x00209ce1, 0x00209ce2, + 0x00209ce2, 0x00209ae0, 0x002199de, 0x001f99df, 0x001d98e0, 0x001e97e0, 0x001f97e0, + 0x001d96df, 0x001c95de, 0x001c94e0, 0x001c94e1, 0x001d93e1, 0x001d92e0, 0x001b93de, + 0x001a94dc, 0x001a93de, 0x001a93e0, 0x001992e0, 0x001891df, 0x00188fdf, 0x00178edf, + 0x00168ede, 0x00158edd, 0x00148ddc, 0x00138ddb, 0x00138cdb, 0x00138bdb, 0x00128adb, + 0x001289db, 0x001288db, 0x001187db, 0x001186db, 0x001085db, 0x000f84da, 0x000e83d9, + 0x000e83d9, 0x000e83d9, 0x000e82d9, 0x000e81d8, 0x000e80d8, 0x000d7fd7, 0x000d7fd7, + 0x000d7fd7, 0x000d7fd7, 0x0027a3e3, 0x002aa4e3, 0x002ea6e3, 0x002aa4e3, 0x0026a2e3, + 0x0026a1e3, 0x0025a1e3, 0x0025a0e3, 0x0025a0e3, 0x0025a0e3, 0x00259fe3, 0x00269fe3, + 0x00269ee4, 0x00279ee1, 0x00279edf, 0x00259ee0, 0x00239ee1, 0x00219ee2, 0x00209ee4, + 0x00209de4, 0x00219de3, 0x00229be0, 0x002499dc, 0x002299de, 0x001f98e0, 0x001d99e4, + 0x001b9ae7, 0x001c98e2, 0x001c96dc, 0x001e94e3, 0x002092ea, 0x001d94e6, 0x001a96e2, + 0x001c96de, 0x001d95da, 0x001c94de, 0x001b94e1, 0x001a93e0, 0x001a92e0, 0x001991e0, + 0x001890e0, 0x001790df, 0x00178fde, 0x00168fde, 0x00158edd, 0x00148ddd, 0x00138cdc, + 0x00138bdc, 0x00128adc, 0x001289dc, 0x001188dc, 0x001187dd, 0x001086dd, 0x000f85db, + 0x000e83d9, 0x000e84da, 0x000f84da, 0x000e83da, 0x000e82d9, 0x000e81d9, 0x000e80d8, + 0x000e80d8, 0x000e80d8, 0x000e80d8, 0x002aa7e5, 0x002da7e4, 0x0031a8e3, 0x002ca6e3, + 0x0027a4e4, 0x0027a3e4, 0x0027a3e4, 0x0027a3e4, 0x0026a2e4, 0x0026a2e4, 0x0027a1e5, + 0x0027a0e5, 0x0027a0e6, 0x0026a0e5, 0x0025a0e4, 0x00259fe4, 0x00259ee3, 0x00239ee5, + 0x00229fe6, 0x00229fe5, 0x00229fe4, 0x0013a5e6, 0x001b9fe8, 0x0016a0e8, 0x0011a0e7, + 0x00129fef, 0x00139ef7, 0x001b99ec, 0x00179ae2, 0x00149ce4, 0x001d98e5, 0x001c97e6, + 0x001b96e7, 0x001c98dc, 0x001d97df, 0x001c96e1, 0x001c94e2, 0x001b94e1, 0x001b93e1, + 0x001a93e0, 0x001a92e0, 0x001991e0, 0x001890e0, 0x001790df, 0x00168fdf, 0x00158ede, + 0x00158dde, 0x00148cdd, 0x00138bdc, 0x00128add, 0x001289dd, 0x001188de, 0x001187de, + 0x000f85dc, 0x000d83da, 0x000f85db, 0x001086db, 0x000f84db, 0x000f83da, 0x000e82da, + 0x000e81da, 0x000e81da, 0x000e81da, 0x000e81da, 0x002caae7, 0x0030aae5, 0x0034abe3, + 0x002ea8e4, 0x0029a6e5, 0x0028a6e5, 0x0028a5e5, 0x0028a5e5, 0x0028a5e6, 0x0028a4e6, + 0x0028a3e7, 0x0028a2e7, 0x0028a1e8, 0x0025a2e9, 0x0023a3ea, 0x0025a0e8, 0x00279ee6, + 0x00259fe7, 0x0023a0e9, 0x0018a4f5, 0x000ea7ff, 0x001ba6de, 0x00558ebb, 0x006f839c, + 0x0089797e, 0x008d797c, 0x00917979, 0x007f7b94, 0x005687af, 0x00229bd6, 0x0004a4fd, + 0x00109df4, 0x001c97eb, 0x001c9ada, 0x001c98e4, 0x001c97e3, 0x001d95e2, 0x001c95e2, + 0x001c94e2, 0x001c94e1, 0x001b94e1, 0x001a93e1, 0x001a92e1, 0x001991e1, 0x001890e1, + 0x00178fe0, 0x00158edf, 0x00148dde, 0x00138cdd, 0x00128bde, 0x00128adf, 0x001289df, + 0x001188e0, 0x000f85dd, 0x000d83da, 0x000f85db, 0x001187dd, 0x001086dc, 0x000f84dc, + 0x000e83db, 0x000e81db, 0x000e81db, 0x000e81db, 0x000e81db, 0x0030abe5, 0x0036afe8, + 0x0034abe4, 0x002faae5, 0x002ba8e6, 0x0036aee8, 0x0026a6e8, 0x0029a7e7, 0x002ca8e7, + 0x002da7e6, 0x002fa5e5, 0x002ca5e7, 0x0029a4e9, 0x002ba5e5, 0x002ca5e2, 0x0010aaef, + 0x0013adf6, 0x0023a3f8, 0x006091a5, 0x00a6755d, 0x00ec5915, 0x00ff490c, 0x00fa5504, + 0x00ff590f, 0x00ff5d1b, 0x00ff6116, 0x00fa6412, 0x00ff550f, 0x00ff4b0d, 0x00fb4918, + 0x00f54823, 0x008e737e, 0x00269eda, 0x0006a2ff, 0x001d97e2, 0x001799ea, 0x001c97e4, + 0x001a98e4, 0x001898e4, 0x001a96e3, 0x001b95e3, 0x001a94e2, 0x001a93e0, 0x001992e1, + 0x001891e2, 0x001790e1, 0x00168fe0, 0x00158fdf, 0x00138ede, 0x00138ddf, 0x00138ce0, + 0x00128be0, 0x001189e0, 0x001087de, 0x000f85db, 0x00138ae0, 0x000f87dc, 0x000f86dc, + 0x000f85dc, 0x000f84dc, 0x000e83db, 0x000e83db, 0x000e83db, 0x000e83db, 0x0034abe2, + 0x003cb4ec, 0x0034ace5, 0x0031abe6, 0x002daae8, 0x0044b6eb, 0x0024a7ea, 0x0029aaea, + 0x002face9, 0x0032a9e6, 0x0035a7e3, 0x0030a7e6, 0x002ba8ea, 0x0025aaf0, 0x0020adf6, + 0x004d8ba7, 0x00b8674c, 0x00ff5510, 0x00f7650c, 0x00f86313, 0x00fa611b, 0x00f0671f, + 0x00fc6222, 0x00fb6926, 0x00f96f29, 0x00f67122, 0x00f3721b, 0x00f26b20, 0x00f16424, + 0x00ff5622, 0x00ff531f, 0x00ff4b17, 0x00ff440e, 0x00b1615b, 0x001f95e0, 0x00129bf0, + 0x001c9ae5, 0x00189ae6, 0x00159be7, 0x001898e6, 0x001b95e5, 0x001b95e2, 0x001995e0, + 0x001994e1, 0x001892e2, 0x001792e1, 0x001691e0, 0x001590df, 0x00148fdf, 0x00148fe0, + 0x00148fe1, 0x00128de1, 0x00108be0, 0x001189de, 0x001186dd, 0x00178fe4, 0x000e87db, + 0x000e87dc, 0x000f87dd, 0x000f85dc, 0x000e84dc, 0x000e84dc, 0x000e84dc, 0x000e84dc, + 0x0036b1eb, 0x0036b4f0, 0x002eafed, 0x002caeec, 0x002aadec, 0x0041b4ef, 0x0029abe9, + 0x002cabe8, 0x002fabe7, 0x0031abe6, 0x0032aae6, 0x002faae7, 0x002ca9e8, 0x0025a7eb, + 0x00946a5f, 0x00ff3e06, 0x00f95618, 0x00e27312, 0x00f87329, 0x00f77427, 0x00f77626, + 0x00f27628, 0x00f8712b, 0x00f9772e, 0x00f97e30, 0x00f77f2e, 0x00f5812b, 0x00f57b2c, + 0x00f5752d, 0x00fd6a2b, 0x00fb652a, 0x00f65e2c, 0x00f1572e, 0x00ff4810, 0x00ff460f, + 0x00817680, 0x0002a7f1, 0x002496ea, 0x00199be4, 0x001b98e4, 0x001d96e5, 0x001b96e2, + 0x001a96e0, 0x001995e1, 0x001794e3, 0x001793e2, 0x001692e1, 0x001691e0, 0x001590df, + 0x001591e1, 0x001591e3, 0x00138fe1, 0x00108ce0, 0x00128be0, 0x00158ae0, 0x00168de2, + 0x000f89dd, 0x000f88dd, 0x000f88dd, 0x000f86dd, 0x000f85dc, 0x000f85dc, 0x000f85dc, + 0x000f85dc, 0x005fc1e7, 0x0057bee8, 0x004fbbe9, 0x004ebae6, 0x004ebae3, 0x0051b6ee, + 0x002eaee8, 0x002eade6, 0x002fabe5, 0x002face7, 0x002eade9, 0x002eace7, 0x002daae5, + 0x0015b2ff, 0x00ec4310, 0x00f15016, 0x00f75d1c, 0x00f87123, 0x00f9862a, 0x00f6882d, + 0x00f48b31, 0x00f48532, 0x00f47f33, 0x00f78535, 0x00fa8c37, 0x00f88e39, 0x00f7903a, + 0x00f88b38, 0x00f98635, 0x00f87e35, 0x00f77635, 0x00f76d34, 0x00f76532, 0x00f85e31, + 0x00f95730, 0x00ff5125, 0x00f65237, 0x0003a5fd, 0x001e9be1, 0x001e98e3, 0x001f96e5, + 0x001c97e2, 0x001a97df, 0x001896e1, 0x001795e4, 0x001794e3, 0x001793e2, 0x001692e1, + 0x001692e0, 0x001693e2, 0x001794e4, 0x001391e2, 0x000f8ee0, 0x00148ee1, 0x00198ee3, + 0x00148ce1, 0x000f8bde, 0x000f8ade, 0x000f89de, 0x000f88dd, 0x000f86dd, 0x000f86dd, + 0x000f86dd, 0x000f86dd, 0x003cb6ee, 0x0036b4ef, 0x0030b2f0, 0x0030b1ee, 0x002fb1ec, + 0x0038b0ef, 0x002eaee9, 0x002faee8, 0x0031ade6, 0x002fafe8, 0x002eb1ea, 0x0031adec, + 0x0029afee, 0x0030aac8, 0x00ff3d05, 0x00fa501a, 0x00f96021, 0x00f87428, 0x00f7882f, + 0x00fa9638, 0x00f59b38, 0x00f5973b, 0x00f6923e, 0x00f89440, 0x00fa9742, 0x00fa9a44, + 0x00fa9d46, 0x00f99845, 0x00f89444, 0x00f98d43, 0x00fa8641, 0x00f97d3f, 0x00f9743d, + 0x00f77039, 0x00f56d35, 0x00ff6122, 0x00bf6c63, 0x00129eef, 0x00229ae8, 0x001c99ed, + 0x00179ce4, 0x001498f0, 0x001b94e1, 0x001a96e2, 0x001998e3, 0x001897e4, 0x001896e5, + 0x001895e4, 0x001993e2, 0x001792e1, 0x001590df, 0x001692e2, 0x001793e5, 0x001490e4, + 0x00128ee2, 0x00118de3, 0x00108de3, 0x00118bde, 0x001289d9, 0x000f88e2, 0x000c89dd, + 0x001085e0, 0x000987e4, 0x000987e4, 0x0040b5e9, 0x003bb4e9, 0x0037b2ea, 0x0037b2e9, + 0x0038b1e8, 0x0033b0ea, 0x002eaeeb, 0x0030afe9, 0x0033afe8, 0x0030b2ea, 0x002eb5ec, + 0x0034aff2, 0x0025b4f7, 0x008d7f86, 0x00f64f00, 0x00ed5c1e, 0x00fa6326, 0x00f7762d, + 0x00f58a35, 0x00fea242, 0x00f7ab3f, 0x00f7a843, 0x00f7a548, 0x00f9a34a, 0x00faa24c, + 0x00fba64f, 0x00fcaa52, 0x00f9a652, 0x00f7a252, 0x00fa9c50, 0x00fd974e, 0x00fc8d4b, + 0x00fb8348, 0x00f68341, 0x00f1823a, 0x00f5732c, 0x00718cac, 0x00179af0, 0x002599ef, + 0x002697e9, 0x00269bc6, 0x001696f1, 0x001d91e3, 0x001c96e3, 0x001b9be3, 0x001a99e6, + 0x001998e9, 0x001b97e7, 0x001c95e5, 0x001891df, 0x00138dda, 0x001992e2, 0x001e98ea, + 0x001592e6, 0x000b8de2, 0x000e8ee5, 0x00108fe9, 0x00128cdf, 0x001489d4, 0x000e88e6, + 0x00088cdc, 0x001184e4, 0x000488ec, 0x000488ec, 0x003eb6ea, 0x003bb5eb, 0x0038b4eb, + 0x0038b4eb, 0x0038b3eb, 0x0035b2eb, 0x0033b1ec, 0x0034b1eb, 0x0035b1ea, 0x0032b3e9, + 0x0030b5e9, 0x0034b0f0, 0x0023b6f8, 0x00c56044, 0x00f9540c, 0x00f26322, 0x00f77029, + 0x00f77d2f, 0x00f78b35, 0x00fba142, 0x00f6b046, 0x00fbb44f, 0x00f7b051, 0x00f9af54, + 0x00fbad56, 0x00fcb25a, 0x00feb75d, 0x00fab35f, 0x00f6b061, 0x00faac5d, 0x00fda95a, + 0x00fb9f55, 0x00f99551, 0x00f7914b, 0x00f68d45, 0x00ff7e23, 0x001ba5f0, 0x00129ef4, + 0x002896f1, 0x00239fb1, 0x006c9600, 0x003c9c82, 0x00179ef8, 0x00169cf4, 0x00149de3, + 0x00169ae5, 0x001897e7, 0x001995e6, 0x001a93e5, 0x001993e3, 0x001793e0, 0x001c98e6, + 0x001a95e5, 0x001692e5, 0x00138fe5, 0x00138ceb, 0x00138be3, 0x000087e4, 0x00007cf5, + 0x001a86d3, 0x000d8cf1, 0x00008fe2, 0x000d85ea, 0x000886f1, 0x003cb7ec, 0x003bb7ed, + 0x003ab6ed, 0x0039b6ed, 0x0038b5ed, 0x0037b5ed, 0x0037b4ed, 0x0037b3ed, 0x0036b3ec, + 0x0034b4e9, 0x0031b5e5, 0x0035b1ef, 0x0021b8fa, 0x00fd4203, 0x00fc581e, 0x00f86a26, + 0x00f47c2d, 0x00f78431, 0x00f98c36, 0x00f8a041, 0x00f6b54d, 0x00fec05b, 0x00f6bc5a, + 0x00f8ba5d, 0x00fbb861, 0x00fdbe65, 0x00ffc469, 0x00fbc16c, 0x00f5bd70, 0x00fabc6b, + 0x00febb66, 0x00fab160, 0x00f6a75a, 0x00f89f55, 0x00fa984f, 0x00df956f, 0x0008a6fc, + 0x00259ddb, 0x00159ff3, 0x004aa172, 0x0069a90d, 0x0062a406, 0x005a981b, 0x0034969b, + 0x000e99ff, 0x001297f2, 0x001695e4, 0x001793e5, 0x001892e5, 0x001995e6, 0x001a98e7, + 0x00209deb, 0x001593df, 0x001892e4, 0x001a91e9, 0x002095eb, 0x00259dd1, 0x00d0f772, + 0x00c1f396, 0x000083f1, 0x001782a0, 0x003c7e2f, 0x001787cc, 0x000b8ada, 0x003db9ed, + 0x003cb8ed, 0x003bb8ed, 0x003ab7ed, 0x0039b7ed, 0x0039b7ed, 0x0039b6ed, 0x003ab6ed, + 0x003ab6ed, 0x0037b4ed, 0x0034b2ec, 0x0035abf3, 0x006e96b3, 0x00ff4601, 0x00f86520, + 0x00f67329, 0x00f58131, 0x00f78b37, 0x00f9953e, 0x00f8a649, 0x00f8b854, 0x00fcc260, + 0x00f8c465, 0x00f9c36a, 0x00fac26e, 0x00fac773, 0x00facb77, 0x00fbcb7b, 0x00fccb7e, + 0x00fac87b, 0x00f8c578, 0x00f9bc72, 0x00fbb46d, 0x00f6b069, 0x00feaa57, 0x0094a0a5, + 0x0013a1f3, 0x00219df0, 0x00199eff, 0x0071c124, 0x0079b826, 0x0072b21e, 0x006aaa24, + 0x0067a125, 0x00649a19, 0x00419d72, 0x001f9fcb, 0x001994ff, 0x001399f1, 0x00199cf4, + 0x001ea0f8, 0x001b9cff, 0x001193f6, 0x001293f1, 0x001393ec, 0x000083ff, 0x0072cca0, + 0x00cbf982, 0x00d0ffac, 0x0079a046, 0x00337700, 0x003a7c03, 0x000d8de2, 0x000d8edb, + 0x003fbbee, 0x003ebaed, 0x003db9ed, 0x003cb9ed, 0x003bb8ed, 0x003bb8ed, 0x003cb9ee, + 0x003cb9ee, 0x003db9ef, 0x003ab4f1, 0x0037aff3, 0x0032b3fe, 0x00b48f7d, 0x00ff5907, + 0x00f37122, 0x00f57c2b, 0x00f68735, 0x00f7923d, 0x00f89d45, 0x00f9ac50, 0x00f9bb5a, + 0x00f9c465, 0x00facd71, 0x00facd76, 0x00facd7b, 0x00f7cf80, 0x00f4d286, 0x00fcd689, + 0x00ffd98c, 0x00fbd48b, 0x00f3cf8a, 0x00f9c885, 0x00ffc17f, 0x00f5c27d, 0x00ffbc5e, + 0x0048abdc, 0x001e9deb, 0x001ea2e8, 0x001da8e5, 0x0099d31c, 0x008acb22, 0x0082c427, + 0x007abc2c, 0x0075b429, 0x0070ad25, 0x006dab17, 0x006ba908, 0x005ea912, 0x00519f54, + 0x00489b6d, 0x003e9887, 0x003b9592, 0x00389880, 0x00449663, 0x00509446, 0x0083b43c, + 0x004f851b, 0x00afe187, 0x009fcc83, 0x00368011, 0x0043821c, 0x0032853c, 0x000492f9, + 0x001092dd, 0x0040bcee, 0x003fbcee, 0x003ebbee, 0x003dbaed, 0x003cbaed, 0x003cb9ed, + 0x003cb9ec, 0x003cb9ec, 0x003cb8ec, 0x003fb4f0, 0x0043aff5, 0x000ebbe9, 0x00ffb897, + 0x00f7814d, 0x00f57623, 0x00f6812e, 0x00f88c39, 0x00f89943, 0x00f8a64d, 0x00f8b257, + 0x00f9bd60, 0x00fac96d, 0x00fbd47b, 0x00fad681, 0x00fad788, 0x00fbd98e, 0x00fbda93, + 0x00fae5a1, 0x00fed692, 0x00fadea0, 0x00f9db98, 0x00fad694, 0x00fbd090, 0x00ffd285, + 0x00ffc778, 0x00009afd, 0x0026a8f2, 0x0020a4f8, 0x0053bea5, 0x00a4da31, 0x009dd638, + 0x0097d03a, 0x0091ca3d, 0x008bc539, 0x0085c035, 0x007dbe31, 0x0074bc2d, 0x0076b81c, + 0x0077b027, 0x0072ab25, 0x006da724, 0x006ba328, 0x0068a31f, 0x0058951a, 0x0078b745, + 0x00bbf181, 0x0073ad4c, 0x00417c15, 0x00508b1e, 0x0043861c, 0x00498614, 0x0017868b, + 0x000b90f6, 0x00168ee8, 0x0042beef, 0x0041bdee, 0x0040bcee, 0x003fbced, 0x003ebbed, + 0x003dbaec, 0x003db9eb, 0x003cb8ea, 0x003bb7e9, 0x0039b9f0, 0x0037bbf7, 0x0050b5dc, + 0x00ff9744, 0x00fec49d, 0x00f87a24, 0x00f88530, 0x00f9913d, 0x00f8a049, 0x00f7af55, + 0x00f8b85d, 0x00f9c065, 0x00face75, 0x00fcdb85, 0x00fbde8d, 0x00fae195, 0x00fee29b, + 0x00ffe2a0, 0x00fbe9a4, 0x00ffbe6b, 0x00fdde9f, 0x00ffe8a6, 0x00fbe3a3, 0x00f8dea0, + 0x00fdd899, 0x00b6bdab, 0x00119ff1, 0x001ea4e9, 0x001a9fff, 0x0089d465, 0x00b0e245, + 0x00b0e04e, 0x00acdc4e, 0x00a7d94e, 0x00a1d649, 0x009ad345, 0x0097ce3d, 0x0094c935, + 0x008dc534, 0x0086c133, 0x007bbc32, 0x006fb731, 0x006db330, 0x006cae2e, 0x007eba3f, + 0x0070a531, 0x007bb54f, 0x00579a20, 0x005c9f2b, 0x00519425, 0x0080b965, 0x00609a1d, + 0x000390e3, 0x00118ef2, 0x001c89f2, 0x0044c0ef, 0x0043bfef, 0x0042beee, 0x0040bdee, + 0x003fbcee, 0x003fbbed, 0x0040baeb, 0x003eb9ed, 0x003cb9ee, 0x0037b9eb, 0x0027bcf7, + 0x00949c8f, 0x00fb9637, 0x00f9bc7c, 0x00f9b585, 0x00f7994a, 0x00f69b43, 0x00f6a64e, + 0x00f7b259, 0x00f8bc66, 0x00fac672, 0x00fad380, 0x00fae08d, 0x00f9e698, 0x00f9eba2, + 0x00feeaa6, 0x00ffeaab, 0x00fcefa9, 0x00faba62, 0x00fbdc99, 0x00fff4b9, 0x00fbecb2, + 0x00f7e6ab, 0x00ffe5a3, 0x0064b1d1, 0x00199ff0, 0x00269fe9, 0x000499f2, 0x00e3f051, + 0x00d5ef58, 0x00c0e364, 0x00bde165, 0x00bae065, 0x00b5de5d, 0x00b0dc56, 0x00aad74e, + 0x00a3d346, 0x009bd043, 0x0093cd3f, 0x008cc93e, 0x0084c63c, 0x0081c139, 0x007dbc36, + 0x008bc746, 0x0089c245, 0x0063a02c, 0x0065aa2c, 0x005ea42d, 0x00509626, 0x00a4cf98, + 0x00d9eadd, 0x00b9ddff, 0x00389ef4, 0x00008fd4, 0x0046c1ef, 0x0044c0ef, 0x0043bfef, + 0x0042beef, 0x0040bdef, 0x0042bced, 0x0043baec, 0x0040baf0, 0x003dbaf4, 0x0035b8e7, + 0x0017bdf7, 0x00d97f50, 0x00f79147, 0x00f7a554, 0x00ffdbba, 0x00f8a24d, 0x00f3a549, + 0x00f5ad53, 0x00f7b55e, 0x00f9c16f, 0x00fbcc7f, 0x00f9d88a, 0x00f8e595, 0x00f8eda2, + 0x00f8f5ae, 0x00fff3b2, 0x00fff2b6, 0x00fef5ae, 0x00f4b659, 0x00f9db93, 0x00feffcd, + 0x00fbf6c1, 0x00f7edb6, 0x00fff2ac, 0x0013a4f7, 0x0016a5f0, 0x0018a5e8, 0x0056b4cd, + 0x00f1f271, 0x00d5ef84, 0x00cfe67b, 0x00cde77c, 0x00cbe77c, 0x00c9e672, 0x00c7e567, + 0x00bce15f, 0x00b1dd57, 0x00a9dc51, 0x00a0da4b, 0x009dd749, 0x009ad447, 0x0094cf43, + 0x008fcb3f, 0x0088c43c, 0x0082be39, 0x0072b430, 0x0063a928, 0x0059a028, 0x004e9827, + 0x00a0c479, 0x00fffbf7, 0x007fd3f5, 0x00038fe2, 0x000e89e2, 0x0048c3ef, 0x0046c2ef, + 0x0045c1f0, 0x0043c0f0, 0x0042bff0, 0x0042beee, 0x0043bdec, 0x0041bcef, 0x003fbcf2, + 0x002fc0fe, 0x0036bdfc, 0x00f54c00, 0x00ff8a52, 0x00faa65e, 0x00fdc48e, 0x00fbc185, + 0x00f5ae50, 0x00f7b65e, 0x00f9be6c, 0x00fac978, 0x00fbd485, 0x00fede98, 0x00ffe8aa, + 0x00fdeeae, 0x00f9f5b2, 0x00fcf6ba, 0x00fff7c2, 0x00fcf0b2, 0x00f7cc6e, 0x00fbde91, + 0x00fdfcca, 0x00fffbd1, 0x00fffdc8, 0x00cae4c8, 0x0016a1f2, 0x001da4ef, 0x0012a1f1, + 0x009fd5b9, 0x00eaf28c, 0x00dcf095, 0x00d9eb90, 0x00d9ec93, 0x00d9ec95, 0x00d6eb8c, + 0x00d4ea83, 0x00c9e779, 0x00bfe36f, 0x00b8e368, 0x00b1e262, 0x00afe05e, 0x00addf5a, + 0x00a3d952, 0x0099d449, 0x008ecb41, 0x0084c33a, 0x0075b833, 0x0066ac2c, 0x005da329, + 0x00559927, 0x004b9421, 0x002499b9, 0x001593fe, 0x000993d8, 0x000f90d8, 0x004ac5ef, + 0x0048c4f0, 0x0046c2f0, 0x0045c1f1, 0x0043c0f1, 0x0043bfef, 0x0043bfed, 0x0042beee, + 0x0041bdf0, 0x0038bbf0, 0x0072a1b8, 0x00ff5d1e, 0x00f97931, 0x00f5a151, 0x00f9ad61, + 0x00fee0bd, 0x00f8b758, 0x00fabf69, 0x00fcc87a, 0x00fcd282, 0x00fcdc8b, 0x00fbde8f, + 0x00fbe193, 0x00fbeba4, 0x00fbf5b5, 0x00faf8c2, 0x00f9fcce, 0x00f9ecb7, 0x00fae183, + 0x00fee290, 0x00fbfac8, 0x00fdf8d8, 0x00fffccb, 0x008bcedc, 0x00189fee, 0x0025a3ee, + 0x000b9dfb, 0x00e8f6a5, 0x00e4f1a6, 0x00e4f0a6, 0x00e4efa6, 0x00e5f1aa, 0x00e6f2ad, + 0x00e3f1a6, 0x00e0ef9e, 0x00d7ec93, 0x00cde987, 0x00c8ea80, 0x00c2eb78, 0x00c1ea73, + 0x00c0e96e, 0x00b1e360, 0x00a3dd53, 0x0094d247, 0x0086c83b, 0x0078bc35, 0x0069b030, + 0x0062a52b, 0x005b9b27, 0x0057920a, 0x000995fc, 0x000d96e5, 0x001091eb, 0x001091eb, + 0x004ac5f0, 0x0049c4f0, 0x0047c3f1, 0x0045c2f1, 0x0044c1f2, 0x0041c1f2, 0x003fc1f2, + 0x003fbff1, 0x003fbcf0, 0x0032c3fe, 0x00be7f6e, 0x00fe6526, 0x00f67b35, 0x00f59a4d, + 0x00f8ab5c, 0x00fbd0a0, 0x00f7c783, 0x00fec16b, 0x00fdd17f, 0x00fbdb87, 0x00f9e590, + 0x00f8ed9a, 0x00f7f4a5, 0x00fbea9a, 0x00ffdf8e, 0x00fce3a0, 0x00f7e6b1, 0x00fceecc, + 0x00fffbcb, 0x00fff3c7, 0x00fcf1c3, 0x00fef5d2, 0x00fffcd3, 0x004bb5e7, 0x0021a5ed, + 0x001ca2ee, 0x003daae2, 0x00eef6ac, 0x00e6f2b1, 0x00e8f2b5, 0x00e9f3b8, 0x00eaf4ba, + 0x00ebf5bc, 0x00e8f3b6, 0x00e6f2af, 0x00e0f0a8, 0x00dbeea2, 0x00d6ef9a, 0x00d1f092, + 0x00c9ed82, 0x00c1eb73, 0x00b0e362, 0x00a1dc51, 0x0094d347, 0x0088ca3e, 0x007bbf38, + 0x006eb433, 0x0066a92e, 0x005da01b, 0x003d9448, 0x000a93f6, 0x000e94ec, 0x001193f0, + 0x001193f0, 0x004bc5f1, 0x004ac5f1, 0x0048c4f1, 0x0047c3f2, 0x0045c3f2, 0x0040c3f4, + 0x003bc4f6, 0x003cbff3, 0x003ebbf0, 0x002dcaff, 0x00ff5d25, 0x00fe6d2f, 0x00f37d39, + 0x00f59348, 0x00f8a958, 0x00f7c083, 0x00f7d7ae, 0x00ffc36d, 0x00ffda84, 0x00fbe48c, + 0x00f7ee94, 0x00f8ed9e, 0x00faeca7, 0x00f9f1b4, 0x00f8f6c1, 0x00fcf6c8, 0x00fff6d0, + 0x00fef2d3, 0x00fcf4ba, 0x00fffee8, 0x00f7fdea, 0x00fdfde3, 0x00fffcdc, 0x000b9df1, + 0x002aaaed, 0x001baaf6, 0x0080c8da, 0x00fdffbb, 0x00e8f2bd, 0x00ebf4c4, 0x00eff7cb, + 0x00eff7cb, 0x00eff7cb, 0x00edf6c5, 0x00ebf5c0, 0x00eaf4be, 0x00e8f3bd, 0x00e4f4b4, + 0x00e0f6ab, 0x00d0f191, 0x00c1ec77, 0x00b0e463, 0x009edb4e, 0x0095d448, 0x008bcc42, + 0x007fc23b, 0x0073b935, 0x006aac31, 0x0060a510, 0x00229687, 0x000b91f1, 0x000e93f3, + 0x001294f5, 0x001294f5, 0x004cc6f1, 0x004bc5f2, 0x0049c5f2, 0x0047c4f2, 0x0046c4f2, + 0x0043c4f1, 0x0040c4f0, 0x0042c0f3, 0x0039c1f6, 0x005eacca, 0x00fb591e, 0x00f36e31, + 0x00f88135, 0x00fb923f, 0x00fbaf5e, 0x00ffc373, 0x00fde2ba, 0x00ffcd75, 0x00ffd372, + 0x00ffe584, 0x00fff796, 0x00fef4a2, 0x00fdf1ae, 0x00fff8c2, 0x00fcf8cd, 0x00fef8d2, + 0x00fff9d6, 0x00fef6e1, 0x00fcf5dd, 0x00fffbee, 0x00fbfce8, 0x00fffce0, 0x00b2e0e8, + 0x0019a4f0, 0x0026abec, 0x0016a8f6, 0x00c2e4d8, 0x00f9fac5, 0x00eff6cb, 0x00f0f7ce, + 0x00f1f8d2, 0x00f1f8d1, 0x00f2f9d1, 0x00f1f9cd, 0x00f1f9ca, 0x00f2fbca, 0x00f4fdca, + 0x00e7f8b6, 0x00daf3a2, 0x00cbef8a, 0x00bcec71, 0x00b0e661, 0x00a5e151, 0x009ad949, + 0x008fd240, 0x0083c73b, 0x0077bc35, 0x006ab31d, 0x005ea905, 0x00138dea, 0x001193ef, + 0x001093f0, 0x000f93f0, 0x000f93f0, 0x004dc6f2, 0x004cc6f2, 0x004ac5f3, 0x0048c5f3, + 0x0047c5f3, 0x0046c4ef, 0x0046c4eb, 0x0048c0f3, 0x0034c7fb, 0x00989591, 0x00fc6428, + 0x00f1773b, 0x00fc8432, 0x00ff9135, 0x00ffb564, 0x00ffbe5a, 0x00f3ddb6, 0x00ccd097, + 0x00b4cea5, 0x00b0d3b1, 0x00abd7bd, 0x00c3e1bf, 0x00daebc1, 0x00f5fdc7, 0x00ffffbd, + 0x00fffecd, 0x00fffcdc, 0x00fffce0, 0x00fbfce5, 0x00fdfbe6, 0x00fffae7, 0x00fffbdd, + 0x0061c4f4, 0x0026aaee, 0x0022abec, 0x0010a7f6, 0x00ffffd7, 0x00f5f5d0, 0x00f6fad9, + 0x00f4f9d9, 0x00f2f9da, 0x00f3fad8, 0x00f4fbd7, 0x00f5fcd5, 0x00f7fdd4, 0x00f3face, + 0x00f0f7c8, 0x00e2f4b0, 0x00d4f199, 0x00c5ee82, 0x00b7eb6b, 0x00b1e95f, 0x00abe754, + 0x009fdf49, 0x0094d83f, 0x0087cc3a, 0x007bc034, 0x006bb425, 0x005ba332, 0x000495f9, + 0x001795ee, 0x001293ed, 0x000c91eb, 0x000c91eb, 0x004fc8f3, 0x004dc8f3, 0x004cc8f4, + 0x004bc8f4, 0x0049c8f4, 0x0047c5f2, 0x0045c2ef, 0x0042c2f8, 0x0034c8ff, 0x00df6746, + 0x00ff632a, 0x00ff701b, 0x00e18b53, 0x00a4a185, 0x0063c1cd, 0x0026c0ff, 0x002ab8ff, + 0x0025b5f1, 0x0027b7f9, 0x0026b5f6, 0x0023b3f2, 0x0024b5fa, 0x0025b7ff, 0x00189ddf, + 0x0043bbf4, 0x009edae8, 0x00f9f9dc, 0x00f3fbe6, 0x00ffffea, 0x00fdffe6, 0x00fafce2, + 0x00ffffff, 0x001ea8ef, 0x001ca8f1, 0x001ba8f2, 0x005bc4f1, 0x00ffffe7, 0x00fbf9e1, + 0x00fbfce3, 0x00f8fbe0, 0x00f5fadd, 0x00f5fbdb, 0x00f5fbda, 0x00f6fcd7, 0x00f6fdd3, + 0x00f0f8c9, 0x00ebf4be, 0x00dff2a9, 0x00d4f094, 0x00c7f47b, 0x00baf862, 0x00b0ef58, + 0x00a6e64e, 0x00a3e248, 0x0098d73a, 0x008acd38, 0x007bc435, 0x0070b821, 0x003b9c84, + 0x000d93f4, 0x001394ed, 0x001193e9, 0x000f92e6, 0x000f92e6, 0x0050c9f4, 0x004fcaf4, + 0x004ecaf5, 0x004dcaf5, 0x004ccaf6, 0x0048c5f4, 0x0045c0f3, 0x0047c2ef, 0x004ac4eb, + 0x00ff521f, 0x00a79a92, 0x0051b7e6, 0x0028c7ff, 0x002cc4f9, 0x0031c1f1, 0x003fbbf0, + 0x0037c0ef, 0x0039b9f0, 0x003bb3f1, 0x0038b5f4, 0x0036b7f7, 0x0032b9f0, 0x002fbbe8, + 0x002fb8eb, 0x002fb5ed, 0x0020acf3, 0x0010a3fa, 0x0070c9f3, 0x00f5f9df, 0x00f6fbde, + 0x00f6fdde, 0x00d8ebe4, 0x0011a5ee, 0x002db2f5, 0x0014a5f8, 0x00a5e2ec, 0x00fffff8, + 0x00fffef3, 0x00fffded, 0x00fcfde6, 0x00f8fce0, 0x00f7fcde, 0x00f6fcdd, 0x00f6fcd8, + 0x00f5fdd3, 0x00edf7c4, 0x00e5f1b4, 0x00e5f5b8, 0x00e4f9bb, 0x00ecfed2, 0x00f3ffe9, + 0x00edfedb, 0x00e8f9cd, 0x00caef89, 0x009cd636, 0x0084c72e, 0x006bb826, 0x006cb315, + 0x001a95d6, 0x001591ef, 0x001093eb, 0x001193e6, 0x001294e1, 0x001294e1, 0x0052cbf4, + 0x0050caf4, 0x004ecaf4, 0x004ccaf3, 0x004ac9f3, 0x0048c8f5, 0x0046c7f6, 0x0040bfed, + 0x0041bfeb, 0x0041d4f9, 0x0033c9fc, 0x002fc9ff, 0x0042c3ec, 0x0040c3f4, 0x003ec3fc, + 0x0035bbf4, 0x0033bbf3, 0x0049bdf7, 0x0039b7f9, 0x0037b7f6, 0x0035b7f2, 0x002eb5f4, + 0x0028b3f5, 0x002fbbf8, 0x002fbaf2, 0x0030b5f2, 0x0031b0f1, 0x001facf6, 0x000dabed, + 0x007fd2ed, 0x00ffffe6, 0x0080d9d2, 0x002faaf8, 0x001dafec, 0x0003aae6, 0x00fff8ff, + 0x00fffffe, 0x00fffff9, 0x00fffdf4, 0x00fdfeeb, 0x00fbfee3, 0x00f9fde1, 0x00f7fce0, + 0x00f5fdd8, 0x00f4fdcf, 0x00f5fce2, 0x00f6fde8, 0x00f3fde8, 0x00f1fde9, 0x00ebfdd3, + 0x00e6fdbe, 0x00e0f8ba, 0x00daf2b7, 0x00eafcd2, 0x00f2fde6, 0x00b7de8d, 0x0084c73d, + 0x009ab848, 0x0014a1f9, 0x000494f3, 0x001094ef, 0x001095ec, 0x001095e9, 0x001095e9, + 0x0054ccf5, 0x0051cbf4, 0x004ecaf3, 0x004cc9f2, 0x0049c8f1, 0x0048cbf5, 0x0048cef9, + 0x0040c4f3, 0x0049cafc, 0x0040c2f1, 0x0047caf5, 0x0046c7f4, 0x0046c4f3, 0x0039b5ee, + 0x002ca5e8, 0x002eb1e1, 0x0056c1ea, 0x006dc9e9, 0x0037c2e5, 0x0051caeb, 0x006bd2f1, + 0x0074d1f5, 0x007dcff9, 0x0056c7f8, 0x001fafe8, 0x0025b1ee, 0x002cb3f4, 0x003eb5f9, + 0x002bb3ee, 0x001baff5, 0x0032b5f0, 0x003fb2f9, 0x0026a9f2, 0x001faeeb, 0x003fb8f4, + 0x00fcfff3, 0x00ffffff, 0x00ffffff, 0x00fffefb, 0x00fefff1, 0x00feffe6, 0x00fbffe5, + 0x00f8fde3, 0x00f5fdd7, 0x00f3fecb, 0x00f5fbeb, 0x00f7feee, 0x00f2fdde, 0x00edfccf, + 0x00e3f9b0, 0x00d9f692, 0x00d2f48b, 0x00ccf184, 0x00ceee97, 0x00d0eaa9, 0x00daebc1, + 0x00f4fbe9, 0x007fc679, 0x005ac1ff, 0x001aa1eb, 0x001195f2, 0x000f96f2, 0x000e97f2, + 0x000e97f2, 0x0054cdf5, 0x0052ccf4, 0x004fcbf3, 0x004dc9f3, 0x004ac8f2, 0x0049c6f2, + 0x0047c4f2, 0x0049d2f3, 0x0046c8f3, 0x004dc5fc, 0x002c9add, 0x001883cd, 0x00046cbe, + 0x000080c5, 0x000f96d4, 0x002eaddb, 0x0060c6eb, 0x0076cdef, 0x0051caea, 0x0069d2f0, + 0x0081daf5, 0x009ae4f7, 0x00b3eff9, 0x00cffaff, 0x00e3feff, 0x009ae1ff, 0x0048bcf7, + 0x0011b5dd, 0x0032aef0, 0x0028acfc, 0x0031b2f3, 0x0034b1f6, 0x0025adf0, 0x0026acf6, + 0x0098d1fc, 0x00fffdf8, 0x00ffffff, 0x00fffffb, 0x00fefff4, 0x00fdffee, 0x00fcfde7, + 0x00fbfee4, 0x00faffe0, 0x00f8fde7, 0x00f7fcef, 0x00f3fbeb, 0x00effdd9, 0x00e9fbc2, + 0x00e3f9ac, 0x00d9f49b, 0x00ceef8b, 0x00c1ea76, 0x00b4e562, 0x00abdd5a, 0x00a2d261, + 0x00c1e98e, 0x00dbe8b9, 0x0096d4ff, 0x008ed0fa, 0x0042aeee, 0x001095f1, 0x001096f1, + 0x000f96f1, 0x000f96f1, 0x0055cef5, 0x0053ccf4, 0x0050cbf4, 0x004ecaf4, 0x004cc8f4, + 0x0051caf7, 0x0057cbfa, 0x0045c0ea, 0x001a75c7, 0x000058ad, 0x00015bb4, 0x00066fc0, + 0x000b84cd, 0x000093ce, 0x0011a7e0, 0x003eb9e6, 0x006bcbeb, 0x007ed1f6, 0x006cd3f0, + 0x0082dbf4, 0x0098e3f9, 0x00a5ecf7, 0x00b2f4f5, 0x00c7f7f9, 0x00ddfafd, 0x00f2ffff, + 0x00f8fff6, 0x00bcebfe, 0x0022b4f2, 0x0029afff, 0x002fb0f7, 0x0029b1f2, 0x0023b1ee, + 0x001aa7fa, 0x00cae6f4, 0x00f7f8f4, 0x00feffff, 0x00fefff7, 0x00feffed, 0x00fcffeb, + 0x00fbfae9, 0x00fbfee3, 0x00fbffdc, 0x00fbffe9, 0x00fbfff7, 0x00f1fedd, 0x00e7fbc3, + 0x00e0f6b4, 0x00d8f0a5, 0x00ceec94, 0x00c4e884, 0x00b8e678, 0x00ace36c, 0x00a0df53, + 0x0094d455, 0x0080bd41, 0x00d2e599, 0x002ca1f4, 0x0030a2f6, 0x00209cf3, 0x001096f1, + 0x001096f1, 0x001096f1, 0x001096f1, 0x0055cef4, 0x0053cdf4, 0x0051cbf5, 0x0050cbf5, + 0x004ecaf6, 0x004dc9f4, 0x0054d0fa, 0x002b86ce, 0x000752b1, 0x00045fb9, 0x000a74c9, + 0x000882ce, 0x000691d4, 0x0002a0d5, 0x0024b5e7, 0x004cc4ea, 0x0074d3ee, 0x0083d9f5, + 0x007fddf4, 0x0093e4f6, 0x00a8ecf9, 0x00b6f2f9, 0x00c3f9f9, 0x00d3fafb, 0x00e3fcfc, + 0x00edfefb, 0x00f0f9f3, 0x00ffffff, 0x00fffdff, 0x007edcef, 0x0026adfd, 0x002aaff7, + 0x002db2f2, 0x0034b1e0, 0x0009a7f7, 0x008dd3f5, 0x00fdfbf9, 0x00fffff6, 0x00fdffeb, + 0x00fcffe6, 0x00fcfce0, 0x00f9fcde, 0x00f7fcdd, 0x00fcffef, 0x00f9fdec, 0x00e8f5d0, + 0x00dff5bd, 0x00d9f1ad, 0x00d2ed9d, 0x00c5e97e, 0x00b8e26d, 0x00abdd5e, 0x009fd74f, + 0x0098c95f, 0x0092c735, 0x008bc942, 0x0080b34d, 0x00009bf2, 0x001894f8, 0x001595f5, + 0x001397f2, 0x001296f1, 0x001195f0, 0x001195f0, 0x0056cff4, 0x0054cdf5, 0x0052ccf5, + 0x0051cbf7, 0x0051cbf9, 0x0049c8f1, 0x0051d5fa, 0x001662c1, 0x00005cbb, 0x000874cd, + 0x00037cce, 0x00028dd4, 0x00019edb, 0x0009aedc, 0x0037c2ee, 0x005acfef, 0x007edcf0, + 0x0088e1f4, 0x0092e6f8, 0x00a5eef8, 0x00b9f5f9, 0x00c7f9fb, 0x00d5fdfe, 0x00dffdfc, + 0x00e9fdfa, 0x00f0fefe, 0x00f8ffff, 0x00fafffe, 0x00fdfffc, 0x00fdfbff, 0x001db0e8, + 0x002ab1ee, 0x0037b2f5, 0x0025b9f7, 0x0029b4f8, 0x0022aff5, 0x001baaf2, 0x009fd7f6, + 0x00fdffea, 0x00fcfee0, 0x00fcfdd7, 0x00f8fada, 0x00f4f7dd, 0x00fdfef5, 0x00f6fae1, + 0x00dfecc3, 0x00d8efb6, 0x00d2eca6, 0x00ccea95, 0x00bce567, 0x00abdb56, 0x009fd344, + 0x0092cb33, 0x0085c824, 0x0079b46a, 0x003a9eaf, 0x000c97ff, 0x001994f9, 0x000f9bee, + 0x00139af0, 0x001699f3, 0x001497f1, 0x001295ef, 0x001295ef, 0x0058d0f5, 0x0056cef5, + 0x0053cdf4, 0x0053ccf6, 0x0052cbf8, 0x0053d6fb, 0x004fc8fc, 0x00004cad, 0x00096fca, + 0x000b80d4, 0x000588d5, 0x000598db, 0x0005a8e1, 0x0018b6e6, 0x003fc8f2, 0x0063d3f3, + 0x0086dff5, 0x0091e4f7, 0x009ce9fa, 0x00aef0f9, 0x00c0f7f9, 0x00cbfafb, 0x00d7fdfd, + 0x00defdfc, 0x00e6fefb, 0x00f0fffe, 0x00faffff, 0x00f2fefb, 0x00fefffd, 0x00c6e9fb, + 0x001eb0ec, 0x0030b4f6, 0x0030b7f8, 0x0019a8f7, 0x0026b0f0, 0x0022aef3, 0x001eabf5, + 0x0027aafa, 0x001ca6f6, 0x007dcdea, 0x00dff4dd, 0x00eaffb0, 0x00fdfeed, 0x00ffffef, + 0x00fcf9d3, 0x00edeeb4, 0x00e6e9ac, 0x00d9e68a, 0x00cbe367, 0x00b9e153, 0x00a6dd4d, + 0x0075c57f, 0x0043adb0, 0x00229bf3, 0x000a9cff, 0x000998f6, 0x00109cef, 0x00189aee, + 0x00149ded, 0x00159bf0, 0x001599f2, 0x001397f0, 0x001195ee, 0x001195ee, 0x005ad1f6, + 0x0057cff5, 0x0054cef4, 0x0054cdf6, 0x0053cbf8, 0x004dd3f4, 0x002c9add, 0x00045ec1, + 0x000572c9, 0x000683d2, 0x000794dc, 0x0008a2e2, 0x0008b1e8, 0x0028bfef, 0x0048cef6, + 0x006bd8f8, 0x008fe3fa, 0x009be8fa, 0x00a6edfb, 0x00b7f3fb, 0x00c7f9fa, 0x00d0fbfc, + 0x00d9fdfd, 0x00defefd, 0x00e2fffc, 0x00effffe, 0x00fcffff, 0x00ebfef7, 0x00fffffe, + 0x008fd7f8, 0x001eb0f1, 0x002eb0f6, 0x0018abec, 0x00e0f7fd, 0x0024ade9, 0x0023acf1, + 0x0021acf8, 0x0026aef7, 0x002cb0f6, 0x001aa9f5, 0x0008a3f4, 0x0022a7f9, 0x004cc2f2, + 0x006dcdef, 0x007ec9db, 0x007fcac2, 0x0081c6c6, 0x0061bccb, 0x0041b3d0, 0x0024a7e9, + 0x00089bff, 0x00119dff, 0x001a9fff, 0x000f99e9, 0x00149cf9, 0x00159cf7, 0x00159cf5, + 0x00179df1, 0x00199eed, 0x00179cef, 0x001599f1, 0x001397ef, 0x001195ed, 0x001195ed, + 0x005cd2f6, 0x0059d0f5, 0x0055cff3, 0x0054cdf5, 0x0053ccf8, 0x0051d5f6, 0x00167bcf, + 0x000467c6, 0x00067bcf, 0x00068bd7, 0x00059cdf, 0x0008a9e5, 0x000ab6eb, 0x002bc4f1, + 0x004cd2f7, 0x006ddbf9, 0x008ee5fa, 0x009deafb, 0x00aceffb, 0x00bdf5fb, 0x00cefbfa, + 0x00d5fbfc, 0x00dcfcfd, 0x00dcfefd, 0x00ddfffd, 0x00e4fffd, 0x00eafffd, 0x00fffffe, + 0x00ffffff, 0x0027c0de, 0x0026b5f6, 0x001fb0f9, 0x004dc6ff, 0x00fff9ef, 0x00fefffa, + 0x008bd8f7, 0x0018a7f3, 0x001daaf4, 0x0023acf6, 0x0022acf3, 0x0022abf0, 0x001aa3f2, + 0x001aa6ee, 0x0018a8f5, 0x000ea2f3, 0x0011a4f2, 0x0014a4ff, 0x0015a3fc, 0x0016a3fa, + 0x0017a2f3, 0x0019a2ec, 0x000e99fe, 0x00169bed, 0x0000a1ff, 0x002b9de8, 0x0061b5b0, + 0x00109af7, 0x00149cf2, 0x00189eed, 0x00169cef, 0x00149af0, 0x001298ee, 0x001096ec, + 0x001096ec, 0x005fd3f7, 0x005bd2f5, 0x0056d0f3, 0x0055cef5, 0x0053cdf7, 0x0056d8f8, + 0x00005cc0, 0x000370cb, 0x000785d6, 0x000594dc, 0x0004a3e2, 0x0008afe8, 0x000cbcee, + 0x002ec8f3, 0x0050d5f9, 0x006fdefa, 0x008de7fb, 0x009fecfb, 0x00b1f2fb, 0x00c3f7fb, + 0x00d4fcfa, 0x00d9fcfc, 0x00defcfd, 0x00dbfdfd, 0x00d9fffd, 0x00d9fdfb, 0x00d9fcfa, + 0x00e5fafa, 0x00a4eaf7, 0x002badfb, 0x002fb9fa, 0x001aaeed, 0x0099dbf8, 0x00ffffff, + 0x00fefdfc, 0x00fffefd, 0x00fffffd, 0x008cd4fa, 0x0019a9f6, 0x0018a9f7, 0x0016aaf9, + 0x001aa7f3, 0x001ea5ee, 0x001fa7f2, 0x0021a9f6, 0x001ea7f7, 0x001ba5f7, 0x0017a4f9, + 0x0012a2fb, 0x000b9dfd, 0x000399fe, 0x0026a2fa, 0x006fc0b0, 0x00cfca5e, 0x00ffe528, + 0x0074b4b3, 0x000b98fa, 0x00119af4, 0x00179dee, 0x00159cee, 0x00139aef, 0x001198ed, + 0x000f96eb, 0x000f96eb, 0x005dd1f6, 0x005bd2f5, 0x0058d2f4, 0x0053cef4, 0x0056d2fb, + 0x0040b2e6, 0x000164c6, 0x000376cf, 0x000487d7, 0x000296dd, 0x0001a4e4, 0x0004b1ea, + 0x0007bdf1, 0x001bc8f2, 0x0043d5fc, 0x0064ddfb, 0x0085e6fb, 0x0098ebfc, 0x00acf1fd, + 0x00bef9ff, 0x00cfffff, 0x00cffdff, 0x00cff9fb, 0x00d2fefe, 0x00d5ffff, 0x00c6f9ff, + 0x00b8efff, 0x005ad7d9, 0x0040b9e9, 0x002fb9ff, 0x002bb2f0, 0x0028afeb, 0x00def0f2, + 0x00ffffff, 0x00feffff, 0x00fffefe, 0x00fffefa, 0x00fffffa, 0x00fffff9, 0x00c2e8f0, + 0x0084cde7, 0x0053bbe9, 0x0022a9eb, 0x0014a1ff, 0x00069ff8, 0x000fa0f8, 0x0019a3eb, + 0x0043b1e1, 0x006ec2c9, 0x00b0d79a, 0x00f2eb6b, 0x00ebee32, 0x00f8e647, 0x00ffe23a, + 0x00fde142, 0x000098f4, 0x0019a1fc, 0x00169ef7, 0x00129bf1, 0x00139af1, 0x00149af0, + 0x001298ee, 0x001096ec, 0x001096ec, 0x005ccff6, 0x005bd2f6, 0x005ad4f6, 0x0052cdf2, + 0x005ad6fe, 0x00298cd5, 0x00026ccc, 0x00027bd2, 0x000189d8, 0x000097df, 0x0000a6e6, + 0x0000b2ed, 0x0002bef4, 0x0009c7f1, 0x0035d5ff, 0x0059ddfd, 0x007ce5fb, 0x0091eafd, + 0x00a6f0ff, 0x00b1f2ff, 0x00bbf5ff, 0x00bef5fc, 0x00c1f6f9, 0x00c1f7f7, 0x00c1f9f4, + 0x00c7fdfc, 0x00cdffff, 0x00c2f9f8, 0x005acdf4, 0x0039b1f3, 0x0038baf5, 0x002ab4f7, + 0x00fcfbf8, 0x00fdfeff, 0x00feffff, 0x00fffeff, 0x00fffcf6, 0x00fdfef2, 0x00f7ffee, + 0x00fcffea, 0x00ffffe5, 0x00ffffd8, 0x00ffffcb, 0x00fffbf1, 0x00ffffdf, 0x00fdfdc2, + 0x00f7ff88, 0x00fbfe92, 0x00ffff7f, 0x00fdfc6c, 0x00faf759, 0x00f8f059, 0x00f7e958, + 0x00f7e359, 0x00d0d368, 0x000998ff, 0x00189aef, 0x00129af2, 0x000c99f5, 0x001199f3, + 0x001599f2, 0x001397f0, 0x001195ee, 0x001195ee, 0x005fd2f9, 0x005cd3f8, 0x0059d4f6, + 0x0058d3f8, 0x005edaff, 0x001971cd, 0x00026ecd, 0x00037bd3, 0x000488d9, 0x000497e0, + 0x0005a6e6, 0x0001ade7, 0x0000b5e8, 0x0007beea, 0x0023cbf5, 0x004cd7f8, 0x0074e4fc, + 0x0089e8fd, 0x009fecfe, 0x00a5edfe, 0x00abeffe, 0x00aeeffc, 0x00b0eff9, 0x00b3f3f9, + 0x00b6f6f8, 0x00b6f9fc, 0x00b5fcff, 0x00daf3ff, 0x001ab9f1, 0x0028b3f4, 0x002bb3f6, + 0x0073cef4, 0x00fdfdf5, 0x00fdfefa, 0x00fdfffe, 0x00fffef9, 0x00fffdf3, 0x00fdfeee, + 0x00faffe9, 0x00fdffe4, 0x00ffffde, 0x00ffffd0, 0x00ffffc2, 0x00fdfad7, 0x00fffcf3, + 0x00ffffc0, 0x00fcfbc5, 0x00fcff84, 0x00fcfb8b, 0x00fbf67a, 0x00f9f269, 0x00f7ed5e, + 0x00f4e954, 0x00f7e948, 0x0087bda9, 0x00109afc, 0x00179cf2, 0x00149bf1, 0x00119af1, + 0x001399f2, 0x001698f3, 0x001496f1, 0x001294ef, 0x001294ef, 0x0062d4fc, 0x005dd4f9, + 0x0059d4f6, 0x0056d1f6, 0x0053cef5, 0x00014ebe, 0x00026fcd, 0x00057bd4, 0x000787da, + 0x000996e0, 0x000ca5e7, 0x000bb0e9, 0x0009bbeb, 0x0015c5f3, 0x0021d0fc, 0x0046dafc, + 0x006ce3fc, 0x0082e6fd, 0x0097e9fe, 0x0099e9fe, 0x009ce8fe, 0x009ee9fb, 0x00a0e9f9, + 0x00a6eefa, 0x00acf3fc, 0x00b0effc, 0x00b5ecfb, 0x0089ddf9, 0x0028b4f3, 0x003ebef7, + 0x001eadf7, 0x00bde8f0, 0x00fefff2, 0x00fefff3, 0x00fdfff4, 0x00fefef2, 0x00fefef0, + 0x00fefeea, 0x00fefee4, 0x00fefede, 0x00fefed8, 0x00fcffc9, 0x00fbffba, 0x00f6fea0, + 0x00ffffce, 0x00fff9f6, 0x00ffffc9, 0x00fdf7be, 0x00f8f87a, 0x00f9f66b, 0x00f9f35c, + 0x00f5ee56, 0x00f1e84f, 0x00f8ee37, 0x003fa7ea, 0x00189df5, 0x00179df4, 0x00169cf1, + 0x00159bee, 0x00169af2, 0x001798f5, 0x001596f3, 0x001394f1, 0x001394f1, 0x0066d7fc, + 0x005fd1f5, 0x0060d4f6, 0x0059d8f9, 0x00399ddb, 0x000858be, 0x00096ccd, 0x000c7ad2, + 0x001087d7, 0x001296df, 0x0013a6e8, 0x0013b0eb, 0x001bc3f5, 0x000fc8f3, 0x0017d0f9, + 0x0027d3f4, 0x004bd7f7, 0x0061dbf8, 0x0077def9, 0x007fe0fa, 0x0088e1fa, 0x008de4fb, + 0x0091e7fb, 0x0096eafc, 0x009aedfd, 0x009feafb, 0x00a3e7fa, 0x005eccfb, 0x002db7f5, + 0x0024b8f9, 0x0014b1f5, 0x00fffbff, 0x00feffec, 0x00ffffed, 0x00ffffee, 0x00ffffec, + 0x00fefdeb, 0x00fefde4, 0x00fefddd, 0x00fefed6, 0x00fefece, 0x00fcfdc1, 0x00fcfcb5, + 0x00f6fb8d, 0x00f8fc8a, 0x00f8facc, 0x00f8fef2, 0x00f9ffbe, 0x00fbf9c2, 0x00fbf8ac, + 0x00fcf796, 0x00faf491, 0x00f7f18d, 0x00ffe5a9, 0x000096f7, 0x00089af7, 0x00159ef7, + 0x00169df4, 0x00169cf0, 0x00169bf2, 0x001699f4, 0x001497f3, 0x001396f1, 0x001396f1, + 0x006bd9fb, 0x0061cef1, 0x0067d3f7, 0x005cdefd, 0x001f6cc0, 0x000f63bf, 0x000f6acd, + 0x001478d1, 0x001887d4, 0x001997df, 0x001aa6e9, 0x0014a9e4, 0x001dbbef, 0x000dbeeb, + 0x0023c5f6, 0x0013c6ed, 0x002acbf3, 0x0040cff4, 0x0056d4f4, 0x0065d7f6, 0x0074daf7, + 0x007bdffb, 0x0083e5fe, 0x0086e6fe, 0x0089e8fd, 0x008ee5fb, 0x0092e2fa, 0x0033bcfc, + 0x0032b9f7, 0x0031bafd, 0x0057c5f7, 0x00f4ffde, 0x00fdffe7, 0x00ffffe7, 0x00ffffe7, + 0x00ffffe6, 0x00fdfce6, 0x00fdfddd, 0x00fdfdd5, 0x00fdfdcd, 0x00fefdc5, 0x00fdfaba, + 0x00fcf8af, 0x00fef99f, 0x00fffb8e, 0x00fafe77, 0x00f4fb7d, 0x00f9f8d2, 0x00fdffee, + 0x00fefedf, 0x00fffcd0, 0x00fefacd, 0x00fdf9ca, 0x00a6d3ce, 0x000399eb, 0x001ea1ec, + 0x00149ffa, 0x00159ef6, 0x00179ef2, 0x00169cf3, 0x00159af3, 0x001499f2, 0x001398f1, + 0x001398f1, 0x0055d4f4, 0x005bd1f1, 0x0069d6f6, 0x006ee2ff, 0x000c50a8, 0x001161be, + 0x000f6acd, 0x001f83d6, 0x001f89dc, 0x000f8cdd, 0x001a9be0, 0x0022b1f4, 0x001dabe1, + 0x0014aedf, 0x0026bdee, 0x0015bae7, 0x001fc1ef, 0x0025c7ef, 0x002bcdef, 0x003dcdf1, + 0x004ecef3, 0x005bd6f9, 0x0068defe, 0x006eddfc, 0x0073ddfb, 0x0076ddf5, 0x0070d3f7, + 0x0031bafb, 0x0033b9f6, 0x0024b6ff, 0x00a4dee5, 0x00f9ffdc, 0x00fdfedc, 0x00ffffdc, + 0x00ffffdc, 0x00fefedb, 0x00fcfdda, 0x00fdfdd2, 0x00fdfdcb, 0x00fdfdc3, 0x00fefdbc, + 0x00fdfbaf, 0x00fcfaa2, 0x00fdfb93, 0x00fefb83, 0x00fcfd6b, 0x00f9fc60, 0x00fbf85d, + 0x00fdf74c, 0x00fef576, 0x00fff2a1, 0x00f6ec87, 0x00f8e360, 0x0051bbb4, 0x000d9afe, + 0x001a9ef7, 0x00159ef6, 0x00159df4, 0x00159df2, 0x00149bf2, 0x001299f2, 0x001299f2, + 0x001299f2, 0x001299f2, 0x0067d4fd, 0x0069d6f9, 0x006cd9f5, 0x004fb7dc, 0x001953af, + 0x001c67c6, 0x00005abd, 0x001a7eca, 0x00157bd4, 0x000581dc, 0x002aa1e7, 0x000189d3, + 0x002dabe3, 0x0023a7dc, 0x0029b4e6, 0x0017ade1, 0x0014b7ec, 0x0015b9ea, 0x0016bbe9, + 0x001fbfec, 0x0028c2ef, 0x003bcdf7, 0x004ed8ff, 0x0056d5fb, 0x005dd2f8, 0x005ed6f0, + 0x004ec5f4, 0x002fb9fa, 0x0035b8f4, 0x0017b1ff, 0x00f0f7d2, 0x00feffda, 0x00fdfcd2, + 0x00fdfdd1, 0x00fdfed1, 0x00fdfecf, 0x00fcfecd, 0x00fcfdc7, 0x00fdfdc0, 0x00fdfdb9, + 0x00fdfdb2, 0x00fdfca4, 0x00fdfc95, 0x00fdfc87, 0x00fdfc79, 0x00fdfa6c, 0x00fef85f, + 0x00f9f645, 0x00f6ef47, 0x00f2e938, 0x00efe428, 0x00eee425, 0x00ffdd05, 0x000399ff, + 0x0017a1f5, 0x00179ef4, 0x00169cf3, 0x00159cf3, 0x00149cf3, 0x00129bf1, 0x001099f0, + 0x00119af1, 0x00129bf2, 0x00129bf2, 0x0066d5fb, 0x0070d5fc, 0x0078e2ff, 0x003b86c7, + 0x00235fba, 0x001e6aba, 0x00227ad1, 0x002787d8, 0x00248cd7, 0x001d8dd4, 0x002189d1, + 0x002ca1ea, 0x002296d5, 0x0031aaef, 0x0020a1db, 0x0017a1dd, 0x000ea1e0, 0x001aace3, + 0x0013b1eb, 0x0010b8ed, 0x000dc0ef, 0x001cc1ef, 0x002cc3f0, 0x0036c4f2, 0x0040c5f4, + 0x0047c9f2, 0x0045c3f6, 0x0031bafa, 0x0031b7f7, 0x004cc2f4, 0x00f5fac0, 0x00fdffc6, + 0x00fdfcc5, 0x00fdfdc4, 0x00fdfdc4, 0x00fcfdc2, 0x00fbfdc1, 0x00f8f9b6, 0x00fdfdb3, + 0x00fdfdab, 0x00fdfca3, 0x00fcfc95, 0x00fcfb88, 0x00fcfb7b, 0x00fbfb6d, 0x00fcf962, + 0x00fcf757, 0x00f8f245, 0x00f4eb41, 0x00f0e532, 0x00ebe023, 0x00fbe01c, 0x00c5d244, + 0x000aa2fe, 0x00169ff9, 0x00179ff6, 0x00189ff3, 0x00179ef2, 0x00159df2, 0x00179ff5, + 0x0018a1f8, 0x00159ef5, 0x00129bf2, 0x00129bf2, 0x0065d7fa, 0x0064d1f7, 0x005de7ff, + 0x0004439b, 0x000e4ca5, 0x00317bcd, 0x000455c1, 0x000053c9, 0x000368c6, 0x002687ca, + 0x002881ca, 0x002789d1, 0x002791d7, 0x000774c9, 0x00178dcf, 0x001f9ce1, 0x00179be4, + 0x001e9eda, 0x000097de, 0x0003a5e6, 0x0008b1ee, 0x0009b0e8, 0x000aafe2, 0x0017b4e9, + 0x0024b9ef, 0x0030bdf4, 0x003cc1f9, 0x0034bcf9, 0x002cb6f9, 0x0080d2e8, 0x00fafdaf, + 0x00fcfdb3, 0x00fdfcb7, 0x00fdfcb7, 0x00fdfdb7, 0x00fcfcb6, 0x00fbfcb5, 0x00f4f4a5, + 0x00fdfda5, 0x00fcfc9d, 0x00fcfc94, 0x00fbfb87, 0x00fbfb7b, 0x00fafa6e, 0x00fafa61, + 0x00faf758, 0x00faf54e, 0x00f7ee44, 0x00f3e73a, 0x00ede12c, 0x00e7db1e, 0x00ffd21a, + 0x0078b090, 0x0009a0fd, 0x00159dfd, 0x0018a0f8, 0x001aa2f2, 0x0018a0f2, 0x00169ef2, + 0x00139bf2, 0x001099f1, 0x00119af2, 0x00129bf3, 0x00129bf3, 0x0060d4f7, 0x0067dcfd, + 0x004fc2f0, 0x00002c8a, 0x002e6bc0, 0x000547ad, 0x000044ba, 0x003685c4, 0x00064ebc, + 0x001462c3, 0x002d70cb, 0x000f5ab4, 0x002274cd, 0x001169c2, 0x001979c2, 0x001d80d0, + 0x001980d7, 0x001a86d3, 0x001090de, 0x00038dda, 0x000599e6, 0x00059ce1, 0x00049edd, + 0x0005a6e1, 0x0000a7de, 0x001fb6ee, 0x0039bdf7, 0x0038bcf6, 0x0024b5fc, 0x00bfe8b9, + 0x00fafea2, 0x00fbfca5, 0x00fcfaa8, 0x00fcfca7, 0x00fdfda6, 0x00fbfca3, 0x00f9fb9f, + 0x00f6f795, 0x00fafb92, 0x00fbfb8b, 0x00fbfb85, 0x00fafa79, 0x00fafa6d, 0x00f9f961, + 0x00f8f956, 0x00f9f64c, 0x00f9f442, 0x00f5ec39, 0x00f2e531, 0x00efde28, 0x00ecd620, + 0x00eed900, 0x0032a6e5, 0x0019a4ff, 0x0029a4f4, 0x0020a2f4, 0x0018a0f5, 0x00179ef4, + 0x00159df4, 0x00139bf3, 0x001199f2, 0x00129af2, 0x00129af3, 0x00129af3, 0x005bd1f5, + 0x0063dffa, 0x00318dcc, 0x00062d91, 0x000e499a, 0x0000369f, 0x00003897, 0x00155fb6, + 0x0053aad9, 0x0031a6e2, 0x0045bcef, 0x006dddff, 0x0076defa, 0x006dd9f9, 0x0064d5f9, + 0x0054c5f3, 0x0045b5ed, 0x00238ed6, 0x001277ce, 0x00006cc6, 0x000282de, 0x000187db, + 0x00008dd7, 0x00079be1, 0x000099dc, 0x0022b1f0, 0x0036baf4, 0x003cbcf4, 0x001cb5ff, + 0x00fffe89, 0x00fbff96, 0x00fbfc98, 0x00fbf99a, 0x00fcfb98, 0x00fdfd96, 0x00fafb90, + 0x00f6f98a, 0x00f7f984, 0x00f8fa7f, 0x00fafa7a, 0x00fbfb75, 0x00fafa6a, 0x00f9f960, + 0x00f8f855, 0x00f7f84a, 0x00f7f540, 0x00f8f336, 0x00f4eb2f, 0x00f0e328, 0x00f0da24, + 0x00f0d121, 0x00e9ca24, 0x00049bff, 0x0020a3f6, 0x0016a1f7, 0x0016a0f7, 0x00169ef7, + 0x00159df6, 0x00149cf5, 0x00139bf4, 0x00129af3, 0x00129af3, 0x00129af3, 0x00129af3, + 0x005ae3ff, 0x0064d8ff, 0x000d4798, 0x00002682, 0x001d6bb7, 0x003aa2de, 0x005fe5ff, + 0x0052d8fd, 0x004dd6f6, 0x0048ccf5, 0x005fd0f6, 0x0068d9ff, 0x0061d3f8, 0x005bd2f8, + 0x0042cbff, 0x0053cefe, 0x0051cff5, 0x0049caf6, 0x004acdff, 0x0040baff, 0x000e7edb, + 0x000069c2, 0x000584da, 0x000184d5, 0x00068cd8, 0x0038bef8, 0x003abef7, 0x0035beff, + 0x0062c7e2, 0x00fbf379, 0x00f8fa83, 0x00f9f983, 0x00faf884, 0x00f9f77f, 0x00f7f77b, + 0x00f8f979, 0x00f9fa77, 0x00f8f972, 0x00f7f86c, 0x00fcfc6c, 0x00f9f864, 0x00f8f85b, + 0x00f8f752, 0x00f7f649, 0x00f6f53f, 0x00f5f237, 0x00f4ef2f, 0x00f1e628, 0x00eede20, + 0x00ead61f, 0x00f2cc11, 0x009db96c, 0x000c9ffe, 0x001ba3f9, 0x0017a2f9, 0x0017a0f9, + 0x00169ef8, 0x00169df7, 0x00159cf6, 0x00149bf5, 0x00139af5, 0x00139af5, 0x00139af5, + 0x00139af5, 0x0060d8f9, 0x005bd9f8, 0x004cadd7, 0x0069ddff, 0x0056ddf8, 0x0055d6fc, + 0x0055d0ff, 0x005cd5ff, 0x0053cbf2, 0x004bcaf6, 0x0043cafa, 0x0047c9f8, 0x004cc8f6, + 0x005ccff1, 0x0046ccf8, 0x0055caff, 0x003ec4fa, 0x0043c3fb, 0x0048c2fd, 0x003ebff4, + 0x0044ccfb, 0x0037b3fc, 0x000b7bdd, 0x00006dc9, 0x000d80d4, 0x004eccff, 0x003ec3fa, + 0x002ec2ff, 0x00a7dea8, 0x00f8ec5b, 0x00f5f570, 0x00f7f66f, 0x00faf76e, 0x00f5f467, + 0x00f1f060, 0x00f6f663, 0x00fbfc65, 0x00f8f95f, 0x00f6f659, 0x00fefe5d, 0x00f7f652, + 0x00f7f54c, 0x00f7f545, 0x00f6f33d, 0x00f6f235, 0x00f3ef2f, 0x00f1eb29, 0x00efe221, + 0x00ecd818, 0x00e5d21a, 0x00f3c700, 0x0052a9b4, 0x0014a4fb, 0x0015a3fb, 0x0017a3fc, + 0x0017a1fa, 0x00179ff8, 0x00169df8, 0x00159cf7, 0x00159bf7, 0x001499f6, 0x001499f6, + 0x001499f6, 0x001499f6, 0x0058cff2, 0x0059ddfd, 0x0055d5f9, 0x005ddeff, 0x004dcef3, + 0x004dcbf3, 0x004cc8f3, 0x0056d2fc, 0x0059d3fd, 0x0050cefb, 0x0047cafa, 0x0048c9f9, + 0x0049c7f9, 0x0051cbf6, 0x0045c9f9, 0x004bc8fd, 0x003fc5f9, 0x0041c4fa, 0x0043c2fb, + 0x003bbdf3, 0x003ac0f4, 0x003ec7fc, 0x003ac6fc, 0x0025a1e3, 0x001f8dd9, 0x0037b9f7, + 0x0026bbfa, 0x002abbf4, 0x00ced857, 0x00f9fa5b, 0x00d9db49, 0x00edec58, 0x00faf560, + 0x00f2ef4d, 0x00e9ea3b, 0x00eeef46, 0x00f2f451, 0x00f9f34f, 0x00edf145, 0x00fef84b, + 0x00f4f542, 0x00f5f43d, 0x00f6f337, 0x00f5f131, 0x00f5ef2b, 0x00f2eb27, 0x00f0e622, + 0x00eedb1d, 0x00ecd117, 0x00f1cc09, 0x00f5c509, 0x000fadff, 0x0017a1f9, 0x0018a1f9, + 0x0018a1f8, 0x0018a0f9, 0x00179ff9, 0x00169df9, 0x00169cf8, 0x00159bf8, 0x001599f8, + 0x001599f8, 0x001599f8, 0x001599f8, 0x0060d5fb, 0x005bd3fb, 0x0056d2fb, 0x0055d1fc, + 0x0055d0fe, 0x0054d0fa, 0x0053d1f6, 0x0051cef7, 0x004ecbf8, 0x004dcbf9, 0x004ccafb, + 0x0049c8fb, 0x0047c6fc, 0x0045c6fb, 0x0043c6fa, 0x0041c6fa, 0x0040c7f9, 0x003fc5f9, + 0x003ec3f9, 0x003fc3fb, 0x0041c4fd, 0x0038baf2, 0x0040c1f8, 0x003dc3fb, 0x003bc5fe, + 0x0037c1f6, 0x0034beef, 0x002ebcf0, 0x00ded722, 0x00bfdc38, 0x00dee142, 0x00ecea4a, + 0x00eae442, 0x00eee942, 0x00f2ee42, 0x00eeed3f, 0x00eaec3d, 0x00fbee3f, 0x00e5ec31, + 0x00fff239, 0x00f2f531, 0x00f4f32e, 0x00f5f12a, 0x00f5ee25, 0x00f4ec21, 0x00f2e71e, + 0x00f0e11c, 0x00eed519, 0x00ecc917, 0x00dec40c, 0x00bbbe39, 0x000798f8, 0x001a9ff8, + 0x001a9ff7, 0x001a9ff5, 0x00189ff7, 0x00179ff9, 0x00179ef9, 0x00169cf9, 0x00169bf9, + 0x001699f9, 0x001699f9, 0x001699f9, 0x001699f9, 0x005cd4f9, 0x0058d4f9, 0x0055d3f9, + 0x0056d2fa, 0x0058d0fb, 0x0056d0f8, 0x0054d0f6, 0x0051cef7, 0x004dccf9, 0x004ccbfa, + 0x004bcafb, 0x0049c8fb, 0x0047c7fb, 0x0045c7fb, 0x0043c6fa, 0x0041c6fa, 0x0040c6f9, + 0x003fc4f9, 0x003ec3f9, 0x003ec2fa, 0x003ec2fb, 0x003abef5, 0x003ec2f8, 0x003bc1f9, + 0x0037c0f9, 0x0036beff, 0x0035bbff, 0x0067bb84, 0x00b0d219, 0x00b4d31a, 0x00d3da39, + 0x00e2dd3d, 0x00d6d532, 0x00e1df38, 0x00ece93e, 0x00e1e636, 0x00e9e536, 0x00f1e634, + 0x00e5e42b, 0x00f6e62e, 0x00e9eb29, 0x00f0ee2a, 0x00f0e824, 0x00ece420, 0x00e9e01d, + 0x00ebdb1c, 0x00edd71c, 0x00e9ce19, 0x00e5c516, 0x00e7c004, 0x006cb292, 0x00109dfc, + 0x0018a1f7, 0x001aa0f5, 0x001ca0f3, 0x0019a0f6, 0x00179ff9, 0x00169ef9, 0x00169cf9, + 0x00159bf8, 0x00159af8, 0x001499f8, 0x001499f7, 0x001499f7, 0x0058d4f6, 0x0056d4f6, + 0x0054d5f7, 0x0057d3f7, 0x005bd1f8, 0x0058d0f6, 0x0054cff5, 0x0050cef8, 0x004dcdfa, + 0x004bcbfb, 0x004acafb, 0x0048c9fb, 0x0046c7fb, 0x0045c7fa, 0x0043c7fa, 0x0042c6fa, + 0x0040c6f9, 0x003fc4f9, 0x003ec3f9, 0x003dc1f9, 0x003cc0f9, 0x003cc1f8, 0x003cc2f7, + 0x0038bff6, 0x0034bbf5, 0x0035bdfd, 0x0037beff, 0x0046bcfc, 0x0082c92c, 0x00a0be02, + 0x00b8c420, 0x00d8cf31, 0x00d2d632, 0x00d4d52e, 0x00d7d42a, 0x00cdd725, 0x00e9df2f, + 0x00e6dd2a, 0x00e4dc25, 0x00edd922, 0x00e0e220, 0x00ede927, 0x00eae01e, 0x00e4da1c, + 0x00ded319, 0x00e5d01a, 0x00ebcd1b, 0x00e5c818, 0x00dec214, 0x00f0bc00, 0x001da5eb, + 0x0019a1ff, 0x0016a2f7, 0x0019a2f4, 0x001ea2f1, 0x001aa0f5, 0x00169ff9, 0x00169ef8, + 0x00159df8, 0x00159cf8, 0x00149bf8, 0x00139af7, 0x001299f6, 0x001299f6, 0x005ed5f9, + 0x0063d6fc, 0x0068d6ff, 0x005fd3fc, 0x0056d0f8, 0x0053cff8, 0x0051cef8, 0x004ecdf9, + 0x004bccfb, 0x004acbfb, 0x0048cafb, 0x0047c9fa, 0x0046c8fb, 0x0044c7fa, 0x0043c7fa, + 0x0042c6fa, 0x0040c5f9, 0x003fc4f9, 0x003ec3f9, 0x003dc1f9, 0x003cc0f9, 0x003bc1f9, + 0x003bc1f8, 0x0038bff7, 0x0036bdf7, 0x0035bdfa, 0x0034bdfe, 0x0022c3f6, 0x0027bbfc, + 0x0053b0b2, 0x009bc606, 0x00c1d322, 0x00d3dd36, 0x00b4ba12, 0x00c4c71f, 0x00c5cf22, + 0x00d9d82d, 0x00dfdb30, 0x00dcd52b, 0x00e8d520, 0x00d5d51c, 0x00e8e428, 0x00ece324, + 0x00d1ce1f, 0x00d3c51d, 0x00dcc302, 0x00cfc312, 0x00e3c209, 0x00e3be00, 0x0084bf6e, + 0x000ca0f6, 0x00129ffd, 0x0018a2f6, 0x0019a1f5, 0x001ba1f4, 0x0018a0f6, 0x00169ff8, + 0x00159ef8, 0x00159df8, 0x00149cf7, 0x00139bf7, 0x00129af6, 0x001098f4, 0x001098f4, + 0x0065d7fb, 0x005dd4fa, 0x0056d2f8, 0x0053d0f9, 0x0050cff9, 0x004fcef9, 0x004dcdfa, + 0x004bcdfa, 0x004accfb, 0x0048cbfb, 0x0047cafb, 0x0046c9fa, 0x0045c8fa, 0x0044c7fa, + 0x0043c7fa, 0x0042c6fa, 0x0040c5fa, 0x003fc4f9, 0x003ec3f9, 0x003dc1f9, 0x003bc0f9, + 0x003ac0f9, 0x0039c0f9, 0x0038bff9, 0x0037bff9, 0x0034bef8, 0x0031bcf7, 0x0033bbf8, + 0x0035bbfa, 0x002cbcff, 0x0061c2df, 0x0093cb85, 0x00c5d52b, 0x00cbd82f, 0x00b0bb13, + 0x00b5be17, 0x00b9c21b, 0x00c7c826, 0x00c5bf21, 0x00dbc817, 0x00cac819, 0x00dbd722, + 0x00ddd61a, 0x00b7bd0d, 0x00c8bd04, 0x00d0c000, 0x00adc951, 0x006cb8b1, 0x0004a3ff, + 0x0013a4fb, 0x0021a4f5, 0x001ea3f5, 0x001aa1f6, 0x0019a1f6, 0x0018a0f7, 0x0017a0f7, + 0x00169ff8, 0x00159ef7, 0x00149ef7, 0x00139df7, 0x00139cf6, 0x00119af4, 0x000f98f2, + 0x000f98f2, 0x005cd5f9, 0x0058d3f8, 0x0053d1f8, 0x0052d0f9, 0x0050cff9, 0x004ecefa, + 0x004ccdfa, 0x004accfa, 0x0048ccfa, 0x0047cbfa, 0x0046cafa, 0x0045c9fa, 0x0044c8fa, + 0x0043c7fa, 0x0042c7fa, 0x0041c6fa, 0x0040c5fa, 0x003fc4f9, 0x003ec2f9, 0x003cc1f9, + 0x003bc0f9, 0x003ac0f9, 0x0038bff9, 0x0037bff9, 0x0036bff9, 0x0035bdf6, 0x0034bbf3, + 0x0035b9f7, 0x0035b8fb, 0x0022b5ff, 0x002fb5ff, 0x004dbae6, 0x006bbfce, 0x0027b1c5, + 0x006cbc7c, 0x008abd49, 0x00a7be15, 0x00b9bf09, 0x00ccc000, 0x00dac43d, 0x00bbca20, + 0x00aec73e, 0x0099bc54, 0x005aad8b, 0x0036abc4, 0x0004b3ff, 0x0015a7ff, 0x0021a4ff, + 0x0019a0fb, 0x001ba2fa, 0x001da4f9, 0x001ba3f8, 0x001aa1f7, 0x0019a1f7, 0x0018a0f7, + 0x0017a0f7, 0x00169ff8, 0x00159ef7, 0x00149ef7, 0x00139df7, 0x00129cf6, 0x00119af5, + 0x000f99f3, 0x000f99f3, 0x0053d2f6, 0x0052d1f7, 0x0051d1f8, 0x0050d0f9, 0x004fcffa, + 0x004dcefa, 0x004bcdfa, 0x0049ccfa, 0x0047cbfa, 0x0046caf9, 0x0045caf9, 0x0044c9f9, + 0x0044c8fa, 0x0043c7fa, 0x0042c6f9, 0x0041c6f9, 0x0040c5fa, 0x003fc4f9, 0x003dc2f9, + 0x003cc1f9, 0x003ac0f9, 0x0039c0f9, 0x0038bff9, 0x0036bff9, 0x0035bef8, 0x0036bcf4, + 0x0038baf0, 0x0036b8f6, 0x0034b5fc, 0x002cb6f9, 0x0023b7f6, 0x0025b5fa, 0x0028b4ff, + 0x0028b6ff, 0x0029b7ff, 0x001fb5ff, 0x0015b2ff, 0x0020aef7, 0x003cb9ff, 0x005acbf0, + 0x0042befa, 0x002ab6fc, 0x0012adff, 0x0018acfc, 0x001eacfa, 0x001ea9fd, 0x001ea7ff, + 0x001ba8fa, 0x0018a8f4, 0x0018a6f8, 0x0018a4fd, 0x0019a3fa, 0x001aa1f7, 0x0019a1f7, + 0x0018a0f8, 0x0017a0f8, 0x00169ff8, 0x00159ef7, 0x00149df7, 0x00139cf6, 0x00129bf6, + 0x00119af5, 0x001099f4, 0x001099f4, 0x0054d1f8, 0x0052d1f8, 0x0051d0f9, 0x004fcff9, + 0x004ecffa, 0x004ccefa, 0x004acdf9, 0x0048ccf9, 0x0045cbf9, 0x0045caf9, 0x0044c9f9, + 0x0043c8f9, 0x0043c8f9, 0x0042c7f9, 0x0042c6f9, 0x0041c5f9, 0x0040c5fa, 0x003fc4f9, + 0x003dc2f9, 0x003bc1f9, 0x003ac0fa, 0x0038bff9, 0x0037bff9, 0x0036bef9, 0x0034bef8, + 0x0035bcf6, 0x0035baf5, 0x0034b8f8, 0x0033b6fc, 0x002eb6f9, 0x0029b6f7, 0x0029b5f8, + 0x002ab4fa, 0x002ab5fb, 0x002ab5fc, 0x002ab2f6, 0x002aafef, 0x001ba9f6, 0x009bcfd9, + 0x006dcfe9, 0x0074c7e4, 0x0080c9dd, 0x0019adfb, 0x001cacf9, 0x001fabf8, 0x001fa9f9, + 0x001ea7fb, 0x001ca7f9, 0x001aa7f6, 0x001aa5f8, 0x001aa4fb, 0x001aa3fa, 0x001aa2f8, + 0x0019a1f8, 0x0018a0f8, 0x0017a0f8, 0x00169ff8, 0x00159ef7, 0x00149df7, 0x00139cf6, + 0x00129bf6, 0x00119bf5, 0x00119af5, 0x00119af5, 0x0055d0f9, 0x0053d0fa, 0x0051d0fa, + 0x004fcffa, 0x004dcffa, 0x004bcefa, 0x0049cdf9, 0x0046ccf9, 0x0044caf8, 0x0043caf8, + 0x0043c9f8, 0x0043c8f9, 0x0042c8f9, 0x0042c7f9, 0x0041c6f9, 0x0041c6f9, 0x0040c5fa, + 0x003ec3f9, 0x003dc2fa, 0x003bc1fa, 0x0039c0fa, 0x0038bff9, 0x0036bff9, 0x0035bef9, + 0x0034bdf8, 0x0033bcf9, 0x0033bafa, 0x0032b9fb, 0x0032b8fc, 0x0030b7fa, 0x002eb6f8, + 0x002db5f7, 0x002bb4f5, 0x002bb4f6, 0x002bb3f7, 0x0029b2f9, 0x0028b2fc, 0x0030b2f7, + 0x0012a8fe, 0x007fd4e1, 0x0058bbe6, 0x0015aafb, 0x001fadf8, 0x0020acf7, 0x0020aaf5, + 0x001fa9f6, 0x001ea8f7, 0x001da6f7, 0x001ca5f8, 0x001ca4f8, 0x001ba3f9, 0x001ba3f9, + 0x001ba2f9, 0x0019a1f9, 0x0018a0f8, 0x0017a0f8, 0x00169ff8, 0x00159ef7, 0x00149df7, + 0x00139cf6, 0x00129bf5, 0x00129bf5, 0x00129bf5, 0x00129bf5, 0x0055d0f9, 0x0053d0fa, + 0x0051d0fa, 0x004fcffa, 0x004dcffa, 0x004bcefa, 0x0049cdf9, 0x0046ccf9, 0x0044caf8, + 0x0043caf8, 0x0043c9f8, 0x0043c8f9, 0x0042c8f9, 0x0042c7f9, 0x0041c6f9, 0x0041c6f9, + 0x0040c5fa, 0x003ec3f9, 0x003dc2fa, 0x003bc1fa, 0x0039c0fa, 0x0038bff9, 0x0036bff9, + 0x0035bef9, 0x0034bdf8, 0x0033bcf9, 0x0033bafa, 0x0032b9fb, 0x0032b8fc, 0x0030b7fa, + 0x002eb6f8, 0x002db5f7, 0x002bb4f5, 0x002bb4f6, 0x002bb3f7, 0x002ab2f8, 0x0029b2fa, + 0x002db6f5, 0x001db5f6, 0x00239bff, 0x0020b6f3, 0x000cacfb, 0x001eacf7, 0x001fabf6, + 0x0020aaf5, 0x001fa9f6, 0x001ea8f7, 0x001da6f7, 0x001ca5f8, 0x001ca4f8, 0x001ba3f9, + 0x001ba3f9, 0x001ba2f9, 0x0019a1f9, 0x0018a0f8, 0x0017a0f8, 0x00169ff8, 0x00159ef7, + 0x00149df7, 0x00139cf6, 0x00129bf5, 0x00129bf5, 0x00129bf5, 0x00129bf5, 0x0055d0f9, + 0x0053d0fa, 0x0051d0fa, 0x004fcffa, 0x004dcffa, 0x004bcefa, 0x0049cdf9, 0x0046ccf9, + 0x0044caf8, 0x0043caf8, 0x0043c9f8, 0x0043c8f9, 0x0042c8f9, 0x0042c7f9, 0x0041c6f9, + 0x0041c6f9, 0x0040c5fa, 0x003ec3f9, 0x003dc2fa, 0x003bc1fa, 0x0039c0fa, 0x0038bff9, + 0x0036bff9, 0x0035bef9, 0x0034bdf8, 0x0033bcf9, 0x0033bafa, 0x0032b9fb, 0x0032b8fc, + 0x0030b7fa, 0x002eb6f8, 0x002db5f7, 0x002bb4f5, 0x002bb4f6, 0x002bb3f7, 0x002bb2f8, + 0x002bb1f8, 0x0022aff9, 0x0019acfa, 0x001eadf7, 0x0024aef3, 0x0020adf5, 0x001dabf6, + 0x001fabf6, 0x0020aaf5, 0x001fa9f6, 0x001ea8f7, 0x001da6f7, 0x001ca5f8, 0x001ca4f8, + 0x001ba3f9, 0x001ba3f9, 0x001ba2f9, 0x0019a1f9, 0x0018a0f8, 0x0017a0f8, 0x00169ff8, + 0x00159ef7, 0x00149df7, 0x00139cf6, 0x00129bf5, 0x00129bf5, 0x00129bf5, 0x00129bf5, + 0x0055d0f9, 0x0053d0fa, 0x0051d0fa, 0x004fcffa, 0x004dcffa, 0x004bcefa, 0x0049cdf9, + 0x0046ccf9, 0x0044caf8, 0x0043caf8, 0x0043c9f8, 0x0043c8f9, 0x0042c8f9, 0x0042c7f9, + 0x0041c6f9, 0x0041c6f9, 0x0040c5fa, 0x003ec3f9, 0x003dc2fa, 0x003bc1fa, 0x0039c0fa, + 0x0038bff9, 0x0036bff9, 0x0035bef9, 0x0034bdf8, 0x0033bcf9, 0x0033bafa, 0x0032b9fb, + 0x0032b8fc, 0x0030b7fa, 0x002eb6f8, 0x002db5f7, 0x002bb4f5, 0x002bb4f6, 0x002bb3f7, + 0x002bb2f8, 0x002bb1f8, 0x0022aff9, 0x0019acfa, 0x001eadf7, 0x0024aef3, 0x0020adf5, + 0x001dabf6, 0x001fabf6, 0x0020aaf5, 0x001fa9f6, 0x001ea8f7, 0x001da6f7, 0x001ca5f8, + 0x001ca4f8, 0x001ba3f9, 0x001ba3f9, 0x001ba2f9, 0x0019a1f9, 0x0018a0f8, 0x0017a0f8, + 0x00169ff8, 0x00159ef7, 0x00149df7, 0x00139cf6, 0x00129bf5, 0x00129bf5, 0x00129bf5, + 0x00129bf5 + }; + +#define IMG_WIDTH 64 +#define IMG_HEIGHT 64 +#define FORMAT_SIZE 4 +#define FORMAT PIXEL_FORMAT_XRGB32 + +static INLINE size_t fuzzyCompare(BYTE b1, BYTE b2) +{ + if (b1 > b2) + return b1 - b2; + return b2 - b1; +} + +static BOOL fuzzyCompareImage(const UINT32* crefImage, const BYTE* img, size_t npixels) +{ + size_t totalDelta = 0; + + for (size_t i = 0; i < npixels; i++, crefImage++) + { + BYTE A = *img++; + BYTE R = *img++; + BYTE G = *img++; + BYTE B = *img++; + size_t delta = 0; + + if (A != 0x00) + return FALSE; + + delta = fuzzyCompare(R, (*crefImage & 0x00ff0000) >> 16); + if (delta > 1) + return FALSE; + totalDelta += delta; + + delta = fuzzyCompare(G, (*crefImage & 0x0000ff00) >> 8); + if (delta > 1) + return FALSE; + totalDelta += delta; + + delta = fuzzyCompare(B, (*crefImage & 0x0000ff)); + if (delta > 1) + return FALSE; + totalDelta += delta; + } + + WLog_DBG("test", "totalDelta=%d (npixels=%d)", totalDelta, npixels); + return TRUE; +} + +int TestFreeRDPCodecRemoteFX(int argc, char* argv[]) +{ + int rc = -1; + REGION16 region = { 0 }; + RFX_CONTEXT* context = NULL; + BYTE* dest = NULL; + size_t stride = FORMAT_SIZE * IMG_WIDTH; + + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + /* use default threading options here, pass zero as + * ThreadingFlags */ + context = rfx_context_new(FALSE); + if (!context) + goto fail; + + dest = calloc(IMG_WIDTH * IMG_HEIGHT, FORMAT_SIZE); + if (!dest) + goto fail; + + region16_init(®ion); + if (!rfx_process_message(context, encodeHeaderSample, sizeof(encodeHeaderSample), 0, 0, dest, + FORMAT, stride, IMG_HEIGHT, ®ion)) + goto fail; + + region16_clear(®ion); + if (!rfx_process_message(context, encodeDataSample, sizeof(encodeDataSample), 0, 0, dest, + FORMAT, stride, IMG_HEIGHT, ®ion)) + goto fail; + region16_print(®ion); + +#if 0 + FILE *f = fopen("/tmp/windows.data", "w"); + if (f) { + fwrite(dest, IMG_WIDTH * IMG_HEIGHT, FORMAT_SIZE, f); + fclose(f); + } +#endif + + if (!fuzzyCompareImage(srefImage, dest, IMG_WIDTH * IMG_HEIGHT)) + goto fail; + + rc = 0; +fail: + region16_uninit(®ion); + rfx_context_free(context); + free(dest); + return rc; +} diff --git a/libfreerdp/codec/test/TestFreeRDPCodecXCrush.c b/libfreerdp/codec/test/TestFreeRDPCodecXCrush.c new file mode 100644 index 0000000..5b13532 --- /dev/null +++ b/libfreerdp/codec/test/TestFreeRDPCodecXCrush.c @@ -0,0 +1,130 @@ +#include <winpr/crt.h> +#include <winpr/print.h> + +#include "../xcrush.h" + +static const BYTE TEST_BELLS_DATA[] = "for.whom.the.bell.tolls,.the.bell.tolls.for.thee!"; + +static const BYTE TEST_BELLS_DATA_XCRUSH[] = + "\x12\x00\x66\x6f\x72\x2e\x77\x68\x6f\x6d\x2e\x74\x68\x65\x2e\x62" + "\x65\x6c\x6c\x2e\x74\x6f\x6c\x6c\x73\x2c\x2e\x74\x68\x65\x2e\x62" + "\x65\x6c\x6c\x2e\x74\x6f\x6c\x6c\x73\x2e\x66\x6f\x72\x2e\x74\x68" + "\x65"; + +static const BYTE TEST_ISLAND_DATA[] = "No man is an island entire of itself; every man " + "is a piece of the continent, a part of the main; " + "if a clod be washed away by the sea, Europe " + "is the less, as well as if a promontory were, as" + "well as any manner of thy friends or of thine " + "own were; any man's death diminishes me, " + "because I am involved in mankind. " + "And therefore never send to know for whom " + "the bell tolls; it tolls for thee."; + +static const BYTE TEST_ISLAND_DATA_XCRUSH[] = + "\x12\x61\x4e\x6f\x20\x6d\x61\x6e\x20\x69\x73\x20\xf8\xd2\xd8\xc2" + "\xdc\xc8\x40\xca\xdc\xe8\xd2\xe4\xca\x40\xde\xcc\x40\xd2\xe8\xe6" + "\xca\xd8\xcc\x76\x40\xca\xec\xca\xe4\xf3\xfa\x71\x20\x70\x69\x65" + "\x63\xfc\x12\xe8\xd0\xca\x40\xc6\xdf\xfb\xcd\xdf\xd0\x58\x40\xc2" + "\x40\xe0\xc2\xe4\xe9\xfe\x63\xec\xc3\x6b\x0b\x4b\x71\xd9\x03\x4b" + "\x37\xd7\x31\xb6\x37\xb2\x10\x31\x32\x90\x3b\xb0\xb9\xb4\x32\xb2" + "\x10\x30\xbb\xb0\xbc\x90\x31\x3c\x90\x7e\x68\x73\x65\x61\x2c\x20" + "\x45\x75\x72\x6f\x70\x65\xf2\x34\x7d\x38\x6c\x65\x73\x73\xf0\x69" + "\xcc\x81\xdd\x95\xb1\xb0\x81\x85\xcf\xc0\x94\xe0\xe4\xde\xdb\xe2" + "\xb3\x7f\x92\x4e\xec\xae\x4c\xbf\x86\x3f\x06\x0c\x2d\xde\x5d\x96" + "\xe6\x57\x2f\x1e\x53\xc9\x03\x33\x93\x4b\x2b\x73\x23\x99\x03\x7f" + "\xd2\xb6\x96\xef\x38\x1d\xdb\xbc\x24\x72\x65\x3b\xf5\x5b\xf8\x49" + "\x3b\x99\x03\x23\x2b\x0b\xa3\x41\x03\x23\x4b\x6b\x4b\x73\x4f\x96" + "\xce\x64\x0d\xbe\x19\x31\x32\xb1\xb0\xba\xb9\xb2\x90\x24\x90\x30" + "\xb6\x90\x34\xb7\x3b\x37\xb6\x3b\x79\xd4\xd2\xdd\xec\x18\x6b\x69" + "\x6e\x64\x2e\x20\x41\xf7\x33\xcd\x47\x26\x56\x66\xff\x74\x9b\xbd" + "\xbf\x04\x0e\x7e\x31\x10\x3a\x37\x90\x35\xb7\x37\xbb\x90\x7d\x81" + "\x03\xbb\x43\x7b\x6f\xa8\xe5\x8b\xd0\xf0\xe8\xde\xd8\xd8\xe7\xec" + "\xf3\xa7\xe4\x7c\xa7\xe2\x9f\x01\x99\x4b\x80"; + +static void test_dump(const char* fkt, const void* generated, size_t generated_size, + const void* expected, size_t expected_size) +{ + printf("[%s] output size mismatch: Actual: %" PRIuz ", Expected: %" PRIuz "\n", fkt, + generated_size, expected_size); + printf("[%s] Actual\n", fkt); + BitDump(fkt, WLOG_INFO, generated, generated_size * 8ull, 0); + printf("[%s] Expected\n", fkt); + BitDump(fkt, WLOG_INFO, expected, expected_size * 8ull, 0); +} + +static BOOL test_compare(const char* fkt, const void* generated, size_t generated_size, + const void* expected, size_t expected_size) +{ + if (generated_size != expected_size) + { + test_dump(fkt, generated, generated_size, expected, expected_size); + return FALSE; + } + + if (memcmp(generated, expected, generated_size) != 0) + { + test_dump(fkt, generated, generated_size, expected, expected_size); + return FALSE; + } + + return TRUE; +} + +static BOOL test_run(const char* fkt, const void* src, UINT32 src_size, const void* expected, + size_t expected_size) +{ + BOOL rc = FALSE; + int status = -1; + UINT32 Flags = 0; + const BYTE* pDstData = NULL; + BYTE OutputBuffer[65536] = { 0 }; + UINT32 DstSize = sizeof(OutputBuffer); + XCRUSH_CONTEXT* xcrush = xcrush_context_new(TRUE); + if (!xcrush) + return -1; + status = xcrush_compress(xcrush, src, src_size, OutputBuffer, &pDstData, &DstSize, &Flags); + printf("[%s] status: %d Flags: 0x%08" PRIX32 " DstSize: %" PRIu32 "\n", fkt, status, Flags, + DstSize); + + rc = test_compare(fkt, pDstData, DstSize, expected, expected_size); + + xcrush_context_free(xcrush); + return rc; +} + +struct test_argument +{ + const char* name; + const void* src; + UINT32 src_size; + const void* expected; + size_t expected_size; +}; + +static const struct test_argument tests[] = { + { "XCrushCompressIsland", TEST_ISLAND_DATA, sizeof(TEST_ISLAND_DATA) - 1, + TEST_ISLAND_DATA_XCRUSH, sizeof(TEST_ISLAND_DATA_XCRUSH) - 1 } +#if 0 + ,{ "XCrushCompressBells", TEST_BELLS_DATA, sizeof(TEST_BELLS_DATA) - 1, TEST_BELLS_DATA_XCRUSH, + sizeof(TEST_BELLS_DATA_XCRUSH) - 1 } +#endif +}; + +int TestFreeRDPCodecXCrush(int argc, char* argv[]) +{ + int rc = 0; + + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + for (size_t x = 0; x < ARRAYSIZE(tests); x++) + { + const struct test_argument* arg = &tests[x]; + + if (!test_run(arg->name, arg->src, arg->src_size, arg->expected, arg->expected_size)) + rc = -1; + } + + return rc; +} diff --git a/libfreerdp/codec/test/TestFreeRDPCodecZGfx.c b/libfreerdp/codec/test/TestFreeRDPCodecZGfx.c new file mode 100644 index 0000000..68db553 --- /dev/null +++ b/libfreerdp/codec/test/TestFreeRDPCodecZGfx.c @@ -0,0 +1,274 @@ +#include <winpr/crt.h> +#include <winpr/print.h> +#include <winpr/bitstream.h> + +#include <freerdp/freerdp.h> +#include <freerdp/codec/zgfx.h> +#include <freerdp/log.h> + +/* Sample from [MS-RDPEGFX] */ +static const BYTE TEST_FOX_DATA[] = "The quick brown " + "fox jumps over t" + "he lazy dog"; + +static const BYTE TEST_FOX_DATA_SINGLE[] = + "\xE0\x04\x54\x68\x65\x20\x71\x75\x69\x63\x6B\x20\x62\x72\x6F\x77" + "\x6E\x20\x66\x6F\x78\x20\x6A\x75\x6D\x70\x73\x20\x6F\x76\x65\x72" + "\x20\x74\x68\x65\x20\x6C\x61\x7A\x79\x20\x64\x6F\x67"; + +static const BYTE TEST_FOX_DATA_MULTIPART[] = + "\xE1\x03\x00\x2B\x00\x00\x00\x11\x00\x00\x00\x04\x54\x68\x65\x20" + "\x71\x75\x69\x63\x6B\x20\x62\x72\x6F\x77\x6E\x20\x0E\x00\x00\x00" + "\x04\x66\x6F\x78\x20\x6A\x75\x6D\x70\x73\x20\x6F\x76\x65\x10\x00" + "\x00\x00\x24\x39\x08\x0E\x91\xF8\xD8\x61\x3D\x1E\x44\x06\x43\x79" + "\x9C\x02"; + +static int test_ZGfxCompressFox(void) +{ + int rc = -1; + int status = 0; + UINT32 Flags = 0; + const BYTE* pSrcData = NULL; + UINT32 SrcSize = 0; + UINT32 DstSize = 0; + BYTE* pDstData = NULL; + ZGFX_CONTEXT* zgfx = NULL; + UINT32 expectedSize = 0; + zgfx = zgfx_context_new(TRUE); + + if (!zgfx) + return -1; + + SrcSize = sizeof(TEST_FOX_DATA) - 1; + pSrcData = (const BYTE*)TEST_FOX_DATA; + Flags = 0; + expectedSize = sizeof(TEST_FOX_DATA_SINGLE) - 1; + status = zgfx_compress(zgfx, pSrcData, SrcSize, &pDstData, &DstSize, &Flags); + + if (status < 0) + goto fail; + + printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize); + + if (DstSize != expectedSize) + { + printf("test_ZGfxCompressFox: output size mismatch: Actual: %" PRIu32 ", Expected: %" PRIu32 + "\n", + DstSize, expectedSize); + goto fail; + } + + if (memcmp(pDstData, TEST_FOX_DATA_SINGLE, DstSize) != 0) + { + printf("test_ZGfxCompressFox: output mismatch\n"); + printf("Actual\n"); + BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0); + printf("Expected\n"); + BitDump(__func__, WLOG_INFO, TEST_FOX_DATA_SINGLE, DstSize * 8, 0); + goto fail; + } + + rc = 0; +fail: + free(pDstData); + zgfx_context_free(zgfx); + return rc; +} + +static int test_ZGfxDecompressFoxSingle(void) +{ + int rc = -1; + int status = 0; + UINT32 Flags = 0; + const BYTE* pSrcData = NULL; + UINT32 SrcSize = 0; + UINT32 DstSize = 0; + BYTE* pDstData = NULL; + ZGFX_CONTEXT* zgfx = NULL; + UINT32 expectedSize = 0; + zgfx = zgfx_context_new(TRUE); + + if (!zgfx) + return -1; + + SrcSize = sizeof(TEST_FOX_DATA_SINGLE) - 1; + pSrcData = (const BYTE*)TEST_FOX_DATA_SINGLE; + Flags = 0; + expectedSize = sizeof(TEST_FOX_DATA) - 1; + status = zgfx_decompress(zgfx, pSrcData, SrcSize, &pDstData, &DstSize, Flags); + + if (status < 0) + goto fail; + + printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize); + + if (DstSize != expectedSize) + { + printf("test_ZGfxDecompressFoxSingle: output size mismatch: Actual: %" PRIu32 + ", Expected: %" PRIu32 "\n", + DstSize, expectedSize); + goto fail; + } + + if (memcmp(pDstData, TEST_FOX_DATA, DstSize) != 0) + { + printf("test_ZGfxDecompressFoxSingle: output mismatch\n"); + printf("Actual\n"); + BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0); + printf("Expected\n"); + BitDump(__func__, WLOG_INFO, TEST_FOX_DATA, DstSize * 8, 0); + goto fail; + } + + rc = 0; +fail: + free(pDstData); + zgfx_context_free(zgfx); + return rc; +} + +static int test_ZGfxDecompressFoxMultipart(void) +{ + int rc = -1; + int status = 0; + UINT32 Flags = 0; + const BYTE* pSrcData = NULL; + UINT32 SrcSize = 0; + UINT32 DstSize = 0; + BYTE* pDstData = NULL; + ZGFX_CONTEXT* zgfx = NULL; + UINT32 expectedSize = 0; + zgfx = zgfx_context_new(TRUE); + + if (!zgfx) + return -1; + + SrcSize = sizeof(TEST_FOX_DATA_MULTIPART) - 1; + pSrcData = (const BYTE*)TEST_FOX_DATA_MULTIPART; + Flags = 0; + expectedSize = sizeof(TEST_FOX_DATA) - 1; + status = zgfx_decompress(zgfx, pSrcData, SrcSize, &pDstData, &DstSize, Flags); + + if (status < 0) + goto fail; + + printf("flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize); + + if (DstSize != expectedSize) + { + printf("test_ZGfxDecompressFoxSingle: output size mismatch: Actual: %" PRIu32 + ", Expected: %" PRIu32 "\n", + DstSize, expectedSize); + goto fail; + } + + if (memcmp(pDstData, TEST_FOX_DATA, DstSize) != 0) + { + printf("test_ZGfxDecompressFoxSingle: output mismatch\n"); + printf("Actual\n"); + BitDump(__func__, WLOG_INFO, pDstData, DstSize * 8, 0); + printf("Expected\n"); + BitDump(__func__, WLOG_INFO, TEST_FOX_DATA, DstSize * 8, 0); + goto fail; + } + + rc = 0; +fail: + free(pDstData); + zgfx_context_free(zgfx); + return rc; +} + +static int test_ZGfxCompressConsistent(void) +{ + int rc = -1; + int status = 0; + + UINT32 Flags = 0; + const BYTE* pSrcData = NULL; + UINT32 SrcSize = 0; + UINT32 DstSize = 0; + BYTE* pDstData = NULL; + UINT32 DstSize2 = 0; + BYTE* pDstData2 = NULL; + ZGFX_CONTEXT* zgfx = NULL; + UINT32 expectedSize = 0; + BYTE BigBuffer[65536]; + memset(BigBuffer, 0xaa, sizeof(BigBuffer)); + memcpy(BigBuffer, TEST_FOX_DATA, sizeof(TEST_FOX_DATA) - 1); + zgfx = zgfx_context_new(TRUE); + + if (!zgfx) + return -1; + + /* Compress */ + expectedSize = SrcSize = sizeof(BigBuffer); + pSrcData = (const BYTE*)BigBuffer; + Flags = 0; + status = zgfx_compress(zgfx, pSrcData, SrcSize, &pDstData2, &DstSize2, &Flags); + + if (status < 0) + goto fail; + + printf("Compress: flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize2); + /* Decompress */ + status = zgfx_decompress(zgfx, pDstData2, DstSize2, &pDstData, &DstSize, Flags); + + if (status < 0) + goto fail; + + printf("Decompress: flags: 0x%08" PRIX32 " size: %" PRIu32 "\n", Flags, DstSize); + + if (DstSize != expectedSize) + { + printf("test_ZGfxDecompressFoxSingle: output size mismatch: Actual: %" PRIu32 + ", Expected: %" PRIu32 "\n", + DstSize, expectedSize); + goto fail; + } + + if (memcmp(pDstData, BigBuffer, DstSize) != 0) + { + printf("test_ZGfxDecompressFoxSingle: output mismatch\n"); + printf("Actual\n"); + BitDump(__func__, WLOG_INFO, pDstData, 64 * 8, 0); + printf("...\n"); + BitDump(__func__, WLOG_INFO, pDstData + DstSize - 64, 64 * 8, 0); + printf("Expected\n"); + BitDump(__func__, WLOG_INFO, BigBuffer, 64 * 8, 0); + printf("...\n"); + BitDump(__func__, WLOG_INFO, BigBuffer + DstSize - 64, 64 * 8, 0); + printf("Middle Result\n"); + BitDump(__func__, WLOG_INFO, pDstData2, 64 * 8, 0); + printf("...\n"); + BitDump(__func__, WLOG_INFO, pDstData2 + DstSize2 - 64, 64 * 8, 0); + goto fail; + } + + rc = 0; +fail: + free(pDstData); + free(pDstData2); + zgfx_context_free(zgfx); + return rc; +} + +int TestFreeRDPCodecZGfx(int argc, char* argv[]) +{ + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + if (test_ZGfxCompressFox() < 0) + return -1; + + if (test_ZGfxDecompressFoxSingle() < 0) + return -1; + + if (test_ZGfxDecompressFoxMultipart() < 0) + return -1; + + if (test_ZGfxCompressConsistent() < 0) + return -1; + + return 0; +} diff --git a/libfreerdp/codec/test/TestFreeRDPRegion.c b/libfreerdp/codec/test/TestFreeRDPRegion.c new file mode 100644 index 0000000..6f75320 --- /dev/null +++ b/libfreerdp/codec/test/TestFreeRDPRegion.c @@ -0,0 +1,863 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * + * Copyright 2014 Thincast Technologies GmbH + * Copyright 2014 Hardening <contact@hardening-consulting.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/crt.h> +#include <winpr/print.h> + +#include <freerdp/codec/region.h> + +static BOOL compareRectangles(const RECTANGLE_16* src1, const RECTANGLE_16* src2, int nb) +{ + for (int i = 0; i < nb; i++, src1++, src2++) + { + if (memcmp(src1, src2, sizeof(RECTANGLE_16))) + { + fprintf(stderr, + "expecting rect %d (%" PRIu16 ",%" PRIu16 "-%" PRIu16 ",%" PRIu16 + ") and have (%" PRIu16 ",%" PRIu16 "-%" PRIu16 ",%" PRIu16 ")\n", + i, src2->left, src2->top, src2->right, src2->bottom, src1->left, src1->top, + src1->right, src1->bottom); + return FALSE; + } + } + + return TRUE; +} + +static int test_basic(void) +{ + REGION16 region; + int retCode = -1; + const RECTANGLE_16* rects = NULL; + UINT32 nbRects = 0; + /* R1 + R2 ==> disjointed rects */ + RECTANGLE_16 r1 = { 0, 101, 200, 201 }; + RECTANGLE_16 r2 = { 150, 301, 250, 401 }; + RECTANGLE_16 r1_r2[] = { { 0, 101, 200, 201 }, { 150, 301, 250, 401 } }; + /* r1 */ + region16_init(®ion); + + if (!region16_union_rect(®ion, ®ion, &r1)) + goto out; + + rects = region16_rects(®ion, &nbRects); + + if (!rects || nbRects != 1 || memcmp(rects, &r1, sizeof(RECTANGLE_16))) + goto out; + + /* r1 + r2 */ + if (!region16_union_rect(®ion, ®ion, &r2)) + goto out; + + rects = region16_rects(®ion, &nbRects); + + if (!rects || nbRects != 2 || !compareRectangles(rects, r1_r2, nbRects)) + goto out; + + /* clear region */ + region16_clear(®ion); + region16_rects(®ion, &nbRects); + + if (nbRects) + goto out; + + retCode = 0; +out: + region16_uninit(®ion); + return retCode; +} + +static int test_r1_r3(void) +{ + REGION16 region; + int retCode = -1; + const RECTANGLE_16* rects = NULL; + UINT32 nbRects = 0; + RECTANGLE_16 r1 = { 0, 101, 200, 201 }; + RECTANGLE_16 r3 = { 150, 151, 250, 251 }; + RECTANGLE_16 r1_r3[] = { { 0, 101, 200, 151 }, { 0, 151, 250, 201 }, { 150, 201, 250, 251 } }; + region16_init(®ion); + /* + * +=============================================================== + * | + * |+-----+ +-----+ + * || r1 | | | + * || +-+------+ +-----+--------+ + * || | r3 | | | + * |+---+ | ====> +-----+--------+ + * | | | | | + * | +--------+ +--------+ + */ + + /* R1 + R3 */ + if (!region16_union_rect(®ion, ®ion, &r1)) + goto out; + + if (!region16_union_rect(®ion, ®ion, &r3)) + goto out; + + rects = region16_rects(®ion, &nbRects); + + if (!rects || nbRects != 3 || !compareRectangles(rects, r1_r3, nbRects)) + goto out; + + /* R3 + R1 */ + region16_clear(®ion); + + if (!region16_union_rect(®ion, ®ion, &r3)) + goto out; + + if (!region16_union_rect(®ion, ®ion, &r1)) + goto out; + + rects = region16_rects(®ion, &nbRects); + + if (!rects || nbRects != 3 || !compareRectangles(rects, r1_r3, nbRects)) + goto out; + + retCode = 0; +out: + region16_uninit(®ion); + return retCode; +} + +static int test_r9_r10(void) +{ + REGION16 region; + int retCode = -1; + const RECTANGLE_16* rects = NULL; + UINT32 nbRects = 0; + /* + * +=============================================================== + * | + * | +---+ +---+ + * |+--|r10|-+ +--+---+-+ + * ||r9| | | | | + * || | | | | | + * || | | | =====> | | + * || | | | | | + * || | | | | | + * |+--| |-+ +--+---+-+ + * | +---+ +---+ + */ + RECTANGLE_16 r9 = { 0, 100, 400, 200 }; + RECTANGLE_16 r10 = { 200, 0, 300, 300 }; + RECTANGLE_16 r9_r10[] = { + { 200, 0, 300, 100 }, + { 0, 100, 400, 200 }, + { 200, 200, 300, 300 }, + }; + region16_init(®ion); + + if (!region16_union_rect(®ion, ®ion, &r9)) + goto out; + + if (!region16_union_rect(®ion, ®ion, &r10)) + goto out; + + rects = region16_rects(®ion, &nbRects); + + if (!rects || nbRects != 3 || !compareRectangles(rects, r9_r10, nbRects)) + goto out; + + retCode = 0; +out: + region16_uninit(®ion); + return retCode; +} + +static int test_r1_r5(void) +{ + REGION16 region; + int retCode = -1; + const RECTANGLE_16* rects = NULL; + UINT32 nbRects = 0; + RECTANGLE_16 r1 = { 0, 101, 200, 201 }; + RECTANGLE_16 r5 = { 150, 121, 300, 131 }; + RECTANGLE_16 r1_r5[] = { { 0, 101, 200, 121 }, { 0, 121, 300, 131 }, { 0, 131, 200, 201 } }; + region16_init(®ion); + + /* + * +=============================================================== + * | + * |+--------+ +--------+ + * || r1 | | | + * || +--+----+ +--------+----+ + * || | r5 | =====> | | + * || +-------+ +--------+----+ + * || | | | + * |+--------+ +--------+ + * | + * + */ + if (!region16_union_rect(®ion, ®ion, &r1)) + goto out; + + if (!region16_union_rect(®ion, ®ion, &r5)) + goto out; + + rects = region16_rects(®ion, &nbRects); + + if (!rects || nbRects != 3 || !compareRectangles(rects, r1_r5, nbRects)) + goto out; + + retCode = 0; +out: + region16_uninit(®ion); + return retCode; +} + +static int test_r1_r6(void) +{ + REGION16 region; + int retCode = -1; + const RECTANGLE_16* rects = NULL; + UINT32 nbRects = 0; + RECTANGLE_16 r1 = { 0, 101, 200, 201 }; + RECTANGLE_16 r6 = { 150, 121, 170, 131 }; + region16_init(®ion); + /* + * +=============================================================== + * | + * |+--------+ +--------+ + * || r1 | | | + * || +--+ | | | + * || |r6| | =====> | | + * || +--+ | | | + * || | | | + * |+--------+ +--------+ + * | + */ + region16_clear(®ion); + + if (!region16_union_rect(®ion, ®ion, &r1)) + goto out; + + if (!region16_union_rect(®ion, ®ion, &r6)) + goto out; + + rects = region16_rects(®ion, &nbRects); + + if (!rects || nbRects != 1 || !compareRectangles(rects, &r1, nbRects)) + goto out; + + retCode = 0; +out: + region16_uninit(®ion); + return retCode; +} + +static int test_r1_r2_r4(void) +{ + REGION16 region; + int retCode = -1; + const RECTANGLE_16* rects = NULL; + UINT32 nbRects = 0; + RECTANGLE_16 r1 = { 0, 101, 200, 201 }; + RECTANGLE_16 r2 = { 150, 301, 250, 401 }; + RECTANGLE_16 r4 = { 150, 251, 250, 301 }; + RECTANGLE_16 r1_r2_r4[] = { { 0, 101, 200, 201 }, { 150, 251, 250, 401 } }; + /* + * +=============================================================== + * | + * |+-----+ +-----+ + * || r1 | | | + * || | | | + * || | | | + * |+-----+ ====> +-----+ + * | + * | +--------+ +--------+ + * | | r4 | | | + * | +--------+ | | + * | | r2 | | | + * | | | | | + * | +--------+ +--------+ + * + */ + region16_init(®ion); + + if (!region16_union_rect(®ion, ®ion, &r1)) + goto out; + + if (!region16_union_rect(®ion, ®ion, &r2)) + goto out; + + if (!region16_union_rect(®ion, ®ion, &r4)) + goto out; + + rects = region16_rects(®ion, &nbRects); + + if (!rects || nbRects != 2 || !compareRectangles(rects, r1_r2_r4, nbRects)) + goto out; + + retCode = 0; +out: + region16_uninit(®ion); + return retCode; +} + +static int test_r1_r7_r8(void) +{ + REGION16 region; + int retCode = -1; + const RECTANGLE_16* rects = NULL; + UINT32 nbRects = 0; + RECTANGLE_16 r1 = { 0, 101, 200, 201 }; + RECTANGLE_16 r7 = { 300, 101, 500, 201 }; + RECTANGLE_16 r8 = { 150, 121, 400, 131 }; + RECTANGLE_16 r1_r7_r8[] = { + { 0, 101, 200, 121 }, { 300, 101, 500, 121 }, { 0, 121, 500, 131 }, + { 0, 131, 200, 201 }, { 300, 131, 500, 201 }, + }; + /* + * +=============================================================== + * | + * |+--------+ +--------+ +--------+ +--------+ + * || r1 | | r7 | | | | | + * || +------------+ | +--------+---+--------+ + * || | r8 | | =====> | | + * || +------------+ | +--------+---+--------+ + * || | | | | | | | + * |+--------+ +--------+ +--------+ +--------+ + * | + */ + region16_init(®ion); + + if (!region16_union_rect(®ion, ®ion, &r1)) + goto out; + + if (!region16_union_rect(®ion, ®ion, &r7)) + goto out; + + if (!region16_union_rect(®ion, ®ion, &r8)) + goto out; + + rects = region16_rects(®ion, &nbRects); + + if (!rects || nbRects != 5 || !compareRectangles(rects, r1_r7_r8, nbRects)) + goto out; + + region16_clear(®ion); + + if (!region16_union_rect(®ion, ®ion, &r1)) + goto out; + + if (!region16_union_rect(®ion, ®ion, &r8)) + goto out; + + if (!region16_union_rect(®ion, ®ion, &r7)) + goto out; + + rects = region16_rects(®ion, &nbRects); + + if (!rects || nbRects != 5 || !compareRectangles(rects, r1_r7_r8, nbRects)) + goto out; + + region16_clear(®ion); + + if (!region16_union_rect(®ion, ®ion, &r8)) + goto out; + + if (!region16_union_rect(®ion, ®ion, &r7)) + goto out; + + if (!region16_union_rect(®ion, ®ion, &r1)) + goto out; + + rects = region16_rects(®ion, &nbRects); + + if (!rects || nbRects != 5 || !compareRectangles(rects, r1_r7_r8, nbRects)) + goto out; + + retCode = 0; +out: + region16_uninit(®ion); + return retCode; +} + +static int test_r1_r2_r3_r4(void) +{ + REGION16 region; + int retCode = -1; + const RECTANGLE_16* rects = NULL; + UINT32 nbRects = 0; + RECTANGLE_16 r1 = { 0, 101, 200, 201 }; + RECTANGLE_16 r2 = { 150, 301, 250, 401 }; + RECTANGLE_16 r3 = { 150, 151, 250, 251 }; + RECTANGLE_16 r4 = { 150, 251, 250, 301 }; + RECTANGLE_16 r1_r2_r3[] = { + { 0, 101, 200, 151 }, { 0, 151, 250, 201 }, { 150, 201, 250, 251 }, { 150, 301, 250, 401 } + }; + RECTANGLE_16 r1_r2_r3_r4[] = { { 0, 101, 200, 151 }, + { 0, 151, 250, 201 }, + { 150, 201, 250, 401 } }; + region16_init(®ion); + + /* + * +=============================================================== + * | + * |+-----+ +-----+ + * || r1 | | | + * || +-+------+ +-----+--------+ + * || | r3 | | | + * |+---+ | ====> +-----+--------+ + * | | | | | + * | +--------+ +--------+ + * | +--------+ +--------+ + * | | r2 | | | + * | | | | | + * | +--------+ +--------+ + */ + if (!region16_union_rect(®ion, ®ion, &r1)) + goto out; + + if (!region16_union_rect(®ion, ®ion, &r2)) + goto out; + + if (!region16_union_rect(®ion, ®ion, &r3)) + goto out; + + rects = region16_rects(®ion, &nbRects); + + if (!rects || nbRects != 4 || !compareRectangles(rects, r1_r2_r3, 4)) + goto out; + + /* + * +=============================================================== + * | + * |+-----+ +-----+ + * || | | | + * |+-----+--------+ +-----+--------+ + * || | ==> | | + * |+-----+--------+ +-----+--------+ + * | | | | | + * | +--------+ | | + * | | + r4 | | | + * | +--------+ | | + * | | | | | + * | | | | | + * | +--------+ +--------+ + */ + if (!region16_union_rect(®ion, ®ion, &r4)) + goto out; + + rects = region16_rects(®ion, &nbRects); + + if (!rects || nbRects != 3 || !compareRectangles(rects, r1_r2_r3_r4, 3)) + goto out; + + retCode = 0; +out: + region16_uninit(®ion); + return retCode; +} + +static int test_from_weston(void) +{ + /* + * 0: 0,0 -> 640,32 (w=640 h=32) + * 1: 236,169 -> 268,201 (w=32 h=32) + * 2: 246,258 -> 278,290 (w=32 h=32) + */ + REGION16 region; + int retCode = -1; + const RECTANGLE_16* rects = NULL; + UINT32 nbRects = 0; + RECTANGLE_16 r1 = { 0, 0, 640, 32 }; + RECTANGLE_16 r2 = { 236, 169, 268, 201 }; + RECTANGLE_16 r3 = { 246, 258, 278, 290 }; + RECTANGLE_16 r1_r2_r3[] = { { 0, 0, 640, 32 }, { 236, 169, 268, 201 }, { 246, 258, 278, 290 } }; + region16_init(®ion); + + /* + * +=============================================================== + * |+-------------------------------------------------------------+ + * || r1 | + * |+-------------------------------------------------------------+ + * | + * | +---------------+ + * | | r2 | + * | +---------------+ + * | + * | +---------------+ + * | | r3 | + * | +---------------+ + * | + */ + if (!region16_union_rect(®ion, ®ion, &r1)) + goto out; + + if (!region16_union_rect(®ion, ®ion, &r2)) + goto out; + + if (!region16_union_rect(®ion, ®ion, &r3)) + goto out; + + rects = region16_rects(®ion, &nbRects); + + if (!rects || nbRects != 3 || !compareRectangles(rects, r1_r2_r3, 3)) + goto out; + + retCode = 0; +out: + region16_uninit(®ion); + return retCode; +} + +static int test_r1_inter_r3(void) +{ + REGION16 region; + REGION16 intersection; + int retCode = -1; + const RECTANGLE_16* rects = NULL; + UINT32 nbRects = 0; + RECTANGLE_16 r1 = { 0, 101, 200, 201 }; + RECTANGLE_16 r3 = { 150, 151, 250, 251 }; + RECTANGLE_16 r1_inter_r3[] = { + { 150, 151, 200, 201 }, + }; + region16_init(®ion); + region16_init(&intersection); + + /* + * +=============================================================== + * | + * |+-----+ + * || r1 | + * || +-+------+ +-+ + * || | r3 | r1&r3 | | + * |+---+ | ====> +-+ + * | | | + * | +--------+ + */ + if (!region16_union_rect(®ion, ®ion, &r1)) + goto out; + + if (!region16_intersects_rect(®ion, &r3)) + goto out; + + if (!region16_intersect_rect(&intersection, ®ion, &r3)) + goto out; + + rects = region16_rects(&intersection, &nbRects); + + if (!rects || nbRects != 1 || !compareRectangles(rects, r1_inter_r3, nbRects)) + goto out; + + retCode = 0; +out: + region16_uninit(®ion); + region16_uninit(&intersection); + return retCode; +} + +static int test_r1_r3_inter_r11(void) +{ + REGION16 region; + REGION16 intersection; + int retCode = -1; + const RECTANGLE_16* rects = NULL; + UINT32 nbRects = 0; + RECTANGLE_16 r1 = { 0, 101, 200, 201 }; + RECTANGLE_16 r3 = { 150, 151, 250, 251 }; + RECTANGLE_16 r11 = { 170, 151, 600, 301 }; + RECTANGLE_16 r1_r3_inter_r11[] = { + { 170, 151, 250, 251 }, + }; + region16_init(®ion); + region16_init(&intersection); + + /* + * +=============================================================== + * | + * |+-----+ + * || | + * || +------+ + * || r1+r3 | (r1+r3) & r11 + * || +----------------+ +--------+ + * |+---+ | | | ====> | | + * | | | | | | | + * | | | | | | | + * | +-|------+ | +--------+ + * | | r11 | + * | +----------------+ + * + * + * R1+R3 is made of 3 bands, R11 overlap the second and the third band. The + * intersection is made of two band that must be reassembled to give only + * one + */ + if (!region16_union_rect(®ion, ®ion, &r1)) + goto out; + + if (!region16_union_rect(®ion, ®ion, &r3)) + goto out; + + if (!region16_intersects_rect(®ion, &r11)) + goto out; + + if (!region16_intersect_rect(&intersection, ®ion, &r11)) + goto out; + + rects = region16_rects(&intersection, &nbRects); + + if (!rects || nbRects != 1 || !compareRectangles(rects, r1_r3_inter_r11, nbRects)) + goto out; + + retCode = 0; +out: + region16_uninit(&intersection); + region16_uninit(®ion); + return retCode; +} + +static int test_norbert_case(void) +{ + REGION16 region; + REGION16 intersection; + int retCode = -1; + const RECTANGLE_16* rects = NULL; + UINT32 nbRects = 0; + RECTANGLE_16 inRectangles[5] = { { 1680, 0, 1920, 242 }, + { 294, 242, 971, 776 }, + { 1680, 242, 1920, 776 }, + { 1680, 776, 1920, 1036 }, + { 2, 1040, 53, 1078 } }; + RECTANGLE_16 screenRect = { 0, 0, 1920, 1080 }; + RECTANGLE_16 expected_inter_extents = { 2, 0, 1920, 1078 }; + region16_init(®ion); + region16_init(&intersection); + + /* + * Consider following as a screen with resolution 1920*1080 + * | | | | | | | + * | |2 |53 |294 |971 |1680 | + * | | | | | | | + * 0 +=+======================================+======+ + * | | | | + * | | R[0]| + * 242 | +-----------+ +------+ + * | | | | | | + * | | | | | + * | | R[1]| | R[2]| + * 776 | | +-----------+ +------+ + * | | | + * | | R[3]| + * 1036 | | +------+ + * 1040 | +----+ + * | |R[4]| Union of R[0-4]| + * 1078 | +----+ - - - - - - - -+ + * 1080 | + * + * + * The result is union of R[0] - R[4]. + * After intersected with the full screen rect, the + * result should keep the same. + */ + for (int i = 0; i < 5; i++) + { + if (!region16_union_rect(®ion, ®ion, &inRectangles[i])) + goto out; + } + + if (!compareRectangles(region16_extents(®ion), &expected_inter_extents, 1)) + goto out; + + if (!region16_intersect_rect(&intersection, ®ion, &screenRect)) + goto out; + + rects = region16_rects(&intersection, &nbRects); + + if (!rects || nbRects != 5 || !compareRectangles(rects, inRectangles, nbRects)) + goto out; + + if (!compareRectangles(region16_extents(&intersection), &expected_inter_extents, 1)) + goto out; + + retCode = 0; +out: + region16_uninit(&intersection); + region16_uninit(®ion); + return retCode; +} + +static int test_norbert2_case(void) +{ + REGION16 region; + int retCode = -1; + const RECTANGLE_16* rects = NULL; + UINT32 nbRects = 0; + RECTANGLE_16 rect1 = { 464, 696, 476, 709 }; + RECTANGLE_16 rect2 = { 0, 0, 1024, 32 }; + region16_init(®ion); + + if (!region16_union_rect(®ion, ®ion, &rect1)) + { + fprintf(stderr, "%s: Error 1 - region16_union_rect failed\n", __func__); + goto out; + } + + if (!(rects = region16_rects(®ion, &nbRects))) + { + fprintf(stderr, "%s: Error 2 - region16_rects failed\n", __func__); + goto out; + } + + if (nbRects != 1) + { + fprintf(stderr, "%s: Error 3 - expected nbRects == 1 but got %" PRIu32 "\n", __func__, + nbRects); + goto out; + } + + if (!compareRectangles(&rects[0], &rect1, 1)) + { + fprintf(stderr, "%s: Error 4 - compare failed\n", __func__); + goto out; + } + + if (!region16_union_rect(®ion, ®ion, &rect2)) + { + fprintf(stderr, "%s: Error 5 - region16_union_rect failed\n", __func__); + goto out; + } + + if (!(rects = region16_rects(®ion, &nbRects))) + { + fprintf(stderr, "%s: Error 6 - region16_rects failed\n", __func__); + goto out; + } + + if (nbRects != 2) + { + fprintf(stderr, "%s: Error 7 - expected nbRects == 2 but got %" PRIu32 "\n", __func__, + nbRects); + goto out; + } + + if (!compareRectangles(&rects[0], &rect2, 1)) + { + fprintf(stderr, "%s: Error 8 - compare failed\n", __func__); + goto out; + } + + if (!compareRectangles(&rects[1], &rect1, 1)) + { + fprintf(stderr, "%s: Error 9 - compare failed\n", __func__); + goto out; + } + + retCode = 0; +out: + region16_uninit(®ion); + return retCode; +} + +static int test_empty_rectangle(void) +{ + REGION16 region; + REGION16 intersection; + int retCode = -1; + RECTANGLE_16 emptyRectangles[3] = { { 0, 0, 0, 0 }, { 10, 10, 10, 11 }, { 10, 10, 11, 10 } }; + RECTANGLE_16 firstRect = { 0, 0, 100, 100 }; + RECTANGLE_16 anotherRect = { 100, 100, 200, 200 }; + RECTANGLE_16 expected_inter_extents = { 0, 0, 0, 0 }; + region16_init(®ion); + region16_init(&intersection); + + /* Check for empty rectangles */ + for (int i = 0; i < 3; i++) + { + if (!rectangle_is_empty(&emptyRectangles[i])) + goto out; + } + + /* Check for non-empty rectangles */ + if (rectangle_is_empty(&firstRect)) + goto out; + + /* Intersect 2 non-intersect rectangle, result should be empty */ + if (!region16_union_rect(®ion, ®ion, &firstRect)) + goto out; + + if (!region16_intersect_rect(®ion, ®ion, &anotherRect)) + goto out; + + if (!compareRectangles(region16_extents(®ion), &expected_inter_extents, 1)) + goto out; + + if (!region16_is_empty(®ion)) + goto out; + + if (!rectangle_is_empty(region16_extents(&intersection))) + goto out; + + retCode = 0; +out: + region16_uninit(&intersection); + region16_uninit(®ion); + return retCode; +} + +typedef int (*TestFunction)(void); +struct UnitaryTest +{ + const char* name; + TestFunction func; +}; + +static struct UnitaryTest tests[] = { { "Basic trivial tests", test_basic }, + { "R1+R3 and R3+R1", test_r1_r3 }, + { "R1+R5", test_r1_r5 }, + { "R1+R6", test_r1_r6 }, + { "R9+R10", test_r9_r10 }, + { "R1+R2+R4", test_r1_r2_r4 }, + { "R1+R7+R8 in many orders", test_r1_r7_r8 }, + { "R1+R2+R3+R4", test_r1_r2_r3_r4 }, + { "data from weston", test_from_weston }, + { "R1 & R3", test_r1_inter_r3 }, + { "(R1+R3)&R11 (band merge)", test_r1_r3_inter_r11 }, + { "norbert's case", test_norbert_case }, + { "norbert's case 2", test_norbert2_case }, + { "empty rectangle case", test_empty_rectangle }, + + { NULL, NULL } }; + +int TestFreeRDPRegion(int argc, char* argv[]) +{ + int testNb = 0; + int retCode = -1; + WINPR_UNUSED(argc); + WINPR_UNUSED(argv); + + for (int i = 0; tests[i].func; i++) + { + testNb++; + fprintf(stderr, "%d: %s\n", testNb, tests[i].name); + retCode = tests[i].func(); + + if (retCode < 0) + break; + } + + if (retCode < 0) + fprintf(stderr, "failed for test %d\n", testNb); + + return retCode; +} diff --git a/libfreerdp/codec/test/progressive.bmp b/libfreerdp/codec/test/progressive.bmp Binary files differnew file mode 100644 index 0000000..1b19c3b --- /dev/null +++ b/libfreerdp/codec/test/progressive.bmp diff --git a/libfreerdp/codec/test/rfx.bmp b/libfreerdp/codec/test/rfx.bmp Binary files differnew file mode 100644 index 0000000..2b66651 --- /dev/null +++ b/libfreerdp/codec/test/rfx.bmp diff --git a/libfreerdp/codec/test/test01.bmp b/libfreerdp/codec/test/test01.bmp Binary files differnew file mode 100644 index 0000000..fea498f --- /dev/null +++ b/libfreerdp/codec/test/test01.bmp diff --git a/libfreerdp/codec/xcrush.c b/libfreerdp/codec/xcrush.c new file mode 100644 index 0000000..eed2a86 --- /dev/null +++ b/libfreerdp/codec/xcrush.c @@ -0,0 +1,1175 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * XCrush (RDP6.1) Bulk Data Compression + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2017 Armin Novak <armin.novak@thincast.com> + * Copyright 2017 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <winpr/assert.h> +#include <freerdp/config.h> + +#include <winpr/crt.h> +#include <winpr/print.h> +#include <winpr/bitstream.h> + +#include <freerdp/log.h> +#include "xcrush.h" + +#define TAG FREERDP_TAG("codec") + +#pragma pack(push, 1) + +typedef struct +{ + UINT32 MatchOffset; + UINT32 ChunkOffset; + UINT32 MatchLength; +} XCRUSH_MATCH_INFO; + +typedef struct +{ + UINT32 offset; + UINT32 next; +} XCRUSH_CHUNK; + +typedef struct +{ + UINT16 seed; + UINT16 size; +} XCRUSH_SIGNATURE; + +typedef struct +{ + UINT16 MatchLength; + UINT16 MatchOutputOffset; + UINT32 MatchHistoryOffset; +} RDP61_MATCH_DETAILS; + +typedef struct +{ + BYTE Level1ComprFlags; + BYTE Level2ComprFlags; + UINT16 MatchCount; + RDP61_MATCH_DETAILS* MatchDetails; + BYTE* Literals; +} RDP61_COMPRESSED_DATA; + +#pragma pack(pop) + +struct s_XCRUSH_CONTEXT +{ + ALIGN64 BOOL Compressor; + ALIGN64 MPPC_CONTEXT* mppc; + ALIGN64 BYTE* HistoryPtr; + ALIGN64 UINT32 HistoryOffset; + ALIGN64 UINT32 HistoryBufferSize; + ALIGN64 BYTE HistoryBuffer[2000000]; + ALIGN64 BYTE BlockBuffer[16384]; + ALIGN64 UINT32 CompressionFlags; + ALIGN64 UINT32 SignatureIndex; + ALIGN64 UINT32 SignatureCount; + ALIGN64 XCRUSH_SIGNATURE Signatures[1000]; + ALIGN64 UINT32 ChunkHead; + ALIGN64 UINT32 ChunkTail; + ALIGN64 XCRUSH_CHUNK Chunks[65534]; + ALIGN64 UINT16 NextChunks[65536]; + ALIGN64 UINT32 OriginalMatchCount; + ALIGN64 UINT32 OptimizedMatchCount; + ALIGN64 XCRUSH_MATCH_INFO OriginalMatches[1000]; + ALIGN64 XCRUSH_MATCH_INFO OptimizedMatches[1000]; +}; + +//#define DEBUG_XCRUSH 1 +#if defined(DEBUG_XCRUSH) +static const char* xcrush_get_level_2_compression_flags_string(UINT32 flags) +{ + flags &= 0xE0; + + if (flags == 0) + return "PACKET_UNCOMPRESSED"; + else if (flags == PACKET_COMPRESSED) + return "PACKET_COMPRESSED"; + else if (flags == PACKET_AT_FRONT) + return "PACKET_AT_FRONT"; + else if (flags == PACKET_FLUSHED) + return "PACKET_FLUSHED"; + else if (flags == (PACKET_COMPRESSED | PACKET_AT_FRONT)) + return "PACKET_COMPRESSED | PACKET_AT_FRONT"; + else if (flags == (PACKET_COMPRESSED | PACKET_FLUSHED)) + return "PACKET_COMPRESSED | PACKET_FLUSHED"; + else if (flags == (PACKET_AT_FRONT | PACKET_FLUSHED)) + return "PACKET_AT_FRONT | PACKET_FLUSHED"; + else if (flags == (PACKET_COMPRESSED | PACKET_AT_FRONT | PACKET_FLUSHED)) + return "PACKET_COMPRESSED | PACKET_AT_FRONT | PACKET_FLUSHED"; + + return "PACKET_UNKNOWN"; +} + +static const char* xcrush_get_level_1_compression_flags_string(UINT32 flags) +{ + flags &= 0x17; + + if (flags == 0) + return "L1_UNKNOWN"; + else if (flags == L1_PACKET_AT_FRONT) + return "L1_PACKET_AT_FRONT"; + else if (flags == L1_NO_COMPRESSION) + return "L1_NO_COMPRESSION"; + else if (flags == L1_COMPRESSED) + return "L1_COMPRESSED"; + else if (flags == L1_INNER_COMPRESSION) + return "L1_INNER_COMPRESSION"; + else if (flags == (L1_PACKET_AT_FRONT | L1_NO_COMPRESSION)) + return "L1_PACKET_AT_FRONT | L1_NO_COMPRESSION"; + else if (flags == (L1_PACKET_AT_FRONT | L1_COMPRESSED)) + return "L1_PACKET_AT_FRONT | L1_COMPRESSED"; + else if (flags == (L1_PACKET_AT_FRONT | L1_INNER_COMPRESSION)) + return "L1_PACKET_AT_FRONT | L1_INNER_COMPRESSION"; + else if (flags == (L1_NO_COMPRESSION | L1_COMPRESSED)) + return "L1_NO_COMPRESSION | L1_COMPRESSED"; + else if (flags == (L1_NO_COMPRESSION | L1_INNER_COMPRESSION)) + return "L1_NO_COMPRESSION | L1_INNER_COMPRESSION"; + else if (flags == (L1_COMPRESSED | L1_INNER_COMPRESSION)) + return "L1_COMPRESSED | L1_INNER_COMPRESSION"; + else if (flags == (L1_NO_COMPRESSION | L1_COMPRESSED | L1_INNER_COMPRESSION)) + return "L1_NO_COMPRESSION | L1_COMPRESSED | L1_INNER_COMPRESSION"; + else if (flags == (L1_PACKET_AT_FRONT | L1_COMPRESSED | L1_INNER_COMPRESSION)) + return "L1_PACKET_AT_FRONT | L1_COMPRESSED | L1_INNER_COMPRESSION"; + else if (flags == (L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_INNER_COMPRESSION)) + return "L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_INNER_COMPRESSION"; + else if (flags == (L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_COMPRESSED)) + return "L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_COMPRESSED"; + else if (flags == + (L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_COMPRESSED | L1_INNER_COMPRESSION)) + return "L1_PACKET_AT_FRONT | L1_NO_COMPRESSION | L1_COMPRESSED | L1_INNER_COMPRESSION"; + + return "L1_UNKNOWN"; +} +#endif + +static UINT32 xcrush_update_hash(const BYTE* data, UINT32 size) +{ + const BYTE* end = NULL; + UINT32 seed = 5381; /* same value as in djb2 */ + + WINPR_ASSERT(data); + WINPR_ASSERT(size >= 4); + + if (size > 32) + { + size = 32; + seed = 5413; + } + + end = &data[size - 4]; + + while (data < end) + { + seed += (data[3] ^ data[0]) + (data[1] << 8); + data += 4; + } + + return (UINT16)seed; +} + +static int xcrush_append_chunk(XCRUSH_CONTEXT* xcrush, const BYTE* data, UINT32* beg, UINT32 end) +{ + UINT32 size = 0; + + WINPR_ASSERT(xcrush); + WINPR_ASSERT(data); + WINPR_ASSERT(beg); + + if (xcrush->SignatureIndex >= xcrush->SignatureCount) + return 0; + + size = end - *beg; + + if (size > 65535) + return 0; + + if (size >= 15) + { + UINT32 seed = xcrush_update_hash(&data[*beg], (UINT16)size); + xcrush->Signatures[xcrush->SignatureIndex].size = size; + xcrush->Signatures[xcrush->SignatureIndex].seed = seed; + xcrush->SignatureIndex++; + *beg = end; + } + + return 1; +} + +static int xcrush_compute_chunks(XCRUSH_CONTEXT* xcrush, const BYTE* data, UINT32 size, + UINT32* pIndex) +{ + UINT32 offset = 0; + UINT32 rotation = 0; + UINT32 accumulator = 0; + + WINPR_ASSERT(xcrush); + WINPR_ASSERT(data); + WINPR_ASSERT(pIndex); + + *pIndex = 0; + xcrush->SignatureIndex = 0; + + if (size < 128) + return 0; + + for (UINT32 i = 0; i < 32; i++) + { + rotation = _rotl(accumulator, 1); + accumulator = data[i] ^ rotation; + } + + for (UINT32 i = 0; i < size - 64; i++) + { + rotation = _rotl(accumulator, 1); + accumulator = data[i + 32] ^ data[i] ^ rotation; + + if (!(accumulator & 0x7F)) + { + if (!xcrush_append_chunk(xcrush, data, &offset, i + 32)) + return 0; + } + + i++; + rotation = _rotl(accumulator, 1); + accumulator = data[i + 32] ^ data[i] ^ rotation; + + if (!(accumulator & 0x7F)) + { + if (!xcrush_append_chunk(xcrush, data, &offset, i + 32)) + return 0; + } + + i++; + rotation = _rotl(accumulator, 1); + accumulator = data[i + 32] ^ data[i] ^ rotation; + + if (!(accumulator & 0x7F)) + { + if (!xcrush_append_chunk(xcrush, data, &offset, i + 32)) + return 0; + } + + i++; + rotation = _rotl(accumulator, 1); + accumulator = data[i + 32] ^ data[i] ^ rotation; + + if (!(accumulator & 0x7F)) + { + if (!xcrush_append_chunk(xcrush, data, &offset, i + 32)) + return 0; + } + } + + if ((size == offset) || xcrush_append_chunk(xcrush, data, &offset, size)) + { + *pIndex = xcrush->SignatureIndex; + return 1; + } + + return 0; +} + +static UINT32 xcrush_compute_signatures(XCRUSH_CONTEXT* xcrush, const BYTE* data, UINT32 size) +{ + UINT32 index = 0; + + if (xcrush_compute_chunks(xcrush, data, size, &index)) + return index; + + return 0; +} + +static void xcrush_clear_hash_table_range(XCRUSH_CONTEXT* xcrush, UINT32 beg, UINT32 end) +{ + WINPR_ASSERT(xcrush); + + for (UINT32 index = 0; index < 65536; index++) + { + if (xcrush->NextChunks[index] >= beg) + { + if (xcrush->NextChunks[index] <= end) + { + xcrush->NextChunks[index] = 0; + } + } + } + + for (UINT32 index = 0; index < 65534; index++) + { + if (xcrush->Chunks[index].next >= beg) + { + if (xcrush->Chunks[index].next <= end) + { + xcrush->Chunks[index].next = 0; + } + } + } +} + +static int xcrush_find_next_matching_chunk(XCRUSH_CONTEXT* xcrush, XCRUSH_CHUNK* chunk, + XCRUSH_CHUNK** pNextChunk) +{ + UINT32 index = 0; + XCRUSH_CHUNK* next = NULL; + + WINPR_ASSERT(xcrush); + + if (!chunk) + return -4001; /* error */ + + if (chunk->next) + { + index = (chunk - xcrush->Chunks) / sizeof(XCRUSH_CHUNK); + + if (index >= 65534) + return -4002; /* error */ + + if ((index < xcrush->ChunkHead) || (chunk->next >= xcrush->ChunkHead)) + { + if (chunk->next >= 65534) + return -4003; /* error */ + + next = &xcrush->Chunks[chunk->next]; + } + } + + WINPR_ASSERT(pNextChunk); + *pNextChunk = next; + return 1; +} + +static int xcrush_insert_chunk(XCRUSH_CONTEXT* xcrush, XCRUSH_SIGNATURE* signature, UINT32 offset, + XCRUSH_CHUNK** pPrevChunk) +{ + UINT32 seed = 0; + UINT32 index = 0; + + WINPR_ASSERT(xcrush); + + if (xcrush->ChunkHead >= 65530) + { + xcrush->ChunkHead = 1; + xcrush->ChunkTail = 1; + } + + if (xcrush->ChunkHead >= xcrush->ChunkTail) + { + xcrush_clear_hash_table_range(xcrush, xcrush->ChunkTail, xcrush->ChunkTail + 10000); + xcrush->ChunkTail += 10000; + } + + index = xcrush->ChunkHead++; + + if (xcrush->ChunkHead >= 65534) + return -3001; /* error */ + + xcrush->Chunks[index].offset = offset; + seed = signature->seed; + + if (seed >= 65536) + return -3002; /* error */ + + if (xcrush->NextChunks[seed]) + { + if (xcrush->NextChunks[seed] >= 65534) + return -3003; /* error */ + + WINPR_ASSERT(pPrevChunk); + *pPrevChunk = &xcrush->Chunks[xcrush->NextChunks[seed]]; + } + + xcrush->Chunks[index].next = xcrush->NextChunks[seed] & 0xFFFF; + xcrush->NextChunks[seed] = index; + return 1; +} + +static int xcrush_find_match_length(XCRUSH_CONTEXT* xcrush, UINT32 MatchOffset, UINT32 ChunkOffset, + UINT32 HistoryOffset, UINT32 SrcSize, UINT32 MaxMatchLength, + XCRUSH_MATCH_INFO* MatchInfo) +{ + UINT32 MatchSymbol = 0; + UINT32 ChunkSymbol = 0; + BYTE* ChunkBuffer = NULL; + BYTE* MatchBuffer = NULL; + BYTE* MatchStartPtr = NULL; + BYTE* ForwardChunkPtr = NULL; + BYTE* ReverseChunkPtr = NULL; + BYTE* ForwardMatchPtr = NULL; + BYTE* ReverseMatchPtr = NULL; + BYTE* HistoryBufferEnd = NULL; + UINT32 ReverseMatchLength = 0; + UINT32 ForwardMatchLength = 0; + UINT32 TotalMatchLength = 0; + BYTE* HistoryBuffer = NULL; + UINT32 HistoryBufferSize = 0; + + WINPR_ASSERT(xcrush); + WINPR_ASSERT(MatchInfo); + + HistoryBuffer = xcrush->HistoryBuffer; + HistoryBufferSize = xcrush->HistoryBufferSize; + HistoryBufferEnd = &HistoryBuffer[HistoryOffset + SrcSize]; + + if (MatchOffset > HistoryBufferSize) + return -2001; /* error */ + + MatchBuffer = &HistoryBuffer[MatchOffset]; + + if (ChunkOffset > HistoryBufferSize) + return -2002; /* error */ + + ChunkBuffer = &HistoryBuffer[ChunkOffset]; + + if (MatchOffset == ChunkOffset) + return -2003; /* error */ + + if (MatchBuffer < HistoryBuffer) + return -2004; /* error */ + + if (ChunkBuffer < HistoryBuffer) + return -2005; /* error */ + + ForwardMatchPtr = &HistoryBuffer[MatchOffset]; + ForwardChunkPtr = &HistoryBuffer[ChunkOffset]; + + if ((&MatchBuffer[MaxMatchLength + 1] < HistoryBufferEnd) && + (MatchBuffer[MaxMatchLength + 1] != ChunkBuffer[MaxMatchLength + 1])) + { + return 0; + } + + while (1) + { + MatchSymbol = *ForwardMatchPtr++; + ChunkSymbol = *ForwardChunkPtr++; + + if (MatchSymbol != ChunkSymbol) + break; + + if (ForwardMatchPtr > HistoryBufferEnd) + break; + + ForwardMatchLength++; + } + + ReverseMatchPtr = MatchBuffer - 1; + ReverseChunkPtr = ChunkBuffer - 1; + + while ((ReverseMatchPtr > &HistoryBuffer[HistoryOffset]) && (ReverseChunkPtr > HistoryBuffer) && + (*ReverseMatchPtr == *ReverseChunkPtr)) + { + ReverseMatchLength++; + ReverseMatchPtr--; + ReverseChunkPtr--; + } + + MatchStartPtr = MatchBuffer - ReverseMatchLength; + TotalMatchLength = ReverseMatchLength + ForwardMatchLength; + + if (TotalMatchLength < 11) + return 0; + + if (MatchStartPtr < HistoryBuffer) + return -2006; /* error */ + + MatchInfo->MatchOffset = MatchStartPtr - HistoryBuffer; + MatchInfo->ChunkOffset = ChunkBuffer - ReverseMatchLength - HistoryBuffer; + MatchInfo->MatchLength = TotalMatchLength; + return (int)TotalMatchLength; +} + +static int xcrush_find_all_matches(XCRUSH_CONTEXT* xcrush, UINT32 SignatureIndex, + UINT32 HistoryOffset, UINT32 SrcOffset, UINT32 SrcSize) +{ + UINT32 j = 0; + int status = 0; + UINT32 ChunkIndex = 0; + UINT32 ChunkCount = 0; + XCRUSH_CHUNK* chunk = NULL; + UINT32 MatchLength = 0; + UINT32 MaxMatchLength = 0; + UINT32 PrevMatchEnd = 0; + XCRUSH_SIGNATURE* Signatures = NULL; + XCRUSH_MATCH_INFO MaxMatchInfo = { 0 }; + + WINPR_ASSERT(xcrush); + + Signatures = xcrush->Signatures; + + for (UINT32 i = 0; i < SignatureIndex; i++) + { + XCRUSH_MATCH_INFO MatchInfo = { 0 }; + UINT32 offset = SrcOffset + HistoryOffset; + + if (!Signatures[i].size) + return -1001; /* error */ + + status = xcrush_insert_chunk(xcrush, &Signatures[i], offset, &chunk); + + if (status < 0) + return status; + + if (chunk && (SrcOffset + HistoryOffset + Signatures[i].size >= PrevMatchEnd)) + { + ChunkCount = 0; + MaxMatchLength = 0; + + while (chunk) + { + if ((chunk->offset < HistoryOffset) || (chunk->offset < offset) || + (chunk->offset > SrcSize + HistoryOffset)) + { + status = xcrush_find_match_length(xcrush, offset, chunk->offset, HistoryOffset, + SrcSize, MaxMatchLength, &MatchInfo); + + if (status < 0) + return status; /* error */ + + MatchLength = (UINT32)status; + + if (MatchLength > MaxMatchLength) + { + MaxMatchLength = MatchLength; + MaxMatchInfo.MatchOffset = MatchInfo.MatchOffset; + MaxMatchInfo.ChunkOffset = MatchInfo.ChunkOffset; + MaxMatchInfo.MatchLength = MatchInfo.MatchLength; + + if (MatchLength > 256) + break; + } + } + + ChunkIndex = ChunkCount++; + + if (ChunkIndex > 4) + break; + + status = xcrush_find_next_matching_chunk(xcrush, chunk, &chunk); + + if (status < 0) + return status; /* error */ + } + + if (MaxMatchLength) + { + xcrush->OriginalMatches[j].MatchOffset = MaxMatchInfo.MatchOffset; + xcrush->OriginalMatches[j].ChunkOffset = MaxMatchInfo.ChunkOffset; + xcrush->OriginalMatches[j].MatchLength = MaxMatchInfo.MatchLength; + + if (xcrush->OriginalMatches[j].MatchOffset < HistoryOffset) + return -1002; /* error */ + + PrevMatchEnd = + xcrush->OriginalMatches[j].MatchLength + xcrush->OriginalMatches[j].MatchOffset; + j++; + + if (j >= 1000) + return -1003; /* error */ + } + } + + SrcOffset += Signatures[i].size; + + if (SrcOffset > SrcSize) + return -1004; /* error */ + } + + if (SrcOffset > SrcSize) + return -1005; /* error */ + + return (int)j; +} + +static int xcrush_optimize_matches(XCRUSH_CONTEXT* xcrush) +{ + UINT32 j = 0; + UINT32 MatchDiff = 0; + UINT32 PrevMatchEnd = 0; + UINT32 TotalMatchLength = 0; + UINT32 OriginalMatchCount = 0; + UINT32 OptimizedMatchCount = 0; + XCRUSH_MATCH_INFO* OriginalMatch = NULL; + XCRUSH_MATCH_INFO* OptimizedMatch = NULL; + XCRUSH_MATCH_INFO* OriginalMatches = NULL; + XCRUSH_MATCH_INFO* OptimizedMatches = NULL; + + WINPR_ASSERT(xcrush); + + OriginalMatches = xcrush->OriginalMatches; + OriginalMatchCount = xcrush->OriginalMatchCount; + OptimizedMatches = xcrush->OptimizedMatches; + + for (UINT32 i = 0; i < OriginalMatchCount; i++) + { + if (OriginalMatches[i].MatchOffset <= PrevMatchEnd) + { + if ((OriginalMatches[i].MatchOffset < PrevMatchEnd) && + (OriginalMatches[i].MatchLength + OriginalMatches[i].MatchOffset > + PrevMatchEnd + 6)) + { + MatchDiff = PrevMatchEnd - OriginalMatches[i].MatchOffset; + OriginalMatch = &OriginalMatches[i]; + OptimizedMatch = &OptimizedMatches[j]; + OptimizedMatch->MatchOffset = OriginalMatch->MatchOffset; + OptimizedMatch->ChunkOffset = OriginalMatch->ChunkOffset; + OptimizedMatch->MatchLength = OriginalMatch->MatchLength; + + if (OptimizedMatches[j].MatchLength <= MatchDiff) + return -5001; /* error */ + + if (MatchDiff >= 20000) + return -5002; /* error */ + + OptimizedMatches[j].MatchLength -= MatchDiff; + OptimizedMatches[j].MatchOffset += MatchDiff; + OptimizedMatches[j].ChunkOffset += MatchDiff; + PrevMatchEnd = OptimizedMatches[j].MatchLength + OptimizedMatches[j].MatchOffset; + TotalMatchLength += OptimizedMatches[j].MatchLength; + j++; + } + } + else + { + OriginalMatch = &OriginalMatches[i]; + OptimizedMatch = &OptimizedMatches[j]; + OptimizedMatch->MatchOffset = OriginalMatch->MatchOffset; + OptimizedMatch->ChunkOffset = OriginalMatch->ChunkOffset; + OptimizedMatch->MatchLength = OriginalMatch->MatchLength; + PrevMatchEnd = OptimizedMatches[j].MatchLength + OptimizedMatches[j].MatchOffset; + TotalMatchLength += OptimizedMatches[j].MatchLength; + j++; + } + } + + OptimizedMatchCount = j; + xcrush->OptimizedMatchCount = OptimizedMatchCount; + return (int)TotalMatchLength; +} + +static int xcrush_generate_output(XCRUSH_CONTEXT* xcrush, BYTE* OutputBuffer, UINT32 OutputSize, + UINT32 HistoryOffset, UINT32* pDstSize) +{ + BYTE* Literals = NULL; + BYTE* OutputEnd = NULL; + UINT32 MatchIndex = 0; + UINT32 MatchOffset = 0; + UINT16 MatchLength = 0; + UINT32 MatchCount = 0; + UINT32 CurrentOffset = 0; + UINT32 MatchOffsetDiff = 0; + UINT32 HistoryOffsetDiff = 0; + RDP61_MATCH_DETAILS* MatchDetails = NULL; + + WINPR_ASSERT(xcrush); + WINPR_ASSERT(OutputBuffer); + WINPR_ASSERT(OutputSize >= 2); + WINPR_ASSERT(pDstSize); + + MatchCount = xcrush->OptimizedMatchCount; + OutputEnd = &OutputBuffer[OutputSize]; + + if (&OutputBuffer[2] >= &OutputBuffer[OutputSize]) + return -6001; /* error */ + + Data_Write_UINT16(OutputBuffer, MatchCount); + MatchDetails = (RDP61_MATCH_DETAILS*)&OutputBuffer[2]; + Literals = (BYTE*)&MatchDetails[MatchCount]; + + if (Literals > OutputEnd) + return -6002; /* error */ + + for (MatchIndex = 0; MatchIndex < MatchCount; MatchIndex++) + { + Data_Write_UINT16(&MatchDetails[MatchIndex].MatchLength, + xcrush->OptimizedMatches[MatchIndex].MatchLength); + Data_Write_UINT16(&MatchDetails[MatchIndex].MatchOutputOffset, + xcrush->OptimizedMatches[MatchIndex].MatchOffset - HistoryOffset); + Data_Write_UINT32(&MatchDetails[MatchIndex].MatchHistoryOffset, + xcrush->OptimizedMatches[MatchIndex].ChunkOffset); + } + + CurrentOffset = HistoryOffset; + + for (MatchIndex = 0; MatchIndex < MatchCount; MatchIndex++) + { + MatchLength = (UINT16)(xcrush->OptimizedMatches[MatchIndex].MatchLength); + MatchOffset = xcrush->OptimizedMatches[MatchIndex].MatchOffset; + + if (MatchOffset <= CurrentOffset) + { + if (MatchOffset != CurrentOffset) + return -6003; /* error */ + + CurrentOffset = MatchOffset + MatchLength; + } + else + { + MatchOffsetDiff = MatchOffset - CurrentOffset; + + if (Literals + MatchOffset - CurrentOffset >= OutputEnd) + return -6004; /* error */ + + CopyMemory(Literals, &xcrush->HistoryBuffer[CurrentOffset], MatchOffsetDiff); + + if (Literals >= OutputEnd) + return -6005; /* error */ + + Literals += MatchOffsetDiff; + CurrentOffset = MatchOffset + MatchLength; + } + } + + HistoryOffsetDiff = xcrush->HistoryOffset - CurrentOffset; + + if (Literals + HistoryOffsetDiff >= OutputEnd) + return -6006; /* error */ + + CopyMemory(Literals, &xcrush->HistoryBuffer[CurrentOffset], HistoryOffsetDiff); + *pDstSize = Literals + HistoryOffsetDiff - OutputBuffer; + return 1; +} + +static INLINE size_t xcrush_copy_bytes(BYTE* dst, const BYTE* src, size_t num) +{ + WINPR_ASSERT(dst); + WINPR_ASSERT(src); + + if (src + num < dst || src > dst + num) + memcpy(dst, src, num); + else if (src != dst) + { + // src and dst overlaps + // we should copy the area that doesn't overlap repeatly + const size_t diff = (dst > src) ? dst - src : src - dst; + const size_t rest = num % diff; + const size_t end = num - rest; + + for (size_t a = 0; a < end; a += diff) + memcpy(&dst[a], &src[a], diff); + + if (rest != 0) + memcpy(&dst[end], &src[end], rest); + } + + return num; +} + +static int xcrush_decompress_l1(XCRUSH_CONTEXT* xcrush, const BYTE* pSrcData, UINT32 SrcSize, + const BYTE** ppDstData, UINT32* pDstSize, UINT32 flags) +{ + const BYTE* pSrcEnd = NULL; + const BYTE* Literals = NULL; + UINT16 MatchCount = 0; + UINT16 MatchIndex = 0; + BYTE* OutputPtr = NULL; + size_t OutputLength = 0; + UINT32 OutputOffset = 0; + BYTE* HistoryPtr = NULL; + BYTE* HistoryBuffer = NULL; + BYTE* HistoryBufferEnd = NULL; + UINT32 HistoryBufferSize = 0; + UINT16 MatchLength = 0; + UINT16 MatchOutputOffset = 0; + UINT32 MatchHistoryOffset = 0; + const RDP61_MATCH_DETAILS* MatchDetails = NULL; + + WINPR_ASSERT(xcrush); + + if (SrcSize < 1) + return -1001; + + WINPR_ASSERT(pSrcData); + WINPR_ASSERT(ppDstData); + WINPR_ASSERT(pDstSize); + + if (flags & L1_PACKET_AT_FRONT) + xcrush->HistoryOffset = 0; + + pSrcEnd = &pSrcData[SrcSize]; + HistoryBuffer = xcrush->HistoryBuffer; + HistoryBufferSize = xcrush->HistoryBufferSize; + HistoryBufferEnd = &(HistoryBuffer[HistoryBufferSize]); + xcrush->HistoryPtr = HistoryPtr = &(HistoryBuffer[xcrush->HistoryOffset]); + + if (flags & L1_NO_COMPRESSION) + { + Literals = pSrcData; + } + else + { + if (!(flags & L1_COMPRESSED)) + return -1002; + + if ((pSrcData + 2) > pSrcEnd) + return -1003; + + Data_Read_UINT16(pSrcData, MatchCount); + MatchDetails = (const RDP61_MATCH_DETAILS*)&pSrcData[2]; + Literals = (const BYTE*)&MatchDetails[MatchCount]; + OutputOffset = 0; + + if (Literals > pSrcEnd) + return -1004; + + for (MatchIndex = 0; MatchIndex < MatchCount; MatchIndex++) + { + Data_Read_UINT16(&MatchDetails[MatchIndex].MatchLength, MatchLength); + Data_Read_UINT16(&MatchDetails[MatchIndex].MatchOutputOffset, MatchOutputOffset); + Data_Read_UINT32(&MatchDetails[MatchIndex].MatchHistoryOffset, MatchHistoryOffset); + + if (MatchOutputOffset < OutputOffset) + return -1005; + + if (MatchLength > HistoryBufferSize) + return -1006; + + if (MatchHistoryOffset > HistoryBufferSize) + return -1007; + + OutputLength = MatchOutputOffset - OutputOffset; + + if ((MatchOutputOffset - OutputOffset) > HistoryBufferSize) + return -1008; + + if (OutputLength > 0) + { + if ((&HistoryPtr[OutputLength] >= HistoryBufferEnd) || (Literals >= pSrcEnd) || + (&Literals[OutputLength] > pSrcEnd)) + return -1009; + + xcrush_copy_bytes(HistoryPtr, Literals, OutputLength); + HistoryPtr += OutputLength; + Literals += OutputLength; + OutputOffset += OutputLength; + + if (Literals > pSrcEnd) + return -1010; + } + + OutputPtr = &xcrush->HistoryBuffer[MatchHistoryOffset]; + + if ((&HistoryPtr[MatchLength] >= HistoryBufferEnd) || + (&OutputPtr[MatchLength] >= HistoryBufferEnd)) + return -1011; + + xcrush_copy_bytes(HistoryPtr, OutputPtr, MatchLength); + OutputOffset += MatchLength; + HistoryPtr += MatchLength; + } + } + + if (Literals < pSrcEnd) + { + OutputLength = pSrcEnd - Literals; + + if ((&HistoryPtr[OutputLength] >= HistoryBufferEnd) || (&Literals[OutputLength] > pSrcEnd)) + return -1012; + + xcrush_copy_bytes(HistoryPtr, Literals, OutputLength); + HistoryPtr += OutputLength; + } + + xcrush->HistoryOffset = HistoryPtr - HistoryBuffer; + *pDstSize = HistoryPtr - xcrush->HistoryPtr; + *ppDstData = xcrush->HistoryPtr; + return 1; +} + +int xcrush_decompress(XCRUSH_CONTEXT* xcrush, const BYTE* pSrcData, UINT32 SrcSize, + const BYTE** ppDstData, UINT32* pDstSize, UINT32 flags) +{ + int status = 0; + UINT32 DstSize = 0; + const BYTE* pDstData = NULL; + BYTE Level1ComprFlags = 0; + BYTE Level2ComprFlags = 0; + + WINPR_ASSERT(xcrush); + + if (SrcSize < 2) + return -1; + + WINPR_ASSERT(pSrcData); + WINPR_ASSERT(ppDstData); + WINPR_ASSERT(pDstSize); + + Level1ComprFlags = pSrcData[0]; + Level2ComprFlags = pSrcData[1]; + pSrcData += 2; + SrcSize -= 2; + + if (flags & PACKET_FLUSHED) + { + ZeroMemory(xcrush->HistoryBuffer, xcrush->HistoryBufferSize); + xcrush->HistoryOffset = 0; + } + + if (!(Level2ComprFlags & PACKET_COMPRESSED)) + { + status = + xcrush_decompress_l1(xcrush, pSrcData, SrcSize, ppDstData, pDstSize, Level1ComprFlags); + return status; + } + + status = + mppc_decompress(xcrush->mppc, pSrcData, SrcSize, &pDstData, &DstSize, Level2ComprFlags); + + if (status < 0) + return status; + + status = xcrush_decompress_l1(xcrush, pDstData, DstSize, ppDstData, pDstSize, Level1ComprFlags); + return status; +} + +static int xcrush_compress_l1(XCRUSH_CONTEXT* xcrush, const BYTE* pSrcData, UINT32 SrcSize, + BYTE* pDstData, UINT32* pDstSize, UINT32* pFlags) +{ + int status = 0; + UINT32 Flags = 0; + UINT32 HistoryOffset = 0; + BYTE* HistoryPtr = NULL; + BYTE* HistoryBuffer = NULL; + UINT32 SignatureIndex = 0; + + WINPR_ASSERT(xcrush); + WINPR_ASSERT(pSrcData); + WINPR_ASSERT(SrcSize > 0); + WINPR_ASSERT(pDstData); + WINPR_ASSERT(pDstSize); + WINPR_ASSERT(pFlags); + + if (xcrush->HistoryOffset + SrcSize + 8 > xcrush->HistoryBufferSize) + { + xcrush->HistoryOffset = 0; + Flags |= L1_PACKET_AT_FRONT; + } + + HistoryOffset = xcrush->HistoryOffset; + HistoryBuffer = xcrush->HistoryBuffer; + HistoryPtr = &HistoryBuffer[HistoryOffset]; + MoveMemory(HistoryPtr, pSrcData, SrcSize); + xcrush->HistoryOffset += SrcSize; + + if (SrcSize > 50) + { + SignatureIndex = xcrush_compute_signatures(xcrush, pSrcData, SrcSize); + + if (SignatureIndex) + { + status = xcrush_find_all_matches(xcrush, SignatureIndex, HistoryOffset, 0, SrcSize); + + if (status < 0) + return status; + + xcrush->OriginalMatchCount = (UINT32)status; + xcrush->OptimizedMatchCount = 0; + + if (xcrush->OriginalMatchCount) + { + status = xcrush_optimize_matches(xcrush); + + if (status < 0) + return status; + } + + if (xcrush->OptimizedMatchCount) + { + status = xcrush_generate_output(xcrush, pDstData, SrcSize, HistoryOffset, pDstSize); + + if (status < 0) + return status; + + Flags |= L1_COMPRESSED; + } + } + } + + if (!(Flags & L1_COMPRESSED)) + { + Flags |= L1_NO_COMPRESSION; + *pDstSize = SrcSize; + } + + *pFlags = Flags; + return 1; +} + +int xcrush_compress(XCRUSH_CONTEXT* xcrush, const BYTE* pSrcData, UINT32 SrcSize, BYTE* pDstBuffer, + const BYTE** ppDstData, UINT32* pDstSize, UINT32* pFlags) +{ + int status = 0; + UINT32 DstSize = 0; + BYTE* pDstData = NULL; + const BYTE* CompressedData = NULL; + UINT32 CompressedDataSize = 0; + BYTE* OriginalData = NULL; + UINT32 OriginalDataSize = 0; + UINT32 Level1ComprFlags = 0; + UINT32 Level2ComprFlags = 0; + UINT32 CompressionLevel = 3; + + WINPR_ASSERT(xcrush); + WINPR_ASSERT(pSrcData); + WINPR_ASSERT(SrcSize > 0); + WINPR_ASSERT(ppDstData); + WINPR_ASSERT(pDstSize); + WINPR_ASSERT(pFlags); + + if (SrcSize > 16384) + return -1001; + + if ((SrcSize + 2) > *pDstSize) + return -1002; + + OriginalData = pDstBuffer; + *ppDstData = pDstBuffer; + OriginalDataSize = SrcSize; + pDstData = xcrush->BlockBuffer; + CompressedDataSize = SrcSize; + status = xcrush_compress_l1(xcrush, pSrcData, SrcSize, pDstData, &CompressedDataSize, + &Level1ComprFlags); + + if (status < 0) + return status; + + if (Level1ComprFlags & L1_COMPRESSED) + { + CompressedData = pDstData; + + if (CompressedDataSize > SrcSize) + return -1003; + } + else + { + CompressedData = pSrcData; + + if (CompressedDataSize != SrcSize) + return -1004; + } + + status = 0; + pDstData = &OriginalData[2]; + DstSize = OriginalDataSize - 2; + + if (CompressedDataSize > 50) + { + const BYTE* pUnusedDstData = NULL; + status = mppc_compress(xcrush->mppc, CompressedData, CompressedDataSize, pDstData, + &pUnusedDstData, &DstSize, &Level2ComprFlags); + } + + if (status < 0) + return status; + + if (!status || (Level2ComprFlags & PACKET_FLUSHED)) + { + if (CompressedDataSize > DstSize) + { + xcrush_context_reset(xcrush, TRUE); + *ppDstData = pSrcData; + *pDstSize = SrcSize; + *pFlags = 0; + return 1; + } + + DstSize = CompressedDataSize; + CopyMemory(&OriginalData[2], CompressedData, CompressedDataSize); + } + + if (Level2ComprFlags & PACKET_COMPRESSED) + { + Level2ComprFlags |= xcrush->CompressionFlags; + xcrush->CompressionFlags = 0; + } + else if (Level2ComprFlags & PACKET_FLUSHED) + { + xcrush->CompressionFlags = PACKET_FLUSHED; + } + + Level1ComprFlags |= L1_INNER_COMPRESSION; + OriginalData[0] = (BYTE)Level1ComprFlags; + OriginalData[1] = (BYTE)Level2ComprFlags; +#if defined(DEBUG_XCRUSH) + WLog_DBG(TAG, "XCrushCompress: Level1ComprFlags: %s Level2ComprFlags: %s", + xcrush_get_level_1_compression_flags_string(Level1ComprFlags), + xcrush_get_level_2_compression_flags_string(Level2ComprFlags)); +#endif + + if (*pDstSize < (DstSize + 2)) + return -1006; + + *pDstSize = DstSize + 2; + *pFlags = PACKET_COMPRESSED | CompressionLevel; + return 1; +} + +void xcrush_context_reset(XCRUSH_CONTEXT* xcrush, BOOL flush) +{ + WINPR_ASSERT(xcrush); + + xcrush->SignatureIndex = 0; + xcrush->SignatureCount = 1000; + ZeroMemory(&(xcrush->Signatures), sizeof(XCRUSH_SIGNATURE) * xcrush->SignatureCount); + xcrush->CompressionFlags = 0; + xcrush->ChunkHead = xcrush->ChunkTail = 1; + ZeroMemory(&(xcrush->Chunks), sizeof(xcrush->Chunks)); + ZeroMemory(&(xcrush->NextChunks), sizeof(xcrush->NextChunks)); + ZeroMemory(&(xcrush->OriginalMatches), sizeof(xcrush->OriginalMatches)); + ZeroMemory(&(xcrush->OptimizedMatches), sizeof(xcrush->OptimizedMatches)); + + if (flush) + xcrush->HistoryOffset = xcrush->HistoryBufferSize + 1; + else + xcrush->HistoryOffset = 0; + + mppc_context_reset(xcrush->mppc, flush); +} + +XCRUSH_CONTEXT* xcrush_context_new(BOOL Compressor) +{ + XCRUSH_CONTEXT* xcrush = (XCRUSH_CONTEXT*)calloc(1, sizeof(XCRUSH_CONTEXT)); + + if (!xcrush) + goto fail; + + xcrush->Compressor = Compressor; + xcrush->mppc = mppc_context_new(1, Compressor); + if (!xcrush->mppc) + goto fail; + xcrush->HistoryBufferSize = 2000000; + xcrush_context_reset(xcrush, FALSE); + + return xcrush; +fail: + xcrush_context_free(xcrush); + + return NULL; +} + +void xcrush_context_free(XCRUSH_CONTEXT* xcrush) +{ + if (xcrush) + { + mppc_context_free(xcrush->mppc); + free(xcrush); + } +} diff --git a/libfreerdp/codec/xcrush.h b/libfreerdp/codec/xcrush.h new file mode 100644 index 0000000..5997c21 --- /dev/null +++ b/libfreerdp/codec/xcrush.h @@ -0,0 +1,51 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * XCrush (RDP6.1) Bulk Data Compression + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FREERDP_CODEC_XCRUSH_H +#define FREERDP_CODEC_XCRUSH_H + +#include <freerdp/api.h> +#include <freerdp/types.h> + +#include "mppc.h" + +typedef struct s_XCRUSH_CONTEXT XCRUSH_CONTEXT; + +#ifdef __cplusplus +extern "C" +{ +#endif + + FREERDP_LOCAL int xcrush_compress(XCRUSH_CONTEXT* xcrush, const BYTE* pSrcData, UINT32 SrcSize, + BYTE* pDstBuffer, const BYTE** ppDstData, UINT32* pDstSize, + UINT32* pFlags); + FREERDP_LOCAL int xcrush_decompress(XCRUSH_CONTEXT* xcrush, const BYTE* pSrcData, + UINT32 SrcSize, const BYTE** ppDstData, UINT32* pDstSize, + UINT32 flags); + + FREERDP_LOCAL void xcrush_context_reset(XCRUSH_CONTEXT* xcrush, BOOL flush); + + FREERDP_LOCAL XCRUSH_CONTEXT* xcrush_context_new(BOOL Compressor); + FREERDP_LOCAL void xcrush_context_free(XCRUSH_CONTEXT* xcrush); + +#ifdef __cplusplus +} +#endif + +#endif /* FREERDP_CODEC_XCRUSH_H */ diff --git a/libfreerdp/codec/yuv.c b/libfreerdp/codec/yuv.c new file mode 100644 index 0000000..c546566 --- /dev/null +++ b/libfreerdp/codec/yuv.c @@ -0,0 +1,887 @@ +#include <winpr/sysinfo.h> +#include <winpr/assert.h> +#include <winpr/pool.h> + +#include <freerdp/settings.h> +#include <freerdp/codec/region.h> +#include <freerdp/primitives.h> +#include <freerdp/log.h> +#include <freerdp/codec/yuv.h> + +#define TAG FREERDP_TAG("codec") + +#define TILE_SIZE 64 + +typedef struct +{ + YUV_CONTEXT* context; + const BYTE* pYUVData[3]; + UINT32 iStride[3]; + DWORD DstFormat; + BYTE* dest; + UINT32 nDstStep; + RECTANGLE_16 rect; +} YUV_PROCESS_WORK_PARAM; + +typedef struct +{ + YUV_CONTEXT* context; + const BYTE* pYUVData[3]; + UINT32 iStride[3]; + BYTE* pYUVDstData[3]; + UINT32 iDstStride[3]; + RECTANGLE_16 rect; + BYTE type; +} YUV_COMBINE_WORK_PARAM; + +typedef struct +{ + YUV_CONTEXT* context; + const BYTE* pSrcData; + + DWORD SrcFormat; + UINT32 nSrcStep; + RECTANGLE_16 rect; + BYTE version; + + BYTE* pYUVLumaData[3]; + BYTE* pYUVChromaData[3]; + UINT32 iStride[3]; +} YUV_ENCODE_WORK_PARAM; + +struct S_YUV_CONTEXT +{ + UINT32 width, height; + BOOL useThreads; + BOOL encoder; + UINT32 nthreads; + UINT32 heightStep; + + PTP_POOL threadPool; + TP_CALLBACK_ENVIRON ThreadPoolEnv; + + UINT32 work_object_count; + PTP_WORK* work_objects; + YUV_ENCODE_WORK_PARAM* work_enc_params; + YUV_PROCESS_WORK_PARAM* work_dec_params; + YUV_COMBINE_WORK_PARAM* work_combined_params; +}; + +static INLINE BOOL avc420_yuv_to_rgb(const BYTE* pYUVData[3], const UINT32 iStride[3], + const RECTANGLE_16* rect, UINT32 nDstStep, BYTE* pDstData, + DWORD DstFormat) +{ + primitives_t* prims = primitives_get(); + prim_size_t roi; + const BYTE* pYUVPoint[3]; + + WINPR_ASSERT(pYUVData); + WINPR_ASSERT(iStride); + WINPR_ASSERT(rect); + WINPR_ASSERT(pDstData); + + const INT32 width = rect->right - rect->left; + const INT32 height = rect->bottom - rect->top; + BYTE* pDstPoint = + pDstData + rect->top * nDstStep + rect->left * FreeRDPGetBytesPerPixel(DstFormat); + + pYUVPoint[0] = pYUVData[0] + rect->top * iStride[0] + rect->left; + pYUVPoint[1] = pYUVData[1] + rect->top / 2 * iStride[1] + rect->left / 2; + pYUVPoint[2] = pYUVData[2] + rect->top / 2 * iStride[2] + rect->left / 2; + + roi.width = width; + roi.height = height; + + if (prims->YUV420ToRGB_8u_P3AC4R(pYUVPoint, iStride, pDstPoint, nDstStep, DstFormat, &roi) != + PRIMITIVES_SUCCESS) + return FALSE; + + return TRUE; +} + +static INLINE BOOL avc444_yuv_to_rgb(const BYTE* pYUVData[3], const UINT32 iStride[3], + const RECTANGLE_16* rect, UINT32 nDstStep, BYTE* pDstData, + DWORD DstFormat) +{ + primitives_t* prims = primitives_get(); + prim_size_t roi; + const BYTE* pYUVPoint[3]; + + WINPR_ASSERT(pYUVData); + WINPR_ASSERT(iStride); + WINPR_ASSERT(rect); + WINPR_ASSERT(pDstData); + + const INT32 width = rect->right - rect->left; + const INT32 height = rect->bottom - rect->top; + BYTE* pDstPoint = + pDstData + rect->top * nDstStep + rect->left * FreeRDPGetBytesPerPixel(DstFormat); + + pYUVPoint[0] = pYUVData[0] + rect->top * iStride[0] + rect->left; + pYUVPoint[1] = pYUVData[1] + rect->top * iStride[1] + rect->left; + pYUVPoint[2] = pYUVData[2] + rect->top * iStride[2] + rect->left; + + roi.width = width; + roi.height = height; + + if (prims->YUV444ToRGB_8u_P3AC4R(pYUVPoint, iStride, pDstPoint, nDstStep, DstFormat, &roi) != + PRIMITIVES_SUCCESS) + return FALSE; + + return TRUE; +} + +static void CALLBACK yuv420_process_work_callback(PTP_CALLBACK_INSTANCE instance, void* context, + PTP_WORK work) +{ + YUV_PROCESS_WORK_PARAM* param = (YUV_PROCESS_WORK_PARAM*)context; + WINPR_UNUSED(instance); + WINPR_UNUSED(work); + WINPR_ASSERT(param); + + if (!avc420_yuv_to_rgb(param->pYUVData, param->iStride, ¶m->rect, param->nDstStep, + param->dest, param->DstFormat)) + WLog_WARN(TAG, "avc420_yuv_to_rgb failed"); +} + +static void CALLBACK yuv444_process_work_callback(PTP_CALLBACK_INSTANCE instance, void* context, + PTP_WORK work) +{ + YUV_PROCESS_WORK_PARAM* param = (YUV_PROCESS_WORK_PARAM*)context; + WINPR_UNUSED(instance); + WINPR_UNUSED(work); + WINPR_ASSERT(param); + + if (!avc444_yuv_to_rgb(param->pYUVData, param->iStride, ¶m->rect, param->nDstStep, + param->dest, param->DstFormat)) + WLog_WARN(TAG, "avc444_yuv_to_rgb failed"); +} + +BOOL yuv_context_reset(YUV_CONTEXT* context, UINT32 width, UINT32 height) +{ + BOOL rc = FALSE; + WINPR_ASSERT(context); + + context->width = width; + context->height = height; + context->heightStep = (height / context->nthreads); + + if (context->useThreads) + { + const UINT32 pw = (width + TILE_SIZE - width % TILE_SIZE) / TILE_SIZE; + const UINT32 ph = (height + TILE_SIZE - height % TILE_SIZE) / TILE_SIZE; + + /* We´ve calculated the amount of workers for 64x64 tiles, but the decoder + * might get 16x16 tiles mixed in. */ + const UINT32 count = pw * ph * 16; + + context->work_object_count = 0; + if (context->encoder) + { + void* tmp = winpr_aligned_recalloc(context->work_enc_params, count, + sizeof(YUV_ENCODE_WORK_PARAM), 32); + if (!tmp) + goto fail; + memset(tmp, 0, count * sizeof(YUV_ENCODE_WORK_PARAM)); + + context->work_enc_params = tmp; + } + else + { + void* tmp = winpr_aligned_recalloc(context->work_dec_params, count, + sizeof(YUV_PROCESS_WORK_PARAM), 32); + if (!tmp) + goto fail; + memset(tmp, 0, count * sizeof(YUV_PROCESS_WORK_PARAM)); + + context->work_dec_params = tmp; + + void* ctmp = winpr_aligned_recalloc(context->work_combined_params, count, + sizeof(YUV_COMBINE_WORK_PARAM), 32); + if (!ctmp) + goto fail; + memset(ctmp, 0, count * sizeof(YUV_COMBINE_WORK_PARAM)); + + context->work_combined_params = ctmp; + } + + void* wtmp = winpr_aligned_recalloc(context->work_objects, count, sizeof(PTP_WORK), 32); + if (!wtmp) + goto fail; + memset(wtmp, 0, count * sizeof(PTP_WORK)); + + context->work_objects = wtmp; + context->work_object_count = count; + } + rc = TRUE; +fail: + return rc; +} + +YUV_CONTEXT* yuv_context_new(BOOL encoder, UINT32 ThreadingFlags) +{ + SYSTEM_INFO sysInfos; + YUV_CONTEXT* ret = winpr_aligned_calloc(1, sizeof(*ret), 32); + if (!ret) + return NULL; + + /** do it here to avoid a race condition between threads */ + primitives_get(); + + ret->encoder = encoder; + ret->nthreads = 1; + if (!(ThreadingFlags & THREADING_FLAGS_DISABLE_THREADS)) + { + GetNativeSystemInfo(&sysInfos); + ret->useThreads = (sysInfos.dwNumberOfProcessors > 1); + if (ret->useThreads) + { + ret->nthreads = sysInfos.dwNumberOfProcessors; + ret->threadPool = CreateThreadpool(NULL); + if (!ret->threadPool) + { + goto error_threadpool; + } + + InitializeThreadpoolEnvironment(&ret->ThreadPoolEnv); + SetThreadpoolCallbackPool(&ret->ThreadPoolEnv, ret->threadPool); + } + } + + return ret; + +error_threadpool: + yuv_context_free(ret); + return NULL; +} + +void yuv_context_free(YUV_CONTEXT* context) +{ + if (!context) + return; + if (context->useThreads) + { + if (context->threadPool) + CloseThreadpool(context->threadPool); + DestroyThreadpoolEnvironment(&context->ThreadPoolEnv); + winpr_aligned_free(context->work_objects); + winpr_aligned_free(context->work_combined_params); + winpr_aligned_free(context->work_enc_params); + winpr_aligned_free(context->work_dec_params); + } + winpr_aligned_free(context); +} + +static INLINE YUV_PROCESS_WORK_PARAM pool_decode_param(const RECTANGLE_16* rect, + YUV_CONTEXT* context, + const BYTE* pYUVData[3], + const UINT32 iStride[3], UINT32 DstFormat, + BYTE* dest, UINT32 nDstStep) +{ + YUV_PROCESS_WORK_PARAM current = { 0 }; + + WINPR_ASSERT(rect); + WINPR_ASSERT(context); + WINPR_ASSERT(pYUVData); + WINPR_ASSERT(iStride); + WINPR_ASSERT(dest); + + current.context = context; + current.DstFormat = DstFormat; + current.pYUVData[0] = pYUVData[0]; + current.pYUVData[1] = pYUVData[1]; + current.pYUVData[2] = pYUVData[2]; + current.iStride[0] = iStride[0]; + current.iStride[1] = iStride[1]; + current.iStride[2] = iStride[2]; + current.nDstStep = nDstStep; + current.dest = dest; + current.rect = *rect; + return current; +} + +static BOOL submit_object(PTP_WORK* work_object, PTP_WORK_CALLBACK cb, const void* param, + YUV_CONTEXT* context) +{ + union + { + const void* cpv; + void* pv; + } cnv; + + cnv.cpv = param; + + if (!work_object) + return FALSE; + + *work_object = NULL; + + if (!param || !context) + return FALSE; + + *work_object = CreateThreadpoolWork(cb, cnv.pv, &context->ThreadPoolEnv); + if (!*work_object) + return FALSE; + + SubmitThreadpoolWork(*work_object); + return TRUE; +} + +static void free_objects(PTP_WORK* work_objects, UINT32 waitCount) +{ + WINPR_ASSERT(work_objects || (waitCount == 0)); + + for (UINT32 i = 0; i < waitCount; i++) + { + PTP_WORK cur = work_objects[i]; + work_objects[i] = NULL; + + if (!cur) + continue; + + WaitForThreadpoolWorkCallbacks(cur, FALSE); + CloseThreadpoolWork(cur); + } +} + +static BOOL intersects(UINT32 pos, const RECTANGLE_16* regionRects, UINT32 numRegionRects) +{ + WINPR_ASSERT(regionRects || (numRegionRects == 0)); + + for (UINT32 x = pos + 1; x < numRegionRects; x++) + { + const RECTANGLE_16* what = ®ionRects[pos]; + const RECTANGLE_16* rect = ®ionRects[x]; + + if (rectangles_intersects(what, rect)) + { + WLog_WARN(TAG, "YUV decoder: intersecting rectangles, aborting"); + return TRUE; + } + } + + return FALSE; +} + +static RECTANGLE_16 clamp(YUV_CONTEXT* context, const RECTANGLE_16* rect, UINT32 srcHeight) +{ + WINPR_ASSERT(context); + WINPR_ASSERT(rect); + + RECTANGLE_16 c = *rect; + const UINT32 height = MIN(context->height, srcHeight); + if (c.top > height) + c.top = height; + if (c.bottom > height) + c.bottom = height; + return c; +} + +static BOOL pool_decode(YUV_CONTEXT* context, PTP_WORK_CALLBACK cb, const BYTE* pYUVData[3], + const UINT32 iStride[3], UINT32 yuvHeight, UINT32 DstFormat, BYTE* dest, + UINT32 nDstStep, const RECTANGLE_16* regionRects, UINT32 numRegionRects) +{ + BOOL rc = FALSE; + UINT32 waitCount = 0; + primitives_t* prims = primitives_get(); + + WINPR_ASSERT(context); + WINPR_ASSERT(cb); + WINPR_ASSERT(pYUVData); + WINPR_ASSERT(iStride); + WINPR_ASSERT(dest); + WINPR_ASSERT(regionRects || (numRegionRects == 0)); + + if (context->encoder) + { + WLog_ERR(TAG, "YUV context set up for encoding, can not decode with it, aborting"); + return FALSE; + } + + if (!context->useThreads || (primitives_flags(prims) & PRIM_FLAGS_HAVE_EXTGPU)) + { + for (UINT32 y = 0; y < numRegionRects; y++) + { + const RECTANGLE_16 rect = clamp(context, ®ionRects[y], yuvHeight); + YUV_PROCESS_WORK_PARAM current = + pool_decode_param(&rect, context, pYUVData, iStride, DstFormat, dest, nDstStep); + cb(NULL, ¤t, NULL); + } + return TRUE; + } + + /* case where we use threads */ + for (UINT32 x = 0; x < numRegionRects; x++) + { + RECTANGLE_16 r = clamp(context, ®ionRects[x], yuvHeight); + + if (intersects(x, regionRects, numRegionRects)) + continue; + + while (r.left < r.right) + { + RECTANGLE_16 y = r; + y.right = MIN(r.right, r.left + TILE_SIZE); + + while (y.top < y.bottom) + { + RECTANGLE_16 z = y; + + if (context->work_object_count <= waitCount) + { + WLog_ERR(TAG, + "YUV decoder: invalid number of tiles, only support less than %" PRIu32 + ", got %" PRIu32, + context->work_object_count, waitCount); + goto fail; + } + + YUV_PROCESS_WORK_PARAM* cur = &context->work_dec_params[waitCount]; + z.bottom = MIN(z.bottom, z.top + TILE_SIZE); + if (rectangle_is_empty(&z)) + continue; + *cur = pool_decode_param(&z, context, pYUVData, iStride, DstFormat, dest, nDstStep); + if (!submit_object(&context->work_objects[waitCount], cb, cur, context)) + goto fail; + waitCount++; + y.top += TILE_SIZE; + } + + r.left += TILE_SIZE; + } + } + rc = TRUE; +fail: + free_objects(context->work_objects, context->work_object_count); + return rc; +} + +static INLINE BOOL check_rect(const YUV_CONTEXT* yuv, const RECTANGLE_16* rect, UINT32 nDstWidth, + UINT32 nDstHeight) +{ + WINPR_ASSERT(yuv); + WINPR_ASSERT(rect); + + /* Check, if the output rectangle is valid in decoded h264 frame. */ + if ((rect->right > yuv->width) || (rect->left > yuv->width)) + return FALSE; + + if ((rect->top > yuv->height) || (rect->bottom > yuv->height)) + return FALSE; + + /* Check, if the output rectangle is valid in destination buffer. */ + if ((rect->right > nDstWidth) || (rect->left > nDstWidth)) + return FALSE; + + if ((rect->bottom > nDstHeight) || (rect->top > nDstHeight)) + return FALSE; + + return TRUE; +} + +static void CALLBACK yuv444_combine_work_callback(PTP_CALLBACK_INSTANCE instance, void* context, + PTP_WORK work) +{ + YUV_COMBINE_WORK_PARAM* param = (YUV_COMBINE_WORK_PARAM*)context; + primitives_t* prims = primitives_get(); + + WINPR_ASSERT(param); + YUV_CONTEXT* yuv = param->context; + WINPR_ASSERT(yuv); + + const RECTANGLE_16* rect = ¶m->rect; + WINPR_ASSERT(rect); + + const UINT32 alignedWidth = yuv->width + ((yuv->width % 16 != 0) ? 16 - yuv->width % 16 : 0); + const UINT32 alignedHeight = + yuv->height + ((yuv->height % 16 != 0) ? 16 - yuv->height % 16 : 0); + + WINPR_UNUSED(instance); + WINPR_UNUSED(work); + + if (!check_rect(param->context, rect, yuv->width, yuv->height)) + return; + + if (prims->YUV420CombineToYUV444(param->type, param->pYUVData, param->iStride, alignedWidth, + alignedHeight, param->pYUVDstData, param->iDstStride, + rect) != PRIMITIVES_SUCCESS) + WLog_WARN(TAG, "YUV420CombineToYUV444 failed"); +} + +static INLINE YUV_COMBINE_WORK_PARAM pool_decode_rect_param( + const RECTANGLE_16* rect, YUV_CONTEXT* context, BYTE type, const BYTE* pYUVData[3], + const UINT32 iStride[3], BYTE* pYUVDstData[3], const UINT32 iDstStride[3]) +{ + YUV_COMBINE_WORK_PARAM current = { 0 }; + + WINPR_ASSERT(rect); + WINPR_ASSERT(context); + WINPR_ASSERT(pYUVData); + WINPR_ASSERT(iStride); + WINPR_ASSERT(pYUVDstData); + WINPR_ASSERT(iDstStride); + + current.context = context; + current.pYUVData[0] = pYUVData[0]; + current.pYUVData[1] = pYUVData[1]; + current.pYUVData[2] = pYUVData[2]; + current.pYUVDstData[0] = pYUVDstData[0]; + current.pYUVDstData[1] = pYUVDstData[1]; + current.pYUVDstData[2] = pYUVDstData[2]; + current.iStride[0] = iStride[0]; + current.iStride[1] = iStride[1]; + current.iStride[2] = iStride[2]; + current.iDstStride[0] = iDstStride[0]; + current.iDstStride[1] = iDstStride[1]; + current.iDstStride[2] = iDstStride[2]; + current.type = type; + current.rect = *rect; + return current; +} + +static BOOL pool_decode_rect(YUV_CONTEXT* context, BYTE type, const BYTE* pYUVData[3], + const UINT32 iStride[3], BYTE* pYUVDstData[3], + const UINT32 iDstStride[3], const RECTANGLE_16* regionRects, + UINT32 numRegionRects) +{ + BOOL rc = FALSE; + UINT32 waitCount = 0; + PTP_WORK_CALLBACK cb = yuv444_combine_work_callback; + primitives_t* prims = primitives_get(); + + WINPR_ASSERT(context); + WINPR_ASSERT(pYUVData); + WINPR_ASSERT(iStride); + WINPR_ASSERT(pYUVDstData); + WINPR_ASSERT(iDstStride); + WINPR_ASSERT(regionRects || (numRegionRects == 0)); + + if (!context->useThreads || (primitives_flags(prims) & PRIM_FLAGS_HAVE_EXTGPU)) + { + for (UINT32 y = 0; y < numRegionRects; y++) + { + YUV_COMBINE_WORK_PARAM current = pool_decode_rect_param( + ®ionRects[y], context, type, pYUVData, iStride, pYUVDstData, iDstStride); + cb(NULL, ¤t, NULL); + } + return TRUE; + } + + /* case where we use threads */ + for (waitCount = 0; waitCount < numRegionRects; waitCount++) + { + YUV_COMBINE_WORK_PARAM* current = NULL; + + if (context->work_object_count <= waitCount) + { + WLog_ERR(TAG, + "YUV rect decoder: invalid number of tiles, only support less than %" PRIu32 + ", got %" PRIu32, + context->work_object_count, waitCount); + goto fail; + } + current = &context->work_combined_params[waitCount]; + *current = pool_decode_rect_param(®ionRects[waitCount], context, type, pYUVData, iStride, + pYUVDstData, iDstStride); + + if (!submit_object(&context->work_objects[waitCount], cb, current, context)) + goto fail; + } + + rc = TRUE; +fail: + free_objects(context->work_objects, context->work_object_count); + return rc; +} + +BOOL yuv444_context_decode(YUV_CONTEXT* context, BYTE type, const BYTE* pYUVData[3], + const UINT32 iStride[3], UINT32 srcYuvHeight, BYTE* pYUVDstData[3], + const UINT32 iDstStride[3], DWORD DstFormat, BYTE* dest, UINT32 nDstStep, + const RECTANGLE_16* regionRects, UINT32 numRegionRects) +{ + const BYTE* pYUVCDstData[3]; + + WINPR_ASSERT(context); + WINPR_ASSERT(pYUVData); + WINPR_ASSERT(iStride); + WINPR_ASSERT(pYUVDstData); + WINPR_ASSERT(iDstStride); + WINPR_ASSERT(dest); + WINPR_ASSERT(regionRects || (numRegionRects == 0)); + + if (context->encoder) + { + WLog_ERR(TAG, "YUV context set up for encoding, can not decode with it, aborting"); + return FALSE; + } + if (!pool_decode_rect(context, type, pYUVData, iStride, pYUVDstData, iDstStride, regionRects, + numRegionRects)) + return FALSE; + + pYUVCDstData[0] = pYUVDstData[0]; + pYUVCDstData[1] = pYUVDstData[1]; + pYUVCDstData[2] = pYUVDstData[2]; + return pool_decode(context, yuv444_process_work_callback, pYUVCDstData, iDstStride, + srcYuvHeight, DstFormat, dest, nDstStep, regionRects, numRegionRects); +} + +BOOL yuv420_context_decode(YUV_CONTEXT* context, const BYTE* pYUVData[3], const UINT32 iStride[3], + UINT32 yuvHeight, DWORD DstFormat, BYTE* dest, UINT32 nDstStep, + const RECTANGLE_16* regionRects, UINT32 numRegionRects) +{ + return pool_decode(context, yuv420_process_work_callback, pYUVData, iStride, yuvHeight, + DstFormat, dest, nDstStep, regionRects, numRegionRects); +} + +static void CALLBACK yuv420_encode_work_callback(PTP_CALLBACK_INSTANCE instance, void* context, + PTP_WORK work) +{ + prim_size_t roi; + YUV_ENCODE_WORK_PARAM* param = (YUV_ENCODE_WORK_PARAM*)context; + primitives_t* prims = primitives_get(); + BYTE* pYUVData[3]; + const BYTE* src = NULL; + + WINPR_UNUSED(instance); + WINPR_UNUSED(work); + WINPR_ASSERT(param); + + roi.width = param->rect.right - param->rect.left; + roi.height = param->rect.bottom - param->rect.top; + src = param->pSrcData + param->nSrcStep * param->rect.top + + param->rect.left * FreeRDPGetBytesPerPixel(param->SrcFormat); + pYUVData[0] = param->pYUVLumaData[0] + param->rect.top * param->iStride[0] + param->rect.left; + pYUVData[1] = + param->pYUVLumaData[1] + param->rect.top / 2 * param->iStride[1] + param->rect.left / 2; + pYUVData[2] = + param->pYUVLumaData[2] + param->rect.top / 2 * param->iStride[2] + param->rect.left / 2; + + if (prims->RGBToYUV420_8u_P3AC4R(src, param->SrcFormat, param->nSrcStep, pYUVData, + param->iStride, &roi) != PRIMITIVES_SUCCESS) + { + WLog_ERR(TAG, "error when decoding lines"); + } +} + +static void CALLBACK yuv444v1_encode_work_callback(PTP_CALLBACK_INSTANCE instance, void* context, + PTP_WORK work) +{ + prim_size_t roi; + YUV_ENCODE_WORK_PARAM* param = (YUV_ENCODE_WORK_PARAM*)context; + primitives_t* prims = primitives_get(); + BYTE* pYUVLumaData[3]; + BYTE* pYUVChromaData[3]; + const BYTE* src = NULL; + + WINPR_UNUSED(instance); + WINPR_UNUSED(work); + WINPR_ASSERT(param); + + roi.width = param->rect.right - param->rect.left; + roi.height = param->rect.bottom - param->rect.top; + src = param->pSrcData + param->nSrcStep * param->rect.top + + param->rect.left * FreeRDPGetBytesPerPixel(param->SrcFormat); + pYUVLumaData[0] = + param->pYUVLumaData[0] + param->rect.top * param->iStride[0] + param->rect.left; + pYUVLumaData[1] = + param->pYUVLumaData[1] + param->rect.top / 2 * param->iStride[1] + param->rect.left / 2; + pYUVLumaData[2] = + param->pYUVLumaData[2] + param->rect.top / 2 * param->iStride[2] + param->rect.left / 2; + pYUVChromaData[0] = + param->pYUVChromaData[0] + param->rect.top * param->iStride[0] + param->rect.left; + pYUVChromaData[1] = + param->pYUVChromaData[1] + param->rect.top / 2 * param->iStride[1] + param->rect.left / 2; + pYUVChromaData[2] = + param->pYUVChromaData[2] + param->rect.top / 2 * param->iStride[2] + param->rect.left / 2; + if (prims->RGBToAVC444YUV(src, param->SrcFormat, param->nSrcStep, pYUVLumaData, param->iStride, + pYUVChromaData, param->iStride, &roi) != PRIMITIVES_SUCCESS) + { + WLog_ERR(TAG, "error when decoding lines"); + } +} + +static void CALLBACK yuv444v2_encode_work_callback(PTP_CALLBACK_INSTANCE instance, void* context, + PTP_WORK work) +{ + prim_size_t roi; + YUV_ENCODE_WORK_PARAM* param = (YUV_ENCODE_WORK_PARAM*)context; + primitives_t* prims = primitives_get(); + BYTE* pYUVLumaData[3]; + BYTE* pYUVChromaData[3]; + const BYTE* src = NULL; + + WINPR_UNUSED(instance); + WINPR_UNUSED(work); + WINPR_ASSERT(param); + + roi.width = param->rect.right - param->rect.left; + roi.height = param->rect.bottom - param->rect.top; + src = param->pSrcData + param->nSrcStep * param->rect.top + + param->rect.left * FreeRDPGetBytesPerPixel(param->SrcFormat); + pYUVLumaData[0] = + param->pYUVLumaData[0] + param->rect.top * param->iStride[0] + param->rect.left; + pYUVLumaData[1] = + param->pYUVLumaData[1] + param->rect.top / 2 * param->iStride[1] + param->rect.left / 2; + pYUVLumaData[2] = + param->pYUVLumaData[2] + param->rect.top / 2 * param->iStride[2] + param->rect.left / 2; + pYUVChromaData[0] = + param->pYUVChromaData[0] + param->rect.top * param->iStride[0] + param->rect.left; + pYUVChromaData[1] = + param->pYUVChromaData[1] + param->rect.top / 2 * param->iStride[1] + param->rect.left / 2; + pYUVChromaData[2] = + param->pYUVChromaData[2] + param->rect.top / 2 * param->iStride[2] + param->rect.left / 2; + if (prims->RGBToAVC444YUVv2(src, param->SrcFormat, param->nSrcStep, pYUVLumaData, + param->iStride, pYUVChromaData, param->iStride, + &roi) != PRIMITIVES_SUCCESS) + { + WLog_ERR(TAG, "error when decoding lines"); + } +} + +static INLINE YUV_ENCODE_WORK_PARAM pool_encode_fill(const RECTANGLE_16* rect, YUV_CONTEXT* context, + const BYTE* pSrcData, UINT32 nSrcStep, + UINT32 SrcFormat, const UINT32 iStride[], + BYTE* pYUVLumaData[], BYTE* pYUVChromaData[]) +{ + YUV_ENCODE_WORK_PARAM current = { 0 }; + + WINPR_ASSERT(rect); + WINPR_ASSERT(context); + WINPR_ASSERT(pSrcData); + WINPR_ASSERT(iStride); + WINPR_ASSERT(pYUVLumaData); + + current.context = context; + current.pSrcData = pSrcData; + current.SrcFormat = SrcFormat; + current.nSrcStep = nSrcStep; + current.pYUVLumaData[0] = pYUVLumaData[0]; + current.pYUVLumaData[1] = pYUVLumaData[1]; + current.pYUVLumaData[2] = pYUVLumaData[2]; + if (pYUVChromaData) + { + current.pYUVChromaData[0] = pYUVChromaData[0]; + current.pYUVChromaData[1] = pYUVChromaData[1]; + current.pYUVChromaData[2] = pYUVChromaData[2]; + } + current.iStride[0] = iStride[0]; + current.iStride[1] = iStride[1]; + current.iStride[2] = iStride[2]; + + current.rect = *rect; + + return current; +} + +static BOOL pool_encode(YUV_CONTEXT* context, PTP_WORK_CALLBACK cb, const BYTE* pSrcData, + UINT32 nSrcStep, UINT32 SrcFormat, const UINT32 iStride[], + BYTE* pYUVLumaData[], BYTE* pYUVChromaData[], + const RECTANGLE_16* regionRects, UINT32 numRegionRects) +{ + BOOL rc = FALSE; + primitives_t* prims = primitives_get(); + UINT32 waitCount = 0; + + WINPR_ASSERT(context); + WINPR_ASSERT(cb); + WINPR_ASSERT(pSrcData); + WINPR_ASSERT(iStride); + WINPR_ASSERT(regionRects || (numRegionRects == 0)); + + if (!context->encoder) + { + + WLog_ERR(TAG, "YUV context set up for decoding, can not encode with it, aborting"); + return FALSE; + } + + if (!context->useThreads || (primitives_flags(prims) & PRIM_FLAGS_HAVE_EXTGPU)) + { + for (UINT32 x = 0; x < numRegionRects; x++) + { + YUV_ENCODE_WORK_PARAM current = + pool_encode_fill(®ionRects[x], context, pSrcData, nSrcStep, SrcFormat, iStride, + pYUVLumaData, pYUVChromaData); + cb(NULL, ¤t, NULL); + } + return TRUE; + } + + /* case where we use threads */ + for (UINT32 x = 0; x < numRegionRects; x++) + { + const RECTANGLE_16* rect = ®ionRects[x]; + const UINT32 height = rect->bottom - rect->top; + const UINT32 steps = (height + context->heightStep / 2) / context->heightStep; + + waitCount += steps; + } + + for (UINT32 x = 0; x < numRegionRects; x++) + { + const RECTANGLE_16* rect = ®ionRects[x]; + const UINT32 height = rect->bottom - rect->top; + const UINT32 steps = (height + context->heightStep / 2) / context->heightStep; + + for (UINT32 y = 0; y < steps; y++) + { + RECTANGLE_16 r = *rect; + YUV_ENCODE_WORK_PARAM* current = NULL; + + if (context->work_object_count <= waitCount) + { + WLog_ERR(TAG, + "YUV encoder: invalid number of tiles, only support less than %" PRIu32 + ", got %" PRIu32, + context->work_object_count, waitCount); + goto fail; + } + + current = &context->work_enc_params[waitCount]; + r.top += y * context->heightStep; + *current = pool_encode_fill(&r, context, pSrcData, nSrcStep, SrcFormat, iStride, + pYUVLumaData, pYUVChromaData); + if (!submit_object(&context->work_objects[waitCount], cb, current, context)) + goto fail; + waitCount++; + } + } + + rc = TRUE; +fail: + free_objects(context->work_objects, context->work_object_count); + return rc; +} + +BOOL yuv420_context_encode(YUV_CONTEXT* context, const BYTE* pSrcData, UINT32 nSrcStep, + UINT32 SrcFormat, const UINT32 iStride[3], BYTE* pYUVData[3], + const RECTANGLE_16* regionRects, UINT32 numRegionRects) +{ + if (!context || !pSrcData || !iStride || !pYUVData || !regionRects) + return FALSE; + + return pool_encode(context, yuv420_encode_work_callback, pSrcData, nSrcStep, SrcFormat, iStride, + pYUVData, NULL, regionRects, numRegionRects); +} + +BOOL yuv444_context_encode(YUV_CONTEXT* context, BYTE version, const BYTE* pSrcData, + UINT32 nSrcStep, UINT32 SrcFormat, const UINT32 iStride[3], + BYTE* pYUVLumaData[3], BYTE* pYUVChromaData[3], + const RECTANGLE_16* regionRects, UINT32 numRegionRects) +{ + PTP_WORK_CALLBACK cb = NULL; + switch (version) + { + case 1: + cb = yuv444v1_encode_work_callback; + break; + case 2: + cb = yuv444v2_encode_work_callback; + break; + default: + return FALSE; + } + + return pool_encode(context, cb, pSrcData, nSrcStep, SrcFormat, iStride, pYUVLumaData, + pYUVChromaData, regionRects, numRegionRects); +} diff --git a/libfreerdp/codec/zgfx.c b/libfreerdp/codec/zgfx.c new file mode 100644 index 0000000..881823a --- /dev/null +++ b/libfreerdp/codec/zgfx.c @@ -0,0 +1,603 @@ +/** + * FreeRDP: A Remote Desktop Protocol Implementation + * ZGFX (RDP8) Bulk Data Compression + * + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2017 Armin Novak <armin.novak@thincast.com> + * Copyright 2017 Thincast Technologies GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <freerdp/config.h> + +#include <winpr/crt.h> +#include <winpr/print.h> +#include <winpr/bitstream.h> + +#include <freerdp/log.h> +#include <freerdp/codec/zgfx.h> + +#define TAG FREERDP_TAG("codec") + +/** + * RDP8 Compressor Limits: + * + * Maximum number of uncompressed bytes in a single segment: 65535 + * Maximum match distance / minimum history size: 2500000 bytes. + * Maximum number of segments: 65535 + * Maximum expansion of a segment (when compressed size exceeds uncompressed): 1000 bytes + * Minimum match length: 3 bytes + */ + +typedef struct +{ + UINT32 prefixLength; + UINT32 prefixCode; + UINT32 valueBits; + UINT32 tokenType; + UINT32 valueBase; +} ZGFX_TOKEN; + +struct S_ZGFX_CONTEXT +{ + BOOL Compressor; + + const BYTE* pbInputCurrent; + const BYTE* pbInputEnd; + + UINT32 bits; + UINT32 cBitsRemaining; + UINT32 BitsCurrent; + UINT32 cBitsCurrent; + + BYTE OutputBuffer[65536]; + UINT32 OutputCount; + + BYTE HistoryBuffer[2500000]; + UINT32 HistoryIndex; + UINT32 HistoryBufferSize; +}; + +static const ZGFX_TOKEN ZGFX_TOKEN_TABLE[] = { + // len code vbits type vbase + { 1, 0, 8, 0, 0 }, // 0 + { 5, 17, 5, 1, 0 }, // 10001 + { 5, 18, 7, 1, 32 }, // 10010 + { 5, 19, 9, 1, 160 }, // 10011 + { 5, 20, 10, 1, 672 }, // 10100 + { 5, 21, 12, 1, 1696 }, // 10101 + { 5, 24, 0, 0, 0x00 }, // 11000 + { 5, 25, 0, 0, 0x01 }, // 11001 + { 6, 44, 14, 1, 5792 }, // 101100 + { 6, 45, 15, 1, 22176 }, // 101101 + { 6, 52, 0, 0, 0x02 }, // 110100 + { 6, 53, 0, 0, 0x03 }, // 110101 + { 6, 54, 0, 0, 0xFF }, // 110110 + { 7, 92, 18, 1, 54944 }, // 1011100 + { 7, 93, 20, 1, 317088 }, // 1011101 + { 7, 110, 0, 0, 0x04 }, // 1101110 + { 7, 111, 0, 0, 0x05 }, // 1101111 + { 7, 112, 0, 0, 0x06 }, // 1110000 + { 7, 113, 0, 0, 0x07 }, // 1110001 + { 7, 114, 0, 0, 0x08 }, // 1110010 + { 7, 115, 0, 0, 0x09 }, // 1110011 + { 7, 116, 0, 0, 0x0A }, // 1110100 + { 7, 117, 0, 0, 0x0B }, // 1110101 + { 7, 118, 0, 0, 0x3A }, // 1110110 + { 7, 119, 0, 0, 0x3B }, // 1110111 + { 7, 120, 0, 0, 0x3C }, // 1111000 + { 7, 121, 0, 0, 0x3D }, // 1111001 + { 7, 122, 0, 0, 0x3E }, // 1111010 + { 7, 123, 0, 0, 0x3F }, // 1111011 + { 7, 124, 0, 0, 0x40 }, // 1111100 + { 7, 125, 0, 0, 0x80 }, // 1111101 + { 8, 188, 20, 1, 1365664 }, // 10111100 + { 8, 189, 21, 1, 2414240 }, // 10111101 + { 8, 252, 0, 0, 0x0C }, // 11111100 + { 8, 253, 0, 0, 0x38 }, // 11111101 + { 8, 254, 0, 0, 0x39 }, // 11111110 + { 8, 255, 0, 0, 0x66 }, // 11111111 + { 9, 380, 22, 1, 4511392 }, // 101111100 + { 9, 381, 23, 1, 8705696 }, // 101111101 + { 9, 382, 24, 1, 17094304 }, // 101111110 + { 0 } +}; + +static INLINE BOOL zgfx_GetBits(ZGFX_CONTEXT* zgfx, UINT32 nbits) +{ + if (!zgfx) + return FALSE; + + while (zgfx->cBitsCurrent < nbits) + { + zgfx->BitsCurrent <<= 8; + + if (zgfx->pbInputCurrent < zgfx->pbInputEnd) + zgfx->BitsCurrent += *(zgfx->pbInputCurrent)++; + + zgfx->cBitsCurrent += 8; + } + + zgfx->cBitsRemaining -= nbits; + zgfx->cBitsCurrent -= nbits; + zgfx->bits = zgfx->BitsCurrent >> zgfx->cBitsCurrent; + zgfx->BitsCurrent &= ((1 << zgfx->cBitsCurrent) - 1); + return TRUE; +} + +static void zgfx_history_buffer_ring_write(ZGFX_CONTEXT* zgfx, const BYTE* src, size_t count) +{ + UINT32 front = 0; + + if (count <= 0) + return; + + if (count > zgfx->HistoryBufferSize) + { + const size_t residue = count - zgfx->HistoryBufferSize; + count = zgfx->HistoryBufferSize; + src += residue; + zgfx->HistoryIndex = (zgfx->HistoryIndex + residue) % zgfx->HistoryBufferSize; + } + + if (zgfx->HistoryIndex + count <= zgfx->HistoryBufferSize) + { + CopyMemory(&(zgfx->HistoryBuffer[zgfx->HistoryIndex]), src, count); + + if ((zgfx->HistoryIndex += count) == zgfx->HistoryBufferSize) + zgfx->HistoryIndex = 0; + } + else + { + front = zgfx->HistoryBufferSize - zgfx->HistoryIndex; + CopyMemory(&(zgfx->HistoryBuffer[zgfx->HistoryIndex]), src, front); + CopyMemory(zgfx->HistoryBuffer, &src[front], count - front); + zgfx->HistoryIndex = count - front; + } +} + +static void zgfx_history_buffer_ring_read(ZGFX_CONTEXT* zgfx, int offset, BYTE* dst, UINT32 count) +{ + UINT32 front = 0; + UINT32 index = 0; + INT32 bytes = 0; + UINT32 valid = 0; + INT32 bytesLeft = 0; + BYTE* dptr = dst; + BYTE* origDst = dst; + + if ((count <= 0) || (count > INT32_MAX)) + return; + + bytesLeft = (INT32)count; + index = (zgfx->HistoryIndex + zgfx->HistoryBufferSize - offset) % zgfx->HistoryBufferSize; + bytes = MIN(bytesLeft, offset); + + if ((index + bytes) <= zgfx->HistoryBufferSize) + { + CopyMemory(dptr, &(zgfx->HistoryBuffer[index]), bytes); + } + else + { + front = zgfx->HistoryBufferSize - index; + CopyMemory(dptr, &(zgfx->HistoryBuffer[index]), front); + CopyMemory(&dptr[front], zgfx->HistoryBuffer, bytes - front); + } + + if ((bytesLeft -= bytes) == 0) + return; + + dptr += bytes; + valid = bytes; + + do + { + bytes = valid; + + if (bytes > bytesLeft) + bytes = bytesLeft; + + CopyMemory(dptr, origDst, bytes); + dptr += bytes; + valid <<= 1; + } while ((bytesLeft -= bytes) > 0); +} + +static BOOL zgfx_decompress_segment(ZGFX_CONTEXT* zgfx, wStream* stream, size_t segmentSize) +{ + BYTE c = 0; + BYTE flags = 0; + UINT32 extra = 0; + int opIndex = 0; + UINT32 haveBits = 0; + UINT32 inPrefix = 0; + UINT32 count = 0; + UINT32 distance = 0; + BYTE* pbSegment = NULL; + size_t cbSegment = 0; + + if (!zgfx || !stream || (segmentSize < 2)) + return FALSE; + + cbSegment = segmentSize - 1; + + if (!Stream_CheckAndLogRequiredLength(TAG, stream, segmentSize) || (segmentSize > UINT32_MAX)) + return FALSE; + + Stream_Read_UINT8(stream, flags); /* header (1 byte) */ + zgfx->OutputCount = 0; + pbSegment = Stream_Pointer(stream); + if (!Stream_SafeSeek(stream, cbSegment)) + return FALSE; + + if (!(flags & PACKET_COMPRESSED)) + { + zgfx_history_buffer_ring_write(zgfx, pbSegment, cbSegment); + + if (cbSegment > sizeof(zgfx->OutputBuffer)) + return FALSE; + + CopyMemory(zgfx->OutputBuffer, pbSegment, cbSegment); + zgfx->OutputCount = cbSegment; + return TRUE; + } + + zgfx->pbInputCurrent = pbSegment; + zgfx->pbInputEnd = &pbSegment[cbSegment - 1]; + /* NumberOfBitsToDecode = ((NumberOfBytesToDecode - 1) * 8) - ValueOfLastByte */ + const UINT32 bits = 8u * (cbSegment - 1u); + if (bits < *zgfx->pbInputEnd) + return FALSE; + + zgfx->cBitsRemaining = bits - *zgfx->pbInputEnd; + zgfx->cBitsCurrent = 0; + zgfx->BitsCurrent = 0; + + while (zgfx->cBitsRemaining) + { + haveBits = 0; + inPrefix = 0; + + for (opIndex = 0; ZGFX_TOKEN_TABLE[opIndex].prefixLength != 0; opIndex++) + { + while (haveBits < ZGFX_TOKEN_TABLE[opIndex].prefixLength) + { + zgfx_GetBits(zgfx, 1); + inPrefix = (inPrefix << 1) + zgfx->bits; + haveBits++; + } + + if (inPrefix == ZGFX_TOKEN_TABLE[opIndex].prefixCode) + { + if (ZGFX_TOKEN_TABLE[opIndex].tokenType == 0) + { + /* Literal */ + zgfx_GetBits(zgfx, ZGFX_TOKEN_TABLE[opIndex].valueBits); + c = (BYTE)(ZGFX_TOKEN_TABLE[opIndex].valueBase + zgfx->bits); + zgfx->HistoryBuffer[zgfx->HistoryIndex] = c; + + if (++zgfx->HistoryIndex == zgfx->HistoryBufferSize) + zgfx->HistoryIndex = 0; + + if (zgfx->OutputCount >= sizeof(zgfx->OutputBuffer)) + return FALSE; + + zgfx->OutputBuffer[zgfx->OutputCount++] = c; + } + else + { + zgfx_GetBits(zgfx, ZGFX_TOKEN_TABLE[opIndex].valueBits); + distance = ZGFX_TOKEN_TABLE[opIndex].valueBase + zgfx->bits; + + if (distance != 0) + { + /* Match */ + zgfx_GetBits(zgfx, 1); + + if (zgfx->bits == 0) + { + count = 3; + } + else + { + count = 4; + extra = 2; + zgfx_GetBits(zgfx, 1); + + while (zgfx->bits == 1) + { + count *= 2; + extra++; + zgfx_GetBits(zgfx, 1); + } + + zgfx_GetBits(zgfx, extra); + count += zgfx->bits; + } + + if (count > sizeof(zgfx->OutputBuffer) - zgfx->OutputCount) + return FALSE; + + zgfx_history_buffer_ring_read( + zgfx, distance, &(zgfx->OutputBuffer[zgfx->OutputCount]), count); + zgfx_history_buffer_ring_write( + zgfx, &(zgfx->OutputBuffer[zgfx->OutputCount]), count); + zgfx->OutputCount += count; + } + else + { + /* Unencoded */ + zgfx_GetBits(zgfx, 15); + count = zgfx->bits; + zgfx->cBitsRemaining -= zgfx->cBitsCurrent; + zgfx->cBitsCurrent = 0; + zgfx->BitsCurrent = 0; + + if (count > sizeof(zgfx->OutputBuffer) - zgfx->OutputCount) + return FALSE; + + if (count > zgfx->cBitsRemaining / 8) + return FALSE; + + CopyMemory(&(zgfx->OutputBuffer[zgfx->OutputCount]), zgfx->pbInputCurrent, + count); + zgfx_history_buffer_ring_write(zgfx, zgfx->pbInputCurrent, count); + zgfx->pbInputCurrent += count; + zgfx->cBitsRemaining -= (8 * count); + zgfx->OutputCount += count; + } + } + + break; + } + } + } + + return TRUE; +} + +/* Allocate the buffers a bit larger. + * + * Due to optimizations some h264 decoders will read data beyond + * the actual available data, so ensure that it will never be a + * out of bounds read. + */ +static BYTE* aligned_zgfx_malloc(size_t size) +{ + return malloc(size + 64); +} + +int zgfx_decompress(ZGFX_CONTEXT* zgfx, const BYTE* pSrcData, UINT32 SrcSize, BYTE** ppDstData, + UINT32* pDstSize, UINT32 flags) +{ + int status = -1; + BYTE descriptor = 0; + wStream sbuffer = { 0 }; + wStream* stream = Stream_StaticConstInit(&sbuffer, pSrcData, SrcSize); + + if (!stream) + return -1; + + if (!Stream_CheckAndLogRequiredLength(TAG, stream, 1)) + goto fail; + + Stream_Read_UINT8(stream, descriptor); /* descriptor (1 byte) */ + + if (descriptor == ZGFX_SEGMENTED_SINGLE) + { + if (!zgfx_decompress_segment(zgfx, stream, Stream_GetRemainingLength(stream))) + goto fail; + + *ppDstData = NULL; + + if (zgfx->OutputCount > 0) + *ppDstData = aligned_zgfx_malloc(zgfx->OutputCount); + + if (!*ppDstData) + goto fail; + + *pDstSize = zgfx->OutputCount; + CopyMemory(*ppDstData, zgfx->OutputBuffer, zgfx->OutputCount); + } + else if (descriptor == ZGFX_SEGMENTED_MULTIPART) + { + UINT32 segmentSize = 0; + UINT16 segmentNumber = 0; + UINT16 segmentCount = 0; + UINT32 uncompressedSize = 0; + BYTE* pConcatenated = NULL; + size_t used = 0; + + if (!Stream_CheckAndLogRequiredLength(TAG, stream, 6)) + goto fail; + + Stream_Read_UINT16(stream, segmentCount); /* segmentCount (2 bytes) */ + Stream_Read_UINT32(stream, uncompressedSize); /* uncompressedSize (4 bytes) */ + + if (!Stream_CheckAndLogRequiredLengthOfSize(TAG, stream, segmentCount, sizeof(UINT32))) + goto fail; + + pConcatenated = aligned_zgfx_malloc(uncompressedSize); + + if (!pConcatenated) + goto fail; + + *ppDstData = pConcatenated; + *pDstSize = uncompressedSize; + + for (segmentNumber = 0; segmentNumber < segmentCount; segmentNumber++) + { + if (!Stream_CheckAndLogRequiredLength(TAG, stream, sizeof(UINT32))) + goto fail; + + Stream_Read_UINT32(stream, segmentSize); /* segmentSize (4 bytes) */ + + if (!zgfx_decompress_segment(zgfx, stream, segmentSize)) + goto fail; + + if (zgfx->OutputCount > UINT32_MAX - used) + goto fail; + + if (used + zgfx->OutputCount > uncompressedSize) + goto fail; + + CopyMemory(pConcatenated, zgfx->OutputBuffer, zgfx->OutputCount); + pConcatenated += zgfx->OutputCount; + used += zgfx->OutputCount; + } + } + else + { + goto fail; + } + + status = 1; +fail: + return status; +} + +static BOOL zgfx_compress_segment(ZGFX_CONTEXT* zgfx, wStream* s, const BYTE* pSrcData, + UINT32 SrcSize, UINT32* pFlags) +{ + /* FIXME: Currently compression not implemented. Just copy the raw source */ + if (!Stream_EnsureRemainingCapacity(s, SrcSize + 1)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return FALSE; + } + + (*pFlags) |= ZGFX_PACKET_COMPR_TYPE_RDP8; /* RDP 8.0 compression format */ + Stream_Write_UINT8(s, (*pFlags)); /* header (1 byte) */ + Stream_Write(s, pSrcData, SrcSize); + return TRUE; +} + +int zgfx_compress_to_stream(ZGFX_CONTEXT* zgfx, wStream* sDst, const BYTE* pUncompressed, + UINT32 uncompressedSize, UINT32* pFlags) +{ + int fragment = 0; + UINT16 maxLength = 0; + UINT32 totalLength = 0; + size_t posSegmentCount = 0; + const BYTE* pSrcData = NULL; + int status = 0; + maxLength = ZGFX_SEGMENTED_MAXSIZE; + totalLength = uncompressedSize; + pSrcData = pUncompressed; + + for (; (totalLength > 0) || (fragment == 0); fragment++) + { + UINT32 SrcSize = 0; + size_t posDstSize = 0; + size_t posDataStart = 0; + UINT32 DstSize = 0; + SrcSize = (totalLength > maxLength) ? maxLength : totalLength; + posDstSize = 0; + totalLength -= SrcSize; + + /* Ensure we have enough space for headers */ + if (!Stream_EnsureRemainingCapacity(sDst, 12)) + { + WLog_ERR(TAG, "Stream_EnsureRemainingCapacity failed!"); + return -1; + } + + if (fragment == 0) + { + /* First fragment */ + /* descriptor (1 byte) */ + Stream_Write_UINT8(sDst, (totalLength == 0) ? ZGFX_SEGMENTED_SINGLE + : ZGFX_SEGMENTED_MULTIPART); + + if (totalLength > 0) + { + posSegmentCount = Stream_GetPosition(sDst); /* segmentCount (2 bytes) */ + Stream_Seek(sDst, 2); + Stream_Write_UINT32(sDst, uncompressedSize); /* uncompressedSize (4 bytes) */ + } + } + + if (fragment > 0 || totalLength > 0) + { + /* Multipart */ + posDstSize = Stream_GetPosition(sDst); /* size (4 bytes) */ + Stream_Seek(sDst, 4); + } + + posDataStart = Stream_GetPosition(sDst); + + if (!zgfx_compress_segment(zgfx, sDst, pSrcData, SrcSize, pFlags)) + return -1; + + if (posDstSize) + { + /* Fill segment data size */ + DstSize = Stream_GetPosition(sDst) - posDataStart; + Stream_SetPosition(sDst, posDstSize); + Stream_Write_UINT32(sDst, DstSize); + Stream_SetPosition(sDst, posDataStart + DstSize); + } + + pSrcData += SrcSize; + } + + Stream_SealLength(sDst); + + /* fill back segmentCount */ + if (posSegmentCount) + { + Stream_SetPosition(sDst, posSegmentCount); + Stream_Write_UINT16(sDst, fragment); + Stream_SetPosition(sDst, Stream_Length(sDst)); + } + + return status; +} + +int zgfx_compress(ZGFX_CONTEXT* zgfx, const BYTE* pSrcData, UINT32 SrcSize, BYTE** ppDstData, + UINT32* pDstSize, UINT32* pFlags) +{ + int status = 0; + wStream* s = Stream_New(NULL, SrcSize); + status = zgfx_compress_to_stream(zgfx, s, pSrcData, SrcSize, pFlags); + (*ppDstData) = Stream_Buffer(s); + (*pDstSize) = Stream_GetPosition(s); + Stream_Free(s, FALSE); + return status; +} + +void zgfx_context_reset(ZGFX_CONTEXT* zgfx, BOOL flush) +{ + zgfx->HistoryIndex = 0; +} + +ZGFX_CONTEXT* zgfx_context_new(BOOL Compressor) +{ + ZGFX_CONTEXT* zgfx = NULL; + zgfx = (ZGFX_CONTEXT*)calloc(1, sizeof(ZGFX_CONTEXT)); + + if (zgfx) + { + zgfx->Compressor = Compressor; + zgfx->HistoryBufferSize = sizeof(zgfx->HistoryBuffer); + zgfx_context_reset(zgfx, FALSE); + } + + return zgfx; +} + +void zgfx_context_free(ZGFX_CONTEXT* zgfx) +{ + free(zgfx); +} |