From d7612a3b67a171c7228c4d843d5f5dd3e83cc873 Mon Sep 17 00:00:00 2001 From: Ignacio Date: Wed, 8 Feb 2017 11:42:25 -0800 Subject: [PATCH] Add some external dependencies. --- extern/CMakeLists.txt | 7 + extern/EtcLib/CMakeLists.txt | 24 + extern/EtcLib/Etc/Etc.cpp | 58 + extern/EtcLib/Etc/Etc.h | 47 + extern/EtcLib/Etc/EtcColor.h | 64 + extern/EtcLib/Etc/EtcColorFloatRGBA.h | 321 + extern/EtcLib/Etc/EtcConfig.h | 67 + extern/EtcLib/Etc/EtcImage.cpp | 685 + extern/EtcLib/Etc/EtcImage.h | 249 + extern/EtcLib/Etc/EtcMath.cpp | 64 + extern/EtcLib/Etc/EtcMath.h | 40 + extern/EtcLib/EtcCodec/EtcBlock4x4.cpp | 417 + extern/EtcLib/EtcCodec/EtcBlock4x4.h | 172 + .../EtcLib/EtcCodec/EtcBlock4x4Encoding.cpp | 250 + extern/EtcLib/EtcCodec/EtcBlock4x4Encoding.h | 148 + .../EtcLib/EtcCodec/EtcBlock4x4EncodingBits.h | 315 + .../EtcCodec/EtcBlock4x4Encoding_ETC1.cpp | 1280 ++ .../EtcCodec/EtcBlock4x4Encoding_ETC1.h | 186 + .../EtcCodec/EtcBlock4x4Encoding_R11.cpp | 429 + .../EtcLib/EtcCodec/EtcBlock4x4Encoding_R11.h | 122 + .../EtcCodec/EtcBlock4x4Encoding_RG11.cpp | 447 + .../EtcCodec/EtcBlock4x4Encoding_RG11.h | 86 + .../EtcCodec/EtcBlock4x4Encoding_RGB8.cpp | 1728 ++ .../EtcCodec/EtcBlock4x4Encoding_RGB8.h | 96 + .../EtcCodec/EtcBlock4x4Encoding_RGB8A1.cpp | 1819 ++ .../EtcCodec/EtcBlock4x4Encoding_RGB8A1.h | 129 + .../EtcCodec/EtcBlock4x4Encoding_RGBA8.cpp | 474 + .../EtcCodec/EtcBlock4x4Encoding_RGBA8.h | 121 + .../EtcLib/EtcCodec/EtcDifferentialTrys.cpp | 173 + extern/EtcLib/EtcCodec/EtcDifferentialTrys.h | 97 + extern/EtcLib/EtcCodec/EtcErrorMetric.h | 51 + extern/EtcLib/EtcCodec/EtcIndividualTrys.cpp | 85 + extern/EtcLib/EtcCodec/EtcIndividualTrys.h | 95 + extern/EtcLib/EtcCodec/EtcSortedBlockList.cpp | 228 + extern/EtcLib/EtcCodec/EtcSortedBlockList.h | 124 + extern/butteraugli/CMakeLists.txt | 7 + extern/butteraugli/butteraugli.cc | 1588 ++ extern/butteraugli/butteraugli.h | 560 + extern/etcpack/CMakeLists.txt | 10 + extern/etcpack/etcdec.cxx | 1842 ++ extern/etcpack/etcpack.cxx | 16091 ++++++++++++++++ extern/etcpack/image.cxx | 461 + extern/etcpack/image.h | 65 + extern/poshlib/posh.h | 2 +- extern/pvrtextool/Include/PVRTArray.h | 568 + extern/pvrtextool/Include/PVRTDecompress.h | 58 + extern/pvrtextool/Include/PVRTError.h | 71 + extern/pvrtextool/Include/PVRTGlobal.h | 278 + extern/pvrtextool/Include/PVRTMap.h | 222 + extern/pvrtextool/Include/PVRTString.h | 985 + extern/pvrtextool/Include/PVRTTexture.h | 703 + extern/pvrtextool/Include/PVRTTypes.h | 120 + extern/pvrtextool/Include/PVRTexture.h | 225 + extern/pvrtextool/Include/PVRTextureDefines.h | 109 + extern/pvrtextool/Include/PVRTextureFormat.h | 90 + extern/pvrtextool/Include/PVRTextureHeader.h | 603 + .../pvrtextool/Include/PVRTextureUtilities.h | 171 + extern/pvrtextool/Include/PVRTextureVersion.h | 23 + .../pvrtextool/OSX_x86/Static/libPVRTexLib.a | Bin 0 -> 2270032 bytes .../Windows_x86_32/Static/PVRTexLib.lib | Bin 0 -> 1360140 bytes .../Windows_x86_64/Static/PVRTexLib.lib | Bin 0 -> 2013104 bytes extern/rg_etc1_v104/CMakeLists.txt | 7 + extern/rg_etc1_v104/rg_etc1.cpp | 2452 +++ extern/rg_etc1_v104/rg_etc1.h | 76 + 64 files changed, 38084 insertions(+), 1 deletion(-) create mode 100644 extern/EtcLib/CMakeLists.txt create mode 100644 extern/EtcLib/Etc/Etc.cpp create mode 100644 extern/EtcLib/Etc/Etc.h create mode 100644 extern/EtcLib/Etc/EtcColor.h create mode 100644 extern/EtcLib/Etc/EtcColorFloatRGBA.h create mode 100644 extern/EtcLib/Etc/EtcConfig.h create mode 100644 extern/EtcLib/Etc/EtcImage.cpp create mode 100644 extern/EtcLib/Etc/EtcImage.h create mode 100644 extern/EtcLib/Etc/EtcMath.cpp create mode 100644 extern/EtcLib/Etc/EtcMath.h create mode 100644 extern/EtcLib/EtcCodec/EtcBlock4x4.cpp create mode 100644 extern/EtcLib/EtcCodec/EtcBlock4x4.h create mode 100644 extern/EtcLib/EtcCodec/EtcBlock4x4Encoding.cpp create mode 100644 extern/EtcLib/EtcCodec/EtcBlock4x4Encoding.h create mode 100644 extern/EtcLib/EtcCodec/EtcBlock4x4EncodingBits.h create mode 100644 extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_ETC1.cpp create mode 100644 extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_ETC1.h create mode 100644 extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_R11.cpp create mode 100644 extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_R11.h create mode 100644 extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RG11.cpp create mode 100644 extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RG11.h create mode 100644 extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGB8.cpp create mode 100644 extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGB8.h create mode 100644 extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGB8A1.cpp create mode 100644 extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGB8A1.h create mode 100644 extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGBA8.cpp create mode 100644 extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGBA8.h create mode 100644 extern/EtcLib/EtcCodec/EtcDifferentialTrys.cpp create mode 100644 extern/EtcLib/EtcCodec/EtcDifferentialTrys.h create mode 100644 extern/EtcLib/EtcCodec/EtcErrorMetric.h create mode 100644 extern/EtcLib/EtcCodec/EtcIndividualTrys.cpp create mode 100644 extern/EtcLib/EtcCodec/EtcIndividualTrys.h create mode 100644 extern/EtcLib/EtcCodec/EtcSortedBlockList.cpp create mode 100644 extern/EtcLib/EtcCodec/EtcSortedBlockList.h create mode 100644 extern/butteraugli/CMakeLists.txt create mode 100755 extern/butteraugli/butteraugli.cc create mode 100755 extern/butteraugli/butteraugli.h create mode 100644 extern/etcpack/CMakeLists.txt create mode 100755 extern/etcpack/etcdec.cxx create mode 100755 extern/etcpack/etcpack.cxx create mode 100755 extern/etcpack/image.cxx create mode 100755 extern/etcpack/image.h create mode 100644 extern/pvrtextool/Include/PVRTArray.h create mode 100644 extern/pvrtextool/Include/PVRTDecompress.h create mode 100644 extern/pvrtextool/Include/PVRTError.h create mode 100644 extern/pvrtextool/Include/PVRTGlobal.h create mode 100644 extern/pvrtextool/Include/PVRTMap.h create mode 100644 extern/pvrtextool/Include/PVRTString.h create mode 100644 extern/pvrtextool/Include/PVRTTexture.h create mode 100644 extern/pvrtextool/Include/PVRTTypes.h create mode 100644 extern/pvrtextool/Include/PVRTexture.h create mode 100644 extern/pvrtextool/Include/PVRTextureDefines.h create mode 100644 extern/pvrtextool/Include/PVRTextureFormat.h create mode 100644 extern/pvrtextool/Include/PVRTextureHeader.h create mode 100644 extern/pvrtextool/Include/PVRTextureUtilities.h create mode 100644 extern/pvrtextool/Include/PVRTextureVersion.h create mode 100644 extern/pvrtextool/OSX_x86/Static/libPVRTexLib.a create mode 100644 extern/pvrtextool/Windows_x86_32/Static/PVRTexLib.lib create mode 100644 extern/pvrtextool/Windows_x86_64/Static/PVRTexLib.lib create mode 100644 extern/rg_etc1_v104/CMakeLists.txt create mode 100644 extern/rg_etc1_v104/rg_etc1.cpp create mode 100644 extern/rg_etc1_v104/rg_etc1.h diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt index 90cb4e1..7e71391 100644 --- a/extern/CMakeLists.txt +++ b/extern/CMakeLists.txt @@ -4,3 +4,10 @@ IF(WIN32) ENDIF(WIN32) ADD_SUBDIRECTORY(poshlib) + +ADD_SUBDIRECTORY(EtcLib) +ADD_SUBDIRECTORY(rg_etc1_v104) +ADD_SUBDIRECTORY(etcpack) + +ADD_SUBDIRECTORY(butteraugli) + diff --git a/extern/EtcLib/CMakeLists.txt b/extern/EtcLib/CMakeLists.txt new file mode 100644 index 0000000..b584b88 --- /dev/null +++ b/extern/EtcLib/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright 2015 The Etc2Comp Authors. +# +# 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. + +project(EtcLib) +include_directories(./Etc) +include_directories(./EtcCodec) + +file(GLOB SOURCES + ${PROJECT_SOURCE_DIR}/Etc/*.h + ${PROJECT_SOURCE_DIR}/EtcCodec/*.h + ${PROJECT_SOURCE_DIR}/Etc/*.cpp + ${PROJECT_SOURCE_DIR}/EtcCodec/*.cpp) +ADD_LIBRARY(EtcLib ${SOURCES}) diff --git a/extern/EtcLib/Etc/Etc.cpp b/extern/EtcLib/Etc/Etc.cpp new file mode 100644 index 0000000..87e1d9b --- /dev/null +++ b/extern/EtcLib/Etc/Etc.cpp @@ -0,0 +1,58 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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 "EtcConfig.h" +#include "Etc.h" + +#include + +namespace Etc +{ + // ---------------------------------------------------------------------------------------------------- + // C-style inteface to the encoder + // + void Encode(float *a_pafSourceRGBA, + unsigned int a_uiSourceWidth, + unsigned int a_uiSourceHeight, + Image::Format a_format, + ErrorMetric a_eErrMetric, + float a_fEffort, + unsigned int a_uiJobs, + unsigned int a_uiMaxJobs, + unsigned char **a_ppaucEncodingBits, + unsigned int *a_puiEncodingBitsBytes, + unsigned int *a_puiExtendedWidth, + unsigned int *a_puiExtendedHeight, + int *a_piEncodingTime_ms, bool a_bVerboseOutput) + { + + Image image(a_pafSourceRGBA, a_uiSourceWidth, + a_uiSourceHeight, + a_eErrMetric); + image.m_bVerboseOutput = a_bVerboseOutput; + image.Encode(a_format, a_eErrMetric, a_fEffort, a_uiJobs, a_uiMaxJobs); + + *a_ppaucEncodingBits = image.GetEncodingBits(); + *a_puiEncodingBitsBytes = image.GetEncodingBitsBytes(); + *a_puiExtendedWidth = image.GetExtendedWidth(); + *a_puiExtendedHeight = image.GetExtendedHeight(); + *a_piEncodingTime_ms = image.GetEncodingTimeMs(); + } + + // ---------------------------------------------------------------------------------------------------- + // + +} diff --git a/extern/EtcLib/Etc/Etc.h b/extern/EtcLib/Etc/Etc.h new file mode 100644 index 0000000..df687e2 --- /dev/null +++ b/extern/EtcLib/Etc/Etc.h @@ -0,0 +1,47 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +#pragma once + +#include "EtcConfig.h" +#include "EtcImage.h" +#include "EtcColor.h" +#include "EtcErrorMetric.h" + +#define ETCCOMP_MIN_EFFORT_LEVEL (0.0f) +#define ETCCOMP_DEFAULT_EFFORT_LEVEL (40.0f) +#define ETCCOMP_MAX_EFFORT_LEVEL (100.0f) + +namespace Etc +{ + class Block4x4EncodingBits; + + // C-style inteface to the encoder + void Encode(float *a_pafSourceRGBA, + unsigned int a_uiSourceWidth, + unsigned int a_uiSourceHeight, + Image::Format a_format, + ErrorMetric a_eErrMetric, + float a_fEffort, + unsigned int a_uiJobs, + unsigned int a_uimaxJobs, + unsigned char **a_ppaucEncodingBits, + unsigned int *a_puiEncodingBitsBytes, + unsigned int *a_puiExtendedWidth, + unsigned int *a_puiExtendedHeight, + int *a_piEncodingTime_ms, bool a_bVerboseOutput = false); + +} diff --git a/extern/EtcLib/Etc/EtcColor.h b/extern/EtcLib/Etc/EtcColor.h new file mode 100644 index 0000000..0bd86e8 --- /dev/null +++ b/extern/EtcLib/Etc/EtcColor.h @@ -0,0 +1,64 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +#pragma once + +#include + +namespace Etc +{ + + inline float LogToLinear(float a_fLog) + { + static const float ALPHA = 0.055f; + static const float ONE_PLUS_ALPHA = 1.0f + ALPHA; + + if (a_fLog <= 0.04045f) + { + return a_fLog / 12.92f; + } + else + { + return powf((a_fLog + ALPHA) / ONE_PLUS_ALPHA, 2.4f); + } + } + + inline float LinearToLog(float &a_fLinear) + { + static const float ALPHA = 0.055f; + static const float ONE_PLUS_ALPHA = 1.0f + ALPHA; + + if (a_fLinear <= 0.0031308f) + { + return 12.92f * a_fLinear; + } + else + { + return ONE_PLUS_ALPHA * powf(a_fLinear, (1.0f/2.4f)) - ALPHA; + } + } + + class ColorR8G8B8A8 + { + public: + + unsigned char ucR; + unsigned char ucG; + unsigned char ucB; + unsigned char ucA; + + }; +} diff --git a/extern/EtcLib/Etc/EtcColorFloatRGBA.h b/extern/EtcLib/Etc/EtcColorFloatRGBA.h new file mode 100644 index 0000000..5afd6ef --- /dev/null +++ b/extern/EtcLib/Etc/EtcColorFloatRGBA.h @@ -0,0 +1,321 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +#pragma once + +#include "EtcConfig.h" +#include "EtcColor.h" + +#include + +namespace Etc +{ + + class ColorFloatRGBA + { + public: + + ColorFloatRGBA(void) + { + fR = fG = fB = fA = 0.0f; + } + + ColorFloatRGBA(float a_fR, float a_fG, float a_fB, float a_fA) + { + fR = a_fR; + fG = a_fG; + fB = a_fB; + fA = a_fA; + } + + inline ColorFloatRGBA operator+(ColorFloatRGBA& a_rfrgba) + { + ColorFloatRGBA frgba; + frgba.fR = fR + a_rfrgba.fR; + frgba.fG = fG + a_rfrgba.fG; + frgba.fB = fB + a_rfrgba.fB; + frgba.fA = fA + a_rfrgba.fA; + return frgba; + } + + inline ColorFloatRGBA operator+(float a_f) + { + ColorFloatRGBA frgba; + frgba.fR = fR + a_f; + frgba.fG = fG + a_f; + frgba.fB = fB + a_f; + frgba.fA = fA; + return frgba; + } + + inline ColorFloatRGBA operator-(float a_f) + { + ColorFloatRGBA frgba; + frgba.fR = fR - a_f; + frgba.fG = fG - a_f; + frgba.fB = fB - a_f; + frgba.fA = fA; + return frgba; + } + + inline ColorFloatRGBA operator-(ColorFloatRGBA& a_rfrgba) + { + ColorFloatRGBA frgba; + frgba.fR = fR - a_rfrgba.fR; + frgba.fG = fG - a_rfrgba.fG; + frgba.fB = fB - a_rfrgba.fB; + frgba.fA = fA - a_rfrgba.fA; + return frgba; + } + + inline ColorFloatRGBA operator*(float a_f) + { + ColorFloatRGBA frgba; + frgba.fR = fR * a_f; + frgba.fG = fG * a_f; + frgba.fB = fB * a_f; + frgba.fA = fA; + + return frgba; + } + + inline ColorFloatRGBA ScaleRGB(float a_f) + { + ColorFloatRGBA frgba; + frgba.fR = a_f * fR; + frgba.fG = a_f * fG; + frgba.fB = a_f * fB; + frgba.fA = fA; + + return frgba; + } + + inline ColorFloatRGBA RoundRGB(void) + { + ColorFloatRGBA frgba; + frgba.fR = roundf(fR); + frgba.fG = roundf(fG); + frgba.fB = roundf(fB); + + return frgba; + } + + inline ColorFloatRGBA ToLinear() + { + ColorFloatRGBA frgbaLinear; + frgbaLinear.fR = LogToLinear(fR); + frgbaLinear.fG = LogToLinear(fG); + frgbaLinear.fB = LogToLinear(fB); + frgbaLinear.fA = fA; + + return frgbaLinear; + } + + inline ColorFloatRGBA ToLog(void) + { + ColorFloatRGBA frgbaLog; + frgbaLog.fR = LinearToLog(fR); + frgbaLog.fG = LinearToLog(fG); + frgbaLog.fB = LinearToLog(fB); + frgbaLog.fA = fA; + + return frgbaLog; + } + + inline static ColorFloatRGBA ConvertFromRGBA8(unsigned char a_ucR, + unsigned char a_ucG, unsigned char a_ucB, unsigned char a_ucA) + { + ColorFloatRGBA frgba; + + frgba.fR = (float)a_ucR / 255.0f; + frgba.fG = (float)a_ucG / 255.0f; + frgba.fB = (float)a_ucB / 255.0f; + frgba.fA = (float)a_ucA / 255.0f; + + return frgba; + } + + inline static ColorFloatRGBA ConvertFromRGB4(unsigned char a_ucR4, + unsigned char a_ucG4, + unsigned char a_ucB4) + { + ColorFloatRGBA frgba; + + unsigned char ucR8 = (unsigned char)((a_ucR4 << 4) + a_ucR4); + unsigned char ucG8 = (unsigned char)((a_ucG4 << 4) + a_ucG4); + unsigned char ucB8 = (unsigned char)((a_ucB4 << 4) + a_ucB4); + + frgba.fR = (float)ucR8 / 255.0f; + frgba.fG = (float)ucG8 / 255.0f; + frgba.fB = (float)ucB8 / 255.0f; + frgba.fA = 1.0f; + + return frgba; + } + + inline static ColorFloatRGBA ConvertFromRGB5(unsigned char a_ucR5, + unsigned char a_ucG5, + unsigned char a_ucB5) + { + ColorFloatRGBA frgba; + + unsigned char ucR8 = (unsigned char)((a_ucR5 << 3) + (a_ucR5 >> 2)); + unsigned char ucG8 = (unsigned char)((a_ucG5 << 3) + (a_ucG5 >> 2)); + unsigned char ucB8 = (unsigned char)((a_ucB5 << 3) + (a_ucB5 >> 2)); + + frgba.fR = (float)ucR8 / 255.0f; + frgba.fG = (float)ucG8 / 255.0f; + frgba.fB = (float)ucB8 / 255.0f; + frgba.fA = 1.0f; + + return frgba; + } + + inline static ColorFloatRGBA ConvertFromR6G7B6(unsigned char a_ucR6, + unsigned char a_ucG7, + unsigned char a_ucB6) + { + ColorFloatRGBA frgba; + + unsigned char ucR8 = (unsigned char)((a_ucR6 << 2) + (a_ucR6 >> 4)); + unsigned char ucG8 = (unsigned char)((a_ucG7 << 1) + (a_ucG7 >> 6)); + unsigned char ucB8 = (unsigned char)((a_ucB6 << 2) + (a_ucB6 >> 4)); + + frgba.fR = (float)ucR8 / 255.0f; + frgba.fG = (float)ucG8 / 255.0f; + frgba.fB = (float)ucB8 / 255.0f; + frgba.fA = 1.0f; + + return frgba; + } + + // quantize to 4 bits, expand to 8 bits + inline ColorFloatRGBA QuantizeR4G4B4(void) const + { + ColorFloatRGBA frgba = *this; + + // quantize to 4 bits + frgba = frgba.ClampRGB().ScaleRGB(15.0f).RoundRGB(); + unsigned int uiR4 = (unsigned int)frgba.fR; + unsigned int uiG4 = (unsigned int)frgba.fG; + unsigned int uiB4 = (unsigned int)frgba.fB; + + // expand to 8 bits + frgba.fR = (float) ((uiR4 << 4) + uiR4); + frgba.fG = (float) ((uiG4 << 4) + uiG4); + frgba.fB = (float) ((uiB4 << 4) + uiB4); + + frgba = frgba.ScaleRGB(1.0f/255.0f); + + return frgba; + } + + // quantize to 5 bits, expand to 8 bits + inline ColorFloatRGBA QuantizeR5G5B5(void) const + { + ColorFloatRGBA frgba = *this; + + // quantize to 5 bits + frgba = frgba.ClampRGB().ScaleRGB(31.0f).RoundRGB(); + unsigned int uiR5 = (unsigned int)frgba.fR; + unsigned int uiG5 = (unsigned int)frgba.fG; + unsigned int uiB5 = (unsigned int)frgba.fB; + + // expand to 8 bits + frgba.fR = (float)((uiR5 << 3) + (uiR5 >> 2)); + frgba.fG = (float)((uiG5 << 3) + (uiG5 >> 2)); + frgba.fB = (float)((uiB5 << 3) + (uiB5 >> 2)); + + frgba = frgba.ScaleRGB(1.0f / 255.0f); + + return frgba; + } + + // quantize to 6/7/6 bits, expand to 8 bits + inline ColorFloatRGBA QuantizeR6G7B6(void) const + { + ColorFloatRGBA frgba = *this; + + // quantize to 6/7/6 bits + ColorFloatRGBA frgba6 = frgba.ClampRGB().ScaleRGB(63.0f).RoundRGB(); + ColorFloatRGBA frgba7 = frgba.ClampRGB().ScaleRGB(127.0f).RoundRGB(); + unsigned int uiR6 = (unsigned int)frgba6.fR; + unsigned int uiG7 = (unsigned int)frgba7.fG; + unsigned int uiB6 = (unsigned int)frgba6.fB; + + // expand to 8 bits + frgba.fR = (float)((uiR6 << 2) + (uiR6 >> 4)); + frgba.fG = (float)((uiG7 << 1) + (uiG7 >> 6)); + frgba.fB = (float)((uiB6 << 2) + (uiB6 >> 4)); + + frgba = frgba.ScaleRGB(1.0f / 255.0f); + + return frgba; + } + + inline ColorFloatRGBA ClampRGB(void) + { + ColorFloatRGBA frgba = *this; + if (frgba.fR < 0.0f) { frgba.fR = 0.0f; } + if (frgba.fR > 1.0f) { frgba.fR = 1.0f; } + if (frgba.fG < 0.0f) { frgba.fG = 0.0f; } + if (frgba.fG > 1.0f) { frgba.fG = 1.0f; } + if (frgba.fB < 0.0f) { frgba.fB = 0.0f; } + if (frgba.fB > 1.0f) { frgba.fB = 1.0f; } + + return frgba; + } + + inline ColorFloatRGBA ClampRGBA(void) + { + ColorFloatRGBA frgba = *this; + if (frgba.fR < 0.0f) { frgba.fR = 0.0f; } + if (frgba.fR > 1.0f) { frgba.fR = 1.0f; } + if (frgba.fG < 0.0f) { frgba.fG = 0.0f; } + if (frgba.fG > 1.0f) { frgba.fG = 1.0f; } + if (frgba.fB < 0.0f) { frgba.fB = 0.0f; } + if (frgba.fB > 1.0f) { frgba.fB = 1.0f; } + if (frgba.fA < 0.0f) { frgba.fA = 0.0f; } + if (frgba.fA > 1.0f) { frgba.fA = 1.0f; } + + return frgba; + } + + inline int IntRed(float a_fScale) + { + return (int)roundf(fR * a_fScale); + } + + inline int IntGreen(float a_fScale) + { + return (int)roundf(fG * a_fScale); + } + + inline int IntBlue(float a_fScale) + { + return (int)roundf(fB * a_fScale); + } + + inline int IntAlpha(float a_fScale) + { + return (int)roundf(fA * a_fScale); + } + + float fR, fG, fB, fA; + }; + +} + diff --git a/extern/EtcLib/Etc/EtcConfig.h b/extern/EtcLib/Etc/EtcConfig.h new file mode 100644 index 0000000..6ce1b8c --- /dev/null +++ b/extern/EtcLib/Etc/EtcConfig.h @@ -0,0 +1,67 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +#pragma once + +#ifdef _WIN32 +#define ETC_WINDOWS (1) +#else +#define ETC_WINDOWS (0) +#endif + +#if __APPLE__ +#define ETC_OSX (1) +#else +#define ETC_OSX (0) +#endif + +#if __unix__ +#define ETC_UNIX (1) +#else +#define ETC_UNIX (0) +#endif + + +// short names for common types +#include +typedef int8_t i8; +typedef int16_t i16; +typedef int32_t i32; +typedef int64_t i64; + +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +typedef float f32; +typedef double f64; + +// Keep asserts enabled in release builds during development +#undef NDEBUG + +// 0=disable. stb_image can be used if you need to compress +//other image formats like jpg +#define USE_STB_IMAGE_LOAD 0 + +#if ETC_WINDOWS +#include +#define _CRT_SECURE_NO_WARNINGS (1) +#include +#endif + +#include + diff --git a/extern/EtcLib/Etc/EtcImage.cpp b/extern/EtcLib/Etc/EtcImage.cpp new file mode 100644 index 0000000..7ca3519 --- /dev/null +++ b/extern/EtcLib/Etc/EtcImage.cpp @@ -0,0 +1,685 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +/* +EtcImage.cpp + +Image is an array of 4x4 blocks that represent the encoding of the source image + +*/ + +#include "EtcConfig.h" + +#include + +#include "EtcImage.h" + +#include "Etc.h" +#include "EtcBlock4x4.h" +#include "EtcBlock4x4EncodingBits.h" +#include "EtcSortedBlockList.h" + +#if ETC_WINDOWS +#include +#endif +#include +#include +#include +#include +#include +#include + +// fix conflict with Block4x4::AlphaMix +#ifdef OPAQUE +#undef OPAQUE +#endif +#ifdef TRANSPARENT +#undef TRANSPARENT +#endif + +namespace Etc +{ + + // ---------------------------------------------------------------------------------------------------- + // + Image::Image(void) + { + m_encodingStatus = EncodingStatus::SUCCESS; + m_warningsToCapture = EncodingStatus::SUCCESS; + m_pafrgbaSource = nullptr; + + m_pablock = nullptr; + + m_encodingbitsformat = Block4x4EncodingBits::Format::UNKNOWN; + m_uiEncodingBitsBytes = 0; + m_paucEncodingBits = nullptr; + + m_format = Format::UNKNOWN; + m_iNumOpaquePixels = 0; + m_iNumTranslucentPixels = 0; + m_iNumTransparentPixels = 0; + } + + // ---------------------------------------------------------------------------------------------------- + // constructor using source image + // used to set state before Encode() is called + // + Image::Image(float *a_pafSourceRGBA, unsigned int a_uiSourceWidth, + unsigned int a_uiSourceHeight, + ErrorMetric a_errormetric) + { + m_encodingStatus = EncodingStatus::SUCCESS; + m_warningsToCapture = EncodingStatus::SUCCESS; + m_pafrgbaSource = (ColorFloatRGBA *) a_pafSourceRGBA; + m_uiSourceWidth = a_uiSourceWidth; + m_uiSourceHeight = a_uiSourceHeight; + + m_uiExtendedWidth = CalcExtendedDimension((unsigned short)m_uiSourceWidth); + m_uiExtendedHeight = CalcExtendedDimension((unsigned short)m_uiSourceHeight); + + m_uiBlockColumns = m_uiExtendedWidth >> 2; + m_uiBlockRows = m_uiExtendedHeight >> 2; + + m_pablock = new Block4x4[GetNumberOfBlocks()]; + assert(m_pablock); + + m_format = Format::UNKNOWN; + + m_encodingbitsformat = Block4x4EncodingBits::Format::UNKNOWN; + m_uiEncodingBitsBytes = 0; + m_paucEncodingBits = nullptr; + + m_errormetric = a_errormetric; + m_fEffort = 0.0f; + + m_iEncodeTime_ms = -1; + + m_iNumOpaquePixels = 0; + m_iNumTranslucentPixels = 0; + m_iNumTransparentPixels = 0; + m_bVerboseOutput = false; + + } + + // ---------------------------------------------------------------------------------------------------- + // constructor using encoding bits + // recreates encoding state using a previously encoded image + // + Image::Image(Format a_format, + unsigned int a_uiSourceWidth, unsigned int a_uiSourceHeight, + unsigned char *a_paucEncidingBits, unsigned int a_uiEncodingBitsBytes, + Image *a_pimageSource, ErrorMetric a_errormetric) + { + m_encodingStatus = EncodingStatus::SUCCESS; + m_pafrgbaSource = nullptr; + m_uiSourceWidth = a_uiSourceWidth; + m_uiSourceHeight = a_uiSourceHeight; + + m_uiExtendedWidth = CalcExtendedDimension((unsigned short)m_uiSourceWidth); + m_uiExtendedHeight = CalcExtendedDimension((unsigned short)m_uiSourceHeight); + + m_uiBlockColumns = m_uiExtendedWidth >> 2; + m_uiBlockRows = m_uiExtendedHeight >> 2; + + unsigned int uiBlocks = GetNumberOfBlocks(); + + m_pablock = new Block4x4[uiBlocks]; + assert(m_pablock); + + m_format = a_format; + + m_iNumOpaquePixels = 0; + m_iNumTranslucentPixels = 0; + m_iNumTransparentPixels = 0; + + m_encodingbitsformat = DetermineEncodingBitsFormat(m_format); + if (m_encodingbitsformat == Block4x4EncodingBits::Format::UNKNOWN) + { + AddToEncodingStatus(ERROR_UNKNOWN_FORMAT); + return; + } + m_uiEncodingBitsBytes = a_uiEncodingBitsBytes; + m_paucEncodingBits = a_paucEncidingBits; + + m_errormetric = a_errormetric; + m_fEffort = 0.0f; + m_bVerboseOutput = false; + m_iEncodeTime_ms = -1; + + unsigned char *paucEncodingBits = m_paucEncodingBits; + unsigned int uiEncodingBitsBytesPerBlock = Block4x4EncodingBits::GetBytesPerBlock(m_encodingbitsformat); + + unsigned int uiH = 0; + unsigned int uiV = 0; + for (unsigned int uiBlock = 0; uiBlock < uiBlocks; uiBlock++) + { + m_pablock[uiBlock].InitFromEtcEncodingBits(a_format, uiH, uiV, paucEncodingBits, + a_pimageSource, a_errormetric); + paucEncodingBits += uiEncodingBitsBytesPerBlock; + uiH += 4; + if (uiH >= m_uiSourceWidth) + { + uiH = 0; + uiV += 4; + } + } + + } + + // ---------------------------------------------------------------------------------------------------- + // + Image::~Image(void) + { + if (m_pablock != nullptr) + { + delete[] m_pablock; + m_pablock = nullptr; + } + + /*if (m_paucEncodingBits != nullptr) + { + delete[] m_paucEncodingBits; + m_paucEncodingBits = nullptr; + }*/ + } + + // ---------------------------------------------------------------------------------------------------- + // encode an image + // create a set of encoding bits that conforms to a_format + // find best fit using a_errormetric + // explore a range of possible encodings based on a_fEffort (range = [0:100]) + // speed up process using a_uiJobs as the number of process threads (a_uiJobs must not excede a_uiMaxJobs) + // + Image::EncodingStatus Image::Encode(Format a_format, ErrorMetric a_errormetric, float a_fEffort, unsigned int a_uiJobs, unsigned int a_uiMaxJobs) + { + + auto start = std::chrono::steady_clock::now(); + + m_encodingStatus = EncodingStatus::SUCCESS; + + m_format = a_format; + m_errormetric = a_errormetric; + m_fEffort = a_fEffort; + + if (m_errormetric < 0 || m_errormetric > ERROR_METRICS) + { + AddToEncodingStatus(ERROR_UNKNOWN_ERROR_METRIC); + return m_encodingStatus; + } + + if (m_fEffort < ETCCOMP_MIN_EFFORT_LEVEL) + { + AddToEncodingStatus(WARNING_EFFORT_OUT_OF_RANGE); + m_fEffort = ETCCOMP_MIN_EFFORT_LEVEL; + } + else if (m_fEffort > ETCCOMP_MAX_EFFORT_LEVEL) + { + AddToEncodingStatus(WARNING_EFFORT_OUT_OF_RANGE); + m_fEffort = ETCCOMP_MAX_EFFORT_LEVEL; + } + if (a_uiJobs < 1) + { + a_uiJobs = 1; + AddToEncodingStatus(WARNING_JOBS_OUT_OF_RANGE); + } + else if (a_uiJobs > a_uiMaxJobs) + { + a_uiJobs = a_uiMaxJobs; + AddToEncodingStatus(WARNING_JOBS_OUT_OF_RANGE); + } + + m_encodingbitsformat = DetermineEncodingBitsFormat(m_format); + + if (m_encodingbitsformat == Block4x4EncodingBits::Format::UNKNOWN) + { + AddToEncodingStatus(ERROR_UNKNOWN_FORMAT); + return m_encodingStatus; + } + + assert(m_paucEncodingBits == nullptr); + m_uiEncodingBitsBytes = GetNumberOfBlocks() * Block4x4EncodingBits::GetBytesPerBlock(m_encodingbitsformat); + m_paucEncodingBits = new unsigned char[m_uiEncodingBitsBytes]; + + InitBlocksAndBlockSorter(); + + + std::future *handle = new std::future[a_uiMaxJobs]; + + unsigned int uiNumThreadsNeeded = 0; + unsigned int uiUnfinishedBlocks = GetNumberOfBlocks(); + + uiNumThreadsNeeded = (uiUnfinishedBlocks < a_uiJobs) ? uiUnfinishedBlocks : a_uiJobs; + + for (int i = 0; i < (int)uiNumThreadsNeeded - 1; i++) + { + handle[i] = async(std::launch::async, &Image::RunFirstPass, this, i, uiNumThreadsNeeded); + } + + RunFirstPass(uiNumThreadsNeeded - 1, uiNumThreadsNeeded); + + for (int i = 0; i < (int)uiNumThreadsNeeded - 1; i++) + { + handle[i].get(); + } + + // perform effort-based encoding + if (m_fEffort > ETCCOMP_MIN_EFFORT_LEVEL) + { + unsigned int uiFinishedBlocks = 0; + unsigned int uiTotalEffortBlocks = static_cast(roundf(0.01f * m_fEffort * GetNumberOfBlocks())); + + if (m_bVerboseOutput) + { + printf("effortblocks = %d\n", uiTotalEffortBlocks); + } + unsigned int uiPass = 0; + while (1) + { + if (m_bVerboseOutput) + { + uiPass++; + printf("pass %u\n", uiPass); + } + m_psortedblocklist->Sort(); + uiUnfinishedBlocks = m_psortedblocklist->GetNumberOfSortedBlocks(); + uiFinishedBlocks = GetNumberOfBlocks() - uiUnfinishedBlocks; + if (m_bVerboseOutput) + { + printf(" %u unfinished blocks\n", uiUnfinishedBlocks); + // m_psortedblocklist->Print(); + } + + + + //stop enocding when we did enough to satify the effort percentage + if (uiFinishedBlocks >= uiTotalEffortBlocks) + { + if (m_bVerboseOutput) + { + printf("Finished %d Blocks out of %d\n", uiFinishedBlocks, uiTotalEffortBlocks); + } + break; + } + + unsigned int uiIteratedBlocks = 0; + unsigned int blocksToIterateThisPass = (uiTotalEffortBlocks - uiFinishedBlocks); + uiNumThreadsNeeded = (uiUnfinishedBlocks < a_uiJobs) ? uiUnfinishedBlocks : a_uiJobs; + + if (uiNumThreadsNeeded <= 1) + { + //since we already how many blocks each thread will process + //cap the thread limit to do the proper amount of work, and not more + uiIteratedBlocks = IterateThroughWorstBlocks(blocksToIterateThisPass, 0, 1); + } + else + { + //we have a lot of work to do, so lets multi thread it + std::future *handleToBlockEncoders = new std::future[uiNumThreadsNeeded-1]; + + for (int i = 0; i < (int)uiNumThreadsNeeded - 1; i++) + { + handleToBlockEncoders[i] = async(std::launch::async, &Image::IterateThroughWorstBlocks, this, blocksToIterateThisPass, i, uiNumThreadsNeeded); + } + uiIteratedBlocks = IterateThroughWorstBlocks(blocksToIterateThisPass, uiNumThreadsNeeded - 1, uiNumThreadsNeeded); + + for (int i = 0; i < (int)uiNumThreadsNeeded - 1; i++) + { + uiIteratedBlocks += handleToBlockEncoders[i].get(); + } + + delete[] handleToBlockEncoders; + } + + if (m_bVerboseOutput) + { + printf(" %u iterated blocks\n", uiIteratedBlocks); + } + } + } + + // generate Etc2-compatible bit-format 4x4 blocks + for (int i = 0; i < (int)a_uiJobs - 1; i++) + { + handle[i] = async(std::launch::async, &Image::SetEncodingBits, this, i, a_uiJobs); + } + SetEncodingBits(a_uiJobs - 1, a_uiJobs); + + for (int i = 0; i < (int)a_uiJobs - 1; i++) + { + handle[i].get(); + } + + auto end = std::chrono::steady_clock::now(); + std::chrono::milliseconds elapsed = std::chrono::duration_cast(end - start); + m_iEncodeTime_ms = (int)elapsed.count(); + + delete[] handle; + delete m_psortedblocklist; + return m_encodingStatus; + } + + // ---------------------------------------------------------------------------------------------------- + // iterate the encoding thru the blocks with the worst error + // stop when a_uiMaxBlocks blocks have been iterated + // split the blocks between the process threads using a_uiMultithreadingOffset and a_uiMultithreadingStride + // + unsigned int Image::IterateThroughWorstBlocks(unsigned int a_uiMaxBlocks, + unsigned int a_uiMultithreadingOffset, + unsigned int a_uiMultithreadingStride) + { + assert(a_uiMultithreadingStride > 0); + unsigned int uiIteratedBlocks = a_uiMultithreadingOffset; + + SortedBlockList::Link *plink = m_psortedblocklist->GetLinkToFirstBlock(); + for (plink = plink->Advance(a_uiMultithreadingOffset); + plink != nullptr; + plink = plink->Advance(a_uiMultithreadingStride) ) + { + if (uiIteratedBlocks >= a_uiMaxBlocks) + { + break; + } + + plink->GetBlock()->PerformEncodingIteration(m_fEffort); + + uiIteratedBlocks += a_uiMultithreadingStride; + } + + return uiIteratedBlocks; + } + + // ---------------------------------------------------------------------------------------------------- + // determine which warnings to check for during Encode() based on encoding format + // + void Image::FindEncodingWarningTypesForCurFormat() + { + TrackEncodingWarning(WARNING_ALL_TRANSPARENT_PIXELS); + TrackEncodingWarning(WARNING_SOME_RGBA_NOT_0_TO_1); + switch (m_format) + { + case Image::Format::ETC1: + case Image::Format::RGB8: + case Image::Format::SRGB8: + TrackEncodingWarning(WARNING_SOME_NON_OPAQUE_PIXELS); + TrackEncodingWarning(WARNING_SOME_TRANSLUCENT_PIXELS); + break; + + case Image::Format::RGB8A1: + case Image::Format::SRGB8A1: + TrackEncodingWarning(WARNING_SOME_TRANSLUCENT_PIXELS); + TrackEncodingWarning(WARNING_ALL_OPAQUE_PIXELS); + break; + case Image::Format::RGBA8: + case Image::Format::SRGBA8: + TrackEncodingWarning(WARNING_ALL_OPAQUE_PIXELS); + break; + + case Image::Format::R11: + case Image::Format::SIGNED_R11: + TrackEncodingWarning(WARNING_SOME_NON_OPAQUE_PIXELS); + TrackEncodingWarning(WARNING_SOME_TRANSLUCENT_PIXELS); + TrackEncodingWarning(WARNING_SOME_GREEN_VALUES_ARE_NOT_ZERO); + TrackEncodingWarning(WARNING_SOME_BLUE_VALUES_ARE_NOT_ZERO); + break; + + case Image::Format::RG11: + case Image::Format::SIGNED_RG11: + TrackEncodingWarning(WARNING_SOME_NON_OPAQUE_PIXELS); + TrackEncodingWarning(WARNING_SOME_TRANSLUCENT_PIXELS); + TrackEncodingWarning(WARNING_SOME_BLUE_VALUES_ARE_NOT_ZERO); + break; + case Image::Format::FORMATS: + case Image::Format::UNKNOWN: + default: + assert(0); + break; + } + } + + // ---------------------------------------------------------------------------------------------------- + // examine source pixels to check for warnings + // + void Image::FindAndSetEncodingWarnings() + { + int numPixels = (m_uiBlockRows * 4) * (m_uiBlockColumns * 4); + if (m_iNumOpaquePixels == numPixels) + { + AddToEncodingStatusIfSignfigant(Image::EncodingStatus::WARNING_ALL_OPAQUE_PIXELS); + } + if (m_iNumOpaquePixels < numPixels) + { + AddToEncodingStatusIfSignfigant(Image::EncodingStatus::WARNING_SOME_NON_OPAQUE_PIXELS); + } + if (m_iNumTranslucentPixels > 0) + { + AddToEncodingStatusIfSignfigant(Image::EncodingStatus::WARNING_SOME_TRANSLUCENT_PIXELS); + } + if (m_iNumTransparentPixels == numPixels) + { + AddToEncodingStatusIfSignfigant(Image::EncodingStatus::WARNING_ALL_TRANSPARENT_PIXELS); + } + if (m_numColorValues.fB > 0.0f) + { + AddToEncodingStatusIfSignfigant(Image::EncodingStatus::WARNING_SOME_BLUE_VALUES_ARE_NOT_ZERO); + } + if (m_numColorValues.fG > 0.0f) + { + AddToEncodingStatusIfSignfigant(Image::EncodingStatus::WARNING_SOME_GREEN_VALUES_ARE_NOT_ZERO); + } + + if (m_numOutOfRangeValues.fR > 0.0f || m_numOutOfRangeValues.fG > 0.0f) + { + AddToEncodingStatusIfSignfigant(Image::EncodingStatus::WARNING_SOME_RGBA_NOT_0_TO_1); + } + if (m_numOutOfRangeValues.fB > 0.0f || m_numOutOfRangeValues.fA > 0.0f) + { + AddToEncodingStatusIfSignfigant(Image::EncodingStatus::WARNING_SOME_RGBA_NOT_0_TO_1); + } + } + + // ---------------------------------------------------------------------------------------------------- + // return a string name for a given image format + // + const char * Image::EncodingFormatToString(Image::Format a_format) + { + switch (a_format) + { + case Image::Format::ETC1: + return "ETC1"; + case Image::Format::RGB8: + return "RGB8"; + case Image::Format::SRGB8: + return "SRGB8"; + + case Image::Format::RGB8A1: + return "RGB8A1"; + case Image::Format::SRGB8A1: + return "SRGB8A1"; + case Image::Format::RGBA8: + return "RGBA8"; + case Image::Format::SRGBA8: + return "SRGBA8"; + + case Image::Format::R11: + return "R11"; + case Image::Format::SIGNED_R11: + return "SIGNED_R11"; + + case Image::Format::RG11: + return "RG11"; + case Image::Format::SIGNED_RG11: + return "SIGNED_RG11"; + case Image::Format::FORMATS: + case Image::Format::UNKNOWN: + default: + return "UNKNOWN"; + } + } + + // ---------------------------------------------------------------------------------------------------- + // return a string name for the image's format + // + const char * Image::EncodingFormatToString(void) + { + return EncodingFormatToString(m_format); + } + + // ---------------------------------------------------------------------------------------------------- + // init image blocks prior to encoding + // init block sorter for subsequent sortings + // check for encoding warnings + // + void Image::InitBlocksAndBlockSorter(void) + { + + FindEncodingWarningTypesForCurFormat(); + + // init each block + Block4x4 *pblock = m_pablock; + unsigned char *paucEncodingBits = m_paucEncodingBits; + for (unsigned int uiBlockRow = 0; uiBlockRow < m_uiBlockRows; uiBlockRow++) + { + unsigned int uiBlockV = uiBlockRow * 4; + + for (unsigned int uiBlockColumn = 0; uiBlockColumn < m_uiBlockColumns; uiBlockColumn++) + { + unsigned int uiBlockH = uiBlockColumn * 4; + + pblock->InitFromSource(this, uiBlockH, uiBlockV, paucEncodingBits, m_errormetric); + + paucEncodingBits += Block4x4EncodingBits::GetBytesPerBlock(m_encodingbitsformat); + + pblock++; + } + } + + FindAndSetEncodingWarnings(); + + // init block sorter + { + m_psortedblocklist = new SortedBlockList(GetNumberOfBlocks(), 100); + + for (unsigned int uiBlock = 0; uiBlock < GetNumberOfBlocks(); uiBlock++) + { + pblock = &m_pablock[uiBlock]; + m_psortedblocklist->AddBlock(pblock); + } + } + + } + + // ---------------------------------------------------------------------------------------------------- + // run the first pass of the encoder + // the encoder generally finds a reasonable, fast encoding + // this is run on all blocks regardless of effort to ensure that all blocks have a valid encoding + // + void Image::RunFirstPass(unsigned int a_uiMultithreadingOffset, unsigned int a_uiMultithreadingStride) + { + assert(a_uiMultithreadingStride > 0); + + for (unsigned int uiBlock = a_uiMultithreadingOffset; + uiBlock < GetNumberOfBlocks(); + uiBlock += a_uiMultithreadingStride) + { + Block4x4 *pblock = &m_pablock[uiBlock]; + pblock->PerformEncodingIteration(m_fEffort); + } + } + + // ---------------------------------------------------------------------------------------------------- + // set the encoding bits (for the output file) based on the best encoding for each block + // + void Image::SetEncodingBits(unsigned int a_uiMultithreadingOffset, + unsigned int a_uiMultithreadingStride) + { + assert(a_uiMultithreadingStride > 0); + + for (unsigned int uiBlock = a_uiMultithreadingOffset; + uiBlock < GetNumberOfBlocks(); + uiBlock += a_uiMultithreadingStride) + { + Block4x4 *pblock = &m_pablock[uiBlock]; + pblock->SetEncodingBitsFromEncoding(); + } + + } + + // ---------------------------------------------------------------------------------------------------- + // return the image error + // image error is the sum of all block errors + // + float Image::GetError(void) + { + float fError = 0.0f; + + for (unsigned int uiBlock = 0; uiBlock < GetNumberOfBlocks(); uiBlock++) + { + Block4x4 *pblock = &m_pablock[uiBlock]; + fError += pblock->GetError(); + } + + return fError; + } + + // ---------------------------------------------------------------------------------------------------- + // determine the encoding bits format based on the encoding format + // the encoding bits format is a family of bit encodings that are shared across various encoding formats + // + Block4x4EncodingBits::Format Image::DetermineEncodingBitsFormat(Format a_format) + { + Block4x4EncodingBits::Format encodingbitsformat; + + // determine encoding bits format from image format + switch (a_format) + { + case Format::ETC1: + case Format::RGB8: + case Format::SRGB8: + encodingbitsformat = Block4x4EncodingBits::Format::RGB8; + break; + + case Format::RGBA8: + case Format::SRGBA8: + encodingbitsformat = Block4x4EncodingBits::Format::RGBA8; + break; + + case Format::R11: + case Format::SIGNED_R11: + encodingbitsformat = Block4x4EncodingBits::Format::R11; + break; + + case Format::RG11: + case Format::SIGNED_RG11: + encodingbitsformat = Block4x4EncodingBits::Format::RG11; + break; + + case Format::RGB8A1: + case Format::SRGB8A1: + encodingbitsformat = Block4x4EncodingBits::Format::RGB8A1; + break; + + default: + encodingbitsformat = Block4x4EncodingBits::Format::UNKNOWN; + break; + } + + return encodingbitsformat; + } + + // ---------------------------------------------------------------------------------------------------- + // + +} // namespace Etc diff --git a/extern/EtcLib/Etc/EtcImage.h b/extern/EtcLib/Etc/EtcImage.h new file mode 100644 index 0000000..f937fce --- /dev/null +++ b/extern/EtcLib/Etc/EtcImage.h @@ -0,0 +1,249 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +#pragma once + +//#include "Etc.h" +#include "EtcColorFloatRGBA.h" +#include "EtcBlock4x4EncodingBits.h" +#include "EtcErrorMetric.h" + + +namespace Etc +{ + class Block4x4; + class EncoderSpec; + class SortedBlockList; + + class Image + { + public: + + //the differnt warning and errors that can come up during encoding + enum EncodingStatus + { + SUCCESS = 0, + // + WARNING_THRESHOLD = 1 << 0, + // + WARNING_EFFORT_OUT_OF_RANGE = 1 << 1, + WARNING_JOBS_OUT_OF_RANGE = 1 << 2, + WARNING_SOME_NON_OPAQUE_PIXELS = 1 << 3,//just for opaque formats, etc1, rgb8, r11, rg11 + WARNING_ALL_OPAQUE_PIXELS = 1 << 4, + WARNING_ALL_TRANSPARENT_PIXELS = 1 << 5, + WARNING_SOME_TRANSLUCENT_PIXELS = 1 << 6,//just for rgb8A1 + WARNING_SOME_RGBA_NOT_0_TO_1 = 1 << 7, + WARNING_SOME_BLUE_VALUES_ARE_NOT_ZERO = 1 << 8, + WARNING_SOME_GREEN_VALUES_ARE_NOT_ZERO = 1 << 9, + // + ERROR_THRESHOLD = 1 << 16, + // + ERROR_UNKNOWN_FORMAT = 1 << 17, + ERROR_UNKNOWN_ERROR_METRIC = 1 << 18, + ERROR_ZERO_WIDTH_OR_HEIGHT = 1 << 19, + // + }; + + enum class Format + { + UNKNOWN, + // + ETC1, + // + // ETC2 formats + RGB8, + SRGB8, + RGBA8, + SRGBA8, + R11, + SIGNED_R11, + RG11, + SIGNED_RG11, + RGB8A1, + SRGB8A1, + // + FORMATS, + // + DEFAULT = SRGB8 + }; + + // constructor using source image + Image(float *a_pafSourceRGBA, unsigned int a_uiSourceWidth, + unsigned int a_uiSourceHeight, + ErrorMetric a_errormetric); + + // constructor using encoding bits + Image(Format a_format, + unsigned int a_uiSourceWidth, unsigned int a_uiSourceHeight, + unsigned char *a_paucEncidingBits, unsigned int a_uiEncodingBitsBytes, + Image *a_pimageSource, + ErrorMetric a_errormetric); + + ~Image(void); + + EncodingStatus Encode(Format a_format, ErrorMetric a_errormetric, float a_fEffort, + unsigned int a_uiJobs, unsigned int a_uiMaxJobs); + + inline void AddToEncodingStatus(EncodingStatus a_encStatus) + { + m_encodingStatus = (EncodingStatus)((unsigned int)m_encodingStatus | (unsigned int)a_encStatus); + } + + inline unsigned int GetSourceWidth(void) + { + return m_uiSourceWidth; + } + + inline unsigned int GetSourceHeight(void) + { + return m_uiSourceHeight; + } + + inline unsigned int GetExtendedWidth(void) + { + return m_uiExtendedWidth; + } + + inline unsigned int GetExtendedHeight(void) + { + return m_uiExtendedHeight; + } + + inline unsigned int GetNumberOfBlocks() + { + return m_uiBlockColumns * m_uiBlockRows; + } + + inline Block4x4 * GetBlocks() + { + return m_pablock; + } + + inline unsigned char * GetEncodingBits(void) + { + return m_paucEncodingBits; + } + + inline unsigned int GetEncodingBitsBytes(void) + { + return m_uiEncodingBitsBytes; + } + + inline int GetEncodingTimeMs(void) + { + return m_iEncodeTime_ms; + } + + float GetError(void); + + inline ColorFloatRGBA * GetSourcePixel(unsigned int a_uiH, unsigned int a_uiV) + { + if (a_uiH >= m_uiSourceWidth || a_uiV >= m_uiSourceHeight) + { + return nullptr; + } + + return &m_pafrgbaSource[a_uiV*m_uiSourceWidth + a_uiH]; + } + + inline Format GetFormat(void) + { + return m_format; + } + + static Block4x4EncodingBits::Format DetermineEncodingBitsFormat(Format a_format); + + inline static unsigned short CalcExtendedDimension(unsigned short a_ushOriginalDimension) + { + return (unsigned short)((a_ushOriginalDimension + 3) & ~3); + } + + inline ErrorMetric GetErrorMetric(void) + { + return m_errormetric; + } + + static const char * EncodingFormatToString(Image::Format a_format); + const char * EncodingFormatToString(void); + //used to get basic information about the image data + int m_iNumOpaquePixels; + int m_iNumTranslucentPixels; + int m_iNumTransparentPixels; + + ColorFloatRGBA m_numColorValues; + ColorFloatRGBA m_numOutOfRangeValues; + + bool m_bVerboseOutput; + private: + //add a warning or error to check for while encoding + inline void TrackEncodingWarning(EncodingStatus a_encStatus) + { + m_warningsToCapture = (EncodingStatus)((unsigned int)m_warningsToCapture | (unsigned int)a_encStatus); + } + + //report the warning if it is something we care about for this encoding + inline void AddToEncodingStatusIfSignfigant(EncodingStatus a_encStatus) + { + if ((EncodingStatus)((unsigned int)m_warningsToCapture & (unsigned int)a_encStatus) == a_encStatus) + { + AddToEncodingStatus(a_encStatus); + } + } + + Image(void); + void FindEncodingWarningTypesForCurFormat(); + void FindAndSetEncodingWarnings(); + + void InitBlocksAndBlockSorter(void); + + void RunFirstPass(unsigned int a_uiMultithreadingOffset, + unsigned int a_uiMultithreadingStride); + + void SetEncodingBits(unsigned int a_uiMultithreadingOffset, + unsigned int a_uiMultithreadingStride); + + unsigned int IterateThroughWorstBlocks(unsigned int a_uiMaxBlocks, + unsigned int a_uiMultithreadingOffset, + unsigned int a_uiMultithreadingStride); + + // inputs + ColorFloatRGBA *m_pafrgbaSource; + unsigned int m_uiSourceWidth; + unsigned int m_uiSourceHeight; + unsigned int m_uiExtendedWidth; + unsigned int m_uiExtendedHeight; + unsigned int m_uiBlockColumns; + unsigned int m_uiBlockRows; + // intermediate data + Block4x4 *m_pablock; + // encoding + Format m_format; + Block4x4EncodingBits::Format m_encodingbitsformat; + unsigned int m_uiEncodingBitsBytes; // for entire image + unsigned char *m_paucEncodingBits; + ErrorMetric m_errormetric; + float m_fEffort; + // stats + int m_iEncodeTime_ms; + + SortedBlockList *m_psortedblocklist; + //this will hold any warning or errors that happen during encoding + EncodingStatus m_encodingStatus; + //these will be the warnings we are tracking + EncodingStatus m_warningsToCapture; + }; + +} // namespace Etc diff --git a/extern/EtcLib/Etc/EtcMath.cpp b/extern/EtcLib/Etc/EtcMath.cpp new file mode 100644 index 0000000..cd70a9a --- /dev/null +++ b/extern/EtcLib/Etc/EtcMath.cpp @@ -0,0 +1,64 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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 "EtcConfig.h" +#include "EtcMath.h" + +namespace Etc +{ + + // ---------------------------------------------------------------------------------------------------- + // calculate the line that best fits the set of XY points contained in a_afX[] and a_afY[] + // use a_fSlope and a_fOffset to define that line + // + bool Regression(float a_afX[], float a_afY[], unsigned int a_Points, + float *a_fSlope, float *a_fOffset) + { + float fPoints = (float)a_Points; + + float fSumX = 0.0f; + float fSumY = 0.0f; + float fSumXY = 0.0f; + float fSumX2 = 0.0f; + + for (unsigned int uiPoint = 0; uiPoint < a_Points; uiPoint++) + { + fSumX += a_afX[uiPoint]; + fSumY += a_afY[uiPoint]; + fSumXY += a_afX[uiPoint] * a_afY[uiPoint]; + fSumX2 += a_afX[uiPoint] * a_afX[uiPoint]; + } + + float fDivisor = fPoints*fSumX2 - fSumX*fSumX; + + // if vertical line + if (fDivisor == 0.0f) + { + *a_fSlope = 0.0f; + *a_fOffset = 0.0f; + return true; + } + + *a_fSlope = (fPoints*fSumXY - fSumX*fSumY) / fDivisor; + *a_fOffset = (fSumY - (*a_fSlope)*fSumX) / fPoints; + + return false; + } + + // ---------------------------------------------------------------------------------------------------- + // + +} // namespace Etc diff --git a/extern/EtcLib/Etc/EtcMath.h b/extern/EtcLib/Etc/EtcMath.h new file mode 100644 index 0000000..3d951fe --- /dev/null +++ b/extern/EtcLib/Etc/EtcMath.h @@ -0,0 +1,40 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +#pragma once + +#include + +namespace Etc +{ + + // ---------------------------------------------------------------------------------------------------- + // return true if vertical line + bool Regression(float a_afX[], float a_afY[], unsigned int a_Points, + float *a_fSlope, float *a_fOffset); + + inline float ConvertMSEToPSNR(float a_fMSE) + { + if (a_fMSE == 0.0f) + { + return INFINITY; + } + + return 10.0f * log10f(1.0f / a_fMSE); + } + + +} diff --git a/extern/EtcLib/EtcCodec/EtcBlock4x4.cpp b/extern/EtcLib/EtcCodec/EtcBlock4x4.cpp new file mode 100644 index 0000000..e810369 --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcBlock4x4.cpp @@ -0,0 +1,417 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +/* +EtcBlock4x4.cpp + +Implements the state associated with each 4x4 block of pixels in an image + +Source images that are not a multiple of 4x4 are extended to fill the Block4x4 using pixels with an +alpha of NAN + +*/ + +#include "EtcConfig.h" +#include "EtcBlock4x4.h" + +#include "EtcBlock4x4EncodingBits.h" +#include "EtcColor.h" +#include "EtcImage.h" +#include "EtcColorFloatRGBA.h" +#include "EtcBlock4x4Encoding_RGB8.h" +#include "EtcBlock4x4Encoding_RGBA8.h" +#include "EtcBlock4x4Encoding_RGB8A1.h" +#include "EtcBlock4x4Encoding_R11.h" +#include "EtcBlock4x4Encoding_RG11.h" + +#include +#include +#include + +namespace Etc +{ + // ETC pixels are scanned vertically. + // this mapping is for when someone wants to scan the ETC pixels horizontally + const unsigned int Block4x4::s_auiPixelOrderHScan[PIXELS] = { 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15 }; + + // ---------------------------------------------------------------------------------------------------- + // + Block4x4::Block4x4(void) + { + m_pimageSource = nullptr; + m_uiSourceH = 0; + m_uiSourceV = 0; + + m_sourcealphamix = SourceAlphaMix::UNKNOWN; + m_boolBorderPixels = false; + m_boolPunchThroughPixels = false; + + m_pencoding = nullptr; + + m_errormetric = ErrorMetric::NUMERIC; + + } + Block4x4::~Block4x4() + { + m_pimageSource = nullptr; + if (m_pencoding) + { + delete m_pencoding; + m_pencoding = nullptr; + } + } + // ---------------------------------------------------------------------------------------------------- + // initialization prior to encoding from a source image + // [a_uiSourceH,a_uiSourceV] is the location of the block in a_pimageSource + // a_paucEncodingBits is the place to store the final encoding + // a_errormetric is used for finding the best encoding + // + void Block4x4::InitFromSource(Image *a_pimageSource, + unsigned int a_uiSourceH, unsigned int a_uiSourceV, + unsigned char *a_paucEncodingBits, + ErrorMetric a_errormetric) + { + + Block4x4(); + + m_pimageSource = a_pimageSource; + m_uiSourceH = a_uiSourceH; + m_uiSourceV = a_uiSourceV; + m_errormetric = a_errormetric; + + SetSourcePixels(); + + // set block encoder function + switch (m_pimageSource->GetFormat()) + { + case Image::Format::ETC1: + m_pencoding = new Block4x4Encoding_ETC1; + break; + + case Image::Format::RGB8: + case Image::Format::SRGB8: + m_pencoding = new Block4x4Encoding_RGB8; + break; + + case Image::Format::RGBA8: + case Image::Format::SRGBA8: + switch (m_sourcealphamix) + { + case SourceAlphaMix::OPAQUE: + m_pencoding = new Block4x4Encoding_RGBA8_Opaque; + break; + + case SourceAlphaMix::TRANSPARENT: + m_pencoding = new Block4x4Encoding_RGBA8_Transparent; + break; + + case SourceAlphaMix::TRANSLUCENT: + m_pencoding = new Block4x4Encoding_RGBA8; + break; + + default: + assert(0); + break; + } + break; + + case Image::Format::RGB8A1: + case Image::Format::SRGB8A1: + switch (m_sourcealphamix) + { + case SourceAlphaMix::OPAQUE: + m_pencoding = new Block4x4Encoding_RGB8A1_Opaque; + break; + + case SourceAlphaMix::TRANSPARENT: + m_pencoding = new Block4x4Encoding_RGB8A1_Transparent; + break; + + case SourceAlphaMix::TRANSLUCENT: + if (m_boolPunchThroughPixels) + { + m_pencoding = new Block4x4Encoding_RGB8A1; + } + else + { + m_pencoding = new Block4x4Encoding_RGB8A1_Opaque; + } + break; + + default: + assert(0); + break; + } + break; + + case Image::Format::R11: + case Image::Format::SIGNED_R11: + m_pencoding = new Block4x4Encoding_R11; + break; + case Image::Format::RG11: + case Image::Format::SIGNED_RG11: + m_pencoding = new Block4x4Encoding_RG11; + break; + default: + assert(0); + break; + } + + m_pencoding->InitFromSource(this, m_afrgbaSource, + a_paucEncodingBits, a_errormetric); + + } + + // ---------------------------------------------------------------------------------------------------- + // initialization of encoding state from a prior encoding using encoding bits + // [a_uiSourceH,a_uiSourceV] is the location of the block in a_pimageSource + // a_paucEncodingBits is the place to read the prior encoding + // a_imageformat is used to determine how to interpret a_paucEncodingBits + // a_errormetric was used for the prior encoding + // + void Block4x4::InitFromEtcEncodingBits(Image::Format a_imageformat, + unsigned int a_uiSourceH, unsigned int a_uiSourceV, + unsigned char *a_paucEncodingBits, + Image *a_pimageSource, + ErrorMetric a_errormetric) + { + Block4x4(); + + m_pimageSource = a_pimageSource; + m_uiSourceH = a_uiSourceH; + m_uiSourceV = a_uiSourceV; + m_errormetric = a_errormetric; + + SetSourcePixels(); + + // set block encoder function + switch (a_imageformat) + { + case Image::Format::ETC1: + m_pencoding = new Block4x4Encoding_ETC1; + break; + + case Image::Format::RGB8: + case Image::Format::SRGB8: + m_pencoding = new Block4x4Encoding_RGB8; + break; + + case Image::Format::RGBA8: + case Image::Format::SRGBA8: + m_pencoding = new Block4x4Encoding_RGBA8; + break; + + case Image::Format::RGB8A1: + case Image::Format::SRGB8A1: + m_pencoding = new Block4x4Encoding_RGB8A1; + break; + + case Image::Format::R11: + case Image::Format::SIGNED_R11: + m_pencoding = new Block4x4Encoding_R11; + break; + case Image::Format::RG11: + case Image::Format::SIGNED_RG11: + m_pencoding = new Block4x4Encoding_RG11; + break; + default: + assert(0); + break; + } + + m_pencoding->InitFromEncodingBits(this, a_paucEncodingBits, m_afrgbaSource, + m_pimageSource->GetErrorMetric()); + + } + + // ---------------------------------------------------------------------------------------------------- + // set source pixels from m_pimageSource + // set m_alphamix + // + void Block4x4::SetSourcePixels(void) + { + + Image::Format imageformat = m_pimageSource->GetFormat(); + + // alpha census + unsigned int uiTransparentSourcePixels = 0; + unsigned int uiOpaqueSourcePixels = 0; + + // copy source to consecutive memory locations + // convert from image horizontal scan to block vertical scan + unsigned int uiPixel = 0; + for (unsigned int uiBlockPixelH = 0; uiBlockPixelH < Block4x4::COLUMNS; uiBlockPixelH++) + { + unsigned int uiSourcePixelH = m_uiSourceH + uiBlockPixelH; + + for (unsigned int uiBlockPixelV = 0; uiBlockPixelV < Block4x4::ROWS; uiBlockPixelV++) + { + unsigned int uiSourcePixelV = m_uiSourceV + uiBlockPixelV; + + ColorFloatRGBA *pfrgbaSource = m_pimageSource->GetSourcePixel(uiSourcePixelH, uiSourcePixelV); + + // if pixel extends beyond source image because of block padding + if (pfrgbaSource == nullptr) + { + m_afrgbaSource[uiPixel] = ColorFloatRGBA(0.0f, 0.0f, 0.0f, NAN); // denotes border pixel + m_boolBorderPixels = true; + uiTransparentSourcePixels++; + } + else + { + //get teh current pixel data, and store some of the attributes + //before capping values to fit the encoder type + + m_afrgbaSource[uiPixel] = (*pfrgbaSource).ClampRGBA(); + + if (m_afrgbaSource[uiPixel].fA == 1.0f) + { + m_pimageSource->m_iNumOpaquePixels++; + } + else if (m_afrgbaSource[uiPixel].fA == 0.0f) + { + m_pimageSource->m_iNumTransparentPixels++; + } + else if(m_afrgbaSource[uiPixel].fA > 0.0f && m_afrgbaSource[uiPixel].fA < 1.0f) + { + m_pimageSource->m_iNumTranslucentPixels++; + } + else + { + m_pimageSource->m_numOutOfRangeValues.fA++; + } + + if (m_afrgbaSource[uiPixel].fR != 0.0f) + { + m_pimageSource->m_numColorValues.fR++; + //make sure we are getting a float between 0-1 + if (m_afrgbaSource[uiPixel].fR - 1.0f > 0.0f) + { + m_pimageSource->m_numOutOfRangeValues.fR++; + } + } + + if (m_afrgbaSource[uiPixel].fG != 0.0f) + { + m_pimageSource->m_numColorValues.fG++; + if (m_afrgbaSource[uiPixel].fG - 1.0f > 0.0f) + { + m_pimageSource->m_numOutOfRangeValues.fG++; + } + } + if (m_afrgbaSource[uiPixel].fB != 0.0f) + { + m_pimageSource->m_numColorValues.fB++; + if (m_afrgbaSource[uiPixel].fB - 1.0f > 0.0f) + { + m_pimageSource->m_numOutOfRangeValues.fB++; + } + } + // for formats with no alpha, set source alpha to 1 + if (imageformat == Image::Format::ETC1 || + imageformat == Image::Format::RGB8 || + imageformat == Image::Format::SRGB8) + { + m_afrgbaSource[uiPixel].fA = 1.0f; + } + + if (imageformat == Image::Format::R11 || + imageformat == Image::Format::SIGNED_R11) + { + m_afrgbaSource[uiPixel].fA = 1.0f; + m_afrgbaSource[uiPixel].fG = 0.0f; + m_afrgbaSource[uiPixel].fB = 0.0f; + } + + if (imageformat == Image::Format::RG11 || + imageformat == Image::Format::SIGNED_RG11) + { + m_afrgbaSource[uiPixel].fA = 1.0f; + m_afrgbaSource[uiPixel].fB = 0.0f; + } + + + // for RGB8A1, set source alpha to 0.0 or 1.0 + // set punch through flag + if (imageformat == Image::Format::RGB8A1 || + imageformat == Image::Format::SRGB8A1) + { + if (m_afrgbaSource[uiPixel].fA >= 0.5f) + { + m_afrgbaSource[uiPixel].fA = 1.0f; + } + else + { + m_afrgbaSource[uiPixel].fA = 0.0f; + m_boolPunchThroughPixels = true; + } + } + + if (m_afrgbaSource[uiPixel].fA == 1.0f) + { + uiOpaqueSourcePixels++; + } + else if (m_afrgbaSource[uiPixel].fA == 0.0f) + { + uiTransparentSourcePixels++; + } + + } + + uiPixel += 1; + } + } + + if (uiOpaqueSourcePixels == PIXELS) + { + m_sourcealphamix = SourceAlphaMix::OPAQUE; + } + else if (uiTransparentSourcePixels == PIXELS) + { + m_sourcealphamix = SourceAlphaMix::TRANSPARENT; + } + else + { + m_sourcealphamix = SourceAlphaMix::TRANSLUCENT; + } + + } + + // ---------------------------------------------------------------------------------------------------- + // return a name for the encoding mode + // + const char * Block4x4::GetEncodingModeName(void) + { + + switch (m_pencoding->GetMode()) + { + case Block4x4Encoding::MODE_ETC1: + return "ETC1"; + case Block4x4Encoding::MODE_T: + return "T"; + case Block4x4Encoding::MODE_H: + return "H"; + case Block4x4Encoding::MODE_PLANAR: + return "PLANAR"; + default: + return "???"; + } + } + + // ---------------------------------------------------------------------------------------------------- + // + +} diff --git a/extern/EtcLib/EtcCodec/EtcBlock4x4.h b/extern/EtcLib/EtcCodec/EtcBlock4x4.h new file mode 100644 index 0000000..7716beb --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcBlock4x4.h @@ -0,0 +1,172 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +#pragma once + +#include "EtcColor.h" +#include "EtcColorFloatRGBA.h" +#include "EtcErrorMetric.h" +#include "EtcImage.h" +#include "EtcBlock4x4Encoding.h" + +namespace Etc +{ + class Block4x4EncodingBits; + + class Block4x4 + { + public: + + static const unsigned int ROWS = 4; + static const unsigned int COLUMNS = 4; + static const unsigned int PIXELS = ROWS * COLUMNS; + + // the alpha mix for a 4x4 block of pixels + enum class SourceAlphaMix + { + UNKNOWN, + // + OPAQUE, // all 1.0 + TRANSPARENT, // all 0.0 or NAN + TRANSLUCENT // not all opaque or transparent + }; + + typedef void (Block4x4::*EncoderFunctionPtr)(void); + + Block4x4(void); + ~Block4x4(); + void InitFromSource(Image *a_pimageSource, + unsigned int a_uiSourceH, + unsigned int a_uiSourceV, + unsigned char *a_paucEncodingBits, + ErrorMetric a_errormetric); + + void InitFromEtcEncodingBits(Image::Format a_imageformat, + unsigned int a_uiSourceH, + unsigned int a_uiSourceV, + unsigned char *a_paucEncodingBits, + Image *a_pimageSource, + ErrorMetric a_errormetric); + + // return true if final iteration was performed + inline void PerformEncodingIteration(float a_fEffort) + { + m_pencoding->PerformIteration(a_fEffort); + } + + inline void SetEncodingBitsFromEncoding(void) + { + m_pencoding->SetEncodingBits(); + } + + inline unsigned int GetSourceH(void) + { + return m_uiSourceH; + } + + inline unsigned int GetSourceV(void) + { + return m_uiSourceV; + } + + inline float GetError(void) + { + return m_pencoding->GetError(); + } + + static const unsigned int s_auiPixelOrderHScan[PIXELS]; + + inline ColorFloatRGBA * GetDecodedColors(void) + { + return m_pencoding->GetDecodedColors(); + } + + inline float * GetDecodedAlphas(void) + { + return m_pencoding->GetDecodedAlphas(); + } + + inline Block4x4Encoding::Mode GetEncodingMode(void) + { + return m_pencoding->GetMode(); + } + + inline bool GetFlip(void) + { + return m_pencoding->GetFlip(); + } + + inline bool IsDifferential(void) + { + return m_pencoding->IsDifferential(); + } + + inline ColorFloatRGBA * GetSource() + { + return m_afrgbaSource; + } + + inline ErrorMetric GetErrorMetric() + { + return m_errormetric; + } + + const char * GetEncodingModeName(void); + + inline Block4x4Encoding * GetEncoding(void) + { + return m_pencoding; + } + + inline SourceAlphaMix GetSourceAlphaMix(void) + { + return m_sourcealphamix; + } + + inline Image * GetImageSource(void) + { + return m_pimageSource; + } + + inline bool HasBorderPixels(void) + { + return m_boolBorderPixels; + } + + inline bool HasPunchThroughPixels(void) + { + return m_boolPunchThroughPixels; + } + + private: + + void SetSourcePixels(void); + + Image *m_pimageSource; + unsigned int m_uiSourceH; + unsigned int m_uiSourceV; + ErrorMetric m_errormetric; + ColorFloatRGBA m_afrgbaSource[PIXELS]; // vertical scan + + SourceAlphaMix m_sourcealphamix; + bool m_boolBorderPixels; // marked as rgba(NAN, NAN, NAN, NAN) + bool m_boolPunchThroughPixels; // RGB8A1 or SRGB8A1 with any pixels with alpha < 0.5 + + Block4x4Encoding *m_pencoding; + + }; + +} // namespace Etc diff --git a/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding.cpp b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding.cpp new file mode 100644 index 0000000..de71fe6 --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding.cpp @@ -0,0 +1,250 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +/* +EtcBlock4x4Encoding.cpp + +Block4x4Encoding is the abstract base class for the different encoders. Each encoder targets a +particular file format (e.g. ETC1, RGB8, RGBA8, R11) + +*/ + +#include "EtcConfig.h" +#include "EtcBlock4x4Encoding.h" + +#include "EtcBlock4x4EncodingBits.h" +#include "EtcBlock4x4.h" + +#include +#include +#include + +namespace Etc +{ + // ---------------------------------------------------------------------------------------------------- + // + const float Block4x4Encoding::LUMA_WEIGHT = 3.0f; + const float Block4x4Encoding::CHROMA_BLUE_WEIGHT = 0.5f; + + // ---------------------------------------------------------------------------------------------------- + // + Block4x4Encoding::Block4x4Encoding(void) + { + + m_pblockParent = nullptr; + + m_pafrgbaSource = nullptr; + + m_boolBorderPixels = false; + + m_fError = -1.0f; + + m_mode = MODE_UNKNOWN; + + m_uiEncodingIterations = 0; + m_boolDone = false; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_afrgbaDecodedColors[uiPixel] = ColorFloatRGBA(-1.0f, -1.0f, -1.0f, -1.0f); + m_afDecodedAlphas[uiPixel] = -1.0f; + } + + } + + // ---------------------------------------------------------------------------------------------------- + // initialize the generic encoding for a 4x4 block + // a_pblockParent points to the block associated with this encoding + // a_errormetric is used to choose the best encoding + // init the decoded pixels to -1 to mark them as undefined + // init the error to -1 to mark it as undefined + // + void Block4x4Encoding::Init(Block4x4 *a_pblockParent, + ColorFloatRGBA *a_pafrgbaSource, + ErrorMetric a_errormetric) + { + + m_pblockParent = a_pblockParent; + + m_pafrgbaSource = a_pafrgbaSource; + + m_boolBorderPixels = m_pblockParent->HasBorderPixels(); + + m_fError = -1.0f; + + m_uiEncodingIterations = 0; + + m_errormetric = a_errormetric; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_afrgbaDecodedColors[uiPixel] = ColorFloatRGBA(-1.0f, -1.0f, -1.0f, -1.0f); + m_afDecodedAlphas[uiPixel] = -1.0f; + } + + } + + // ---------------------------------------------------------------------------------------------------- + // calculate the error for the block by summing the pixel errors + // + void Block4x4Encoding::CalcBlockError(void) + { + m_fError = 0.0f; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_fError += CalcPixelError(m_afrgbaDecodedColors[uiPixel], m_afDecodedAlphas[uiPixel], + m_pafrgbaSource[uiPixel]); + } + + } + + // ---------------------------------------------------------------------------------------------------- + // calculate the error between the source pixel and the decoded pixel + // the error amount is base on the error metric + // + float Block4x4Encoding::CalcPixelError(ColorFloatRGBA a_frgbaDecodedColor, float a_fDecodedAlpha, + ColorFloatRGBA a_frgbaSourcePixel) + { + + // if a border pixel + if (isnan(a_frgbaSourcePixel.fA)) + { + return 0.0f; + } + + if (m_errormetric == ErrorMetric::RGBA) + { + assert(a_fDecodedAlpha >= 0.0f); + + float fDRed = (a_fDecodedAlpha * a_frgbaDecodedColor.fR) - + (a_frgbaSourcePixel.fA * a_frgbaSourcePixel.fR); + float fDGreen = (a_fDecodedAlpha * a_frgbaDecodedColor.fG) - + (a_frgbaSourcePixel.fA * a_frgbaSourcePixel.fG); + float fDBlue = (a_fDecodedAlpha * a_frgbaDecodedColor.fB) - + (a_frgbaSourcePixel.fA * a_frgbaSourcePixel.fB); + + float fDAlpha = a_fDecodedAlpha - a_frgbaSourcePixel.fA; + + return fDRed*fDRed + fDGreen*fDGreen + fDBlue*fDBlue + fDAlpha*fDAlpha; + } + else if (m_errormetric == ErrorMetric::REC709) + { + assert(a_fDecodedAlpha >= 0.0f); + + float fLuma1 = a_frgbaSourcePixel.fR*0.2126f + a_frgbaSourcePixel.fG*0.7152f + a_frgbaSourcePixel.fB*0.0722f; + float fChromaR1 = 0.5f * ((a_frgbaSourcePixel.fR - fLuma1) * (1.0f / (1.0f - 0.2126f))); + float fChromaB1 = 0.5f * ((a_frgbaSourcePixel.fB - fLuma1) * (1.0f / (1.0f - 0.0722f))); + + float fLuma2 = a_frgbaDecodedColor.fR*0.2126f + + a_frgbaDecodedColor.fG*0.7152f + + a_frgbaDecodedColor.fB*0.0722f; + float fChromaR2 = 0.5f * ((a_frgbaDecodedColor.fR - fLuma2) * (1.0f / (1.0f - 0.2126f))); + float fChromaB2 = 0.5f * ((a_frgbaDecodedColor.fB - fLuma2) * (1.0f / (1.0f - 0.0722f))); + + float fDeltaL = a_frgbaSourcePixel.fA * fLuma1 - a_fDecodedAlpha * fLuma2; + float fDeltaCr = a_frgbaSourcePixel.fA * fChromaR1 - a_fDecodedAlpha * fChromaR2; + float fDeltaCb = a_frgbaSourcePixel.fA * fChromaB1 - a_fDecodedAlpha * fChromaB2; + + float fDAlpha = a_fDecodedAlpha - a_frgbaSourcePixel.fA; + + // Favor Luma accuracy over Chroma, and Red over Blue + return LUMA_WEIGHT*fDeltaL*fDeltaL + + fDeltaCr*fDeltaCr + + CHROMA_BLUE_WEIGHT*fDeltaCb*fDeltaCb + + fDAlpha*fDAlpha; + #if 0 + float fDRed = a_frgbaDecodedPixel.fR - a_frgbaSourcePixel.fR; + float fDGreen = a_frgbaDecodedPixel.fG - a_frgbaSourcePixel.fG; + float fDBlue = a_frgbaDecodedPixel.fB - a_frgbaSourcePixel.fB; + return 2.0f * 3.0f * fDeltaL * fDeltaL + fDRed*fDRed + fDGreen*fDGreen + fDBlue*fDBlue; +#endif + } + else if (m_errormetric == ErrorMetric::NORMALXYZ) + { + float fDecodedX = 2.0f * a_frgbaDecodedColor.fR - 1.0f; + float fDecodedY = 2.0f * a_frgbaDecodedColor.fG - 1.0f; + float fDecodedZ = 2.0f * a_frgbaDecodedColor.fB - 1.0f; + + float fDecodedLength = sqrtf(fDecodedX*fDecodedX + fDecodedY*fDecodedY + fDecodedZ*fDecodedZ); + + if (fDecodedLength < 0.5f) + { + return 1.0f; + } + else if (fDecodedLength == 0.0f) + { + fDecodedX = 1.0f; + fDecodedY = 0.0f; + fDecodedZ = 0.0f; + } + else + { + fDecodedX /= fDecodedLength; + fDecodedY /= fDecodedLength; + fDecodedZ /= fDecodedLength; + } + + float fSourceX = 2.0f * a_frgbaSourcePixel.fR - 1.0f; + float fSourceY = 2.0f * a_frgbaSourcePixel.fG - 1.0f; + float fSourceZ = 2.0f * a_frgbaSourcePixel.fB - 1.0f; + + float fSourceLength = sqrtf(fSourceX*fSourceX + fSourceY*fSourceY + fSourceZ*fSourceZ); + + if (fSourceLength == 0.0f) + { + fSourceX = 1.0f; + fSourceY = 0.0f; + fSourceZ = 0.0f; + } + else + { + fSourceX /= fSourceLength; + fSourceY /= fSourceLength; + fSourceZ /= fSourceLength; + } + + float fDotProduct = fSourceX*fDecodedX + fSourceY*fDecodedY + fSourceZ*fDecodedZ; + float fNormalizedDotProduct = 1.0f - 0.5f * (fDotProduct + 1.0f); + float fDotProductError = fNormalizedDotProduct * fNormalizedDotProduct; + + float fLength2 = fDecodedX*fDecodedX + fDecodedY*fDecodedY + fDecodedZ*fDecodedZ; + float fLength2Error = fabsf(1.0f - fLength2); + + float fDeltaW = a_frgbaDecodedColor.fA - a_frgbaSourcePixel.fA; + float fErrorW = fDeltaW * fDeltaW; + + return fDotProductError + fLength2Error + fErrorW; + } + else // ErrorMetric::NUMERIC + { + assert(a_fDecodedAlpha >= 0.0f); + + float fDX = a_frgbaDecodedColor.fR - a_frgbaSourcePixel.fR; + float fDY = a_frgbaDecodedColor.fG - a_frgbaSourcePixel.fG; + float fDZ = a_frgbaDecodedColor.fB - a_frgbaSourcePixel.fB; + float fDW = a_frgbaDecodedColor.fA - a_frgbaSourcePixel.fA; + + return fDX*fDX + fDY*fDY + fDZ*fDZ + fDW*fDW; + } + + } + + // ---------------------------------------------------------------------------------------------------- + // + +} // namespace Etc + diff --git a/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding.h b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding.h new file mode 100644 index 0000000..86da2c5 --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding.h @@ -0,0 +1,148 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +#pragma once + +#include "EtcColorFloatRGBA.h" + +#include "EtcErrorMetric.h" + +#include +#include + +namespace Etc +{ + class Block4x4; + + // abstract base class for specific encodings + class Block4x4Encoding + { + public: + + static const unsigned int ROWS = 4; + static const unsigned int COLUMNS = 4; + static const unsigned int PIXELS = ROWS * COLUMNS; + static const float LUMA_WEIGHT; + static const float CHROMA_BLUE_WEIGHT; + + typedef enum + { + MODE_UNKNOWN, + // + MODE_ETC1, + MODE_T, + MODE_H, + MODE_PLANAR, + MODE_R11, + MODE_RG11, + // + MODES + } Mode; + + Block4x4Encoding(void); + //virtual ~Block4x4Encoding(void) =0; + virtual ~Block4x4Encoding(void) {} + virtual void InitFromSource(Block4x4 *a_pblockParent, + ColorFloatRGBA *a_pafrgbaSource, + + unsigned char *a_paucEncodingBits, ErrorMetric a_errormetric) = 0; + + virtual void InitFromEncodingBits(Block4x4 *a_pblockParent, + unsigned char *a_paucEncodingBits, + ColorFloatRGBA *a_pafrgbaSource, + + ErrorMetric a_errormetric) = 0; + + // perform an iteration of the encoding + // the first iteration must generate a complete, valid (if poor) encoding + virtual void PerformIteration(float a_fEffort) = 0; + + void CalcBlockError(void); + + inline float GetError(void) + { + assert(m_fError >= 0.0f); + + return m_fError; + } + + inline ColorFloatRGBA * GetDecodedColors(void) + { + return m_afrgbaDecodedColors; + } + + inline float * GetDecodedAlphas(void) + { + return m_afDecodedAlphas; + } + + virtual void SetEncodingBits(void) = 0; + + virtual bool GetFlip(void) = 0; + + virtual bool IsDifferential(void) = 0; + + virtual bool HasSeverelyBentDifferentialColors(void) const = 0; + + inline Mode GetMode(void) + { + return m_mode; + } + + inline bool IsDone(void) + { + return m_boolDone; + } + + inline void SetDoneIfPerfect() + { + if (GetError() == 0.0f) + { + m_boolDone = true; + } + } + + float CalcPixelError(ColorFloatRGBA a_frgbaDecodedColor, float a_fDecodedAlpha, + ColorFloatRGBA a_frgbaSourcePixel); + + protected: + + void Init(Block4x4 *a_pblockParent, + ColorFloatRGBA *a_pafrgbaSource, + + ErrorMetric a_errormetric); + + Block4x4 *m_pblockParent; + ColorFloatRGBA *m_pafrgbaSource; + + bool m_boolBorderPixels; // if block has any border pixels + + ColorFloatRGBA m_afrgbaDecodedColors[PIXELS]; // decoded RGB components, ignore Alpha + float m_afDecodedAlphas[PIXELS]; // decoded alpha component + float m_fError; // error for RGBA relative to m_pafrgbaSource + + // intermediate encoding + Mode m_mode; + + unsigned int m_uiEncodingIterations; + bool m_boolDone; // all iterations have been done + ErrorMetric m_errormetric; + + private: + + }; + +} // namespace Etc diff --git a/extern/EtcLib/EtcCodec/EtcBlock4x4EncodingBits.h b/extern/EtcLib/EtcCodec/EtcBlock4x4EncodingBits.h new file mode 100644 index 0000000..5ba879e --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcBlock4x4EncodingBits.h @@ -0,0 +1,315 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +#pragma once + +#include + +namespace Etc +{ + + // ################################################################################ + // Block4x4EncodingBits + // Base class for Block4x4EncodingBits_XXXX + // ################################################################################ + + class Block4x4EncodingBits + { + public: + + enum class Format + { + UNKNOWN, + // + RGB8, + RGBA8, + R11, + RG11, + RGB8A1, + // + FORMATS + }; + + static unsigned int GetBytesPerBlock(Format a_format) + { + switch (a_format) + { + case Format::RGB8: + case Format::R11: + case Format::RGB8A1: + return 8; + break; + + case Format::RGBA8: + case Format::RG11: + return 16; + break; + + default: + return 0; + break; + } + + } + + }; + + // ################################################################################ + // Block4x4EncodingBits_RGB8 + // Encoding bits for the RGB portion of ETC1, RGB8, RGB8A1 and RGBA8 + // ################################################################################ + + class Block4x4EncodingBits_RGB8 + { + public: + + static const unsigned int BYTES_PER_BLOCK = 8; + + inline Block4x4EncodingBits_RGB8(void) + { + assert(sizeof(Block4x4EncodingBits_RGB8) == BYTES_PER_BLOCK); + + for (unsigned int uiByte = 0; uiByte < BYTES_PER_BLOCK; uiByte++) + { + auc[uiByte] = 0; + } + + } + + typedef struct + { + unsigned red2 : 4; + unsigned red1 : 4; + // + unsigned green2 : 4; + unsigned green1 : 4; + // + unsigned blue2 : 4; + unsigned blue1 : 4; + // + unsigned flip : 1; + unsigned diff : 1; + unsigned cw2 : 3; + unsigned cw1 : 3; + // + unsigned int selectors; + } Individual; + + typedef struct + { + signed dred2 : 3; + unsigned red1 : 5; + // + signed dgreen2 : 3; + unsigned green1 : 5; + // + signed dblue2 : 3; + unsigned blue1 : 5; + // + unsigned flip : 1; + unsigned diff : 1; + unsigned cw2 : 3; + unsigned cw1 : 3; + // + unsigned int selectors; + } Differential; + + typedef struct + { + unsigned red1b : 2; + unsigned detect2 : 1; + unsigned red1a : 2; + unsigned detect1 : 3; + // + unsigned blue1 : 4; + unsigned green1 : 4; + // + unsigned green2 : 4; + unsigned red2 : 4; + // + unsigned db : 1; + unsigned diff : 1; + unsigned da : 2; + unsigned blue2 : 4; + // + unsigned int selectors; + } T; + + typedef struct + { + unsigned green1a : 3; + unsigned red1 : 4; + unsigned detect1 : 1; + // + unsigned blue1b : 2; + unsigned detect3 : 1; + unsigned blue1a : 1; + unsigned green1b : 1; + unsigned detect2 : 3; + // + unsigned green2a : 3; + unsigned red2 : 4; + unsigned blue1c : 1; + // + unsigned db : 1; + unsigned diff : 1; + unsigned da : 1; + unsigned blue2 : 4; + unsigned green2b : 1; + // + unsigned int selectors; + } H; + + typedef struct + { + unsigned originGreen1 : 1; + unsigned originRed : 6; + unsigned detect1 : 1; + // + unsigned originBlue1 : 1; + unsigned originGreen2 : 6; + unsigned detect2 : 1; + // + unsigned originBlue3 : 2; + unsigned detect4 : 1; + unsigned originBlue2 : 2; + unsigned detect3 : 3; + // + unsigned horizRed2 : 1; + unsigned diff : 1; + unsigned horizRed1 : 5; + unsigned originBlue4 : 1; + // + unsigned horizBlue1: 1; + unsigned horizGreen : 7; + // + unsigned vertRed1 : 3; + unsigned horizBlue2 : 5; + // + unsigned vertGreen1 : 5; + unsigned vertRed2 : 3; + // + unsigned vertBlue : 6; + unsigned vertGreen2 : 2; + } Planar; + + union + { + unsigned char auc[BYTES_PER_BLOCK]; + unsigned long int ul; + Individual individual; + Differential differential; + T t; + H h; + Planar planar; + }; + + }; + + // ################################################################################ + // Block4x4EncodingBits_A8 + // Encoding bits for the A portion of RGBA8 + // ################################################################################ + + class Block4x4EncodingBits_A8 + { + public: + + static const unsigned int BYTES_PER_BLOCK = 8; + static const unsigned int SELECTOR_BYTES = 6; + + typedef struct + { + unsigned base : 8; + unsigned table : 4; + unsigned multiplier : 4; + unsigned selectors0 : 8; + unsigned selectors1 : 8; + unsigned selectors2 : 8; + unsigned selectors3 : 8; + unsigned selectors4 : 8; + unsigned selectors5 : 8; + } Data; + + Data data; + + }; + + // ################################################################################ + // Block4x4EncodingBits_R11 + // Encoding bits for the R portion of R11 + // ################################################################################ + + class Block4x4EncodingBits_R11 + { + public: + + static const unsigned int BYTES_PER_BLOCK = 8; + static const unsigned int SELECTOR_BYTES = 6; + + typedef struct + { + unsigned base : 8; + unsigned table : 4; + unsigned multiplier : 4; + unsigned selectors0 : 8; + unsigned selectors1 : 8; + unsigned selectors2 : 8; + unsigned selectors3 : 8; + unsigned selectors4 : 8; + unsigned selectors5 : 8; + } Data; + + Data data; + + }; + + class Block4x4EncodingBits_RG11 + { + public: + + static const unsigned int BYTES_PER_BLOCK = 16; + static const unsigned int SELECTOR_BYTES = 12; + + typedef struct + { + //Red portion + unsigned baseR : 8; + unsigned tableIndexR : 4; + unsigned multiplierR : 4; + unsigned selectorsR0 : 8; + unsigned selectorsR1 : 8; + unsigned selectorsR2 : 8; + unsigned selectorsR3 : 8; + unsigned selectorsR4 : 8; + unsigned selectorsR5 : 8; + //Green portion + unsigned baseG : 8; + unsigned tableIndexG : 4; + unsigned multiplierG : 4; + unsigned selectorsG0 : 8; + unsigned selectorsG1 : 8; + unsigned selectorsG2 : 8; + unsigned selectorsG3 : 8; + unsigned selectorsG4 : 8; + unsigned selectorsG5 : 8; + } Data; + + Data data; + + }; + +} diff --git a/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_ETC1.cpp b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_ETC1.cpp new file mode 100644 index 0000000..7aa2de3 --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_ETC1.cpp @@ -0,0 +1,1280 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +/* +EtcBlock4x4Encoding_ETC1.cpp + +Block4x4Encoding_ETC1 is the encoder to use when targetting file format ETC1. This encoder is also +used for the ETC1 subset of file format RGB8, RGBA8 and RGB8A1 + +*/ + +#include "EtcConfig.h" +#include "EtcBlock4x4Encoding_ETC1.h" + +#include "EtcBlock4x4.h" +#include "EtcBlock4x4EncodingBits.h" +#include "EtcDifferentialTrys.h" + +#include +#include +#include +#include +#include + +namespace Etc +{ + + // pixel processing order if the flip bit = 0 (horizontal split) + const unsigned int Block4x4Encoding_ETC1::s_auiPixelOrderFlip0[PIXELS] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + + // pixel processing order if the flip bit = 1 (vertical split) + const unsigned int Block4x4Encoding_ETC1::s_auiPixelOrderFlip1[PIXELS] = { 0, 1, 4, 5, 8, 9, 12, 13, 2, 3, 6, 7, 10, 11, 14, 15 }; + + // pixel processing order for horizontal scan (ETC normally does a vertical scan) + const unsigned int Block4x4Encoding_ETC1::s_auiPixelOrderHScan[PIXELS] = { 0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15 }; + + // pixel indices for different block halves + const unsigned int Block4x4Encoding_ETC1::s_auiLeftPixelMapping[8] = { 0, 1, 2, 3, 4, 5, 6, 7 }; + const unsigned int Block4x4Encoding_ETC1::s_auiRightPixelMapping[8] = { 8, 9, 10, 11, 12, 13, 14, 15 }; + const unsigned int Block4x4Encoding_ETC1::s_auiTopPixelMapping[8] = { 0, 1, 4, 5, 8, 9, 12, 13 }; + const unsigned int Block4x4Encoding_ETC1::s_auiBottomPixelMapping[8] = { 2, 3, 6, 7, 10, 11, 14, 15 }; + + // CW ranges that the ETC1 decoders use + // CW is basically a contrast for the different selector bits, since these values are offsets to the base color + // the first axis in the array is indexed by the CW in the encoding bits + // the second axis in the array is indexed by the selector bits + float Block4x4Encoding_ETC1::s_aafCwTable[CW_RANGES][SELECTORS] = + { + { 2.0f / 255.0f, 8.0f / 255.0f, -2.0f / 255.0f, -8.0f / 255.0f }, + { 5.0f / 255.0f, 17.0f / 255.0f, -5.0f / 255.0f, -17.0f / 255.0f }, + { 9.0f / 255.0f, 29.0f / 255.0f, -9.0f / 255.0f, -29.0f / 255.0f }, + { 13.0f / 255.0f, 42.0f / 255.0f, -13.0f / 255.0f, -42.0f / 255.0f }, + { 18.0f / 255.0f, 60.0f / 255.0f, -18.0f / 255.0f, -60.0f / 255.0f }, + { 24.0f / 255.0f, 80.0f / 255.0f, -24.0f / 255.0f, -80.0f / 255.0f }, + { 33.0f / 255.0f, 106.0f / 255.0f, -33.0f / 255.0f, -106.0f / 255.0f }, + { 47.0f / 255.0f, 183.0f / 255.0f, -47.0f / 255.0f, -183.0f / 255.0f } + }; + + // ---------------------------------------------------------------------------------------------------- + // + Block4x4Encoding_ETC1::Block4x4Encoding_ETC1(void) + { + m_mode = MODE_ETC1; + m_boolDiff = false; + m_boolFlip = false; + m_frgbaColor1 = ColorFloatRGBA(); + m_frgbaColor2 = ColorFloatRGBA(); + m_uiCW1 = 0; + m_uiCW2 = 0; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_auiSelectors[uiPixel] = 0; + m_afDecodedAlphas[uiPixel] = 1.0f; + } + + m_boolMostLikelyFlip = false; + + m_fError = -1.0f; + + m_fError1 = -1.0f; + m_fError2 = -1.0f; + m_boolSeverelyBentDifferentialColors = false; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_afDecodedAlphas[uiPixel] = 1.0f; + } + + } + + Block4x4Encoding_ETC1::~Block4x4Encoding_ETC1(void) {} + + // ---------------------------------------------------------------------------------------------------- + // initialization prior to encoding + // a_pblockParent points to the block associated with this encoding + // a_errormetric is used to choose the best encoding + // a_pafrgbaSource points to a 4x4 block subset of the source image + // a_paucEncodingBits points to the final encoding bits + // + void Block4x4Encoding_ETC1::InitFromSource(Block4x4 *a_pblockParent, + ColorFloatRGBA *a_pafrgbaSource, + unsigned char *a_paucEncodingBits, ErrorMetric a_errormetric) + { + + Block4x4Encoding::Init(a_pblockParent, a_pafrgbaSource,a_errormetric); + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_afDecodedAlphas[uiPixel] = 1.0f; + } + + m_fError = -1.0f; + + m_pencodingbitsRGB8 = (Block4x4EncodingBits_RGB8 *)(a_paucEncodingBits); + + } + + // ---------------------------------------------------------------------------------------------------- + // initialization from the encoding bits of a previous encoding + // a_pblockParent points to the block associated with this encoding + // a_errormetric is used to choose the best encoding + // a_pafrgbaSource points to a 4x4 block subset of the source image + // a_paucEncodingBits points to the final encoding bits of a previous encoding + // + void Block4x4Encoding_ETC1::InitFromEncodingBits(Block4x4 *a_pblockParent, + unsigned char *a_paucEncodingBits, + ColorFloatRGBA *a_pafrgbaSource, + ErrorMetric a_errormetric) + { + + Block4x4Encoding::Init(a_pblockParent, a_pafrgbaSource,a_errormetric); + m_fError = -1.0f; + + m_pencodingbitsRGB8 = (Block4x4EncodingBits_RGB8 *)a_paucEncodingBits; + + m_mode = MODE_ETC1; + m_boolDiff = m_pencodingbitsRGB8->individual.diff; + m_boolFlip = m_pencodingbitsRGB8->individual.flip; + if (m_boolDiff) + { + int iR2 = (int)(m_pencodingbitsRGB8->differential.red1 + m_pencodingbitsRGB8->differential.dred2); + if (iR2 < 0) + { + iR2 = 0; + } + else if (iR2 > 31) + { + iR2 = 31; + } + + int iG2 = (int)(m_pencodingbitsRGB8->differential.green1 + m_pencodingbitsRGB8->differential.dgreen2); + if (iG2 < 0) + { + iG2 = 0; + } + else if (iG2 > 31) + { + iG2 = 31; + } + + int iB2 = (int)(m_pencodingbitsRGB8->differential.blue1 + m_pencodingbitsRGB8->differential.dblue2); + if (iB2 < 0) + { + iB2 = 0; + } + else if (iB2 > 31) + { + iB2 = 31; + } + + m_frgbaColor1 = ColorFloatRGBA::ConvertFromRGB5(m_pencodingbitsRGB8->differential.red1, m_pencodingbitsRGB8->differential.green1, m_pencodingbitsRGB8->differential.blue1); + m_frgbaColor2 = ColorFloatRGBA::ConvertFromRGB5((unsigned char)iR2, (unsigned char)iG2, (unsigned char)iB2); + + } + else + { + m_frgbaColor1 = ColorFloatRGBA::ConvertFromRGB4(m_pencodingbitsRGB8->individual.red1, m_pencodingbitsRGB8->individual.green1, m_pencodingbitsRGB8->individual.blue1); + m_frgbaColor2 = ColorFloatRGBA::ConvertFromRGB4(m_pencodingbitsRGB8->individual.red2, m_pencodingbitsRGB8->individual.green2, m_pencodingbitsRGB8->individual.blue2); + } + + m_uiCW1 = m_pencodingbitsRGB8->individual.cw1; + m_uiCW2 = m_pencodingbitsRGB8->individual.cw2; + + InitFromEncodingBits_Selectors(); + + Decode(); + + CalcBlockError(); + } + + // ---------------------------------------------------------------------------------------------------- + // init the selectors from a prior encoding + // + void Block4x4Encoding_ETC1::InitFromEncodingBits_Selectors(void) + { + + unsigned char *paucSelectors = (unsigned char *)&m_pencodingbitsRGB8->individual.selectors; + + for (unsigned int iPixel = 0; iPixel < PIXELS; iPixel++) + { + unsigned int uiByteMSB = (unsigned int)(1 - (iPixel / 8)); + unsigned int uiByteLSB = (unsigned int)(3 - (iPixel / 8)); + unsigned int uiShift = (unsigned int)(iPixel & 7); + + unsigned int uiSelectorMSB = (unsigned int)((paucSelectors[uiByteMSB] >> uiShift) & 1); + unsigned int uiSelectorLSB = (unsigned int)((paucSelectors[uiByteLSB] >> uiShift) & 1); + + m_auiSelectors[iPixel] = (uiSelectorMSB << 1) + uiSelectorLSB; + } + + } + + // ---------------------------------------------------------------------------------------------------- + // perform a single encoding iteration + // replace the encoding if a better encoding was found + // subsequent iterations generally take longer for each iteration + // set m_boolDone if encoding is perfect or encoding is finished based on a_fEffort + // + void Block4x4Encoding_ETC1::PerformIteration(float a_fEffort) + { + assert(!m_boolDone); + + switch (m_uiEncodingIterations) + { + case 0: + PerformFirstIteration(); + break; + + case 1: + TryDifferential(m_boolMostLikelyFlip, 1, 0, 0); + break; + + case 2: + TryIndividual(m_boolMostLikelyFlip, 1); + if (a_fEffort <= 49.5f) + { + m_boolDone = true; + } + break; + + case 3: + TryDifferential(!m_boolMostLikelyFlip, 1, 0, 0); + if (a_fEffort <= 59.5f) + { + m_boolDone = true; + } + break; + + case 4: + TryIndividual(!m_boolMostLikelyFlip, 1); + if (a_fEffort <= 69.5f) + { + m_boolDone = true; + } + break; + + case 5: + TryDegenerates1(); + if (a_fEffort <= 79.5f) + { + m_boolDone = true; + } + break; + + case 6: + TryDegenerates2(); + if (a_fEffort <= 89.5f) + { + m_boolDone = true; + } + break; + + case 7: + TryDegenerates3(); + if (a_fEffort <= 99.5f) + { + m_boolDone = true; + } + break; + + case 8: + TryDegenerates4(); + m_boolDone = true; + break; + + default: + assert(0); + break; + } + + m_uiEncodingIterations++; + SetDoneIfPerfect(); + } + + // ---------------------------------------------------------------------------------------------------- + // find best initial encoding to ensure block has a valid encoding + // + void Block4x4Encoding_ETC1::PerformFirstIteration(void) + { + CalculateMostLikelyFlip(); + + m_fError = FLT_MAX; + + TryDifferential(m_boolMostLikelyFlip, 0, 0, 0); + SetDoneIfPerfect(); + if (m_boolDone) + { + return; + } + + TryIndividual(m_boolMostLikelyFlip, 0); + SetDoneIfPerfect(); + if (m_boolDone) + { + return; + } + TryDifferential(!m_boolMostLikelyFlip, 0, 0, 0); + SetDoneIfPerfect(); + if (m_boolDone) + { + return; + } + TryIndividual(!m_boolMostLikelyFlip, 0); + + } + + // ---------------------------------------------------------------------------------------------------- + // algorithm: + // create a source average color for the Left, Right, Top and Bottom halves using the 8 pixels in each half + // note: the "gray line" is the line of equal delta RGB that goes thru the average color + // for each half: + // see how close each of the 8 pixels are to the "gray line" that goes thru the source average color + // create an error value that is the sum of the distances from the gray line + // h_error is the sum of Left and Right errors + // v_error is the sum of Top and Bottom errors + // + void Block4x4Encoding_ETC1::CalculateMostLikelyFlip(void) + { + static const bool DEBUG_PRINT = false; + + CalculateSourceAverages(); + + float fLeftGrayErrorSum = 0.0f; + float fRightGrayErrorSum = 0.0f; + float fTopGrayErrorSum = 0.0f; + float fBottomGrayErrorSum = 0.0f; + + for (unsigned int uiPixel = 0; uiPixel < 8; uiPixel++) + { + ColorFloatRGBA *pfrgbaLeft = &m_pafrgbaSource[uiPixel]; + ColorFloatRGBA *pfrgbaRight = &m_pafrgbaSource[uiPixel + 8]; + ColorFloatRGBA *pfrgbaTop = &m_pafrgbaSource[s_auiTopPixelMapping[uiPixel]]; + ColorFloatRGBA *pfrgbaBottom = &m_pafrgbaSource[s_auiBottomPixelMapping[uiPixel]]; + + float fLeftGrayError = CalcGrayDistance2(*pfrgbaLeft, m_frgbaSourceAverageLeft); + float fRightGrayError = CalcGrayDistance2(*pfrgbaRight, m_frgbaSourceAverageRight); + float fTopGrayError = CalcGrayDistance2(*pfrgbaTop, m_frgbaSourceAverageTop); + float fBottomGrayError = CalcGrayDistance2(*pfrgbaBottom, m_frgbaSourceAverageBottom); + + fLeftGrayErrorSum += fLeftGrayError; + fRightGrayErrorSum += fRightGrayError; + fTopGrayErrorSum += fTopGrayError; + fBottomGrayErrorSum += fBottomGrayError; + } + + if (DEBUG_PRINT) + { + printf("\n%.2f %.2f\n", fLeftGrayErrorSum + fRightGrayErrorSum, fTopGrayErrorSum + fBottomGrayErrorSum); + } + + m_boolMostLikelyFlip = (fTopGrayErrorSum + fBottomGrayErrorSum) < (fLeftGrayErrorSum + fRightGrayErrorSum); + + } + + // ---------------------------------------------------------------------------------------------------- + // calculate source pixel averages for each 2x2 quadrant in a 4x4 block + // these are used to determine the averages for each of the 4 different halves (left, right, top, bottom) + // ignore pixels that have alpha == NAN (these are border pixels outside of the source image) + // weight the averages based on a pixel's alpha + // + void Block4x4Encoding_ETC1::CalculateSourceAverages(void) + { + static const bool DEBUG_PRINT = false; + + + if (m_pblockParent->GetSourceAlphaMix() == Block4x4::SourceAlphaMix::OPAQUE) + { + ColorFloatRGBA frgbaSumUL = m_pafrgbaSource[0] + m_pafrgbaSource[1] + m_pafrgbaSource[4] + m_pafrgbaSource[5]; + ColorFloatRGBA frgbaSumLL = m_pafrgbaSource[2] + m_pafrgbaSource[3] + m_pafrgbaSource[6] + m_pafrgbaSource[7]; + ColorFloatRGBA frgbaSumUR = m_pafrgbaSource[8] + m_pafrgbaSource[9] + m_pafrgbaSource[12] + m_pafrgbaSource[13]; + ColorFloatRGBA frgbaSumLR = m_pafrgbaSource[10] + m_pafrgbaSource[11] + m_pafrgbaSource[14] + m_pafrgbaSource[15]; + + m_frgbaSourceAverageLeft = (frgbaSumUL + frgbaSumLL) * 0.125f; + m_frgbaSourceAverageRight = (frgbaSumUR + frgbaSumLR) * 0.125f; + m_frgbaSourceAverageTop = (frgbaSumUL + frgbaSumUR) * 0.125f; + m_frgbaSourceAverageBottom = (frgbaSumLL + frgbaSumLR) * 0.125f; + } + else + { + float afSourceAlpha[PIXELS]; + + // treat alpha NAN as 0.0f + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + afSourceAlpha[uiPixel] = isnan(m_pafrgbaSource[uiPixel].fA) ? + 0.0f : + m_pafrgbaSource[uiPixel].fA; + } + + ColorFloatRGBA afrgbaAlphaWeightedSource[PIXELS]; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + afrgbaAlphaWeightedSource[uiPixel] = m_pafrgbaSource[uiPixel] * afSourceAlpha[uiPixel]; + } + + ColorFloatRGBA frgbaSumUL = afrgbaAlphaWeightedSource[0] + + afrgbaAlphaWeightedSource[1] + + afrgbaAlphaWeightedSource[4] + + afrgbaAlphaWeightedSource[5]; + + ColorFloatRGBA frgbaSumLL = afrgbaAlphaWeightedSource[2] + + afrgbaAlphaWeightedSource[3] + + afrgbaAlphaWeightedSource[6] + + afrgbaAlphaWeightedSource[7]; + + ColorFloatRGBA frgbaSumUR = afrgbaAlphaWeightedSource[8] + + afrgbaAlphaWeightedSource[9] + + afrgbaAlphaWeightedSource[12] + + afrgbaAlphaWeightedSource[13]; + + ColorFloatRGBA frgbaSumLR = afrgbaAlphaWeightedSource[10] + + afrgbaAlphaWeightedSource[11] + + afrgbaAlphaWeightedSource[14] + + afrgbaAlphaWeightedSource[15]; + + float fWeightSumUL = afSourceAlpha[0] + + afSourceAlpha[1] + + afSourceAlpha[4] + + afSourceAlpha[5]; + + float fWeightSumLL = afSourceAlpha[2] + + afSourceAlpha[3] + + afSourceAlpha[6] + + afSourceAlpha[7]; + + float fWeightSumUR = afSourceAlpha[8] + + afSourceAlpha[9] + + afSourceAlpha[12] + + afSourceAlpha[13]; + + float fWeightSumLR = afSourceAlpha[10] + + afSourceAlpha[11] + + afSourceAlpha[14] + + afSourceAlpha[15]; + + ColorFloatRGBA frgbaSumLeft = frgbaSumUL + frgbaSumLL; + ColorFloatRGBA frgbaSumRight = frgbaSumUR + frgbaSumLR; + ColorFloatRGBA frgbaSumTop = frgbaSumUL + frgbaSumUR; + ColorFloatRGBA frgbaSumBottom = frgbaSumLL + frgbaSumLR; + + float fWeightSumLeft = fWeightSumUL + fWeightSumLL; + float fWeightSumRight = fWeightSumUR + fWeightSumLR; + float fWeightSumTop = fWeightSumUL + fWeightSumUR; + float fWeightSumBottom = fWeightSumLL + fWeightSumLR; + + // check to see if there is at least 1 pixel with non-zero alpha + // completely transparent block should not make it to this code + assert((fWeightSumLeft + fWeightSumRight) > 0.0f); + assert((fWeightSumTop + fWeightSumBottom) > 0.0f); + + if (fWeightSumLeft > 0.0f) + { + m_frgbaSourceAverageLeft = frgbaSumLeft * (1.0f/fWeightSumLeft); + } + if (fWeightSumRight > 0.0f) + { + m_frgbaSourceAverageRight = frgbaSumRight * (1.0f/fWeightSumRight); + } + if (fWeightSumTop > 0.0f) + { + m_frgbaSourceAverageTop = frgbaSumTop * (1.0f/fWeightSumTop); + } + if (fWeightSumBottom > 0.0f) + { + m_frgbaSourceAverageBottom = frgbaSumBottom * (1.0f/fWeightSumBottom); + } + + if (fWeightSumLeft == 0.0f) + { + assert(fWeightSumRight > 0.0f); + m_frgbaSourceAverageLeft = m_frgbaSourceAverageRight; + } + if (fWeightSumRight == 0.0f) + { + assert(fWeightSumLeft > 0.0f); + m_frgbaSourceAverageRight = m_frgbaSourceAverageLeft; + } + if (fWeightSumTop == 0.0f) + { + assert(fWeightSumBottom > 0.0f); + m_frgbaSourceAverageTop = m_frgbaSourceAverageBottom; + } + if (fWeightSumBottom == 0.0f) + { + assert(fWeightSumTop > 0.0f); + m_frgbaSourceAverageBottom = m_frgbaSourceAverageTop; + } + } + + + + if (DEBUG_PRINT) + { + printf("\ntarget: [%.2f,%.2f,%.2f] [%.2f,%.2f,%.2f] [%.2f,%.2f,%.2f] [%.2f,%.2f,%.2f]\n", + m_frgbaSourceAverageLeft.fR, m_frgbaSourceAverageLeft.fG, m_frgbaSourceAverageLeft.fB, + m_frgbaSourceAverageRight.fR, m_frgbaSourceAverageRight.fG, m_frgbaSourceAverageRight.fB, + m_frgbaSourceAverageTop.fR, m_frgbaSourceAverageTop.fG, m_frgbaSourceAverageTop.fB, + m_frgbaSourceAverageBottom.fR, m_frgbaSourceAverageBottom.fG, m_frgbaSourceAverageBottom.fB); + } + + } + + // ---------------------------------------------------------------------------------------------------- + // try an ETC1 differential mode encoding + // use a_boolFlip to set the encoding F bit + // use a_uiRadius to alter basecolor components in the range[-a_uiRadius:a_uiRadius] + // use a_iGrayOffset1 and a_iGrayOffset2 to offset the basecolor to search for degenerate encodings + // replace the encoding if the encoding error is less than previous encoding + // + void Block4x4Encoding_ETC1::TryDifferential(bool a_boolFlip, unsigned int a_uiRadius, + int a_iGrayOffset1, int a_iGrayOffset2) + { + + ColorFloatRGBA frgbaColor1; + ColorFloatRGBA frgbaColor2; + + const unsigned int *pauiPixelMapping1; + const unsigned int *pauiPixelMapping2; + + if (a_boolFlip) + { + frgbaColor1 = m_frgbaSourceAverageTop; + frgbaColor2 = m_frgbaSourceAverageBottom; + + pauiPixelMapping1 = s_auiTopPixelMapping; + pauiPixelMapping2 = s_auiBottomPixelMapping; + } + else + { + frgbaColor1 = m_frgbaSourceAverageLeft; + frgbaColor2 = m_frgbaSourceAverageRight; + + pauiPixelMapping1 = s_auiLeftPixelMapping; + pauiPixelMapping2 = s_auiRightPixelMapping; + } + + DifferentialTrys trys(frgbaColor1, frgbaColor2, pauiPixelMapping1, pauiPixelMapping2, + a_uiRadius, a_iGrayOffset1, a_iGrayOffset2); + + Block4x4Encoding_ETC1 encodingTry = *this; + encodingTry.m_boolFlip = a_boolFlip; + + encodingTry.TryDifferentialHalf(&trys.m_half1); + encodingTry.TryDifferentialHalf(&trys.m_half2); + + // find best halves that are within differential range + DifferentialTrys::Try *ptryBest1 = nullptr; + DifferentialTrys::Try *ptryBest2 = nullptr; + encodingTry.m_fError = FLT_MAX; + + // see if the best of each half are in differential range + int iDRed = trys.m_half2.m_ptryBest->m_iRed - trys.m_half1.m_ptryBest->m_iRed; + int iDGreen = trys.m_half2.m_ptryBest->m_iGreen - trys.m_half1.m_ptryBest->m_iGreen; + int iDBlue = trys.m_half2.m_ptryBest->m_iBlue - trys.m_half1.m_ptryBest->m_iBlue; + if (iDRed >= -4 && iDRed <= 3 && iDGreen >= -4 && iDGreen <= 3 && iDBlue >= -4 && iDBlue <= 3) + { + ptryBest1 = trys.m_half1.m_ptryBest; + ptryBest2 = trys.m_half2.m_ptryBest; + encodingTry.m_fError = trys.m_half1.m_ptryBest->m_fError + trys.m_half2.m_ptryBest->m_fError; + } + else + { + // else, find the next best halves that are in differential range + for (DifferentialTrys::Try *ptry1 = &trys.m_half1.m_atry[0]; + ptry1 < &trys.m_half1.m_atry[trys.m_half1.m_uiTrys]; + ptry1++) + { + for (DifferentialTrys::Try *ptry2 = &trys.m_half2.m_atry[0]; + ptry2 < &trys.m_half2.m_atry[trys.m_half2.m_uiTrys]; + ptry2++) + { + iDRed = ptry2->m_iRed - ptry1->m_iRed; + bool boolValidRedDelta = iDRed <= 3 && iDRed >= -4; + iDGreen = ptry2->m_iGreen - ptry1->m_iGreen; + bool boolValidGreenDelta = iDGreen <= 3 && iDGreen >= -4; + iDBlue = ptry2->m_iBlue - ptry1->m_iBlue; + bool boolValidBlueDelta = iDBlue <= 3 && iDBlue >= -4; + + if (boolValidRedDelta && boolValidGreenDelta && boolValidBlueDelta) + { + float fError = ptry1->m_fError + ptry2->m_fError; + + if (fError < encodingTry.m_fError) + { + encodingTry.m_fError = fError; + + ptryBest1 = ptry1; + ptryBest2 = ptry2; + } + } + + } + } + assert(encodingTry.m_fError < FLT_MAX); + assert(ptryBest1 != nullptr); + assert(ptryBest2 != nullptr); + } + + if (encodingTry.m_fError < m_fError) + { + m_mode = MODE_ETC1; + m_boolDiff = true; + m_boolFlip = encodingTry.m_boolFlip; + m_frgbaColor1 = ColorFloatRGBA::ConvertFromRGB5((unsigned char)ptryBest1->m_iRed, (unsigned char)ptryBest1->m_iGreen, (unsigned char)ptryBest1->m_iBlue); + m_frgbaColor2 = ColorFloatRGBA::ConvertFromRGB5((unsigned char)ptryBest2->m_iRed, (unsigned char)ptryBest2->m_iGreen, (unsigned char)ptryBest2->m_iBlue); + m_uiCW1 = ptryBest1->m_uiCW; + m_uiCW2 = ptryBest2->m_uiCW; + + for (unsigned int uiPixelOrder = 0; uiPixelOrder < PIXELS / 2; uiPixelOrder++) + { + unsigned int uiPixel1 = pauiPixelMapping1[uiPixelOrder]; + unsigned int uiPixel2 = pauiPixelMapping2[uiPixelOrder]; + + unsigned int uiSelector1 = ptryBest1->m_auiSelectors[uiPixelOrder]; + unsigned int uiSelector2 = ptryBest2->m_auiSelectors[uiPixelOrder]; + + m_auiSelectors[uiPixel1] = uiSelector1; + m_auiSelectors[uiPixel2] = ptryBest2->m_auiSelectors[uiPixelOrder]; + + float fDeltaRGB1 = s_aafCwTable[m_uiCW1][uiSelector1]; + float fDeltaRGB2 = s_aafCwTable[m_uiCW2][uiSelector2]; + + m_afrgbaDecodedColors[uiPixel1] = (m_frgbaColor1 + fDeltaRGB1).ClampRGB(); + m_afrgbaDecodedColors[uiPixel2] = (m_frgbaColor2 + fDeltaRGB2).ClampRGB(); + } + + m_fError1 = ptryBest1->m_fError; + m_fError2 = ptryBest2->m_fError; + m_boolSeverelyBentDifferentialColors = trys.m_boolSeverelyBentColors; + m_fError = m_fError1 + m_fError2; + + // sanity check + { + int iRed1 = m_frgbaColor1.IntRed(31.0f); + int iGreen1 = m_frgbaColor1.IntGreen(31.0f); + int iBlue1 = m_frgbaColor1.IntBlue(31.0f); + + int iRed2 = m_frgbaColor2.IntRed(31.0f); + int iGreen2 = m_frgbaColor2.IntGreen(31.0f); + int iBlue2 = m_frgbaColor2.IntBlue(31.0f); + + iDRed = iRed2 - iRed1; + iDGreen = iGreen2 - iGreen1; + iDBlue = iBlue2 - iBlue1; + + assert(iDRed >= -4 && iDRed < 4); + assert(iDGreen >= -4 && iDGreen < 4); + assert(iDBlue >= -4 && iDBlue < 4); + } + } + + } + + // ---------------------------------------------------------------------------------------------------- + // try an ETC1 differential mode encoding for a half of a 4x4 block + // vary the basecolor components using a radius + // + void Block4x4Encoding_ETC1::TryDifferentialHalf(DifferentialTrys::Half *a_phalf) + { + + a_phalf->m_ptryBest = nullptr; + float fBestTryError = FLT_MAX; + + a_phalf->m_uiTrys = 0; + for (int iRed = a_phalf->m_iRed - (int)a_phalf->m_uiRadius; + iRed <= a_phalf->m_iRed + (int)a_phalf->m_uiRadius; + iRed++) + { + assert(iRed >= 0 && iRed <= 31); + + for (int iGreen = a_phalf->m_iGreen - (int)a_phalf->m_uiRadius; + iGreen <= a_phalf->m_iGreen + (int)a_phalf->m_uiRadius; + iGreen++) + { + assert(iGreen >= 0 && iGreen <= 31); + + for (int iBlue = a_phalf->m_iBlue - (int)a_phalf->m_uiRadius; + iBlue <= a_phalf->m_iBlue + (int)a_phalf->m_uiRadius; + iBlue++) + { + assert(iBlue >= 0 && iBlue <= 31); + + DifferentialTrys::Try *ptry = &a_phalf->m_atry[a_phalf->m_uiTrys]; + assert(ptry < &a_phalf->m_atry[DifferentialTrys::Half::MAX_TRYS]); + + ptry->m_iRed = iRed; + ptry->m_iGreen = iGreen; + ptry->m_iBlue = iBlue; + ptry->m_fError = FLT_MAX; + ColorFloatRGBA frgbaColor = ColorFloatRGBA::ConvertFromRGB5((unsigned char)iRed, (unsigned char)iGreen, (unsigned char)iBlue); + + // try each CW + for (unsigned int uiCW = 0; uiCW < CW_RANGES; uiCW++) + { + unsigned int auiPixelSelectors[PIXELS / 2]; + ColorFloatRGBA afrgbaDecodedPixels[PIXELS / 2]; + float afPixelErrors[PIXELS / 2] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, + FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }; + + // pre-compute decoded pixels for each selector + ColorFloatRGBA afrgbaSelectors[SELECTORS]; + assert(SELECTORS == 4); + afrgbaSelectors[0] = (frgbaColor + s_aafCwTable[uiCW][0]).ClampRGB(); + afrgbaSelectors[1] = (frgbaColor + s_aafCwTable[uiCW][1]).ClampRGB(); + afrgbaSelectors[2] = (frgbaColor + s_aafCwTable[uiCW][2]).ClampRGB(); + afrgbaSelectors[3] = (frgbaColor + s_aafCwTable[uiCW][3]).ClampRGB(); + + for (unsigned int uiPixel = 0; uiPixel < 8; uiPixel++) + { + ColorFloatRGBA *pfrgbaSourcePixel = &m_pafrgbaSource[a_phalf->m_pauiPixelMapping[uiPixel]]; + ColorFloatRGBA frgbaDecodedPixel; + + for (unsigned int uiSelector = 0; uiSelector < SELECTORS; uiSelector++) + { + frgbaDecodedPixel = afrgbaSelectors[uiSelector]; + + float fPixelError; + + fPixelError = CalcPixelError(frgbaDecodedPixel, m_afDecodedAlphas[a_phalf->m_pauiPixelMapping[uiPixel]], + *pfrgbaSourcePixel); + + if (fPixelError < afPixelErrors[uiPixel]) + { + auiPixelSelectors[uiPixel] = uiSelector; + afrgbaDecodedPixels[uiPixel] = frgbaDecodedPixel; + afPixelErrors[uiPixel] = fPixelError; + } + + } + } + + // add up all pixel errors + float fCWError = 0.0f; + for (unsigned int uiPixel = 0; uiPixel < 8; uiPixel++) + { + fCWError += afPixelErrors[uiPixel]; + } + + // if best CW so far + if (fCWError < ptry->m_fError) + { + ptry->m_uiCW = uiCW; + for (unsigned int uiPixel = 0; uiPixel < 8; uiPixel++) + { + ptry->m_auiSelectors[uiPixel] = auiPixelSelectors[uiPixel]; + } + ptry->m_fError = fCWError; + } + + } + + if (ptry->m_fError < fBestTryError) + { + a_phalf->m_ptryBest = ptry; + fBestTryError = ptry->m_fError; + } + + assert(ptry->m_fError < FLT_MAX); + + a_phalf->m_uiTrys++; + } + } + } + + } + + // ---------------------------------------------------------------------------------------------------- + // try an ETC1 individual mode encoding + // use a_boolFlip to set the encoding F bit + // use a_uiRadius to alter basecolor components in the range[-a_uiRadius:a_uiRadius] + // replace the encoding if the encoding error is less than previous encoding + // + void Block4x4Encoding_ETC1::TryIndividual(bool a_boolFlip, unsigned int a_uiRadius) + { + + ColorFloatRGBA frgbaColor1; + ColorFloatRGBA frgbaColor2; + + const unsigned int *pauiPixelMapping1; + const unsigned int *pauiPixelMapping2; + + if (a_boolFlip) + { + frgbaColor1 = m_frgbaSourceAverageTop; + frgbaColor2 = m_frgbaSourceAverageBottom; + + pauiPixelMapping1 = s_auiTopPixelMapping; + pauiPixelMapping2 = s_auiBottomPixelMapping; + } + else + { + frgbaColor1 = m_frgbaSourceAverageLeft; + frgbaColor2 = m_frgbaSourceAverageRight; + + pauiPixelMapping1 = s_auiLeftPixelMapping; + pauiPixelMapping2 = s_auiRightPixelMapping; + } + + IndividualTrys trys(frgbaColor1, frgbaColor2, pauiPixelMapping1, pauiPixelMapping2, a_uiRadius); + + Block4x4Encoding_ETC1 encodingTry = *this; + encodingTry.m_boolFlip = a_boolFlip; + + encodingTry.TryIndividualHalf(&trys.m_half1); + encodingTry.TryIndividualHalf(&trys.m_half2); + + // use the best of each half + IndividualTrys::Try *ptryBest1 = trys.m_half1.m_ptryBest; + IndividualTrys::Try *ptryBest2 = trys.m_half2.m_ptryBest; + encodingTry.m_fError = trys.m_half1.m_ptryBest->m_fError + trys.m_half2.m_ptryBest->m_fError; + + if (encodingTry.m_fError < m_fError) + { + m_mode = MODE_ETC1; + m_boolDiff = false; + m_boolFlip = encodingTry.m_boolFlip; + m_frgbaColor1 = ColorFloatRGBA::ConvertFromRGB4((unsigned char)ptryBest1->m_iRed, (unsigned char)ptryBest1->m_iGreen, (unsigned char)ptryBest1->m_iBlue); + m_frgbaColor2 = ColorFloatRGBA::ConvertFromRGB4((unsigned char)ptryBest2->m_iRed, (unsigned char)ptryBest2->m_iGreen, (unsigned char)ptryBest2->m_iBlue); + m_uiCW1 = ptryBest1->m_uiCW; + m_uiCW2 = ptryBest2->m_uiCW; + + for (unsigned int uiPixelOrder = 0; uiPixelOrder < PIXELS / 2; uiPixelOrder++) + { + unsigned int uiPixel1 = pauiPixelMapping1[uiPixelOrder]; + unsigned int uiPixel2 = pauiPixelMapping2[uiPixelOrder]; + + unsigned int uiSelector1 = ptryBest1->m_auiSelectors[uiPixelOrder]; + unsigned int uiSelector2 = ptryBest2->m_auiSelectors[uiPixelOrder]; + + m_auiSelectors[uiPixel1] = uiSelector1; + m_auiSelectors[uiPixel2] = ptryBest2->m_auiSelectors[uiPixelOrder]; + + float fDeltaRGB1 = s_aafCwTable[m_uiCW1][uiSelector1]; + float fDeltaRGB2 = s_aafCwTable[m_uiCW2][uiSelector2]; + + m_afrgbaDecodedColors[uiPixel1] = (m_frgbaColor1 + fDeltaRGB1).ClampRGB(); + m_afrgbaDecodedColors[uiPixel2] = (m_frgbaColor2 + fDeltaRGB2).ClampRGB(); + } + + m_fError1 = ptryBest1->m_fError; + m_fError2 = ptryBest2->m_fError; + m_fError = m_fError1 + m_fError2; + } + + } + + // ---------------------------------------------------------------------------------------------------- + // try an ETC1 differential mode encoding for a half of a 4x4 block + // vary the basecolor components using a radius + // + void Block4x4Encoding_ETC1::TryIndividualHalf(IndividualTrys::Half *a_phalf) + { + + a_phalf->m_ptryBest = nullptr; + float fBestTryError = FLT_MAX; + + a_phalf->m_uiTrys = 0; + for (int iRed = a_phalf->m_iRed - (int)a_phalf->m_uiRadius; + iRed <= a_phalf->m_iRed + (int)a_phalf->m_uiRadius; + iRed++) + { + assert(iRed >= 0 && iRed <= 15); + + for (int iGreen = a_phalf->m_iGreen - (int)a_phalf->m_uiRadius; + iGreen <= a_phalf->m_iGreen + (int)a_phalf->m_uiRadius; + iGreen++) + { + assert(iGreen >= 0 && iGreen <= 15); + + for (int iBlue = a_phalf->m_iBlue - (int)a_phalf->m_uiRadius; + iBlue <= a_phalf->m_iBlue + (int)a_phalf->m_uiRadius; + iBlue++) + { + assert(iBlue >= 0 && iBlue <= 15); + + IndividualTrys::Try *ptry = &a_phalf->m_atry[a_phalf->m_uiTrys]; + assert(ptry < &a_phalf->m_atry[IndividualTrys::Half::MAX_TRYS]); + + ptry->m_iRed = iRed; + ptry->m_iGreen = iGreen; + ptry->m_iBlue = iBlue; + ptry->m_fError = FLT_MAX; + ColorFloatRGBA frgbaColor = ColorFloatRGBA::ConvertFromRGB4((unsigned char)iRed, (unsigned char)iGreen, (unsigned char)iBlue); + + // try each CW + for (unsigned int uiCW = 0; uiCW < CW_RANGES; uiCW++) + { + unsigned int auiPixelSelectors[PIXELS / 2]; + ColorFloatRGBA afrgbaDecodedPixels[PIXELS / 2]; + float afPixelErrors[PIXELS / 2] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, + FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }; + + // pre-compute decoded pixels for each selector + ColorFloatRGBA afrgbaSelectors[SELECTORS]; + assert(SELECTORS == 4); + afrgbaSelectors[0] = (frgbaColor + s_aafCwTable[uiCW][0]).ClampRGB(); + afrgbaSelectors[1] = (frgbaColor + s_aafCwTable[uiCW][1]).ClampRGB(); + afrgbaSelectors[2] = (frgbaColor + s_aafCwTable[uiCW][2]).ClampRGB(); + afrgbaSelectors[3] = (frgbaColor + s_aafCwTable[uiCW][3]).ClampRGB(); + + for (unsigned int uiPixel = 0; uiPixel < 8; uiPixel++) + { + ColorFloatRGBA *pfrgbaSourcePixel = &m_pafrgbaSource[a_phalf->m_pauiPixelMapping[uiPixel]]; + ColorFloatRGBA frgbaDecodedPixel; + + for (unsigned int uiSelector = 0; uiSelector < SELECTORS; uiSelector++) + { + frgbaDecodedPixel = afrgbaSelectors[uiSelector]; + + float fPixelError; + + fPixelError = CalcPixelError(frgbaDecodedPixel, m_afDecodedAlphas[a_phalf->m_pauiPixelMapping[uiPixel]], + *pfrgbaSourcePixel); + + if (fPixelError < afPixelErrors[uiPixel]) + { + auiPixelSelectors[uiPixel] = uiSelector; + afrgbaDecodedPixels[uiPixel] = frgbaDecodedPixel; + afPixelErrors[uiPixel] = fPixelError; + } + + } + } + + // add up all pixel errors + float fCWError = 0.0f; + for (unsigned int uiPixel = 0; uiPixel < 8; uiPixel++) + { + fCWError += afPixelErrors[uiPixel]; + } + + // if best CW so far + if (fCWError < ptry->m_fError) + { + ptry->m_uiCW = uiCW; + for (unsigned int uiPixel = 0; uiPixel < 8; uiPixel++) + { + ptry->m_auiSelectors[uiPixel] = auiPixelSelectors[uiPixel]; + } + ptry->m_fError = fCWError; + } + + } + + if (ptry->m_fError < fBestTryError) + { + a_phalf->m_ptryBest = ptry; + fBestTryError = ptry->m_fError; + } + + assert(ptry->m_fError < FLT_MAX); + + a_phalf->m_uiTrys++; + } + } + } + + } + + // ---------------------------------------------------------------------------------------------------- + // try version 1 of the degenerate search + // degenerate encodings use basecolor movement and a subset of the selectors to find useful encodings + // each subsequent version of the degenerate search uses more basecolor movement and is less likely to + // be successfull + // + void Block4x4Encoding_ETC1::TryDegenerates1(void) + { + + TryDifferential(m_boolMostLikelyFlip, 1, -2, 0); + TryDifferential(m_boolMostLikelyFlip, 1, 2, 0); + TryDifferential(m_boolMostLikelyFlip, 1, 0, 2); + TryDifferential(m_boolMostLikelyFlip, 1, 0, -2); + + } + + // ---------------------------------------------------------------------------------------------------- + // try version 2 of the degenerate search + // degenerate encodings use basecolor movement and a subset of the selectors to find useful encodings + // each subsequent version of the degenerate search uses more basecolor movement and is less likely to + // be successfull + // + void Block4x4Encoding_ETC1::TryDegenerates2(void) + { + + TryDifferential(!m_boolMostLikelyFlip, 1, -2, 0); + TryDifferential(!m_boolMostLikelyFlip, 1, 2, 0); + TryDifferential(!m_boolMostLikelyFlip, 1, 0, 2); + TryDifferential(!m_boolMostLikelyFlip, 1, 0, -2); + + } + + // ---------------------------------------------------------------------------------------------------- + // try version 3 of the degenerate search + // degenerate encodings use basecolor movement and a subset of the selectors to find useful encodings + // each subsequent version of the degenerate search uses more basecolor movement and is less likely to + // be successfull + // + void Block4x4Encoding_ETC1::TryDegenerates3(void) + { + + TryDifferential(m_boolMostLikelyFlip, 1, -2, -2); + TryDifferential(m_boolMostLikelyFlip, 1, -2, 2); + TryDifferential(m_boolMostLikelyFlip, 1, 2, -2); + TryDifferential(m_boolMostLikelyFlip, 1, 2, 2); + + } + + // ---------------------------------------------------------------------------------------------------- + // try version 4 of the degenerate search + // degenerate encodings use basecolor movement and a subset of the selectors to find useful encodings + // each subsequent version of the degenerate search uses more basecolor movement and is less likely to + // be successfull + // + void Block4x4Encoding_ETC1::TryDegenerates4(void) + { + + TryDifferential(m_boolMostLikelyFlip, 1, -4, 0); + TryDifferential(m_boolMostLikelyFlip, 1, 4, 0); + TryDifferential(m_boolMostLikelyFlip, 1, 0, 4); + TryDifferential(m_boolMostLikelyFlip, 1, 0, -4); + + } + + // ---------------------------------------------------------------------------------------------------- + // find the best selector for each pixel based on a particular basecolor and CW that have been previously set + // calculate the selectors for each half of the block separately + // set the block error as the sum of each half's error + // + void Block4x4Encoding_ETC1::CalculateSelectors() + { + if (m_boolFlip) + { + CalculateHalfOfTheSelectors(0, s_auiTopPixelMapping); + CalculateHalfOfTheSelectors(1, s_auiBottomPixelMapping); + } + else + { + CalculateHalfOfTheSelectors(0, s_auiLeftPixelMapping); + CalculateHalfOfTheSelectors(1, s_auiRightPixelMapping); + } + + m_fError = m_fError1 + m_fError2; + } + + // ---------------------------------------------------------------------------------------------------- + // choose best selectors for half of the block + // calculate the error for half of the block + // + void Block4x4Encoding_ETC1::CalculateHalfOfTheSelectors(unsigned int a_uiHalf, + const unsigned int *pauiPixelMapping) + { + static const bool DEBUG_PRINT = false; + + ColorFloatRGBA *pfrgbaColor = a_uiHalf ? &m_frgbaColor2 : &m_frgbaColor1; + unsigned int *puiCW = a_uiHalf ? &m_uiCW2 : &m_uiCW1; + + float *pfHalfError = a_uiHalf ? &m_fError2 : &m_fError1; + *pfHalfError = FLT_MAX; + + // try each CW + for (unsigned int uiCW = 0; uiCW < CW_RANGES; uiCW++) + { + if (DEBUG_PRINT) + { + printf("\ncw=%u\n", uiCW); + } + + unsigned int auiPixelSelectors[PIXELS / 2]; + ColorFloatRGBA afrgbaDecodedPixels[PIXELS / 2]; + float afPixelErrors[PIXELS / 2] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }; + + for (unsigned int uiPixel = 0; uiPixel < 8; uiPixel++) + { + if (DEBUG_PRINT) + { + printf("\tsource [%.2f,%.2f,%.2f]\n", m_pafrgbaSource[pauiPixelMapping[uiPixel]].fR, + m_pafrgbaSource[pauiPixelMapping[uiPixel]].fG, m_pafrgbaSource[pauiPixelMapping[uiPixel]].fB); + } + + ColorFloatRGBA *pfrgbaSourcePixel = &m_pafrgbaSource[pauiPixelMapping[uiPixel]]; + ColorFloatRGBA frgbaDecodedPixel; + + for (unsigned int uiSelector = 0; uiSelector < SELECTORS; uiSelector++) + { + float fDeltaRGB = s_aafCwTable[uiCW][uiSelector]; + + frgbaDecodedPixel = (*pfrgbaColor + fDeltaRGB).ClampRGB(); + + float fPixelError; + + fPixelError = CalcPixelError(frgbaDecodedPixel, m_afDecodedAlphas[pauiPixelMapping[uiPixel]], + *pfrgbaSourcePixel); + + if (DEBUG_PRINT) + { + printf("\tpixel %u, index %u [%.2f,%.2f,%.2f], error %.2f", uiPixel, uiSelector, + frgbaDecodedPixel.fR, + frgbaDecodedPixel.fG, + frgbaDecodedPixel.fB, + fPixelError); + } + + if (fPixelError < afPixelErrors[uiPixel]) + { + if (DEBUG_PRINT) + { + printf(" *"); + } + + auiPixelSelectors[uiPixel] = uiSelector; + afrgbaDecodedPixels[uiPixel] = frgbaDecodedPixel; + afPixelErrors[uiPixel] = fPixelError; + } + + if (DEBUG_PRINT) + { + printf("\n"); + } + } + } + + // add up all pixel errors + float fCWError = 0.0f; + for (unsigned int uiPixel = 0; uiPixel < 8; uiPixel++) + { + fCWError += afPixelErrors[uiPixel]; + } + if (DEBUG_PRINT) + { + printf("\terror %.2f\n", fCWError); + } + + // if best CW so far + if (fCWError < *pfHalfError) + { + *pfHalfError = fCWError; + *puiCW = uiCW; + for (unsigned int uiPixel = 0; uiPixel < 8; uiPixel++) + { + m_auiSelectors[pauiPixelMapping[uiPixel]] = auiPixelSelectors[uiPixel]; + m_afrgbaDecodedColors[pauiPixelMapping[uiPixel]] = afrgbaDecodedPixels[uiPixel]; + } + } + } + + } + + // ---------------------------------------------------------------------------------------------------- + // set the encoding bits based on encoding state + // + void Block4x4Encoding_ETC1::SetEncodingBits(void) + { + assert(m_mode == MODE_ETC1); + + if (m_boolDiff) + { + int iRed1 = m_frgbaColor1.IntRed(31.0f); + int iGreen1 = m_frgbaColor1.IntGreen(31.0f); + int iBlue1 = m_frgbaColor1.IntBlue(31.0f); + + int iRed2 = m_frgbaColor2.IntRed(31.0f); + int iGreen2 = m_frgbaColor2.IntGreen(31.0f); + int iBlue2 = m_frgbaColor2.IntBlue(31.0f); + + int iDRed2 = iRed2 - iRed1; + int iDGreen2 = iGreen2 - iGreen1; + int iDBlue2 = iBlue2 - iBlue1; + + assert(iDRed2 >= -4 && iDRed2 < 4); + assert(iDGreen2 >= -4 && iDGreen2 < 4); + assert(iDBlue2 >= -4 && iDBlue2 < 4); + + m_pencodingbitsRGB8->differential.red1 = (unsigned int)iRed1; + m_pencodingbitsRGB8->differential.green1 = (unsigned int)iGreen1; + m_pencodingbitsRGB8->differential.blue1 = (unsigned int)iBlue1; + + m_pencodingbitsRGB8->differential.dred2 = iDRed2; + m_pencodingbitsRGB8->differential.dgreen2 = iDGreen2; + m_pencodingbitsRGB8->differential.dblue2 = iDBlue2; + } + else + { + m_pencodingbitsRGB8->individual.red1 = (unsigned int)m_frgbaColor1.IntRed(15.0f); + m_pencodingbitsRGB8->individual.green1 = (unsigned int)m_frgbaColor1.IntGreen(15.0f); + m_pencodingbitsRGB8->individual.blue1 = (unsigned int)m_frgbaColor1.IntBlue(15.0f); + + m_pencodingbitsRGB8->individual.red2 = (unsigned int)m_frgbaColor2.IntRed(15.0f); + m_pencodingbitsRGB8->individual.green2 = (unsigned int)m_frgbaColor2.IntGreen(15.0f); + m_pencodingbitsRGB8->individual.blue2 = (unsigned int)m_frgbaColor2.IntBlue(15.0f); + } + + m_pencodingbitsRGB8->individual.cw1 = m_uiCW1; + m_pencodingbitsRGB8->individual.cw2 = m_uiCW2; + + SetEncodingBits_Selectors(); + + m_pencodingbitsRGB8->individual.diff = (unsigned int)m_boolDiff; + m_pencodingbitsRGB8->individual.flip = (unsigned int)m_boolFlip; + + } + + // ---------------------------------------------------------------------------------------------------- + // set the selectors in the encoding bits + // + void Block4x4Encoding_ETC1::SetEncodingBits_Selectors(void) + { + + m_pencodingbitsRGB8->individual.selectors = 0; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + unsigned int uiSelector = m_auiSelectors[uiPixel]; + + // set index msb + m_pencodingbitsRGB8->individual.selectors |= (uiSelector >> 1) << (uiPixel ^ 8); + + // set index lsb + m_pencodingbitsRGB8->individual.selectors |= (uiSelector & 1) << ((16 + uiPixel) ^ 8); + } + + } + + // ---------------------------------------------------------------------------------------------------- + // set the decoded colors and decoded alpha based on the encoding state + // + void Block4x4Encoding_ETC1::Decode(void) + { + + const unsigned int *pauiPixelOrder = m_boolFlip ? s_auiPixelOrderFlip1 : s_auiPixelOrderFlip0; + + for (unsigned int uiPixelOrder = 0; uiPixelOrder < PIXELS; uiPixelOrder++) + { + ColorFloatRGBA *pfrgbaCenter = uiPixelOrder < 8 ? &m_frgbaColor1 : &m_frgbaColor2; + unsigned int uiCW = uiPixelOrder < 8 ? m_uiCW1 : m_uiCW2; + + unsigned int uiPixel = pauiPixelOrder[uiPixelOrder]; + + float fDelta = s_aafCwTable[uiCW][m_auiSelectors[uiPixel]]; + m_afrgbaDecodedColors[uiPixel] = (*pfrgbaCenter + fDelta).ClampRGB(); + m_afDecodedAlphas[uiPixel] = 1.0f; + } + + } + + // ---------------------------------------------------------------------------------------------------- + // + +} // namespace Etc diff --git a/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_ETC1.h b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_ETC1.h new file mode 100644 index 0000000..816aa05 --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_ETC1.h @@ -0,0 +1,186 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +#pragma once + +#include "EtcBlock4x4Encoding.h" +#include "EtcBlock4x4EncodingBits.h" +#include "EtcDifferentialTrys.h" +#include "EtcIndividualTrys.h" + +namespace Etc +{ + + // base class for Block4x4Encoding_RGB8 + class Block4x4Encoding_ETC1 : public Block4x4Encoding + { + public: + + Block4x4Encoding_ETC1(void); + virtual ~Block4x4Encoding_ETC1(void); + + virtual void InitFromSource(Block4x4 *a_pblockParent, + ColorFloatRGBA *a_pafrgbaSource, + + unsigned char *a_paucEncodingBits, + ErrorMetric a_errormetric); + + virtual void InitFromEncodingBits(Block4x4 *a_pblockParent, + unsigned char *a_paucEncodingBits, + ColorFloatRGBA *a_pafrgbaSource, + + ErrorMetric a_errormetric); + + virtual void PerformIteration(float a_fEffort); + + inline virtual bool GetFlip(void) + { + return m_boolFlip; + } + + inline virtual bool IsDifferential(void) + { + return m_boolDiff; + } + + virtual void SetEncodingBits(void); + + void Decode(void); + + inline ColorFloatRGBA GetColor1(void) const + { + return m_frgbaColor1; + } + + inline ColorFloatRGBA GetColor2(void) const + { + return m_frgbaColor2; + } + + inline const unsigned int * GetSelectors(void) const + { + return m_auiSelectors; + } + + inline unsigned int GetCW1(void) const + { + return m_uiCW1; + } + + inline unsigned int GetCW2(void) const + { + return m_uiCW2; + } + + inline bool HasSeverelyBentDifferentialColors(void) const + { + return m_boolSeverelyBentDifferentialColors; + } + + protected: + + static const unsigned int s_auiPixelOrderFlip0[PIXELS]; + static const unsigned int s_auiPixelOrderFlip1[PIXELS]; + static const unsigned int s_auiPixelOrderHScan[PIXELS]; + + static const unsigned int s_auiLeftPixelMapping[8]; + static const unsigned int s_auiRightPixelMapping[8]; + static const unsigned int s_auiTopPixelMapping[8]; + static const unsigned int s_auiBottomPixelMapping[8]; + + static const unsigned int SELECTOR_BITS = 2; + static const unsigned int SELECTORS = 1 << SELECTOR_BITS; + + static const unsigned int CW_BITS = 3; + static const unsigned int CW_RANGES = 1 << CW_BITS; + + static float s_aafCwTable[CW_RANGES][SELECTORS]; + static unsigned char s_aucDifferentialCwRange[256]; + + static const int MAX_DIFFERENTIAL = 3; + static const int MIN_DIFFERENTIAL = -4; + + void InitFromEncodingBits_Selectors(void); + + void PerformFirstIteration(void); + void CalculateMostLikelyFlip(void); + + void TryDifferential(bool a_boolFlip, unsigned int a_uiRadius, + int a_iGrayOffset1, int a_iGrayOffset2); + void TryDifferentialHalf(DifferentialTrys::Half *a_phalf); + + void TryIndividual(bool a_boolFlip, unsigned int a_uiRadius); + void TryIndividualHalf(IndividualTrys::Half *a_phalf); + + void TryDegenerates1(void); + void TryDegenerates2(void); + void TryDegenerates3(void); + void TryDegenerates4(void); + + void CalculateSelectors(); + void CalculateHalfOfTheSelectors(unsigned int a_uiHalf, + const unsigned int *pauiPixelMapping); + + // calculate the distance2 of r_frgbaPixel from r_frgbaTarget's gray line + inline float CalcGrayDistance2(ColorFloatRGBA &r_frgbaPixel, + ColorFloatRGBA &r_frgbaTarget) + { + float fDeltaGray = ((r_frgbaPixel.fR - r_frgbaTarget.fR) + + (r_frgbaPixel.fG - r_frgbaTarget.fG) + + (r_frgbaPixel.fB - r_frgbaTarget.fB)) / 3.0f; + + ColorFloatRGBA frgbaPointOnGrayLine = (r_frgbaTarget + fDeltaGray).ClampRGB(); + + float fDR = r_frgbaPixel.fR - frgbaPointOnGrayLine.fR; + float fDG = r_frgbaPixel.fG - frgbaPointOnGrayLine.fG; + float fDB = r_frgbaPixel.fB - frgbaPointOnGrayLine.fB; + + return (fDR*fDR) + (fDG*fDG) + (fDB*fDB); + } + + void SetEncodingBits_Selectors(void); + + // intermediate encoding + bool m_boolDiff; + bool m_boolFlip; + ColorFloatRGBA m_frgbaColor1; + ColorFloatRGBA m_frgbaColor2; + unsigned int m_uiCW1; + unsigned int m_uiCW2; + unsigned int m_auiSelectors[PIXELS]; + + // state shared between iterations + ColorFloatRGBA m_frgbaSourceAverageLeft; + ColorFloatRGBA m_frgbaSourceAverageRight; + ColorFloatRGBA m_frgbaSourceAverageTop; + ColorFloatRGBA m_frgbaSourceAverageBottom; + bool m_boolMostLikelyFlip; + + // stats + float m_fError1; // error for Etc1 half 1 + float m_fError2; // error for Etc1 half 2 + bool m_boolSeverelyBentDifferentialColors; // only valid if m_boolDiff; + + // final encoding + Block4x4EncodingBits_RGB8 *m_pencodingbitsRGB8; // or RGB8 portion of Block4x4EncodingBits_RGB8A8 + + private: + + void CalculateSourceAverages(void); + + }; + +} // namespace Etc diff --git a/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_R11.cpp b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_R11.cpp new file mode 100644 index 0000000..5dd9884 --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_R11.cpp @@ -0,0 +1,429 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +/* +EtcBlock4x4Encoding_R11.cpp + +Block4x4Encoding_R11 is the encoder to use when targetting file format R11 and SR11 (signed R11). + +*/ + +#include "EtcConfig.h" +#include "EtcBlock4x4Encoding_R11.h" + +#include "EtcBlock4x4EncodingBits.h" +#include "EtcBlock4x4.h" + +#include +#include +#include +#include +#include + +namespace Etc +{ + + // modifier values to use for R11, SR11, RG11 and SRG11 + float Block4x4Encoding_R11::s_aafModifierTable[MODIFIER_TABLE_ENTRYS][SELECTORS] + { + { -3.0f / 255.0f, -6.0f / 255.0f, -9.0f / 255.0f, -15.0f / 255.0f, 2.0f / 255.0f, 5.0f / 255.0f, 8.0f / 255.0f, 14.0f / 255.0f }, + { -3.0f / 255.0f, -7.0f / 255.0f, -10.0f / 255.0f, -13.0f / 255.0f, 2.0f / 255.0f, 6.0f / 255.0f, 9.0f / 255.0f, 12.0f / 255.0f }, + { -2.0f / 255.0f, -5.0f / 255.0f, -8.0f / 255.0f, -13.0f / 255.0f, 1.0f / 255.0f, 4.0f / 255.0f, 7.0f / 255.0f, 12.0f / 255.0f }, + { -2.0f / 255.0f, -4.0f / 255.0f, -6.0f / 255.0f, -13.0f / 255.0f, 1.0f / 255.0f, 3.0f / 255.0f, 5.0f / 255.0f, 12.0f / 255.0f }, + + { -3.0f / 255.0f, -6.0f / 255.0f, -8.0f / 255.0f, -12.0f / 255.0f, 2.0f / 255.0f, 5.0f / 255.0f, 7.0f / 255.0f, 11.0f / 255.0f }, + { -3.0f / 255.0f, -7.0f / 255.0f, -9.0f / 255.0f, -11.0f / 255.0f, 2.0f / 255.0f, 6.0f / 255.0f, 8.0f / 255.0f, 10.0f / 255.0f }, + { -4.0f / 255.0f, -7.0f / 255.0f, -8.0f / 255.0f, -11.0f / 255.0f, 3.0f / 255.0f, 6.0f / 255.0f, 7.0f / 255.0f, 10.0f / 255.0f }, + { -3.0f / 255.0f, -5.0f / 255.0f, -8.0f / 255.0f, -11.0f / 255.0f, 2.0f / 255.0f, 4.0f / 255.0f, 7.0f / 255.0f, 10.0f / 255.0f }, + + { -2.0f / 255.0f, -6.0f / 255.0f, -8.0f / 255.0f, -10.0f / 255.0f, 1.0f / 255.0f, 5.0f / 255.0f, 7.0f / 255.0f, 9.0f / 255.0f }, + { -2.0f / 255.0f, -5.0f / 255.0f, -8.0f / 255.0f, -10.0f / 255.0f, 1.0f / 255.0f, 4.0f / 255.0f, 7.0f / 255.0f, 9.0f / 255.0f }, + { -2.0f / 255.0f, -4.0f / 255.0f, -8.0f / 255.0f, -10.0f / 255.0f, 1.0f / 255.0f, 3.0f / 255.0f, 7.0f / 255.0f, 9.0f / 255.0f }, + { -2.0f / 255.0f, -5.0f / 255.0f, -7.0f / 255.0f, -10.0f / 255.0f, 1.0f / 255.0f, 4.0f / 255.0f, 6.0f / 255.0f, 9.0f / 255.0f }, + + { -3.0f / 255.0f, -4.0f / 255.0f, -7.0f / 255.0f, -10.0f / 255.0f, 2.0f / 255.0f, 3.0f / 255.0f, 6.0f / 255.0f, 9.0f / 255.0f }, + { -1.0f / 255.0f, -2.0f / 255.0f, -3.0f / 255.0f, -10.0f / 255.0f, 0.0f / 255.0f, 1.0f / 255.0f, 2.0f / 255.0f, 9.0f / 255.0f }, + { -4.0f / 255.0f, -6.0f / 255.0f, -8.0f / 255.0f, -9.0f / 255.0f, 3.0f / 255.0f, 5.0f / 255.0f, 7.0f / 255.0f, 8.0f / 255.0f }, + { -3.0f / 255.0f, -5.0f / 255.0f, -7.0f / 255.0f, -9.0f / 255.0f, 2.0f / 255.0f, 4.0f / 255.0f, 6.0f / 255.0f, 8.0f / 255.0f } + }; + + // ---------------------------------------------------------------------------------------------------- + // + Block4x4Encoding_R11::Block4x4Encoding_R11(void) + { + + m_pencodingbitsR11 = nullptr; + + } + + Block4x4Encoding_R11::~Block4x4Encoding_R11(void) {} + // ---------------------------------------------------------------------------------------------------- + // initialization prior to encoding + // a_pblockParent points to the block associated with this encoding + // a_errormetric is used to choose the best encoding + // a_pafrgbaSource points to a 4x4 block subset of the source image + // a_paucEncodingBits points to the final encoding bits + // + void Block4x4Encoding_R11::InitFromSource(Block4x4 *a_pblockParent, + ColorFloatRGBA *a_pafrgbaSource, + unsigned char *a_paucEncodingBits, ErrorMetric a_errormetric) + { + Block4x4Encoding::Init(a_pblockParent, a_pafrgbaSource,a_errormetric); + + m_pencodingbitsR11 = (Block4x4EncodingBits_R11 *)a_paucEncodingBits; + } + + // ---------------------------------------------------------------------------------------------------- + // initialization from the encoding bits of a previous encoding + // a_pblockParent points to the block associated with this encoding + // a_errormetric is used to choose the best encoding + // a_pafrgbaSource points to a 4x4 block subset of the source image + // a_paucEncodingBits points to the final encoding bits of a previous encoding + // + void Block4x4Encoding_R11::InitFromEncodingBits(Block4x4 *a_pblockParent, + unsigned char *a_paucEncodingBits, + ColorFloatRGBA *a_pafrgbaSource, + ErrorMetric a_errormetric) + { + m_pencodingbitsR11 = (Block4x4EncodingBits_R11 *)a_paucEncodingBits; + + // init RGB portion + Block4x4Encoding_RGB8::InitFromEncodingBits(a_pblockParent, + (unsigned char *)m_pencodingbitsR11, + a_pafrgbaSource, + a_errormetric); + + // init R11 portion + { + m_mode = MODE_R11; + if (a_pblockParent->GetImageSource()->GetFormat() == Image::Format::SIGNED_R11 || a_pblockParent->GetImageSource()->GetFormat() == Image::Format::SIGNED_RG11) + { + m_fRedBase = (float)(signed char)m_pencodingbitsR11->data.base; + } + else + { + m_fRedBase = (float)(unsigned char)m_pencodingbitsR11->data.base; + } + m_fRedMultiplier = (float)m_pencodingbitsR11->data.multiplier; + m_uiRedModifierTableIndex = m_pencodingbitsR11->data.table; + + unsigned long long int ulliSelectorBits = 0; + ulliSelectorBits |= (unsigned long long int)m_pencodingbitsR11->data.selectors0 << 40; + ulliSelectorBits |= (unsigned long long int)m_pencodingbitsR11->data.selectors1 << 32; + ulliSelectorBits |= (unsigned long long int)m_pencodingbitsR11->data.selectors2 << 24; + ulliSelectorBits |= (unsigned long long int)m_pencodingbitsR11->data.selectors3 << 16; + ulliSelectorBits |= (unsigned long long int)m_pencodingbitsR11->data.selectors4 << 8; + ulliSelectorBits |= (unsigned long long int)m_pencodingbitsR11->data.selectors5; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + unsigned int uiShift = 45 - (3 * uiPixel); + m_auiRedSelectors[uiPixel] = (ulliSelectorBits >> uiShift) & (SELECTORS - 1); + } + + // decode the red channel + // calc red error + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + float fDecodedPixelData = 0.0f; + if (a_pblockParent->GetImageSource()->GetFormat() == Image::Format::R11 || a_pblockParent->GetImageSource()->GetFormat() == Image::Format::RG11) + { + fDecodedPixelData = DecodePixelRed(m_fRedBase, m_fRedMultiplier, + m_uiRedModifierTableIndex, + m_auiRedSelectors[uiPixel]); + } + else if (a_pblockParent->GetImageSource()->GetFormat() == Image::Format::SIGNED_R11 || a_pblockParent->GetImageSource()->GetFormat() == Image::Format::SIGNED_RG11) + { + fDecodedPixelData = DecodePixelRed(m_fRedBase + 128, m_fRedMultiplier, + m_uiRedModifierTableIndex, + m_auiRedSelectors[uiPixel]); + } + else + { + assert(0); + } + m_afrgbaDecodedColors[uiPixel] = ColorFloatRGBA(fDecodedPixelData, 0.0f, 0.0f, 1.0f); + } + CalcBlockError(); + } + + } + + // ---------------------------------------------------------------------------------------------------- + // perform a single encoding iteration + // replace the encoding if a better encoding was found + // subsequent iterations generally take longer for each iteration + // set m_boolDone if encoding is perfect or encoding is finished based on a_fEffort + // + void Block4x4Encoding_R11::PerformIteration(float a_fEffort) + { + assert(!m_boolDone); + m_mode = MODE_R11; + + switch (m_uiEncodingIterations) + { + case 0: + m_fError = FLT_MAX; + m_fRedBlockError = FLT_MAX; // artificially high value + CalculateR11(8, 0.0f, 0.0f); + m_fError = m_fRedBlockError; + break; + + case 1: + CalculateR11(8, 2.0f, 1.0f); + m_fError = m_fRedBlockError; + if (a_fEffort <= 24.5f) + { + m_boolDone = true; + } + break; + + case 2: + CalculateR11(8, 12.0f, 1.0f); + m_fError = m_fRedBlockError; + if (a_fEffort <= 49.5f) + { + m_boolDone = true; + } + break; + + case 3: + CalculateR11(7, 6.0f, 1.0f); + m_fError = m_fRedBlockError; + break; + + case 4: + CalculateR11(6, 3.0f, 1.0f); + m_fError = m_fRedBlockError; + break; + + case 5: + CalculateR11(5, 1.0f, 0.0f); + m_fError = m_fRedBlockError; + m_boolDone = true; + break; + + default: + assert(0); + break; + } + + m_uiEncodingIterations++; + SetDoneIfPerfect(); + } + + // ---------------------------------------------------------------------------------------------------- + // find the best combination of base color, multiplier and selectors + // + // a_uiSelectorsUsed limits the number of selector combinations to try + // a_fBaseRadius limits the range of base colors to try + // a_fMultiplierRadius limits the range of multipliers to try + // + void Block4x4Encoding_R11::CalculateR11(unsigned int a_uiSelectorsUsed, + float a_fBaseRadius, float a_fMultiplierRadius) + { + // maps from virtual (monotonic) selector to ETC selector + static const unsigned int auiVirtualSelectorMap[8] = {3, 2, 1, 0, 4, 5, 6, 7}; + + // find min/max red + float fMinRed = 1.0f; + float fMaxRed = 0.0f; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + // ignore border pixels + float fAlpha = m_pafrgbaSource[uiPixel].fA; + if (isnan(fAlpha)) + { + continue; + } + + float fRed = m_pafrgbaSource[uiPixel].fR; + + if (fRed < fMinRed) + { + fMinRed = fRed; + } + if (fRed > fMaxRed) + { + fMaxRed = fRed; + } + } + assert(fMinRed <= fMaxRed); + + float fRedRange = (fMaxRed - fMinRed); + + // try each modifier table entry + for (unsigned int uiTableEntry = 0; uiTableEntry < MODIFIER_TABLE_ENTRYS; uiTableEntry++) + { + for (unsigned int uiMinVirtualSelector = 0; + uiMinVirtualSelector <= (8- a_uiSelectorsUsed); + uiMinVirtualSelector++) + { + unsigned int uiMaxVirtualSelector = uiMinVirtualSelector + a_uiSelectorsUsed - 1; + + unsigned int uiMinSelector = auiVirtualSelectorMap[uiMinVirtualSelector]; + unsigned int uiMaxSelector = auiVirtualSelectorMap[uiMaxVirtualSelector]; + + float fTableEntryCenter = -s_aafModifierTable[uiTableEntry][uiMinSelector]; + + float fTableEntryRange = s_aafModifierTable[uiTableEntry][uiMaxSelector] - + s_aafModifierTable[uiTableEntry][uiMinSelector]; + + float fCenterRatio = fTableEntryCenter / fTableEntryRange; + + float fCenter = fMinRed + fCenterRatio*fRedRange; + fCenter = roundf(255.0f * fCenter) / 255.0f; + + float fMinBase = fCenter - (a_fBaseRadius / 255.0f); + if (fMinBase < 0.0f) + { + fMinBase = 0.0f; + } + + float fMaxBase = fCenter + (a_fBaseRadius / 255.0f); + if (fMaxBase > 1.0f) + { + fMaxBase = 1.0f; + } + + for (float fBase = fMinBase; fBase <= fMaxBase; fBase += (0.999999f / 255.0f)) + { + float fRangeMultiplier = roundf(fRedRange / fTableEntryRange); + + float fMinMultiplier = fRangeMultiplier - a_fMultiplierRadius; + if (fMinMultiplier < 1.0f) + { + fMinMultiplier = 0.0f; + } + else if (fMinMultiplier > 15.0f) + { + fMinMultiplier = 15.0f; + } + + float fMaxMultiplier = fRangeMultiplier + a_fMultiplierRadius; + if (fMaxMultiplier < 1.0f) + { + fMaxMultiplier = 1.0f; + } + else if (fMaxMultiplier > 15.0f) + { + fMaxMultiplier = 15.0f; + } + + for (float fMultiplier = fMinMultiplier; fMultiplier <= fMaxMultiplier; fMultiplier += 1.0f) + { + // find best selector for each pixel + unsigned int auiBestSelectors[PIXELS]; + float afBestRedError[PIXELS]; + float afBestPixelRed[PIXELS]; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + float fBestPixelRedError = FLT_MAX; + + for (unsigned int uiSelector = 0; uiSelector < SELECTORS; uiSelector++) + { + float fPixelRed = DecodePixelRed(fBase * 255.0f, fMultiplier, uiTableEntry, uiSelector); + + ColorFloatRGBA frgba(fPixelRed, m_pafrgbaSource[uiPixel].fG,0.0f,1.0f); + + float fPixelRedError = CalcPixelError(frgba, 1.0f, m_pafrgbaSource[uiPixel]); + + if (fPixelRedError < fBestPixelRedError) + { + fBestPixelRedError = fPixelRedError; + auiBestSelectors[uiPixel] = uiSelector; + afBestRedError[uiPixel] = fBestPixelRedError; + afBestPixelRed[uiPixel] = fPixelRed; + } + } + } + float fBlockError = 0.0f; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + fBlockError += afBestRedError[uiPixel]; + } + if (fBlockError < m_fRedBlockError) + { + m_fRedBlockError = fBlockError; + + if (m_pblockParent->GetImageSource()->GetFormat() == Image::Format::R11 || m_pblockParent->GetImageSource()->GetFormat() == Image::Format::RG11) + { + m_fRedBase = 255.0f * fBase; + } + else if (m_pblockParent->GetImageSource()->GetFormat() == Image::Format::SIGNED_R11 || m_pblockParent->GetImageSource()->GetFormat() == Image::Format::SIGNED_RG11) + { + m_fRedBase = (fBase * 255) - 128; + } + else + { + assert(0); + } + m_fRedMultiplier = fMultiplier; + m_uiRedModifierTableIndex = uiTableEntry; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_auiRedSelectors[uiPixel] = auiBestSelectors[uiPixel]; + float fBestPixelRed = afBestPixelRed[uiPixel]; + m_afrgbaDecodedColors[uiPixel] = ColorFloatRGBA(fBestPixelRed, 0.0f, 0.0f, 1.0f); + m_afDecodedAlphas[uiPixel] = 1.0f; + } + } + } + } + + } + } + } + + // ---------------------------------------------------------------------------------------------------- + // set the encoding bits based on encoding state + // + void Block4x4Encoding_R11::SetEncodingBits(void) + { + if (m_pblockParent->GetImageSource()->GetFormat() == Image::Format::R11 || m_pblockParent->GetImageSource()->GetFormat() == Image::Format::RG11) + { + m_pencodingbitsR11->data.base = (unsigned char)roundf(m_fRedBase); + } + else if (m_pblockParent->GetImageSource()->GetFormat() == Image::Format::SIGNED_R11 || m_pblockParent->GetImageSource()->GetFormat() == Image::Format::SIGNED_RG11) + { + m_pencodingbitsR11->data.base = (signed char)roundf(m_fRedBase); + } + else + { + assert(0); + } + m_pencodingbitsR11->data.table = m_uiRedModifierTableIndex; + m_pencodingbitsR11->data.multiplier = (unsigned char)roundf(m_fRedMultiplier); + + unsigned long long int ulliSelectorBits = 0; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + unsigned int uiShift = 45 - (3 * uiPixel); + ulliSelectorBits |= ((unsigned long long int)m_auiRedSelectors[uiPixel]) << uiShift; + } + + m_pencodingbitsR11->data.selectors0 = ulliSelectorBits >> 40; + m_pencodingbitsR11->data.selectors1 = ulliSelectorBits >> 32; + m_pencodingbitsR11->data.selectors2 = ulliSelectorBits >> 24; + m_pencodingbitsR11->data.selectors3 = ulliSelectorBits >> 16; + m_pencodingbitsR11->data.selectors4 = ulliSelectorBits >> 8; + m_pencodingbitsR11->data.selectors5 = ulliSelectorBits; + } + + // ---------------------------------------------------------------------------------------------------- + // +} diff --git a/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_R11.h b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_R11.h new file mode 100644 index 0000000..99cb4df --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_R11.h @@ -0,0 +1,122 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +#pragma once + +#include "EtcBlock4x4Encoding_RGB8.h" + +namespace Etc +{ + class Block4x4EncodingBits_R11; + + // ################################################################################ + // Block4x4Encoding_R11 + // ################################################################################ + + class Block4x4Encoding_R11 : public Block4x4Encoding_RGB8 + { + public: + + Block4x4Encoding_R11(void); + virtual ~Block4x4Encoding_R11(void); + + virtual void InitFromSource(Block4x4 *a_pblockParent, + ColorFloatRGBA *a_pafrgbaSource, + unsigned char *a_paucEncodingBits, ErrorMetric a_errormetric); + + virtual void InitFromEncodingBits(Block4x4 *a_pblockParent, + unsigned char *a_paucEncodingBits, + ColorFloatRGBA *a_pafrgbaSource, + ErrorMetric a_errormetric); + + virtual void PerformIteration(float a_fEffort); + + virtual void SetEncodingBits(void); + + inline float GetRedBase(void) const + { + return m_fRedBase; + } + + inline float GetRedMultiplier(void) const + { + return m_fRedMultiplier; + } + + inline int GetRedTableIndex(void) const + { + return m_uiRedModifierTableIndex; + } + + inline const unsigned int * GetRedSelectors(void) const + { + return m_auiRedSelectors; + } + + protected: + + static const unsigned int MODIFIER_TABLE_ENTRYS = 16; + static const unsigned int SELECTOR_BITS = 3; + static const unsigned int SELECTORS = 1 << SELECTOR_BITS; + + static float s_aafModifierTable[MODIFIER_TABLE_ENTRYS][SELECTORS]; + + void CalculateR11(unsigned int a_uiSelectorsUsed, + float a_fBaseRadius, float a_fMultiplierRadius); + + + + + inline float DecodePixelRed(float a_fBase, float a_fMultiplier, + unsigned int a_uiTableIndex, unsigned int a_uiSelector) + { + float fMultiplier = a_fMultiplier; + if (fMultiplier <= 0.0f) + { + fMultiplier = 1.0f / 8.0f; + } + + float fPixelRed = a_fBase * 8 + 4 + + 8 * fMultiplier*s_aafModifierTable[a_uiTableIndex][a_uiSelector]*255; + fPixelRed /= 2047.0f; + + if (fPixelRed < 0.0f) + { + fPixelRed = 0.0f; + } + else if (fPixelRed > 1.0f) + { + fPixelRed = 1.0f; + } + + return fPixelRed; + } + + Block4x4EncodingBits_R11 *m_pencodingbitsR11; + + float m_fRedBase; + float m_fRedMultiplier; + float m_fRedBlockError; + unsigned int m_uiRedModifierTableIndex; + unsigned int m_auiRedSelectors[PIXELS]; + + + }; + + // ---------------------------------------------------------------------------------------------------- + // + +} // namespace Etc diff --git a/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RG11.cpp b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RG11.cpp new file mode 100644 index 0000000..a1bc9c9 --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RG11.cpp @@ -0,0 +1,447 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +/* +EtcBlock4x4Encoding_RG11.cpp + +Block4x4Encoding_RG11 is the encoder to use when targetting file format RG11 and SRG11 (signed RG11). + +*/ + +#include "EtcConfig.h" +#include "EtcBlock4x4Encoding_RG11.h" + +#include "EtcBlock4x4EncodingBits.h" +#include "EtcBlock4x4.h" + +#include +#include +#include +#include +#include + +namespace Etc +{ + // ---------------------------------------------------------------------------------------------------- + // + Block4x4Encoding_RG11::Block4x4Encoding_RG11(void) + { + m_pencodingbitsRG11 = nullptr; + } + + Block4x4Encoding_RG11::~Block4x4Encoding_RG11(void) {} + // ---------------------------------------------------------------------------------------------------- + // initialization prior to encoding + // a_pblockParent points to the block associated with this encoding + // a_errormetric is used to choose the best encoding + // a_pafrgbaSource points to a 4x4 block subset of the source image + // a_paucEncodingBits points to the final encoding bits + // + void Block4x4Encoding_RG11::InitFromSource(Block4x4 *a_pblockParent, + ColorFloatRGBA *a_pafrgbaSource, + unsigned char *a_paucEncodingBits, ErrorMetric a_errormetric) + { + Block4x4Encoding::Init(a_pblockParent, a_pafrgbaSource,a_errormetric); + + m_pencodingbitsRG11 = (Block4x4EncodingBits_RG11 *)a_paucEncodingBits; + } + + // ---------------------------------------------------------------------------------------------------- + // initialization from the encoding bits of a previous encoding + // a_pblockParent points to the block associated with this encoding + // a_errormetric is used to choose the best encoding + // a_pafrgbaSource points to a 4x4 block subset of the source image + // a_paucEncodingBits points to the final encoding bits of a previous encoding + // + void Block4x4Encoding_RG11::InitFromEncodingBits(Block4x4 *a_pblockParent, + unsigned char *a_paucEncodingBits, + ColorFloatRGBA *a_pafrgbaSource, + ErrorMetric a_errormetric) + { + + m_pencodingbitsRG11 = (Block4x4EncodingBits_RG11 *)a_paucEncodingBits; + + // init RGB portion + Block4x4Encoding_RGB8::InitFromEncodingBits(a_pblockParent, + (unsigned char *)m_pencodingbitsRG11, + a_pafrgbaSource, + a_errormetric); + m_fError = 0.0f; + + { + m_mode = MODE_RG11; + if (a_pblockParent->GetImageSource()->GetFormat() == Image::Format::SIGNED_RG11) + { + m_fRedBase = (float)(signed char)m_pencodingbitsRG11->data.baseR; + m_fGrnBase = (float)(signed char)m_pencodingbitsRG11->data.baseG; + } + else + { + m_fRedBase = (float)(unsigned char)m_pencodingbitsRG11->data.baseR; + m_fGrnBase = (float)(unsigned char)m_pencodingbitsRG11->data.baseG; + } + m_fRedMultiplier = (float)m_pencodingbitsRG11->data.multiplierR; + m_fGrnMultiplier = (float)m_pencodingbitsRG11->data.multiplierG; + m_uiRedModifierTableIndex = m_pencodingbitsRG11->data.tableIndexR; + m_uiGrnModifierTableIndex = m_pencodingbitsRG11->data.tableIndexG; + + unsigned long long int ulliSelectorBitsR = 0; + ulliSelectorBitsR |= (unsigned long long int)m_pencodingbitsRG11->data.selectorsR0 << 40; + ulliSelectorBitsR |= (unsigned long long int)m_pencodingbitsRG11->data.selectorsR1 << 32; + ulliSelectorBitsR |= (unsigned long long int)m_pencodingbitsRG11->data.selectorsR2 << 24; + ulliSelectorBitsR |= (unsigned long long int)m_pencodingbitsRG11->data.selectorsR3 << 16; + ulliSelectorBitsR |= (unsigned long long int)m_pencodingbitsRG11->data.selectorsR4 << 8; + ulliSelectorBitsR |= (unsigned long long int)m_pencodingbitsRG11->data.selectorsR5; + + unsigned long long int ulliSelectorBitsG = 0; + ulliSelectorBitsG |= (unsigned long long int)m_pencodingbitsRG11->data.selectorsG0 << 40; + ulliSelectorBitsG |= (unsigned long long int)m_pencodingbitsRG11->data.selectorsG1 << 32; + ulliSelectorBitsG |= (unsigned long long int)m_pencodingbitsRG11->data.selectorsG2 << 24; + ulliSelectorBitsG |= (unsigned long long int)m_pencodingbitsRG11->data.selectorsG3 << 16; + ulliSelectorBitsG |= (unsigned long long int)m_pencodingbitsRG11->data.selectorsG4 << 8; + ulliSelectorBitsG |= (unsigned long long int)m_pencodingbitsRG11->data.selectorsG5; + + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + unsigned int uiShift = 45 - (3 * uiPixel); + m_auiRedSelectors[uiPixel] = (ulliSelectorBitsR >> uiShift) & (SELECTORS - 1); + m_auiGrnSelectors[uiPixel] = (ulliSelectorBitsG >> uiShift) & (SELECTORS - 1); + } + + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + float fRedDecodedData = 0.0f; + float fGrnDecodedData = 0.0f; + if (a_pblockParent->GetImageSource()->GetFormat() == Image::Format::RG11) + { + fRedDecodedData = DecodePixelRed(m_fRedBase, m_fRedMultiplier, m_uiRedModifierTableIndex, m_auiRedSelectors[uiPixel]); + fGrnDecodedData = DecodePixelRed(m_fGrnBase, m_fGrnMultiplier, m_uiGrnModifierTableIndex, m_auiGrnSelectors[uiPixel]); + } + else if (a_pblockParent->GetImageSource()->GetFormat() == Image::Format::SIGNED_RG11) + { + fRedDecodedData = DecodePixelRed(m_fRedBase + 128, m_fRedMultiplier, m_uiRedModifierTableIndex, m_auiRedSelectors[uiPixel]); + fGrnDecodedData = DecodePixelRed(m_fGrnBase + 128, m_fGrnMultiplier, m_uiGrnModifierTableIndex, m_auiGrnSelectors[uiPixel]); + } + else + { + assert(0); + } + m_afrgbaDecodedColors[uiPixel] = ColorFloatRGBA(fRedDecodedData, fGrnDecodedData, 0.0f, 1.0f); + } + + } + + CalcBlockError(); + } + + // ---------------------------------------------------------------------------------------------------- + // perform a single encoding iteration + // replace the encoding if a better encoding was found + // subsequent iterations generally take longer for each iteration + // set m_boolDone if encoding is perfect or encoding is finished based on a_fEffort + // + void Block4x4Encoding_RG11::PerformIteration(float a_fEffort) + { + assert(!m_boolDone); + + switch (m_uiEncodingIterations) + { + case 0: + m_fError = FLT_MAX; + m_fGrnBlockError = FLT_MAX; // artificially high value + m_fRedBlockError = FLT_MAX; + CalculateR11(8, 0.0f, 0.0f); + CalculateG11(8, 0.0f, 0.0f); + m_fError = (m_fGrnBlockError + m_fRedBlockError); + break; + + case 1: + CalculateR11(8, 2.0f, 1.0f); + CalculateG11(8, 2.0f, 1.0f); + m_fError = (m_fGrnBlockError + m_fRedBlockError); + if (a_fEffort <= 24.5f) + { + m_boolDone = true; + } + break; + + case 2: + CalculateR11(8, 12.0f, 1.0f); + CalculateG11(8, 12.0f, 1.0f); + m_fError = (m_fGrnBlockError + m_fRedBlockError); + if (a_fEffort <= 49.5f) + { + m_boolDone = true; + } + break; + + case 3: + CalculateR11(7, 6.0f, 1.0f); + CalculateG11(7, 6.0f, 1.0f); + m_fError = (m_fGrnBlockError + m_fRedBlockError); + break; + + case 4: + CalculateR11(6, 3.0f, 1.0f); + CalculateG11(6, 3.0f, 1.0f); + m_fError = (m_fGrnBlockError + m_fRedBlockError); + break; + + case 5: + CalculateR11(5, 1.0f, 0.0f); + CalculateG11(5, 1.0f, 0.0f); + m_fError = (m_fGrnBlockError + m_fRedBlockError); + m_boolDone = true; + break; + + default: + assert(0); + break; + } + + m_uiEncodingIterations++; + SetDoneIfPerfect(); + } + + // ---------------------------------------------------------------------------------------------------- + // find the best combination of base color, multiplier and selectors + // + // a_uiSelectorsUsed limits the number of selector combinations to try + // a_fBaseRadius limits the range of base colors to try + // a_fMultiplierRadius limits the range of multipliers to try + // + void Block4x4Encoding_RG11::CalculateG11(unsigned int a_uiSelectorsUsed, + float a_fBaseRadius, float a_fMultiplierRadius) + { + // maps from virtual (monotonic) selector to etc selector + static const unsigned int auiVirtualSelectorMap[8] = { 3, 2, 1, 0, 4, 5, 6, 7 }; + + // find min/max Grn + float fMinGrn = 1.0f; + float fMaxGrn = 0.0f; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + // ignore border pixels + float fAlpha = m_pafrgbaSource[uiPixel].fA; + if (isnan(fAlpha)) + { + continue; + } + + float fGrn = m_pafrgbaSource[uiPixel].fG; + + if (fGrn < fMinGrn) + { + fMinGrn = fGrn; + } + if (fGrn > fMaxGrn) + { + fMaxGrn = fGrn; + } + } + assert(fMinGrn <= fMaxGrn); + + float fGrnRange = (fMaxGrn - fMinGrn); + + // try each modifier table entry + for (unsigned int uiTableEntry = 0; uiTableEntry < MODIFIER_TABLE_ENTRYS; uiTableEntry++) + { + for (unsigned int uiMinVirtualSelector = 0; + uiMinVirtualSelector <= (8 - a_uiSelectorsUsed); + uiMinVirtualSelector++) + { + unsigned int uiMaxVirtualSelector = uiMinVirtualSelector + a_uiSelectorsUsed - 1; + + unsigned int uiMinSelector = auiVirtualSelectorMap[uiMinVirtualSelector]; + unsigned int uiMaxSelector = auiVirtualSelectorMap[uiMaxVirtualSelector]; + + float fTableEntryCenter = -s_aafModifierTable[uiTableEntry][uiMinSelector]; + + float fTableEntryRange = s_aafModifierTable[uiTableEntry][uiMaxSelector] - + s_aafModifierTable[uiTableEntry][uiMinSelector]; + + float fCenterRatio = fTableEntryCenter / fTableEntryRange; + + float fCenter = fMinGrn + fCenterRatio*fGrnRange; + fCenter = roundf(255.0f * fCenter) / 255.0f; + + float fMinBase = fCenter - (a_fBaseRadius / 255.0f); + if (fMinBase < 0.0f) + { + fMinBase = 0.0f; + } + + float fMaxBase = fCenter + (a_fBaseRadius / 255.0f); + if (fMaxBase > 1.0f) + { + fMaxBase = 1.0f; + } + + for (float fBase = fMinBase; fBase <= fMaxBase; fBase += (0.999999f / 255.0f)) + { + float fRangeMultiplier = roundf(fGrnRange / fTableEntryRange); + + float fMinMultiplier = fRangeMultiplier - a_fMultiplierRadius; + if (fMinMultiplier < 1.0f) + { + fMinMultiplier = 0.0f; + } + else if (fMinMultiplier > 15.0f) + { + fMinMultiplier = 15.0f; + } + + float fMaxMultiplier = fRangeMultiplier + a_fMultiplierRadius; + if (fMaxMultiplier < 1.0f) + { + fMaxMultiplier = 1.0f; + } + else if (fMaxMultiplier > 15.0f) + { + fMaxMultiplier = 15.0f; + } + + for (float fMultiplier = fMinMultiplier; fMultiplier <= fMaxMultiplier; fMultiplier += 1.0f) + { + // find best selector for each pixel + unsigned int auiBestSelectors[PIXELS]; + float afBestGrnError[PIXELS]; + float afBestPixelGrn[PIXELS]; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + float fBestPixelGrnError = FLT_MAX; + + for (unsigned int uiSelector = 0; uiSelector < SELECTORS; uiSelector++) + { + //DecodePixelRed is not red channel specific + float fPixelGrn = DecodePixelRed(fBase * 255.0f, fMultiplier, uiTableEntry, uiSelector); + + ColorFloatRGBA frgba(m_pafrgbaSource[uiPixel].fR, fPixelGrn, 0.0f, 1.0f); + + float fPixelGrnError = CalcPixelError(frgba, 1.0f, m_pafrgbaSource[uiPixel]); + + if (fPixelGrnError < fBestPixelGrnError) + { + fBestPixelGrnError = fPixelGrnError; + auiBestSelectors[uiPixel] = uiSelector; + afBestGrnError[uiPixel] = fBestPixelGrnError; + afBestPixelGrn[uiPixel] = fPixelGrn; + } + } + } + float fBlockError = 0.0f; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + fBlockError += afBestGrnError[uiPixel]; + } + + if (fBlockError < m_fGrnBlockError) + { + m_fGrnBlockError = fBlockError; + + if (m_pblockParent->GetImageSource()->GetFormat() == Image::Format::RG11) + { + m_fGrnBase = 255.0f * fBase; + } + else if (m_pblockParent->GetImageSource()->GetFormat() == Image::Format::SIGNED_RG11) + { + m_fGrnBase = (fBase * 255) - 128; + } + else + { + assert(0); + } + m_fGrnMultiplier = fMultiplier; + m_uiGrnModifierTableIndex = uiTableEntry; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_auiGrnSelectors[uiPixel] = auiBestSelectors[uiPixel]; + m_afrgbaDecodedColors[uiPixel].fG = afBestPixelGrn[uiPixel]; + m_afDecodedAlphas[uiPixel] = 1.0f; + } + } + } + } + + } + } + } + + // ---------------------------------------------------------------------------------------------------- + // set the encoding bits based on encoding state + // + void Block4x4Encoding_RG11::SetEncodingBits(void) + { + unsigned long long int ulliSelectorBitsR = 0; + unsigned long long int ulliSelectorBitsG = 0; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + unsigned int uiShift = 45 - (3 * uiPixel); + ulliSelectorBitsR |= ((unsigned long long int)m_auiRedSelectors[uiPixel]) << uiShift; + ulliSelectorBitsG |= ((unsigned long long int)m_auiGrnSelectors[uiPixel]) << uiShift; + } + if (m_pblockParent->GetImageSource()->GetFormat() == Image::Format::RG11) + { + m_pencodingbitsRG11->data.baseR = (unsigned char)roundf(m_fRedBase); + } + else if (m_pblockParent->GetImageSource()->GetFormat() == Image::Format::SIGNED_RG11) + { + m_pencodingbitsRG11->data.baseR = (signed char)roundf(m_fRedBase); + } + else + { + assert(0); + } + m_pencodingbitsRG11->data.tableIndexR = m_uiRedModifierTableIndex; + m_pencodingbitsRG11->data.multiplierR = (unsigned char)roundf(m_fRedMultiplier); + + m_pencodingbitsRG11->data.selectorsR0 = ulliSelectorBitsR >> 40; + m_pencodingbitsRG11->data.selectorsR1 = ulliSelectorBitsR >> 32; + m_pencodingbitsRG11->data.selectorsR2 = ulliSelectorBitsR >> 24; + m_pencodingbitsRG11->data.selectorsR3 = ulliSelectorBitsR >> 16; + m_pencodingbitsRG11->data.selectorsR4 = ulliSelectorBitsR >> 8; + m_pencodingbitsRG11->data.selectorsR5 = ulliSelectorBitsR; + + if (m_pblockParent->GetImageSource()->GetFormat() == Image::Format::RG11) + { + m_pencodingbitsRG11->data.baseG = (unsigned char)roundf(m_fGrnBase); + } + else if (m_pblockParent->GetImageSource()->GetFormat() == Image::Format::SIGNED_RG11) + { + m_pencodingbitsRG11->data.baseG = (signed char)roundf(m_fGrnBase); + } + else + { + assert(0); + } + m_pencodingbitsRG11->data.tableIndexG = m_uiGrnModifierTableIndex; + m_pencodingbitsRG11->data.multiplierG = (unsigned char)roundf(m_fGrnMultiplier); + + m_pencodingbitsRG11->data.selectorsG0 = ulliSelectorBitsG >> 40; + m_pencodingbitsRG11->data.selectorsG1 = ulliSelectorBitsG >> 32; + m_pencodingbitsRG11->data.selectorsG2 = ulliSelectorBitsG >> 24; + m_pencodingbitsRG11->data.selectorsG3 = ulliSelectorBitsG >> 16; + m_pencodingbitsRG11->data.selectorsG4 = ulliSelectorBitsG >> 8; + m_pencodingbitsRG11->data.selectorsG5 = ulliSelectorBitsG; + + } + + // ---------------------------------------------------------------------------------------------------- + // +} diff --git a/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RG11.h b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RG11.h new file mode 100644 index 0000000..4caac1d --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RG11.h @@ -0,0 +1,86 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +#pragma once + +#include "EtcBlock4x4Encoding_RGB8.h" +#include "EtcBlock4x4Encoding_R11.h" + +namespace Etc +{ + class Block4x4EncodingBits_RG11; + + // ################################################################################ + // Block4x4Encoding_RG11 + // ################################################################################ + + class Block4x4Encoding_RG11 : public Block4x4Encoding_R11 + { + float m_fGrnBase; + float m_fGrnMultiplier; + float m_fGrnBlockError; + unsigned int m_auiGrnSelectors[PIXELS]; + unsigned int m_uiGrnModifierTableIndex; + public: + + Block4x4Encoding_RG11(void); + virtual ~Block4x4Encoding_RG11(void); + + virtual void InitFromSource(Block4x4 *a_pblockParent, + ColorFloatRGBA *a_pafrgbaSource, + + unsigned char *a_paucEncodingBits, ErrorMetric a_errormetric); + + virtual void InitFromEncodingBits(Block4x4 *a_pblockParent, + unsigned char *a_paucEncodingBits, + ColorFloatRGBA *a_pafrgbaSource, + + ErrorMetric a_errormetric); + + virtual void PerformIteration(float a_fEffort); + + virtual void SetEncodingBits(void); + + Block4x4EncodingBits_RG11 *m_pencodingbitsRG11; + + void CalculateG11(unsigned int a_uiSelectorsUsed, float a_fBaseRadius, float a_fMultiplierRadius); + + inline float GetGrnBase(void) const + { + return m_fGrnBase; + } + + inline float GetGrnMultiplier(void) const + { + return m_fGrnMultiplier; + } + + inline int GetGrnTableIndex(void) const + { + return m_uiGrnModifierTableIndex; + } + + inline const unsigned int * GetGrnSelectors(void) const + { + return m_auiGrnSelectors; + } + + }; + + // ---------------------------------------------------------------------------------------------------- + // + +} // namespace Etc diff --git a/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGB8.cpp b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGB8.cpp new file mode 100644 index 0000000..4c8b14c --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGB8.cpp @@ -0,0 +1,1728 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +/* +EtcBlock4x4Encoding_RGB8.cpp + +Block4x4Encoding_RGB8 is the encoder to use for the ETC2 extensions when targetting file format RGB8. +This encoder is also used for the ETC2 subset of file format RGBA8. + +Block4x4Encoding_ETC1 encodes the ETC1 subset of RGB8. + +*/ + +#include "EtcConfig.h" +#include "EtcBlock4x4Encoding_RGB8.h" + +#include "EtcBlock4x4EncodingBits.h" +#include "EtcBlock4x4.h" +#include "EtcMath.h" + +#include +#include +#include +#include +#include + +namespace Etc +{ + float Block4x4Encoding_RGB8::s_afTHDistanceTable[TH_DISTANCES] = + { + 3.0f / 255.0f, + 6.0f / 255.0f, + 11.0f / 255.0f, + 16.0f / 255.0f, + 23.0f / 255.0f, + 32.0f / 255.0f, + 41.0f / 255.0f, + 64.0f / 255.0f + }; + + // ---------------------------------------------------------------------------------------------------- + // + Block4x4Encoding_RGB8::Block4x4Encoding_RGB8(void) + { + + m_pencodingbitsRGB8 = nullptr; + + } + + Block4x4Encoding_RGB8::~Block4x4Encoding_RGB8(void) {} + // ---------------------------------------------------------------------------------------------------- + // initialization from the encoding bits of a previous encoding + // a_pblockParent points to the block associated with this encoding + // a_errormetric is used to choose the best encoding + // a_pafrgbaSource points to a 4x4 block subset of the source image + // a_paucEncodingBits points to the final encoding bits of a previous encoding + // + void Block4x4Encoding_RGB8::InitFromEncodingBits(Block4x4 *a_pblockParent, + unsigned char *a_paucEncodingBits, + ColorFloatRGBA *a_pafrgbaSource, + ErrorMetric a_errormetric) + { + + // handle ETC1 modes + Block4x4Encoding_ETC1::InitFromEncodingBits(a_pblockParent, + a_paucEncodingBits, a_pafrgbaSource,a_errormetric); + + m_pencodingbitsRGB8 = (Block4x4EncodingBits_RGB8 *)a_paucEncodingBits; + + // detect if there is a T, H or Planar mode present + if (m_pencodingbitsRGB8->differential.diff) + { + int iRed1 = (int)m_pencodingbitsRGB8->differential.red1; + int iDRed2 = m_pencodingbitsRGB8->differential.dred2; + int iRed2 = iRed1 + iDRed2; + + int iGreen1 = (int)m_pencodingbitsRGB8->differential.green1; + int iDGreen2 = m_pencodingbitsRGB8->differential.dgreen2; + int iGreen2 = iGreen1 + iDGreen2; + + int iBlue1 = (int)m_pencodingbitsRGB8->differential.blue1; + int iDBlue2 = m_pencodingbitsRGB8->differential.dblue2; + int iBlue2 = iBlue1 + iDBlue2; + + if (iRed2 < 0 || iRed2 > 31) + { + InitFromEncodingBits_T(); + } + else if (iGreen2 < 0 || iGreen2 > 31) + { + InitFromEncodingBits_H(); + } + else if (iBlue2 < 0 || iBlue2 > 31) + { + InitFromEncodingBits_Planar(); + } + } + + } + + // ---------------------------------------------------------------------------------------------------- + // initialization from the encoding bits of a previous encoding if T mode is detected + // + void Block4x4Encoding_RGB8::InitFromEncodingBits_T(void) + { + + m_mode = MODE_T; + + unsigned char ucRed1 = (unsigned char)((m_pencodingbitsRGB8->t.red1a << 2) + + m_pencodingbitsRGB8->t.red1b); + unsigned char ucGreen1 = m_pencodingbitsRGB8->t.green1; + unsigned char ucBlue1 = m_pencodingbitsRGB8->t.blue1; + + unsigned char ucRed2 = m_pencodingbitsRGB8->t.red2; + unsigned char ucGreen2 = m_pencodingbitsRGB8->t.green2; + unsigned char ucBlue2 = m_pencodingbitsRGB8->t.blue2; + + m_frgbaColor1 = ColorFloatRGBA::ConvertFromRGB4(ucRed1, ucGreen1, ucBlue1); + m_frgbaColor2 = ColorFloatRGBA::ConvertFromRGB4(ucRed2, ucGreen2, ucBlue2); + + m_uiCW1 = (m_pencodingbitsRGB8->t.da << 1) + m_pencodingbitsRGB8->t.db; + + Block4x4Encoding_ETC1::InitFromEncodingBits_Selectors(); + + DecodePixels_T(); + + CalcBlockError(); + + } + + // ---------------------------------------------------------------------------------------------------- + // initialization from the encoding bits of a previous encoding if H mode is detected + // + void Block4x4Encoding_RGB8::InitFromEncodingBits_H(void) + { + + m_mode = MODE_H; + + unsigned char ucRed1 = m_pencodingbitsRGB8->h.red1; + unsigned char ucGreen1 = (unsigned char)((m_pencodingbitsRGB8->h.green1a << 1) + + m_pencodingbitsRGB8->h.green1b); + unsigned char ucBlue1 = (unsigned char)((m_pencodingbitsRGB8->h.blue1a << 3) + + (m_pencodingbitsRGB8->h.blue1b << 1) + + m_pencodingbitsRGB8->h.blue1c); + + unsigned char ucRed2 = m_pencodingbitsRGB8->h.red2; + unsigned char ucGreen2 = (unsigned char)((m_pencodingbitsRGB8->h.green2a << 1) + + m_pencodingbitsRGB8->h.green2b); + unsigned char ucBlue2 = m_pencodingbitsRGB8->h.blue2; + + m_frgbaColor1 = ColorFloatRGBA::ConvertFromRGB4(ucRed1, ucGreen1, ucBlue1); + m_frgbaColor2 = ColorFloatRGBA::ConvertFromRGB4(ucRed2, ucGreen2, ucBlue2); + + // used to determine the LSB of the CW + unsigned int uiRGB1 = (unsigned int)(((int)ucRed1 << 16) + ((int)ucGreen1 << 8) + (int)ucBlue1); + unsigned int uiRGB2 = (unsigned int)(((int)ucRed2 << 16) + ((int)ucGreen2 << 8) + (int)ucBlue2); + + m_uiCW1 = (m_pencodingbitsRGB8->h.da << 2) + (m_pencodingbitsRGB8->h.db << 1); + if (uiRGB1 >= uiRGB2) + { + m_uiCW1++; + } + + Block4x4Encoding_ETC1::InitFromEncodingBits_Selectors(); + + DecodePixels_H(); + + CalcBlockError(); + + } + + // ---------------------------------------------------------------------------------------------------- + // initialization from the encoding bits of a previous encoding if Planar mode is detected + // + void Block4x4Encoding_RGB8::InitFromEncodingBits_Planar(void) + { + + m_mode = MODE_PLANAR; + + unsigned char ucOriginRed = m_pencodingbitsRGB8->planar.originRed; + unsigned char ucOriginGreen = (unsigned char)((m_pencodingbitsRGB8->planar.originGreen1 << 6) + + m_pencodingbitsRGB8->planar.originGreen2); + unsigned char ucOriginBlue = (unsigned char)((m_pencodingbitsRGB8->planar.originBlue1 << 5) + + (m_pencodingbitsRGB8->planar.originBlue2 << 3) + + (m_pencodingbitsRGB8->planar.originBlue3 << 1) + + m_pencodingbitsRGB8->planar.originBlue4); + + unsigned char ucHorizRed = (unsigned char)((m_pencodingbitsRGB8->planar.horizRed1 << 1) + + m_pencodingbitsRGB8->planar.horizRed2); + unsigned char ucHorizGreen = m_pencodingbitsRGB8->planar.horizGreen; + unsigned char ucHorizBlue = (unsigned char)((m_pencodingbitsRGB8->planar.horizBlue1 << 5) + + m_pencodingbitsRGB8->planar.horizBlue2); + + unsigned char ucVertRed = (unsigned char)((m_pencodingbitsRGB8->planar.vertRed1 << 3) + + m_pencodingbitsRGB8->planar.vertRed2); + unsigned char ucVertGreen = (unsigned char)((m_pencodingbitsRGB8->planar.vertGreen1 << 2) + + m_pencodingbitsRGB8->planar.vertGreen2); + unsigned char ucVertBlue = m_pencodingbitsRGB8->planar.vertBlue; + + m_frgbaColor1 = ColorFloatRGBA::ConvertFromR6G7B6(ucOriginRed, ucOriginGreen, ucOriginBlue); + m_frgbaColor2 = ColorFloatRGBA::ConvertFromR6G7B6(ucHorizRed, ucHorizGreen, ucHorizBlue); + m_frgbaColor3 = ColorFloatRGBA::ConvertFromR6G7B6(ucVertRed, ucVertGreen, ucVertBlue); + + DecodePixels_Planar(); + + CalcBlockError(); + + } + + // ---------------------------------------------------------------------------------------------------- + // perform a single encoding iteration + // replace the encoding if a better encoding was found + // subsequent iterations generally take longer for each iteration + // set m_boolDone if encoding is perfect or encoding is finished based on a_fEffort + // + void Block4x4Encoding_RGB8::PerformIteration(float a_fEffort) + { + assert(!m_boolDone); + + switch (m_uiEncodingIterations) + { + case 0: + Block4x4Encoding_ETC1::PerformFirstIteration(); + if (m_boolDone) + { + break; + } + TryPlanar(0); + SetDoneIfPerfect(); + if (m_boolDone) + { + break; + } + TryTAndH(0); + break; + + case 1: + Block4x4Encoding_ETC1::TryDifferential(m_boolMostLikelyFlip, 1, 0, 0); + break; + + case 2: + Block4x4Encoding_ETC1::TryIndividual(m_boolMostLikelyFlip, 1); + break; + + case 3: + Block4x4Encoding_ETC1::TryDifferential(!m_boolMostLikelyFlip, 1, 0, 0); + break; + + case 4: + Block4x4Encoding_ETC1::TryIndividual(!m_boolMostLikelyFlip, 1); + break; + + case 5: + TryPlanar(1); + if (a_fEffort <= 49.5f) + { + m_boolDone = true; + } + break; + + case 6: + TryTAndH(1); + if (a_fEffort <= 59.5f) + { + m_boolDone = true; + } + break; + + case 7: + Block4x4Encoding_ETC1::TryDegenerates1(); + if (a_fEffort <= 69.5f) + { + m_boolDone = true; + } + break; + + case 8: + Block4x4Encoding_ETC1::TryDegenerates2(); + if (a_fEffort <= 79.5f) + { + m_boolDone = true; + } + break; + + case 9: + Block4x4Encoding_ETC1::TryDegenerates3(); + if (a_fEffort <= 89.5f) + { + m_boolDone = true; + } + break; + + case 10: + Block4x4Encoding_ETC1::TryDegenerates4(); + m_boolDone = true; + break; + + default: + assert(0); + break; + } + + m_uiEncodingIterations++; + + SetDoneIfPerfect(); + } + + // ---------------------------------------------------------------------------------------------------- + // try encoding in Planar mode + // save this encoding if it improves the error + // + void Block4x4Encoding_RGB8::TryPlanar(unsigned int a_uiRadius) + { + Block4x4Encoding_RGB8 encodingTry = *this; + + // init "try" + { + encodingTry.m_mode = MODE_PLANAR; + encodingTry.m_boolDiff = true; + encodingTry.m_boolFlip = false; + } + + encodingTry.CalculatePlanarCornerColors(); + + encodingTry.DecodePixels_Planar(); + + encodingTry.CalcBlockError(); + + if (a_uiRadius > 0) + { + encodingTry.TwiddlePlanar(); + } + + if (encodingTry.m_fError < m_fError) + { + m_mode = MODE_PLANAR; + m_boolDiff = true; + m_boolFlip = false; + m_frgbaColor1 = encodingTry.m_frgbaColor1; + m_frgbaColor2 = encodingTry.m_frgbaColor2; + m_frgbaColor3 = encodingTry.m_frgbaColor3; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_afrgbaDecodedColors[uiPixel] = encodingTry.m_afrgbaDecodedColors[uiPixel]; + } + + m_fError = encodingTry.m_fError; + } + + } + + // ---------------------------------------------------------------------------------------------------- + // try encoding in T mode or H mode + // save this encoding if it improves the error + // + void Block4x4Encoding_RGB8::TryTAndH(unsigned int a_uiRadius) + { + + CalculateBaseColorsForTAndH(); + + TryT(a_uiRadius); + + TryH(a_uiRadius); + + } + + // ---------------------------------------------------------------------------------------------------- + // calculate original values for base colors + // store them in m_frgbaOriginalColor1 and m_frgbaOriginalColor2 + // + void Block4x4Encoding_RGB8::CalculateBaseColorsForTAndH(void) + { + + ColorFloatRGBA frgbaBlockAverage = (m_frgbaSourceAverageLeft + m_frgbaSourceAverageRight) * 0.5f; + + // find pixel farthest from average gray line + unsigned int uiFarthestPixel = 0; + float fFarthestGrayDistance2 = 0.0f; + unsigned int uiTransparentPixels = 0; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + // don't count transparent + if (m_pafrgbaSource[uiPixel].fA == 0.0f) + { + uiTransparentPixels++; + } + else + { + float fGrayDistance2 = CalcGrayDistance2(m_pafrgbaSource[uiPixel], frgbaBlockAverage); + + if (fGrayDistance2 > fFarthestGrayDistance2) + { + uiFarthestPixel = uiPixel; + fFarthestGrayDistance2 = fGrayDistance2; + } + } + } + // a transparent block should not reach this method + assert(uiTransparentPixels < PIXELS); + + // set the original base colors to: + // half way to the farthest pixel and + // the mirror color on the other side of the average + ColorFloatRGBA frgbaOffset = (m_pafrgbaSource[uiFarthestPixel] - frgbaBlockAverage) * 0.5f; + m_frgbaOriginalColor1_TAndH = (frgbaBlockAverage + frgbaOffset).QuantizeR4G4B4(); + m_frgbaOriginalColor2_TAndH = (frgbaBlockAverage - frgbaOffset).ClampRGB().QuantizeR4G4B4(); // the "other side" might be out of range + + // move base colors to find best fit + for (unsigned int uiIteration = 0; uiIteration < 10; uiIteration++) + { + // find the center of pixels closest to each color + float fPixelsCloserToColor1 = 0.0f; + ColorFloatRGBA frgbSumPixelsCloserToColor1; + float fPixelsCloserToColor2 = 0.0f; + ColorFloatRGBA frgbSumPixelsCloserToColor2; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + // don't count transparent pixels + if (m_pafrgbaSource[uiPixel].fA == 0.0f) + { + continue; + } + + float fGrayDistance2ToColor1 = CalcGrayDistance2(m_pafrgbaSource[uiPixel], m_frgbaOriginalColor1_TAndH); + float fGrayDistance2ToColor2 = CalcGrayDistance2(m_pafrgbaSource[uiPixel], m_frgbaOriginalColor2_TAndH); + + ColorFloatRGBA frgbaAlphaWeightedSource = m_pafrgbaSource[uiPixel] * m_pafrgbaSource[uiPixel].fA; + + if (fGrayDistance2ToColor1 <= fGrayDistance2ToColor2) + { + fPixelsCloserToColor1 += m_pafrgbaSource[uiPixel].fA; + frgbSumPixelsCloserToColor1 = frgbSumPixelsCloserToColor1 + frgbaAlphaWeightedSource; + } + else + { + fPixelsCloserToColor2 += m_pafrgbaSource[uiPixel].fA; + frgbSumPixelsCloserToColor2 = frgbSumPixelsCloserToColor2 + frgbaAlphaWeightedSource; + } + } + if (fPixelsCloserToColor1 == 0.0f || fPixelsCloserToColor2 == 0.0f) + { + break; + } + + ColorFloatRGBA frgbAvgColor1Pixels = (frgbSumPixelsCloserToColor1 * (1.0f / fPixelsCloserToColor1)).QuantizeR4G4B4(); + ColorFloatRGBA frgbAvgColor2Pixels = (frgbSumPixelsCloserToColor2 * (1.0f / fPixelsCloserToColor2)).QuantizeR4G4B4(); + + if (frgbAvgColor1Pixels.fR == m_frgbaOriginalColor1_TAndH.fR && + frgbAvgColor1Pixels.fG == m_frgbaOriginalColor1_TAndH.fG && + frgbAvgColor1Pixels.fB == m_frgbaOriginalColor1_TAndH.fB && + frgbAvgColor2Pixels.fR == m_frgbaOriginalColor2_TAndH.fR && + frgbAvgColor2Pixels.fG == m_frgbaOriginalColor2_TAndH.fG && + frgbAvgColor2Pixels.fB == m_frgbaOriginalColor2_TAndH.fB) + { + break; + } + + m_frgbaOriginalColor1_TAndH = frgbAvgColor1Pixels; + m_frgbaOriginalColor2_TAndH = frgbAvgColor2Pixels; + } + + } + + // ---------------------------------------------------------------------------------------------------- + // try encoding in T mode + // save this encoding if it improves the error + // + // since pixels that use base color1 don't use the distance table, color1 and color2 can be twiddled independently + // better encoding can be found if TWIDDLE_RADIUS is set to 2, but it will be much slower + // + void Block4x4Encoding_RGB8::TryT(unsigned int a_uiRadius) + { + Block4x4Encoding_RGB8 encodingTry = *this; + + // init "try" + { + encodingTry.m_mode = MODE_T; + encodingTry.m_boolDiff = true; + encodingTry.m_boolFlip = false; + encodingTry.m_fError = FLT_MAX; + } + + int iColor1Red = m_frgbaOriginalColor1_TAndH.IntRed(15.0f); + int iColor1Green = m_frgbaOriginalColor1_TAndH.IntGreen(15.0f); + int iColor1Blue = m_frgbaOriginalColor1_TAndH.IntBlue(15.0f); + + int iMinRed1 = iColor1Red - (int)a_uiRadius; + if (iMinRed1 < 0) + { + iMinRed1 = 0; + } + int iMaxRed1 = iColor1Red + (int)a_uiRadius; + if (iMaxRed1 > 15) + { + iMinRed1 = 15; + } + + int iMinGreen1 = iColor1Green - (int)a_uiRadius; + if (iMinGreen1 < 0) + { + iMinGreen1 = 0; + } + int iMaxGreen1 = iColor1Green + (int)a_uiRadius; + if (iMaxGreen1 > 15) + { + iMinGreen1 = 15; + } + + int iMinBlue1 = iColor1Blue - (int)a_uiRadius; + if (iMinBlue1 < 0) + { + iMinBlue1 = 0; + } + int iMaxBlue1 = iColor1Blue + (int)a_uiRadius; + if (iMaxBlue1 > 15) + { + iMinBlue1 = 15; + } + + int iColor2Red = m_frgbaOriginalColor2_TAndH.IntRed(15.0f); + int iColor2Green = m_frgbaOriginalColor2_TAndH.IntGreen(15.0f); + int iColor2Blue = m_frgbaOriginalColor2_TAndH.IntBlue(15.0f); + + int iMinRed2 = iColor2Red - (int)a_uiRadius; + if (iMinRed2 < 0) + { + iMinRed2 = 0; + } + int iMaxRed2 = iColor2Red + (int)a_uiRadius; + if (iMaxRed2 > 15) + { + iMinRed2 = 15; + } + + int iMinGreen2 = iColor2Green - (int)a_uiRadius; + if (iMinGreen2 < 0) + { + iMinGreen2 = 0; + } + int iMaxGreen2 = iColor2Green + (int)a_uiRadius; + if (iMaxGreen2 > 15) + { + iMinGreen2 = 15; + } + + int iMinBlue2 = iColor2Blue - (int)a_uiRadius; + if (iMinBlue2 < 0) + { + iMinBlue2 = 0; + } + int iMaxBlue2 = iColor2Blue + (int)a_uiRadius; + if (iMaxBlue2 > 15) + { + iMinBlue2 = 15; + } + + for (unsigned int uiDistance = 0; uiDistance < TH_DISTANCES; uiDistance++) + { + encodingTry.m_uiCW1 = uiDistance; + + // twiddle m_frgbaOriginalColor2_TAndH + // twiddle color2 first, since it affects 3 selectors, while color1 only affects one selector + // + for (int iRed2 = iMinRed2; iRed2 <= iMaxRed2; iRed2++) + { + for (int iGreen2 = iMinGreen2; iGreen2 <= iMaxGreen2; iGreen2++) + { + for (int iBlue2 = iMinBlue2; iBlue2 <= iMaxBlue2; iBlue2++) + { + for (unsigned int uiBaseColorSwaps = 0; uiBaseColorSwaps < 2; uiBaseColorSwaps++) + { + if (uiBaseColorSwaps == 0) + { + encodingTry.m_frgbaColor1 = m_frgbaOriginalColor1_TAndH; + encodingTry.m_frgbaColor2 = ColorFloatRGBA::ConvertFromRGB4((unsigned char)iRed2, (unsigned char)iGreen2, (unsigned char)iBlue2); + } + else + { + encodingTry.m_frgbaColor1 = ColorFloatRGBA::ConvertFromRGB4((unsigned char)iRed2, (unsigned char)iGreen2, (unsigned char)iBlue2); + encodingTry.m_frgbaColor2 = m_frgbaOriginalColor1_TAndH; + } + + encodingTry.TryT_BestSelectorCombination(); + + if (encodingTry.m_fError < m_fError) + { + m_mode = encodingTry.m_mode; + m_boolDiff = encodingTry.m_boolDiff; + m_boolFlip = encodingTry.m_boolFlip; + + m_frgbaColor1 = encodingTry.m_frgbaColor1; + m_frgbaColor2 = encodingTry.m_frgbaColor2; + m_uiCW1 = encodingTry.m_uiCW1; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_auiSelectors[uiPixel] = encodingTry.m_auiSelectors[uiPixel]; + m_afrgbaDecodedColors[uiPixel] = encodingTry.m_afrgbaDecodedColors[uiPixel]; + } + + m_fError = encodingTry.m_fError; + } + } + } + } + } + + // twiddle m_frgbaOriginalColor1_TAndH + for (int iRed1 = iMinRed1; iRed1 <= iMaxRed1; iRed1++) + { + for (int iGreen1 = iMinGreen1; iGreen1 <= iMaxGreen1; iGreen1++) + { + for (int iBlue1 = iMinBlue1; iBlue1 <= iMaxBlue1; iBlue1++) + { + for (unsigned int uiBaseColorSwaps = 0; uiBaseColorSwaps < 2; uiBaseColorSwaps++) + { + if (uiBaseColorSwaps == 0) + { + encodingTry.m_frgbaColor1 = ColorFloatRGBA::ConvertFromRGB4((unsigned char)iRed1, (unsigned char)iGreen1, (unsigned char)iBlue1); + encodingTry.m_frgbaColor2 = m_frgbaOriginalColor2_TAndH; + } + else + { + encodingTry.m_frgbaColor1 = m_frgbaOriginalColor2_TAndH; + encodingTry.m_frgbaColor2 = ColorFloatRGBA::ConvertFromRGB4((unsigned char)iRed1, (unsigned char)iGreen1, (unsigned char)iBlue1); + } + + encodingTry.TryT_BestSelectorCombination(); + + if (encodingTry.m_fError < m_fError) + { + m_mode = encodingTry.m_mode; + m_boolDiff = encodingTry.m_boolDiff; + m_boolFlip = encodingTry.m_boolFlip; + + m_frgbaColor1 = encodingTry.m_frgbaColor1; + m_frgbaColor2 = encodingTry.m_frgbaColor2; + m_uiCW1 = encodingTry.m_uiCW1; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_auiSelectors[uiPixel] = encodingTry.m_auiSelectors[uiPixel]; + m_afrgbaDecodedColors[uiPixel] = encodingTry.m_afrgbaDecodedColors[uiPixel]; + } + + m_fError = encodingTry.m_fError; + } + } + } + } + } + + } + + } + + // ---------------------------------------------------------------------------------------------------- + // find best selector combination for TryT + // called on an encodingTry + // + void Block4x4Encoding_RGB8::TryT_BestSelectorCombination(void) + { + + float fDistance = s_afTHDistanceTable[m_uiCW1]; + + unsigned int auiBestPixelSelectors[PIXELS]; + float afBestPixelErrors[PIXELS] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, + FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }; + ColorFloatRGBA afrgbaBestDecodedPixels[PIXELS]; + ColorFloatRGBA afrgbaDecodedPixel[SELECTORS]; + + assert(SELECTORS == 4); + afrgbaDecodedPixel[0] = m_frgbaColor1; + afrgbaDecodedPixel[1] = (m_frgbaColor2 + fDistance).ClampRGB(); + afrgbaDecodedPixel[2] = m_frgbaColor2; + afrgbaDecodedPixel[3] = (m_frgbaColor2 - fDistance).ClampRGB(); + + // try each selector + for (unsigned int uiSelector = 0; uiSelector < SELECTORS; uiSelector++) + { + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + + float fPixelError = CalcPixelError(afrgbaDecodedPixel[uiSelector], m_afDecodedAlphas[uiPixel], + m_pafrgbaSource[uiPixel]); + + if (fPixelError < afBestPixelErrors[uiPixel]) + { + afBestPixelErrors[uiPixel] = fPixelError; + auiBestPixelSelectors[uiPixel] = uiSelector; + afrgbaBestDecodedPixels[uiPixel] = afrgbaDecodedPixel[uiSelector]; + } + } + } + + + // add up all of the pixel errors + float fBlockError = 0.0f; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + fBlockError += afBestPixelErrors[uiPixel]; + } + + if (fBlockError < m_fError) + { + m_fError = fBlockError; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_auiSelectors[uiPixel] = auiBestPixelSelectors[uiPixel]; + m_afrgbaDecodedColors[uiPixel] = afrgbaBestDecodedPixels[uiPixel]; + } + } + + } + + // ---------------------------------------------------------------------------------------------------- + // try encoding in T mode + // save this encoding if it improves the error + // + // since all pixels use the distance table, color1 and color2 can NOT be twiddled independently + // TWIDDLE_RADIUS of 2 is WAY too slow + // + void Block4x4Encoding_RGB8::TryH(unsigned int a_uiRadius) + { + Block4x4Encoding_RGB8 encodingTry = *this; + + // init "try" + { + encodingTry.m_mode = MODE_H; + encodingTry.m_boolDiff = true; + encodingTry.m_boolFlip = false; + encodingTry.m_fError = FLT_MAX; + } + + int iColor1Red = m_frgbaOriginalColor1_TAndH.IntRed(15.0f); + int iColor1Green = m_frgbaOriginalColor1_TAndH.IntGreen(15.0f); + int iColor1Blue = m_frgbaOriginalColor1_TAndH.IntBlue(15.0f); + + int iMinRed1 = iColor1Red - (int)a_uiRadius; + if (iMinRed1 < 0) + { + iMinRed1 = 0; + } + int iMaxRed1 = iColor1Red + (int)a_uiRadius; + if (iMaxRed1 > 15) + { + iMinRed1 = 15; + } + + int iMinGreen1 = iColor1Green - (int)a_uiRadius; + if (iMinGreen1 < 0) + { + iMinGreen1 = 0; + } + int iMaxGreen1 = iColor1Green + (int)a_uiRadius; + if (iMaxGreen1 > 15) + { + iMinGreen1 = 15; + } + + int iMinBlue1 = iColor1Blue - (int)a_uiRadius; + if (iMinBlue1 < 0) + { + iMinBlue1 = 0; + } + int iMaxBlue1 = iColor1Blue + (int)a_uiRadius; + if (iMaxBlue1 > 15) + { + iMinBlue1 = 15; + } + + int iColor2Red = m_frgbaOriginalColor2_TAndH.IntRed(15.0f); + int iColor2Green = m_frgbaOriginalColor2_TAndH.IntGreen(15.0f); + int iColor2Blue = m_frgbaOriginalColor2_TAndH.IntBlue(15.0f); + + int iMinRed2 = iColor2Red - (int)a_uiRadius; + if (iMinRed2 < 0) + { + iMinRed2 = 0; + } + int iMaxRed2 = iColor2Red + (int)a_uiRadius; + if (iMaxRed2 > 15) + { + iMinRed2 = 15; + } + + int iMinGreen2 = iColor2Green - (int)a_uiRadius; + if (iMinGreen2 < 0) + { + iMinGreen2 = 0; + } + int iMaxGreen2 = iColor2Green + (int)a_uiRadius; + if (iMaxGreen2 > 15) + { + iMinGreen2 = 15; + } + + int iMinBlue2 = iColor2Blue - (int)a_uiRadius; + if (iMinBlue2 < 0) + { + iMinBlue2 = 0; + } + int iMaxBlue2 = iColor2Blue + (int)a_uiRadius; + if (iMaxBlue2 > 15) + { + iMinBlue2 = 15; + } + + for (unsigned int uiDistance = 0; uiDistance < TH_DISTANCES; uiDistance++) + { + encodingTry.m_uiCW1 = uiDistance; + + // twiddle m_frgbaOriginalColor1_TAndH + for (int iRed1 = iMinRed1; iRed1 <= iMaxRed1; iRed1++) + { + for (int iGreen1 = iMinGreen1; iGreen1 <= iMaxGreen1; iGreen1++) + { + for (int iBlue1 = iMinBlue1; iBlue1 <= iMaxBlue1; iBlue1++) + { + encodingTry.m_frgbaColor1 = ColorFloatRGBA::ConvertFromRGB4((unsigned char)iRed1, (unsigned char)iGreen1, (unsigned char)iBlue1); + encodingTry.m_frgbaColor2 = m_frgbaOriginalColor2_TAndH; + + // if color1 == color2, H encoding issues can pop up, so abort + if (iRed1 == iColor2Red && iGreen1 == iColor2Green && iBlue1 == iColor2Blue) + { + continue; + } + + encodingTry.TryH_BestSelectorCombination(); + + if (encodingTry.m_fError < m_fError) + { + m_mode = encodingTry.m_mode; + m_boolDiff = encodingTry.m_boolDiff; + m_boolFlip = encodingTry.m_boolFlip; + + m_frgbaColor1 = encodingTry.m_frgbaColor1; + m_frgbaColor2 = encodingTry.m_frgbaColor2; + m_uiCW1 = encodingTry.m_uiCW1; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_auiSelectors[uiPixel] = encodingTry.m_auiSelectors[uiPixel]; + m_afrgbaDecodedColors[uiPixel] = encodingTry.m_afrgbaDecodedColors[uiPixel]; + } + + m_fError = encodingTry.m_fError; + } + } + } + } + + // twiddle m_frgbaOriginalColor2_TAndH + for (int iRed2 = iMinRed2; iRed2 <= iMaxRed2; iRed2++) + { + for (int iGreen2 = iMinGreen2; iGreen2 <= iMaxGreen2; iGreen2++) + { + for (int iBlue2 = iMinBlue2; iBlue2 <= iMaxBlue2; iBlue2++) + { + encodingTry.m_frgbaColor1 = m_frgbaOriginalColor1_TAndH; + encodingTry.m_frgbaColor2 = ColorFloatRGBA::ConvertFromRGB4((unsigned char)iRed2, (unsigned char)iGreen2, (unsigned char)iBlue2); + + // if color1 == color2, H encoding issues can pop up, so abort + if (iRed2 == iColor1Red && iGreen2 == iColor1Green && iBlue2 == iColor1Blue) + { + continue; + } + + encodingTry.TryH_BestSelectorCombination(); + + if (encodingTry.m_fError < m_fError) + { + m_mode = encodingTry.m_mode; + m_boolDiff = encodingTry.m_boolDiff; + m_boolFlip = encodingTry.m_boolFlip; + + m_frgbaColor1 = encodingTry.m_frgbaColor1; + m_frgbaColor2 = encodingTry.m_frgbaColor2; + m_uiCW1 = encodingTry.m_uiCW1; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_auiSelectors[uiPixel] = encodingTry.m_auiSelectors[uiPixel]; + m_afrgbaDecodedColors[uiPixel] = encodingTry.m_afrgbaDecodedColors[uiPixel]; + } + + m_fError = encodingTry.m_fError; + } + } + } + } + + } + + } + + // ---------------------------------------------------------------------------------------------------- + // find best selector combination for TryH + // called on an encodingTry + // + void Block4x4Encoding_RGB8::TryH_BestSelectorCombination(void) + { + + float fDistance = s_afTHDistanceTable[m_uiCW1]; + + unsigned int auiBestPixelSelectors[PIXELS]; + float afBestPixelErrors[PIXELS] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, + FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }; + ColorFloatRGBA afrgbaBestDecodedPixels[PIXELS]; + ColorFloatRGBA afrgbaDecodedPixel[SELECTORS]; + + assert(SELECTORS == 4); + afrgbaDecodedPixel[0] = (m_frgbaColor1 + fDistance).ClampRGB(); + afrgbaDecodedPixel[1] = (m_frgbaColor1 - fDistance).ClampRGB(); + afrgbaDecodedPixel[2] = (m_frgbaColor2 + fDistance).ClampRGB(); + afrgbaDecodedPixel[3] = (m_frgbaColor2 - fDistance).ClampRGB(); + + // try each selector + for (unsigned int uiSelector = 0; uiSelector < SELECTORS; uiSelector++) + { + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + + float fPixelError = CalcPixelError(afrgbaDecodedPixel[uiSelector], m_afDecodedAlphas[uiPixel], + m_pafrgbaSource[uiPixel]); + + if (fPixelError < afBestPixelErrors[uiPixel]) + { + afBestPixelErrors[uiPixel] = fPixelError; + auiBestPixelSelectors[uiPixel] = uiSelector; + afrgbaBestDecodedPixels[uiPixel] = afrgbaDecodedPixel[uiSelector]; + } + } + } + + + // add up all of the pixel errors + float fBlockError = 0.0f; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + fBlockError += afBestPixelErrors[uiPixel]; + } + + if (fBlockError < m_fError) + { + m_fError = fBlockError; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_auiSelectors[uiPixel] = auiBestPixelSelectors[uiPixel]; + m_afrgbaDecodedColors[uiPixel] = afrgbaBestDecodedPixels[uiPixel]; + } + } + + } + + // ---------------------------------------------------------------------------------------------------- + // use linear regression to find the best fit for colors along the edges of the 4x4 block + // + void Block4x4Encoding_RGB8::CalculatePlanarCornerColors(void) + { + ColorFloatRGBA afrgbaRegression[MAX_PLANAR_REGRESSION_SIZE]; + ColorFloatRGBA frgbaSlope; + ColorFloatRGBA frgbaOffset; + + // top edge + afrgbaRegression[0] = m_pafrgbaSource[0]; + afrgbaRegression[1] = m_pafrgbaSource[4]; + afrgbaRegression[2] = m_pafrgbaSource[8]; + afrgbaRegression[3] = m_pafrgbaSource[12]; + ColorRegression(afrgbaRegression, 4, &frgbaSlope, &frgbaOffset); + m_frgbaColor1 = frgbaOffset; + m_frgbaColor2 = (frgbaSlope * 4.0f) + frgbaOffset; + + // left edge + afrgbaRegression[0] = m_pafrgbaSource[0]; + afrgbaRegression[1] = m_pafrgbaSource[1]; + afrgbaRegression[2] = m_pafrgbaSource[2]; + afrgbaRegression[3] = m_pafrgbaSource[3]; + ColorRegression(afrgbaRegression, 4, &frgbaSlope, &frgbaOffset); + m_frgbaColor1 = (m_frgbaColor1 + frgbaOffset) * 0.5f; // average with top edge + m_frgbaColor3 = (frgbaSlope * 4.0f) + frgbaOffset; + + // right edge + afrgbaRegression[0] = m_pafrgbaSource[12]; + afrgbaRegression[1] = m_pafrgbaSource[13]; + afrgbaRegression[2] = m_pafrgbaSource[14]; + afrgbaRegression[3] = m_pafrgbaSource[15]; + ColorRegression(afrgbaRegression, 4, &frgbaSlope, &frgbaOffset); + m_frgbaColor2 = (m_frgbaColor2 + frgbaOffset) * 0.5f; // average with top edge + + // bottom edge + afrgbaRegression[0] = m_pafrgbaSource[3]; + afrgbaRegression[1] = m_pafrgbaSource[7]; + afrgbaRegression[2] = m_pafrgbaSource[11]; + afrgbaRegression[3] = m_pafrgbaSource[15]; + ColorRegression(afrgbaRegression, 4, &frgbaSlope, &frgbaOffset); + m_frgbaColor3 = (m_frgbaColor3 + frgbaOffset) * 0.5f; // average with left edge + + // quantize corner colors to 6/7/6 + m_frgbaColor1 = m_frgbaColor1.QuantizeR6G7B6(); + m_frgbaColor2 = m_frgbaColor2.QuantizeR6G7B6(); + m_frgbaColor3 = m_frgbaColor3.QuantizeR6G7B6(); + + } + + // ---------------------------------------------------------------------------------------------------- + // try different corner colors by slightly changing R, G and B independently + // + // R, G and B decoding and errors are independent, so R, G and B twiddles can be independent + // + // return true if improvement + // + bool Block4x4Encoding_RGB8::TwiddlePlanar(void) + { + bool boolImprovement = false; + + while (TwiddlePlanarR()) + { + boolImprovement = true; + } + + while (TwiddlePlanarG()) + { + boolImprovement = true; + } + + while (TwiddlePlanarB()) + { + boolImprovement = true; + } + + return boolImprovement; + } + + // ---------------------------------------------------------------------------------------------------- + // try different corner colors by slightly changing R + // + bool Block4x4Encoding_RGB8::TwiddlePlanarR() + { + bool boolImprovement = false; + + Block4x4Encoding_RGB8 encodingTry = *this; + + // init "try" + { + encodingTry.m_mode = MODE_PLANAR; + encodingTry.m_boolDiff = true; + encodingTry.m_boolFlip = false; + } + + int iOriginRed = encodingTry.m_frgbaColor1.IntRed(63.0f); + int iHorizRed = encodingTry.m_frgbaColor2.IntRed(63.0f); + int iVertRed = encodingTry.m_frgbaColor3.IntRed(63.0f); + + for (int iTryOriginRed = iOriginRed - 1; iTryOriginRed <= iOriginRed + 1; iTryOriginRed++) + { + // check for out of range + if (iTryOriginRed < 0 || iTryOriginRed > 63) + { + continue; + } + + encodingTry.m_frgbaColor1.fR = ((iTryOriginRed << 2) + (iTryOriginRed >> 4)) / 255.0f; + + for (int iTryHorizRed = iHorizRed - 1; iTryHorizRed <= iHorizRed + 1; iTryHorizRed++) + { + // check for out of range + if (iTryHorizRed < 0 || iTryHorizRed > 63) + { + continue; + } + + encodingTry.m_frgbaColor2.fR = ((iTryHorizRed << 2) + (iTryHorizRed >> 4)) / 255.0f; + + for (int iTryVertRed = iVertRed - 1; iTryVertRed <= iVertRed + 1; iTryVertRed++) + { + // check for out of range + if (iTryVertRed < 0 || iTryVertRed > 63) + { + continue; + } + + // don't bother with null twiddle + if (iTryOriginRed == iOriginRed && iTryHorizRed == iHorizRed && iTryVertRed == iVertRed) + { + continue; + } + + encodingTry.m_frgbaColor3.fR = ((iTryVertRed << 2) + (iTryVertRed >> 4)) / 255.0f; + + encodingTry.DecodePixels_Planar(); + + encodingTry.CalcBlockError(); + + if (encodingTry.m_fError < m_fError) + { + m_mode = MODE_PLANAR; + m_boolDiff = true; + m_boolFlip = false; + m_frgbaColor1 = encodingTry.m_frgbaColor1; + m_frgbaColor2 = encodingTry.m_frgbaColor2; + m_frgbaColor3 = encodingTry.m_frgbaColor3; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_afrgbaDecodedColors[uiPixel] = encodingTry.m_afrgbaDecodedColors[uiPixel]; + } + + m_fError = encodingTry.m_fError; + + boolImprovement = true; + } + } + } + } + + return boolImprovement; + } + + // ---------------------------------------------------------------------------------------------------- + // try different corner colors by slightly changing G + // + bool Block4x4Encoding_RGB8::TwiddlePlanarG() + { + bool boolImprovement = false; + + Block4x4Encoding_RGB8 encodingTry = *this; + + // init "try" + { + encodingTry.m_mode = MODE_PLANAR; + encodingTry.m_boolDiff = true; + encodingTry.m_boolFlip = false; + } + + int iOriginGreen = encodingTry.m_frgbaColor1.IntGreen(127.0f); + int iHorizGreen = encodingTry.m_frgbaColor2.IntGreen(127.0f); + int iVertGreen = encodingTry.m_frgbaColor3.IntGreen(127.0f); + + for (int iTryOriginGreen = iOriginGreen - 1; iTryOriginGreen <= iOriginGreen + 1; iTryOriginGreen++) + { + // check for out of range + if (iTryOriginGreen < 0 || iTryOriginGreen > 127) + { + continue; + } + + encodingTry.m_frgbaColor1.fG = ((iTryOriginGreen << 1) + (iTryOriginGreen >> 6)) / 255.0f; + + for (int iTryHorizGreen = iHorizGreen - 1; iTryHorizGreen <= iHorizGreen + 1; iTryHorizGreen++) + { + // check for out of range + if (iTryHorizGreen < 0 || iTryHorizGreen > 127) + { + continue; + } + + encodingTry.m_frgbaColor2.fG = ((iTryHorizGreen << 1) + (iTryHorizGreen >> 6)) / 255.0f; + + for (int iTryVertGreen = iVertGreen - 1; iTryVertGreen <= iVertGreen + 1; iTryVertGreen++) + { + // check for out of range + if (iTryVertGreen < 0 || iTryVertGreen > 127) + { + continue; + } + + // don't bother with null twiddle + if (iTryOriginGreen == iOriginGreen && + iTryHorizGreen == iHorizGreen && + iTryVertGreen == iVertGreen) + { + continue; + } + + encodingTry.m_frgbaColor3.fG = ((iTryVertGreen << 1) + (iTryVertGreen >> 6)) / 255.0f; + + encodingTry.DecodePixels_Planar(); + + encodingTry.CalcBlockError(); + + if (encodingTry.m_fError < m_fError) + { + m_mode = MODE_PLANAR; + m_boolDiff = true; + m_boolFlip = false; + m_frgbaColor1 = encodingTry.m_frgbaColor1; + m_frgbaColor2 = encodingTry.m_frgbaColor2; + m_frgbaColor3 = encodingTry.m_frgbaColor3; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_afrgbaDecodedColors[uiPixel] = encodingTry.m_afrgbaDecodedColors[uiPixel]; + } + + m_fError = encodingTry.m_fError; + + boolImprovement = true; + } + } + } + } + + return boolImprovement; + } + + // ---------------------------------------------------------------------------------------------------- + // try different corner colors by slightly changing B + // + bool Block4x4Encoding_RGB8::TwiddlePlanarB() + { + bool boolImprovement = false; + + Block4x4Encoding_RGB8 encodingTry = *this; + + // init "try" + { + encodingTry.m_mode = MODE_PLANAR; + encodingTry.m_boolDiff = true; + encodingTry.m_boolFlip = false; + } + + int iOriginBlue = encodingTry.m_frgbaColor1.IntBlue(63.0f); + int iHorizBlue = encodingTry.m_frgbaColor2.IntBlue(63.0f); + int iVertBlue = encodingTry.m_frgbaColor3.IntBlue(63.0f); + + for (int iTryOriginBlue = iOriginBlue - 1; iTryOriginBlue <= iOriginBlue + 1; iTryOriginBlue++) + { + // check for out of range + if (iTryOriginBlue < 0 || iTryOriginBlue > 63) + { + continue; + } + + encodingTry.m_frgbaColor1.fB = ((iTryOriginBlue << 2) + (iTryOriginBlue >> 4)) / 255.0f; + + for (int iTryHorizBlue = iHorizBlue - 1; iTryHorizBlue <= iHorizBlue + 1; iTryHorizBlue++) + { + // check for out of range + if (iTryHorizBlue < 0 || iTryHorizBlue > 63) + { + continue; + } + + encodingTry.m_frgbaColor2.fB = ((iTryHorizBlue << 2) + (iTryHorizBlue >> 4)) / 255.0f; + + for (int iTryVertBlue = iVertBlue - 1; iTryVertBlue <= iVertBlue + 1; iTryVertBlue++) + { + // check for out of range + if (iTryVertBlue < 0 || iTryVertBlue > 63) + { + continue; + } + + // don't bother with null twiddle + if (iTryOriginBlue == iOriginBlue && iTryHorizBlue == iHorizBlue && iTryVertBlue == iVertBlue) + { + continue; + } + + encodingTry.m_frgbaColor3.fB = ((iTryVertBlue << 2) + (iTryVertBlue >> 4)) / 255.0f; + + encodingTry.DecodePixels_Planar(); + + encodingTry.CalcBlockError(); + + if (encodingTry.m_fError < m_fError) + { + m_mode = MODE_PLANAR; + m_boolDiff = true; + m_boolFlip = false; + m_frgbaColor1 = encodingTry.m_frgbaColor1; + m_frgbaColor2 = encodingTry.m_frgbaColor2; + m_frgbaColor3 = encodingTry.m_frgbaColor3; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_afrgbaDecodedColors[uiPixel] = encodingTry.m_afrgbaDecodedColors[uiPixel]; + } + + m_fError = encodingTry.m_fError; + + boolImprovement = true; + } + } + } + } + + return boolImprovement; + } + + // ---------------------------------------------------------------------------------------------------- + // set the encoding bits based on encoding state + // + void Block4x4Encoding_RGB8::SetEncodingBits(void) + { + + switch (m_mode) + { + case MODE_ETC1: + Block4x4Encoding_ETC1::SetEncodingBits(); + break; + + case MODE_T: + SetEncodingBits_T(); + break; + + case MODE_H: + SetEncodingBits_H(); + break; + + case MODE_PLANAR: + SetEncodingBits_Planar(); + break; + + default: + assert(false); + } + + } + + // ---------------------------------------------------------------------------------------------------- + // set the encoding bits based on encoding state for T mode + // + void Block4x4Encoding_RGB8::SetEncodingBits_T(void) + { + static const bool SANITY_CHECK = true; + + assert(m_mode == MODE_T); + assert(m_boolDiff == true); + + unsigned int uiRed1 = (unsigned int)m_frgbaColor1.IntRed(15.0f); + unsigned int uiGreen1 = (unsigned int)m_frgbaColor1.IntGreen(15.0f); + unsigned int uiBlue1 = (unsigned int)m_frgbaColor1.IntBlue(15.0f); + + unsigned int uiRed2 = (unsigned int)m_frgbaColor2.IntRed(15.0f); + unsigned int uiGreen2 = (unsigned int)m_frgbaColor2.IntGreen(15.0f); + unsigned int uiBlue2 = (unsigned int)m_frgbaColor2.IntBlue(15.0f); + + m_pencodingbitsRGB8->t.red1a = uiRed1 >> 2; + m_pencodingbitsRGB8->t.red1b = uiRed1; + m_pencodingbitsRGB8->t.green1 = uiGreen1; + m_pencodingbitsRGB8->t.blue1 = uiBlue1; + + m_pencodingbitsRGB8->t.red2 = uiRed2; + m_pencodingbitsRGB8->t.green2 = uiGreen2; + m_pencodingbitsRGB8->t.blue2 = uiBlue2; + + m_pencodingbitsRGB8->t.da = m_uiCW1 >> 1; + m_pencodingbitsRGB8->t.db = m_uiCW1; + + m_pencodingbitsRGB8->t.diff = 1; + + Block4x4Encoding_ETC1::SetEncodingBits_Selectors(); + + // create an invalid R differential to trigger T mode + m_pencodingbitsRGB8->t.detect1 = 0; + m_pencodingbitsRGB8->t.detect2 = 0; + int iRed2 = (int)m_pencodingbitsRGB8->differential.red1 + (int)m_pencodingbitsRGB8->differential.dred2; + if (iRed2 >= 4) + { + m_pencodingbitsRGB8->t.detect1 = 7; + m_pencodingbitsRGB8->t.detect2 = 0; + } + else + { + m_pencodingbitsRGB8->t.detect1 = 0; + m_pencodingbitsRGB8->t.detect2 = 1; + } + + if (SANITY_CHECK) + { + iRed2 = (int)m_pencodingbitsRGB8->differential.red1 + (int)m_pencodingbitsRGB8->differential.dred2; + + // make sure red overflows + assert(iRed2 < 0 || iRed2 > 31); + } + + } + + // ---------------------------------------------------------------------------------------------------- + // set the encoding bits based on encoding state for H mode + // + // colors and selectors may need to swap in order to generate lsb of distance index + // + void Block4x4Encoding_RGB8::SetEncodingBits_H(void) + { + static const bool SANITY_CHECK = true; + + assert(m_mode == MODE_H); + assert(m_boolDiff == true); + + unsigned int uiRed1 = (unsigned int)m_frgbaColor1.IntRed(15.0f); + unsigned int uiGreen1 = (unsigned int)m_frgbaColor1.IntGreen(15.0f); + unsigned int uiBlue1 = (unsigned int)m_frgbaColor1.IntBlue(15.0f); + + unsigned int uiRed2 = (unsigned int)m_frgbaColor2.IntRed(15.0f); + unsigned int uiGreen2 = (unsigned int)m_frgbaColor2.IntGreen(15.0f); + unsigned int uiBlue2 = (unsigned int)m_frgbaColor2.IntBlue(15.0f); + + unsigned int uiColor1 = (uiRed1 << 16) + (uiGreen1 << 8) + uiBlue1; + unsigned int uiColor2 = (uiRed2 << 16) + (uiGreen2 << 8) + uiBlue2; + + bool boolOddDistance = m_uiCW1 & 1; + bool boolSwapColors = (uiColor1 < uiColor2) ^ !boolOddDistance; + + if (boolSwapColors) + { + m_pencodingbitsRGB8->h.red1 = uiRed2; + m_pencodingbitsRGB8->h.green1a = uiGreen2 >> 1; + m_pencodingbitsRGB8->h.green1b = uiGreen2; + m_pencodingbitsRGB8->h.blue1a = uiBlue2 >> 3; + m_pencodingbitsRGB8->h.blue1b = uiBlue2 >> 1; + m_pencodingbitsRGB8->h.blue1c = uiBlue2; + + m_pencodingbitsRGB8->h.red2 = uiRed1; + m_pencodingbitsRGB8->h.green2a = uiGreen1 >> 1; + m_pencodingbitsRGB8->h.green2b = uiGreen1; + m_pencodingbitsRGB8->h.blue2 = uiBlue1; + + m_pencodingbitsRGB8->h.da = m_uiCW1 >> 2; + m_pencodingbitsRGB8->h.db = m_uiCW1 >> 1; + } + else + { + m_pencodingbitsRGB8->h.red1 = uiRed1; + m_pencodingbitsRGB8->h.green1a = uiGreen1 >> 1; + m_pencodingbitsRGB8->h.green1b = uiGreen1; + m_pencodingbitsRGB8->h.blue1a = uiBlue1 >> 3; + m_pencodingbitsRGB8->h.blue1b = uiBlue1 >> 1; + m_pencodingbitsRGB8->h.blue1c = uiBlue1; + + m_pencodingbitsRGB8->h.red2 = uiRed2; + m_pencodingbitsRGB8->h.green2a = uiGreen2 >> 1; + m_pencodingbitsRGB8->h.green2b = uiGreen2; + m_pencodingbitsRGB8->h.blue2 = uiBlue2; + + m_pencodingbitsRGB8->h.da = m_uiCW1 >> 2; + m_pencodingbitsRGB8->h.db = m_uiCW1 >> 1; + } + + m_pencodingbitsRGB8->h.diff = 1; + + Block4x4Encoding_ETC1::SetEncodingBits_Selectors(); + + if (boolSwapColors) + { + m_pencodingbitsRGB8->h.selectors ^= 0x0000FFFF; + } + + // create an invalid R differential to trigger T mode + m_pencodingbitsRGB8->h.detect1 = 0; + m_pencodingbitsRGB8->h.detect2 = 0; + m_pencodingbitsRGB8->h.detect3 = 0; + int iRed2 = (int)m_pencodingbitsRGB8->differential.red1 + (int)m_pencodingbitsRGB8->differential.dred2; + int iGreen2 = (int)m_pencodingbitsRGB8->differential.green1 + (int)m_pencodingbitsRGB8->differential.dgreen2; + if (iRed2 < 0 || iRed2 > 31) + { + m_pencodingbitsRGB8->h.detect1 = 1; + } + if (iGreen2 >= 4) + { + m_pencodingbitsRGB8->h.detect2 = 7; + m_pencodingbitsRGB8->h.detect3 = 0; + } + else + { + m_pencodingbitsRGB8->h.detect2 = 0; + m_pencodingbitsRGB8->h.detect3 = 1; + } + + if (SANITY_CHECK) + { + iRed2 = (int)m_pencodingbitsRGB8->differential.red1 + (int)m_pencodingbitsRGB8->differential.dred2; + iGreen2 = (int)m_pencodingbitsRGB8->differential.green1 + (int)m_pencodingbitsRGB8->differential.dgreen2; + + // make sure red doesn't overflow and green does + assert(iRed2 >= 0 && iRed2 <= 31); + assert(iGreen2 < 0 || iGreen2 > 31); + } + + } + + // ---------------------------------------------------------------------------------------------------- + // set the encoding bits based on encoding state for Planar mode + // + void Block4x4Encoding_RGB8::SetEncodingBits_Planar(void) + { + static const bool SANITY_CHECK = true; + + assert(m_mode == MODE_PLANAR); + assert(m_boolDiff == true); + + unsigned int uiOriginRed = (unsigned int)m_frgbaColor1.IntRed(63.0f); + unsigned int uiOriginGreen = (unsigned int)m_frgbaColor1.IntGreen(127.0f); + unsigned int uiOriginBlue = (unsigned int)m_frgbaColor1.IntBlue(63.0f); + + unsigned int uiHorizRed = (unsigned int)m_frgbaColor2.IntRed(63.0f); + unsigned int uiHorizGreen = (unsigned int)m_frgbaColor2.IntGreen(127.0f); + unsigned int uiHorizBlue = (unsigned int)m_frgbaColor2.IntBlue(63.0f); + + unsigned int uiVertRed = (unsigned int)m_frgbaColor3.IntRed(63.0f); + unsigned int uiVertGreen = (unsigned int)m_frgbaColor3.IntGreen(127.0f); + unsigned int uiVertBlue = (unsigned int)m_frgbaColor3.IntBlue(63.0f); + + m_pencodingbitsRGB8->planar.originRed = uiOriginRed; + m_pencodingbitsRGB8->planar.originGreen1 = uiOriginGreen >> 6; + m_pencodingbitsRGB8->planar.originGreen2 = uiOriginGreen; + m_pencodingbitsRGB8->planar.originBlue1 = uiOriginBlue >> 5; + m_pencodingbitsRGB8->planar.originBlue2 = uiOriginBlue >> 3; + m_pencodingbitsRGB8->planar.originBlue3 = uiOriginBlue >> 1; + m_pencodingbitsRGB8->planar.originBlue4 = uiOriginBlue; + + m_pencodingbitsRGB8->planar.horizRed1 = uiHorizRed >> 1; + m_pencodingbitsRGB8->planar.horizRed2 = uiHorizRed; + m_pencodingbitsRGB8->planar.horizGreen = uiHorizGreen; + m_pencodingbitsRGB8->planar.horizBlue1 = uiHorizBlue >> 5; + m_pencodingbitsRGB8->planar.horizBlue2 = uiHorizBlue; + + m_pencodingbitsRGB8->planar.vertRed1 = uiVertRed >> 3; + m_pencodingbitsRGB8->planar.vertRed2 = uiVertRed; + m_pencodingbitsRGB8->planar.vertGreen1 = uiVertGreen >> 2; + m_pencodingbitsRGB8->planar.vertGreen2 = uiVertGreen; + m_pencodingbitsRGB8->planar.vertBlue = uiVertBlue; + + m_pencodingbitsRGB8->planar.diff = 1; + + // create valid RG differentials and an invalid B differential to trigger planar mode + m_pencodingbitsRGB8->planar.detect1 = 0; + m_pencodingbitsRGB8->planar.detect2 = 0; + m_pencodingbitsRGB8->planar.detect3 = 0; + m_pencodingbitsRGB8->planar.detect4 = 0; + int iRed2 = (int)m_pencodingbitsRGB8->differential.red1 + (int)m_pencodingbitsRGB8->differential.dred2; + int iGreen2 = (int)m_pencodingbitsRGB8->differential.green1 + (int)m_pencodingbitsRGB8->differential.dgreen2; + int iBlue2 = (int)m_pencodingbitsRGB8->differential.blue1 + (int)m_pencodingbitsRGB8->differential.dblue2; + if (iRed2 < 0 || iRed2 > 31) + { + m_pencodingbitsRGB8->planar.detect1 = 1; + } + if (iGreen2 < 0 || iGreen2 > 31) + { + m_pencodingbitsRGB8->planar.detect2 = 1; + } + if (iBlue2 >= 4) + { + m_pencodingbitsRGB8->planar.detect3 = 7; + m_pencodingbitsRGB8->planar.detect4 = 0; + } + else + { + m_pencodingbitsRGB8->planar.detect3 = 0; + m_pencodingbitsRGB8->planar.detect4 = 1; + } + + if (SANITY_CHECK) + { + iRed2 = (int)m_pencodingbitsRGB8->differential.red1 + (int)m_pencodingbitsRGB8->differential.dred2; + iGreen2 = (int)m_pencodingbitsRGB8->differential.green1 + (int)m_pencodingbitsRGB8->differential.dgreen2; + iBlue2 = (int)m_pencodingbitsRGB8->differential.blue1 + (int)m_pencodingbitsRGB8->differential.dblue2; + + // make sure red and green don't overflow and blue does + assert(iRed2 >= 0 && iRed2 <= 31); + assert(iGreen2 >= 0 && iGreen2 <= 31); + assert(iBlue2 < 0 || iBlue2 > 31); + } + + } + + // ---------------------------------------------------------------------------------------------------- + // set the decoded colors and decoded alpha based on the encoding state for T mode + // + void Block4x4Encoding_RGB8::DecodePixels_T(void) + { + + float fDistance = s_afTHDistanceTable[m_uiCW1]; + ColorFloatRGBA frgbaDistance(fDistance, fDistance, fDistance, 0.0f); + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + switch (m_auiSelectors[uiPixel]) + { + case 0: + m_afrgbaDecodedColors[uiPixel] = m_frgbaColor1; + break; + + case 1: + m_afrgbaDecodedColors[uiPixel] = (m_frgbaColor2 + frgbaDistance).ClampRGB(); + break; + + case 2: + m_afrgbaDecodedColors[uiPixel] = m_frgbaColor2; + break; + + case 3: + m_afrgbaDecodedColors[uiPixel] = (m_frgbaColor2 - frgbaDistance).ClampRGB(); + break; + } + + } + + } + + // ---------------------------------------------------------------------------------------------------- + // set the decoded colors and decoded alpha based on the encoding state for H mode + // + void Block4x4Encoding_RGB8::DecodePixels_H(void) + { + + float fDistance = s_afTHDistanceTable[m_uiCW1]; + ColorFloatRGBA frgbaDistance(fDistance, fDistance, fDistance, 0.0f); + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + switch (m_auiSelectors[uiPixel]) + { + case 0: + m_afrgbaDecodedColors[uiPixel] = (m_frgbaColor1 + frgbaDistance).ClampRGB(); + break; + + case 1: + m_afrgbaDecodedColors[uiPixel] = (m_frgbaColor1 - frgbaDistance).ClampRGB(); + break; + + case 2: + m_afrgbaDecodedColors[uiPixel] = (m_frgbaColor2 + frgbaDistance).ClampRGB(); + break; + + case 3: + m_afrgbaDecodedColors[uiPixel] = (m_frgbaColor2 - frgbaDistance).ClampRGB(); + break; + } + + } + + } + + // ---------------------------------------------------------------------------------------------------- + // set the decoded colors and decoded alpha based on the encoding state for Planar mode + // + void Block4x4Encoding_RGB8::DecodePixels_Planar(void) + { + + int iRO = (int)roundf(m_frgbaColor1.fR * 255.0f); + int iGO = (int)roundf(m_frgbaColor1.fG * 255.0f); + int iBO = (int)roundf(m_frgbaColor1.fB * 255.0f); + + int iRH = (int)roundf(m_frgbaColor2.fR * 255.0f); + int iGH = (int)roundf(m_frgbaColor2.fG * 255.0f); + int iBH = (int)roundf(m_frgbaColor2.fB * 255.0f); + + int iRV = (int)roundf(m_frgbaColor3.fR * 255.0f); + int iGV = (int)roundf(m_frgbaColor3.fG * 255.0f); + int iBV = (int)roundf(m_frgbaColor3.fB * 255.0f); + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + int iX = (int)(uiPixel >> 2); + int iY = (int)(uiPixel & 3); + + int iR = (iX*(iRH - iRO) + iY*(iRV - iRO) + 4*iRO + 2) >> 2; + int iG = (iX*(iGH - iGO) + iY*(iGV - iGO) + 4*iGO + 2) >> 2; + int iB = (iX*(iBH - iBO) + iY*(iBV - iBO) + 4*iBO + 2) >> 2; + + ColorFloatRGBA frgba; + frgba.fR = (float)iR / 255.0f; + frgba.fG = (float)iG / 255.0f; + frgba.fB = (float)iB / 255.0f; + frgba.fA = 1.0f; + + m_afrgbaDecodedColors[uiPixel] = frgba.ClampRGB(); + } + + } + + // ---------------------------------------------------------------------------------------------------- + // perform a linear regression for the a_uiPixels in a_pafrgbaPixels[] + // + // output the closest color line using a_pfrgbaSlope and a_pfrgbaOffset + // + void Block4x4Encoding_RGB8::ColorRegression(ColorFloatRGBA *a_pafrgbaPixels, unsigned int a_uiPixels, + ColorFloatRGBA *a_pfrgbaSlope, ColorFloatRGBA *a_pfrgbaOffset) + { + typedef struct + { + float f[4]; + } Float4; + + Float4 *paf4Pixels = (Float4 *)(a_pafrgbaPixels); + Float4 *pf4Slope = (Float4 *)(a_pfrgbaSlope); + Float4 *pf4Offset = (Float4 *)(a_pfrgbaOffset); + + float afX[MAX_PLANAR_REGRESSION_SIZE]; + float afY[MAX_PLANAR_REGRESSION_SIZE]; + + // handle r, g and b separately. don't bother with a + for (unsigned int uiComponent = 0; uiComponent < 3; uiComponent++) + { + for (unsigned int uiPixel = 0; uiPixel < a_uiPixels; uiPixel++) + { + afX[uiPixel] = (float)uiPixel; + afY[uiPixel] = paf4Pixels[uiPixel].f[uiComponent]; + + } + Etc::Regression(afX, afY, a_uiPixels, + &(pf4Slope->f[uiComponent]), &(pf4Offset->f[uiComponent])); + } + + } + + // ---------------------------------------------------------------------------------------------------- + // +} diff --git a/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGB8.h b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGB8.h new file mode 100644 index 0000000..e405223 --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGB8.h @@ -0,0 +1,96 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +#pragma once + +#include "EtcBlock4x4Encoding_ETC1.h" + +namespace Etc +{ + + class Block4x4Encoding_RGB8 : public Block4x4Encoding_ETC1 + { + public: + + Block4x4Encoding_RGB8(void); + virtual ~Block4x4Encoding_RGB8(void); + + virtual void InitFromEncodingBits(Block4x4 *a_pblockParent, + unsigned char *a_paucEncodingBits, + ColorFloatRGBA *a_pafrgbaSource, + + ErrorMetric a_errormetric); + + virtual void PerformIteration(float a_fEffort); + + virtual void SetEncodingBits(void); + + inline ColorFloatRGBA GetColor3(void) const + { + return m_frgbaColor3; + } + + protected: + + static const unsigned int PLANAR_CORNER_COLORS = 3; + static const unsigned int MAX_PLANAR_REGRESSION_SIZE = 4; + static const unsigned int TH_DISTANCES = 8; + + static float s_afTHDistanceTable[TH_DISTANCES]; + + void TryPlanar(unsigned int a_uiRadius); + void TryTAndH(unsigned int a_uiRadius); + + void InitFromEncodingBits_Planar(void); + + ColorFloatRGBA m_frgbaColor3; // used for planar + + void SetEncodingBits_T(void); + void SetEncodingBits_H(void); + void SetEncodingBits_Planar(void); + + // state shared between iterations + ColorFloatRGBA m_frgbaOriginalColor1_TAndH; + ColorFloatRGBA m_frgbaOriginalColor2_TAndH; + + void CalculateBaseColorsForTAndH(void); + void TryT(unsigned int a_uiRadius); + void TryT_BestSelectorCombination(void); + void TryH(unsigned int a_uiRadius); + void TryH_BestSelectorCombination(void); + + private: + + void InitFromEncodingBits_T(void); + void InitFromEncodingBits_H(void); + + void CalculatePlanarCornerColors(void); + + void ColorRegression(ColorFloatRGBA *a_pafrgbaPixels, unsigned int a_uiPixels, + ColorFloatRGBA *a_pfrgbaSlope, ColorFloatRGBA *a_pfrgbaOffset); + + bool TwiddlePlanar(void); + bool TwiddlePlanarR(); + bool TwiddlePlanarG(); + bool TwiddlePlanarB(); + + void DecodePixels_T(void); + void DecodePixels_H(void); + void DecodePixels_Planar(void); + + }; + +} // namespace Etc diff --git a/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGB8A1.cpp b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGB8A1.cpp new file mode 100644 index 0000000..fc0678d --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGB8A1.cpp @@ -0,0 +1,1819 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +/* +EtcBlock4x4Encoding_RGB8A1.cpp contains: + Block4x4Encoding_RGB8A1 + Block4x4Encoding_RGB8A1_Opaque + Block4x4Encoding_RGB8A1_Transparent + +These encoders are used when targetting file format RGB8A1. + +Block4x4Encoding_RGB8A1_Opaque is used when all pixels in the 4x4 block are opaque +Block4x4Encoding_RGB8A1_Transparent is used when all pixels in the 4x4 block are transparent +Block4x4Encoding_RGB8A1 is used when there is a mixture of alphas in the 4x4 block + +*/ + +#include "EtcConfig.h" +#include "EtcBlock4x4Encoding_RGB8A1.h" + +#include "EtcBlock4x4.h" +#include "EtcBlock4x4EncodingBits.h" +#include "EtcBlock4x4Encoding_RGB8.h" + +#include +#include +#include + +namespace Etc +{ + + // #################################################################################################### + // Block4x4Encoding_RGB8A1 + // #################################################################################################### + + float Block4x4Encoding_RGB8A1::s_aafCwOpaqueUnsetTable[CW_RANGES][SELECTORS] = + { + { 0.0f / 255.0f, 8.0f / 255.0f, 0.0f / 255.0f, -8.0f / 255.0f }, + { 0.0f / 255.0f, 17.0f / 255.0f, 0.0f / 255.0f, -17.0f / 255.0f }, + { 0.0f / 255.0f, 29.0f / 255.0f, 0.0f / 255.0f, -29.0f / 255.0f }, + { 0.0f / 255.0f, 42.0f / 255.0f, 0.0f / 255.0f, -42.0f / 255.0f }, + { 0.0f / 255.0f, 60.0f / 255.0f, 0.0f / 255.0f, -60.0f / 255.0f }, + { 0.0f / 255.0f, 80.0f / 255.0f, 0.0f / 255.0f, -80.0f / 255.0f }, + { 0.0f / 255.0f, 106.0f / 255.0f, 0.0f / 255.0f, -106.0f / 255.0f }, + { 0.0f / 255.0f, 183.0f / 255.0f, 0.0f / 255.0f, -183.0f / 255.0f } + }; + + // ---------------------------------------------------------------------------------------------------- + // + Block4x4Encoding_RGB8A1::Block4x4Encoding_RGB8A1(void) + { + m_pencodingbitsRGB8 = nullptr; + m_boolOpaque = false; + m_boolTransparent = false; + m_boolPunchThroughPixels = true; + + } + Block4x4Encoding_RGB8A1::~Block4x4Encoding_RGB8A1(void) {} + // ---------------------------------------------------------------------------------------------------- + // initialization prior to encoding + // a_pblockParent points to the block associated with this encoding + // a_errormetric is used to choose the best encoding + // a_pafrgbaSource points to a 4x4 block subset of the source image + // a_paucEncodingBits points to the final encoding bits + // + void Block4x4Encoding_RGB8A1::InitFromSource(Block4x4 *a_pblockParent, + ColorFloatRGBA *a_pafrgbaSource, + unsigned char *a_paucEncodingBits, + ErrorMetric a_errormetric) + { + + Block4x4Encoding_RGB8::InitFromSource(a_pblockParent, + a_pafrgbaSource, + a_paucEncodingBits, + a_errormetric); + + m_boolOpaque = a_pblockParent->GetSourceAlphaMix() == Block4x4::SourceAlphaMix::OPAQUE; + m_boolTransparent = a_pblockParent->GetSourceAlphaMix() == Block4x4::SourceAlphaMix::TRANSPARENT; + m_boolPunchThroughPixels = a_pblockParent->HasPunchThroughPixels(); + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + if (m_pafrgbaSource[uiPixel].fA >= 0.5f) + { + m_afDecodedAlphas[uiPixel] = 1.0f; + } + else + { + m_afDecodedAlphas[uiPixel] = 0.0f; + } + } + + } + + // ---------------------------------------------------------------------------------------------------- + // initialization from the encoding bits of a previous encoding + // a_pblockParent points to the block associated with this encoding + // a_errormetric is used to choose the best encoding + // a_pafrgbaSource points to a 4x4 block subset of the source image + // a_paucEncodingBits points to the final encoding bits of a previous encoding + // + void Block4x4Encoding_RGB8A1::InitFromEncodingBits(Block4x4 *a_pblockParent, + unsigned char *a_paucEncodingBits, + ColorFloatRGBA *a_pafrgbaSource, + ErrorMetric a_errormetric) + { + + + InitFromEncodingBits_ETC1(a_pblockParent, + a_paucEncodingBits, + a_pafrgbaSource, + a_errormetric); + + m_pencodingbitsRGB8 = (Block4x4EncodingBits_RGB8 *)a_paucEncodingBits; + + // detect if there is a T, H or Planar mode present + int iRed1 = m_pencodingbitsRGB8->differential.red1; + int iDRed2 = m_pencodingbitsRGB8->differential.dred2; + int iRed2 = iRed1 + iDRed2; + + int iGreen1 = m_pencodingbitsRGB8->differential.green1; + int iDGreen2 = m_pencodingbitsRGB8->differential.dgreen2; + int iGreen2 = iGreen1 + iDGreen2; + + int iBlue1 = m_pencodingbitsRGB8->differential.blue1; + int iDBlue2 = m_pencodingbitsRGB8->differential.dblue2; + int iBlue2 = iBlue1 + iDBlue2; + + if (iRed2 < 0 || iRed2 > 31) + { + InitFromEncodingBits_T(); + } + else if (iGreen2 < 0 || iGreen2 > 31) + { + InitFromEncodingBits_H(); + } + else if (iBlue2 < 0 || iBlue2 > 31) + { + Block4x4Encoding_RGB8::InitFromEncodingBits_Planar(); + } + + } + + // ---------------------------------------------------------------------------------------------------- + // initialization from the encoding bits of a previous encoding assuming the encoding is an ETC1 mode. + // if it isn't an ETC1 mode, this will be overwritten later + // + void Block4x4Encoding_RGB8A1::InitFromEncodingBits_ETC1(Block4x4 *a_pblockParent, + unsigned char *a_paucEncodingBits, + ColorFloatRGBA *a_pafrgbaSource, + ErrorMetric a_errormetric) + { + Block4x4Encoding::Init(a_pblockParent, a_pafrgbaSource, + a_errormetric); + + m_pencodingbitsRGB8 = (Block4x4EncodingBits_RGB8 *)a_paucEncodingBits; + + m_mode = MODE_ETC1; + m_boolDiff = true; + m_boolFlip = m_pencodingbitsRGB8->differential.flip; + m_boolOpaque = m_pencodingbitsRGB8->differential.diff; + + int iR2 = m_pencodingbitsRGB8->differential.red1 + m_pencodingbitsRGB8->differential.dred2; + if (iR2 < 0) + { + iR2 = 0; + } + else if (iR2 > 31) + { + iR2 = 31; + } + + int iG2 = m_pencodingbitsRGB8->differential.green1 + m_pencodingbitsRGB8->differential.dgreen2; + if (iG2 < 0) + { + iG2 = 0; + } + else if (iG2 > 31) + { + iG2 = 31; + } + + int iB2 = m_pencodingbitsRGB8->differential.blue1 + m_pencodingbitsRGB8->differential.dblue2; + if (iB2 < 0) + { + iB2 = 0; + } + else if (iB2 > 31) + { + iB2 = 31; + } + + m_frgbaColor1 = ColorFloatRGBA::ConvertFromRGB5(m_pencodingbitsRGB8->differential.red1, m_pencodingbitsRGB8->differential.green1, m_pencodingbitsRGB8->differential.blue1); + m_frgbaColor2 = ColorFloatRGBA::ConvertFromRGB5((unsigned char)iR2, (unsigned char)iG2, (unsigned char)iB2); + + m_uiCW1 = m_pencodingbitsRGB8->differential.cw1; + m_uiCW2 = m_pencodingbitsRGB8->differential.cw2; + + Block4x4Encoding_ETC1::InitFromEncodingBits_Selectors(); + + Decode_ETC1(); + + CalcBlockError(); + + } + + // ---------------------------------------------------------------------------------------------------- + // initialization from the encoding bits of a previous encoding if T mode is detected + // + void Block4x4Encoding_RGB8A1::InitFromEncodingBits_T(void) + { + m_mode = MODE_T; + + unsigned char ucRed1 = (unsigned char)((m_pencodingbitsRGB8->t.red1a << 2) + + m_pencodingbitsRGB8->t.red1b); + unsigned char ucGreen1 = m_pencodingbitsRGB8->t.green1; + unsigned char ucBlue1 = m_pencodingbitsRGB8->t.blue1; + + unsigned char ucRed2 = m_pencodingbitsRGB8->t.red2; + unsigned char ucGreen2 = m_pencodingbitsRGB8->t.green2; + unsigned char ucBlue2 = m_pencodingbitsRGB8->t.blue2; + + m_frgbaColor1 = ColorFloatRGBA::ConvertFromRGB4(ucRed1, ucGreen1, ucBlue1); + m_frgbaColor2 = ColorFloatRGBA::ConvertFromRGB4(ucRed2, ucGreen2, ucBlue2); + + m_uiCW1 = (m_pencodingbitsRGB8->t.da << 1) + m_pencodingbitsRGB8->t.db; + + Block4x4Encoding_ETC1::InitFromEncodingBits_Selectors(); + + DecodePixels_T(); + + CalcBlockError(); + } + + // ---------------------------------------------------------------------------------------------------- + // initialization from the encoding bits of a previous encoding if H mode is detected + // + void Block4x4Encoding_RGB8A1::InitFromEncodingBits_H(void) + { + m_mode = MODE_H; + + unsigned char ucRed1 = m_pencodingbitsRGB8->h.red1; + unsigned char ucGreen1 = (unsigned char)((m_pencodingbitsRGB8->h.green1a << 1) + + m_pencodingbitsRGB8->h.green1b); + unsigned char ucBlue1 = (unsigned char)((m_pencodingbitsRGB8->h.blue1a << 3) + + (m_pencodingbitsRGB8->h.blue1b << 1) + + m_pencodingbitsRGB8->h.blue1c); + + unsigned char ucRed2 = m_pencodingbitsRGB8->h.red2; + unsigned char ucGreen2 = (unsigned char)((m_pencodingbitsRGB8->h.green2a << 1) + + m_pencodingbitsRGB8->h.green2b); + unsigned char ucBlue2 = m_pencodingbitsRGB8->h.blue2; + + m_frgbaColor1 = ColorFloatRGBA::ConvertFromRGB4(ucRed1, ucGreen1, ucBlue1); + m_frgbaColor2 = ColorFloatRGBA::ConvertFromRGB4(ucRed2, ucGreen2, ucBlue2); + + // used to determine the LSB of the CW + unsigned int uiRGB1 = (unsigned int)(((int)ucRed1 << 16) + ((int)ucGreen1 << 8) + (int)ucBlue1); + unsigned int uiRGB2 = (unsigned int)(((int)ucRed2 << 16) + ((int)ucGreen2 << 8) + (int)ucBlue2); + + m_uiCW1 = (m_pencodingbitsRGB8->h.da << 2) + (m_pencodingbitsRGB8->h.db << 1); + if (uiRGB1 >= uiRGB2) + { + m_uiCW1++; + } + + Block4x4Encoding_ETC1::InitFromEncodingBits_Selectors(); + + DecodePixels_H(); + + CalcBlockError(); + } + + // ---------------------------------------------------------------------------------------------------- + // for ETC1 modes, set the decoded colors and decoded alpha based on the encoding state + // + void Block4x4Encoding_RGB8A1::Decode_ETC1(void) + { + + const unsigned int *pauiPixelOrder = m_boolFlip ? s_auiPixelOrderFlip1 : s_auiPixelOrderFlip0; + + for (unsigned int uiPixelOrder = 0; uiPixelOrder < PIXELS; uiPixelOrder++) + { + ColorFloatRGBA *pfrgbaCenter = uiPixelOrder < 8 ? &m_frgbaColor1 : &m_frgbaColor2; + unsigned int uiCW = uiPixelOrder < 8 ? m_uiCW1 : m_uiCW2; + + unsigned int uiPixel = pauiPixelOrder[uiPixelOrder]; + + float fDelta; + if (m_boolOpaque) + fDelta = Block4x4Encoding_ETC1::s_aafCwTable[uiCW][m_auiSelectors[uiPixel]]; + else + fDelta = s_aafCwOpaqueUnsetTable[uiCW][m_auiSelectors[uiPixel]]; + + if (m_boolOpaque == false && m_auiSelectors[uiPixel] == TRANSPARENT_SELECTOR) + { + m_afrgbaDecodedColors[uiPixel] = ColorFloatRGBA(); + m_afDecodedAlphas[uiPixel] = 0.0f; + } + else + { + m_afrgbaDecodedColors[uiPixel] = (*pfrgbaCenter + fDelta).ClampRGB(); + m_afDecodedAlphas[uiPixel] = 1.0f; + } + } + + } + + // ---------------------------------------------------------------------------------------------------- + // for T mode, set the decoded colors and decoded alpha based on the encoding state + // + void Block4x4Encoding_RGB8A1::DecodePixels_T(void) + { + + float fDistance = s_afTHDistanceTable[m_uiCW1]; + ColorFloatRGBA frgbaDistance(fDistance, fDistance, fDistance, 0.0f); + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + switch (m_auiSelectors[uiPixel]) + { + case 0: + m_afrgbaDecodedColors[uiPixel] = m_frgbaColor1; + m_afDecodedAlphas[uiPixel] = 1.0f; + break; + + case 1: + m_afrgbaDecodedColors[uiPixel] = (m_frgbaColor2 + frgbaDistance).ClampRGB(); + m_afDecodedAlphas[uiPixel] = 1.0f; + break; + + case 2: + if (m_boolOpaque == false) + { + m_afrgbaDecodedColors[uiPixel] = ColorFloatRGBA(); + m_afDecodedAlphas[uiPixel] = 0.0f; + } + else + { + m_afrgbaDecodedColors[uiPixel] = m_frgbaColor2; + m_afDecodedAlphas[uiPixel] = 1.0f; + } + break; + + case 3: + m_afrgbaDecodedColors[uiPixel] = (m_frgbaColor2 - frgbaDistance).ClampRGB(); + m_afDecodedAlphas[uiPixel] = 1.0f; + break; + } + + } + + } + + // ---------------------------------------------------------------------------------------------------- + // for H mode, set the decoded colors and decoded alpha based on the encoding state + // + void Block4x4Encoding_RGB8A1::DecodePixels_H(void) + { + + float fDistance = s_afTHDistanceTable[m_uiCW1]; + ColorFloatRGBA frgbaDistance(fDistance, fDistance, fDistance, 0.0f); + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + switch (m_auiSelectors[uiPixel]) + { + case 0: + m_afrgbaDecodedColors[uiPixel] = (m_frgbaColor1 + frgbaDistance).ClampRGB(); + m_afDecodedAlphas[uiPixel] = 1.0f; + break; + + case 1: + m_afrgbaDecodedColors[uiPixel] = (m_frgbaColor1 - frgbaDistance).ClampRGB(); + m_afDecodedAlphas[uiPixel] = 1.0f; + break; + + case 2: + if (m_boolOpaque == false) + { + m_afrgbaDecodedColors[uiPixel] = ColorFloatRGBA(); + m_afDecodedAlphas[uiPixel] = 0.0f; + } + else + { + m_afrgbaDecodedColors[uiPixel] = (m_frgbaColor2 + frgbaDistance).ClampRGB(); + m_afDecodedAlphas[uiPixel] = 1.0f; + } + break; + + case 3: + m_afrgbaDecodedColors[uiPixel] = (m_frgbaColor2 - frgbaDistance).ClampRGB(); + m_afDecodedAlphas[uiPixel] = 1.0f; + break; + } + + } + + } + + + // ---------------------------------------------------------------------------------------------------- + // perform a single encoding iteration + // replace the encoding if a better encoding was found + // subsequent iterations generally take longer for each iteration + // set m_boolDone if encoding is perfect or encoding is finished based on a_fEffort + // + // RGB8A1 can't use individual mode + // RGB8A1 with transparent pixels can't use planar mode + // + void Block4x4Encoding_RGB8A1::PerformIteration(float a_fEffort) + { + assert(!m_boolOpaque); + assert(!m_boolTransparent); + assert(!m_boolDone); + + switch (m_uiEncodingIterations) + { + case 0: + PerformFirstIteration(); + break; + + case 1: + TryDifferential(m_boolMostLikelyFlip, 1, 0, 0); + break; + + case 2: + TryDifferential(!m_boolMostLikelyFlip, 1, 0, 0); + if (a_fEffort <= 39.5f) + { + m_boolDone = true; + } + break; + + case 3: + Block4x4Encoding_RGB8::CalculateBaseColorsForTAndH(); + TryT(1); + TryH(1); + if (a_fEffort <= 49.5f) + { + m_boolDone = true; + } + break; + + case 4: + TryDegenerates1(); + if (a_fEffort <= 59.5f) + { + m_boolDone = true; + } + break; + + case 5: + TryDegenerates2(); + if (a_fEffort <= 69.5f) + { + m_boolDone = true; + } + break; + + case 6: + TryDegenerates3(); + if (a_fEffort <= 79.5f) + { + m_boolDone = true; + } + break; + + case 7: + TryDegenerates4(); + m_boolDone = true; + break; + + default: + assert(0); + break; + } + + m_uiEncodingIterations++; + + SetDoneIfPerfect(); + + } + + // ---------------------------------------------------------------------------------------------------- + // find best initial encoding to ensure block has a valid encoding + // + void Block4x4Encoding_RGB8A1::PerformFirstIteration(void) + { + Block4x4Encoding_ETC1::CalculateMostLikelyFlip(); + + m_fError = FLT_MAX; + + TryDifferential(m_boolMostLikelyFlip, 0, 0, 0); + SetDoneIfPerfect(); + if (m_boolDone) + { + return; + } + TryDifferential(!m_boolMostLikelyFlip, 0, 0, 0); + SetDoneIfPerfect(); + + } + + // ---------------------------------------------------------------------------------------------------- + // mostly copied from ETC1 + // differences: + // Block4x4Encoding_RGB8A1 encodingTry = *this; + // + void Block4x4Encoding_RGB8A1::TryDifferential(bool a_boolFlip, unsigned int a_uiRadius, + int a_iGrayOffset1, int a_iGrayOffset2) + { + + ColorFloatRGBA frgbaColor1; + ColorFloatRGBA frgbaColor2; + + const unsigned int *pauiPixelMapping1; + const unsigned int *pauiPixelMapping2; + + if (a_boolFlip) + { + frgbaColor1 = m_frgbaSourceAverageTop; + frgbaColor2 = m_frgbaSourceAverageBottom; + + pauiPixelMapping1 = s_auiTopPixelMapping; + pauiPixelMapping2 = s_auiBottomPixelMapping; + } + else + { + frgbaColor1 = m_frgbaSourceAverageLeft; + frgbaColor2 = m_frgbaSourceAverageRight; + + pauiPixelMapping1 = s_auiLeftPixelMapping; + pauiPixelMapping2 = s_auiRightPixelMapping; + } + + DifferentialTrys trys(frgbaColor1, frgbaColor2, pauiPixelMapping1, pauiPixelMapping2, + a_uiRadius, a_iGrayOffset1, a_iGrayOffset2); + + Block4x4Encoding_RGB8A1 encodingTry = *this; + encodingTry.m_boolFlip = a_boolFlip; + + encodingTry.TryDifferentialHalf(&trys.m_half1); + encodingTry.TryDifferentialHalf(&trys.m_half2); + + // find best halves that are within differential range + DifferentialTrys::Try *ptryBest1 = nullptr; + DifferentialTrys::Try *ptryBest2 = nullptr; + encodingTry.m_fError = FLT_MAX; + + // see if the best of each half are in differential range + int iDRed = trys.m_half2.m_ptryBest->m_iRed - trys.m_half1.m_ptryBest->m_iRed; + int iDGreen = trys.m_half2.m_ptryBest->m_iGreen - trys.m_half1.m_ptryBest->m_iGreen; + int iDBlue = trys.m_half2.m_ptryBest->m_iBlue - trys.m_half1.m_ptryBest->m_iBlue; + if (iDRed >= -4 && iDRed <= 3 && iDGreen >= -4 && iDGreen <= 3 && iDBlue >= -4 && iDBlue <= 3) + { + ptryBest1 = trys.m_half1.m_ptryBest; + ptryBest2 = trys.m_half2.m_ptryBest; + encodingTry.m_fError = trys.m_half1.m_ptryBest->m_fError + trys.m_half2.m_ptryBest->m_fError; + } + else + { + // else, find the next best halves that are in differential range + for (DifferentialTrys::Try *ptry1 = &trys.m_half1.m_atry[0]; + ptry1 < &trys.m_half1.m_atry[trys.m_half1.m_uiTrys]; + ptry1++) + { + for (DifferentialTrys::Try *ptry2 = &trys.m_half2.m_atry[0]; + ptry2 < &trys.m_half2.m_atry[trys.m_half2.m_uiTrys]; + ptry2++) + { + iDRed = ptry2->m_iRed - ptry1->m_iRed; + bool boolValidRedDelta = iDRed <= 3 && iDRed >= -4; + iDGreen = ptry2->m_iGreen - ptry1->m_iGreen; + bool boolValidGreenDelta = iDGreen <= 3 && iDGreen >= -4; + iDBlue = ptry2->m_iBlue - ptry1->m_iBlue; + bool boolValidBlueDelta = iDBlue <= 3 && iDBlue >= -4; + + if (boolValidRedDelta && boolValidGreenDelta && boolValidBlueDelta) + { + float fError = ptry1->m_fError + ptry2->m_fError; + + if (fError < encodingTry.m_fError) + { + encodingTry.m_fError = fError; + + ptryBest1 = ptry1; + ptryBest2 = ptry2; + } + } + + } + } + assert(encodingTry.m_fError < FLT_MAX); + assert(ptryBest1 != nullptr); + assert(ptryBest2 != nullptr); + } + + if (encodingTry.m_fError < m_fError) + { + m_mode = MODE_ETC1; + m_boolDiff = true; + m_boolFlip = encodingTry.m_boolFlip; + m_frgbaColor1 = ColorFloatRGBA::ConvertFromRGB5((unsigned char)ptryBest1->m_iRed, (unsigned char)ptryBest1->m_iGreen, (unsigned char)ptryBest1->m_iBlue); + m_frgbaColor2 = ColorFloatRGBA::ConvertFromRGB5((unsigned char)ptryBest2->m_iRed, (unsigned char)ptryBest2->m_iGreen, (unsigned char)ptryBest2->m_iBlue); + m_uiCW1 = ptryBest1->m_uiCW; + m_uiCW2 = ptryBest2->m_uiCW; + + m_fError = 0.0f; + for (unsigned int uiPixelOrder = 0; uiPixelOrder < PIXELS / 2; uiPixelOrder++) + { + unsigned int uiPixel1 = pauiPixelMapping1[uiPixelOrder]; + unsigned int uiPixel2 = pauiPixelMapping2[uiPixelOrder]; + + unsigned int uiSelector1 = ptryBest1->m_auiSelectors[uiPixelOrder]; + unsigned int uiSelector2 = ptryBest2->m_auiSelectors[uiPixelOrder]; + + m_auiSelectors[uiPixel1] = uiSelector1; + m_auiSelectors[uiPixel2] = ptryBest2->m_auiSelectors[uiPixelOrder]; + + if (uiSelector1 == TRANSPARENT_SELECTOR) + { + m_afrgbaDecodedColors[uiPixel1] = ColorFloatRGBA(); + m_afDecodedAlphas[uiPixel1] = 0.0f; + } + else + { + float fDeltaRGB1 = s_aafCwOpaqueUnsetTable[m_uiCW1][uiSelector1]; + m_afrgbaDecodedColors[uiPixel1] = (m_frgbaColor1 + fDeltaRGB1).ClampRGB(); + m_afDecodedAlphas[uiPixel1] = 1.0f; + } + + if (uiSelector2 == TRANSPARENT_SELECTOR) + { + m_afrgbaDecodedColors[uiPixel2] = ColorFloatRGBA(); + m_afDecodedAlphas[uiPixel2] = 0.0f; + } + else + { + float fDeltaRGB2 = s_aafCwOpaqueUnsetTable[m_uiCW2][uiSelector2]; + m_afrgbaDecodedColors[uiPixel2] = (m_frgbaColor2 + fDeltaRGB2).ClampRGB(); + m_afDecodedAlphas[uiPixel2] = 1.0f; + } + + float fDeltaA1 = m_afDecodedAlphas[uiPixel1] - m_pafrgbaSource[uiPixel1].fA; + m_fError += fDeltaA1 * fDeltaA1; + float fDeltaA2 = m_afDecodedAlphas[uiPixel2] - m_pafrgbaSource[uiPixel2].fA; + m_fError += fDeltaA2 * fDeltaA2; + } + + m_fError1 = ptryBest1->m_fError; + m_fError2 = ptryBest2->m_fError; + m_boolSeverelyBentDifferentialColors = trys.m_boolSeverelyBentColors; + m_fError = m_fError1 + m_fError2; + + // sanity check + { + int iRed1 = m_frgbaColor1.IntRed(31.0f); + int iGreen1 = m_frgbaColor1.IntGreen(31.0f); + int iBlue1 = m_frgbaColor1.IntBlue(31.0f); + + int iRed2 = m_frgbaColor2.IntRed(31.0f); + int iGreen2 = m_frgbaColor2.IntGreen(31.0f); + int iBlue2 = m_frgbaColor2.IntBlue(31.0f); + + iDRed = iRed2 - iRed1; + iDGreen = iGreen2 - iGreen1; + iDBlue = iBlue2 - iBlue1; + + assert(iDRed >= -4 && iDRed < 4); + assert(iDGreen >= -4 && iDGreen < 4); + assert(iDBlue >= -4 && iDBlue < 4); + } + } + + } + + // ---------------------------------------------------------------------------------------------------- + // mostly copied from ETC1 + // differences: + // uses s_aafCwOpaqueUnsetTable + // color for selector set to 0,0,0,0 + // + void Block4x4Encoding_RGB8A1::TryDifferentialHalf(DifferentialTrys::Half *a_phalf) + { + + a_phalf->m_ptryBest = nullptr; + float fBestTryError = FLT_MAX; + + a_phalf->m_uiTrys = 0; + for (int iRed = a_phalf->m_iRed - (int)a_phalf->m_uiRadius; + iRed <= a_phalf->m_iRed + (int)a_phalf->m_uiRadius; + iRed++) + { + assert(iRed >= 0 && iRed <= 31); + + for (int iGreen = a_phalf->m_iGreen - (int)a_phalf->m_uiRadius; + iGreen <= a_phalf->m_iGreen + (int)a_phalf->m_uiRadius; + iGreen++) + { + assert(iGreen >= 0 && iGreen <= 31); + + for (int iBlue = a_phalf->m_iBlue - (int)a_phalf->m_uiRadius; + iBlue <= a_phalf->m_iBlue + (int)a_phalf->m_uiRadius; + iBlue++) + { + assert(iBlue >= 0 && iBlue <= 31); + + DifferentialTrys::Try *ptry = &a_phalf->m_atry[a_phalf->m_uiTrys]; + assert(ptry < &a_phalf->m_atry[DifferentialTrys::Half::MAX_TRYS]); + + ptry->m_iRed = iRed; + ptry->m_iGreen = iGreen; + ptry->m_iBlue = iBlue; + ptry->m_fError = FLT_MAX; + ColorFloatRGBA frgbaColor = ColorFloatRGBA::ConvertFromRGB5((unsigned char)iRed, (unsigned char)iGreen, (unsigned char)iBlue); + + // try each CW + for (unsigned int uiCW = 0; uiCW < CW_RANGES; uiCW++) + { + unsigned int auiPixelSelectors[PIXELS / 2]; + ColorFloatRGBA afrgbaDecodedColors[PIXELS / 2]; + float afPixelErrors[PIXELS / 2] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, + FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }; + + // pre-compute decoded pixels for each selector + ColorFloatRGBA afrgbaSelectors[SELECTORS]; + assert(SELECTORS == 4); + afrgbaSelectors[0] = (frgbaColor + s_aafCwOpaqueUnsetTable[uiCW][0]).ClampRGB(); + afrgbaSelectors[1] = (frgbaColor + s_aafCwOpaqueUnsetTable[uiCW][1]).ClampRGB(); + afrgbaSelectors[2] = ColorFloatRGBA(); + afrgbaSelectors[3] = (frgbaColor + s_aafCwOpaqueUnsetTable[uiCW][3]).ClampRGB(); + + for (unsigned int uiPixel = 0; uiPixel < 8; uiPixel++) + { + ColorFloatRGBA *pfrgbaSourcePixel = &m_pafrgbaSource[a_phalf->m_pauiPixelMapping[uiPixel]]; + ColorFloatRGBA frgbaDecodedPixel; + + for (unsigned int uiSelector = 0; uiSelector < SELECTORS; uiSelector++) + { + if (pfrgbaSourcePixel->fA < 0.5f) + { + uiSelector = TRANSPARENT_SELECTOR; + } + else if (uiSelector == TRANSPARENT_SELECTOR) + { + continue; + } + + frgbaDecodedPixel = afrgbaSelectors[uiSelector]; + + float fPixelError; + + fPixelError = CalcPixelError(frgbaDecodedPixel, m_afDecodedAlphas[a_phalf->m_pauiPixelMapping[uiPixel]], + *pfrgbaSourcePixel); + + if (fPixelError < afPixelErrors[uiPixel]) + { + auiPixelSelectors[uiPixel] = uiSelector; + afrgbaDecodedColors[uiPixel] = frgbaDecodedPixel; + afPixelErrors[uiPixel] = fPixelError; + } + + if (uiSelector == TRANSPARENT_SELECTOR) + { + break; + } + } + } + + // add up all pixel errors + float fCWError = 0.0f; + for (unsigned int uiPixel = 0; uiPixel < 8; uiPixel++) + { + fCWError += afPixelErrors[uiPixel]; + } + + // if best CW so far + if (fCWError < ptry->m_fError) + { + ptry->m_uiCW = uiCW; + for (unsigned int uiPixel = 0; uiPixel < 8; uiPixel++) + { + ptry->m_auiSelectors[uiPixel] = auiPixelSelectors[uiPixel]; + } + ptry->m_fError = fCWError; + } + + } + + if (ptry->m_fError < fBestTryError) + { + a_phalf->m_ptryBest = ptry; + fBestTryError = ptry->m_fError; + } + + assert(ptry->m_fError < FLT_MAX); + + a_phalf->m_uiTrys++; + } + } + } + + } + + // ---------------------------------------------------------------------------------------------------- + // try encoding in T mode + // save this encoding if it improves the error + // + // since pixels that use base color1 don't use the distance table, color1 and color2 can be twiddled independently + // better encoding can be found if TWIDDLE_RADIUS is set to 2, but it will be much slower + // + void Block4x4Encoding_RGB8A1::TryT(unsigned int a_uiRadius) + { + Block4x4Encoding_RGB8A1 encodingTry = *this; + + // init "try" + { + encodingTry.m_mode = MODE_T; + encodingTry.m_boolDiff = true; + encodingTry.m_boolFlip = false; + encodingTry.m_fError = FLT_MAX; + } + + int iColor1Red = m_frgbaOriginalColor1_TAndH.IntRed(15.0f); + int iColor1Green = m_frgbaOriginalColor1_TAndH.IntGreen(15.0f); + int iColor1Blue = m_frgbaOriginalColor1_TAndH.IntBlue(15.0f); + + int iMinRed1 = iColor1Red - (int)a_uiRadius; + if (iMinRed1 < 0) + { + iMinRed1 = 0; + } + int iMaxRed1 = iColor1Red + (int)a_uiRadius; + if (iMaxRed1 > 15) + { + iMinRed1 = 15; + } + + int iMinGreen1 = iColor1Green - (int)a_uiRadius; + if (iMinGreen1 < 0) + { + iMinGreen1 = 0; + } + int iMaxGreen1 = iColor1Green + (int)a_uiRadius; + if (iMaxGreen1 > 15) + { + iMinGreen1 = 15; + } + + int iMinBlue1 = iColor1Blue - (int)a_uiRadius; + if (iMinBlue1 < 0) + { + iMinBlue1 = 0; + } + int iMaxBlue1 = iColor1Blue + (int)a_uiRadius; + if (iMaxBlue1 > 15) + { + iMinBlue1 = 15; + } + + int iColor2Red = m_frgbaOriginalColor2_TAndH.IntRed(15.0f); + int iColor2Green = m_frgbaOriginalColor2_TAndH.IntGreen(15.0f); + int iColor2Blue = m_frgbaOriginalColor2_TAndH.IntBlue(15.0f); + + int iMinRed2 = iColor2Red - (int)a_uiRadius; + if (iMinRed2 < 0) + { + iMinRed2 = 0; + } + int iMaxRed2 = iColor2Red + (int)a_uiRadius; + if (iMaxRed2 > 15) + { + iMinRed2 = 15; + } + + int iMinGreen2 = iColor2Green - (int)a_uiRadius; + if (iMinGreen2 < 0) + { + iMinGreen2 = 0; + } + int iMaxGreen2 = iColor2Green + (int)a_uiRadius; + if (iMaxGreen2 > 15) + { + iMinGreen2 = 15; + } + + int iMinBlue2 = iColor2Blue - (int)a_uiRadius; + if (iMinBlue2 < 0) + { + iMinBlue2 = 0; + } + int iMaxBlue2 = iColor2Blue + (int)a_uiRadius; + if (iMaxBlue2 > 15) + { + iMinBlue2 = 15; + } + + for (unsigned int uiDistance = 0; uiDistance < TH_DISTANCES; uiDistance++) + { + encodingTry.m_uiCW1 = uiDistance; + + // twiddle m_frgbaOriginalColor2_TAndH + // twiddle color2 first, since it affects 3 selectors, while color1 only affects one selector + // + for (int iRed2 = iMinRed2; iRed2 <= iMaxRed2; iRed2++) + { + for (int iGreen2 = iMinGreen2; iGreen2 <= iMaxGreen2; iGreen2++) + { + for (int iBlue2 = iMinBlue2; iBlue2 <= iMaxBlue2; iBlue2++) + { + for (unsigned int uiBaseColorSwaps = 0; uiBaseColorSwaps < 2; uiBaseColorSwaps++) + { + if (uiBaseColorSwaps == 0) + { + encodingTry.m_frgbaColor1 = m_frgbaOriginalColor1_TAndH; + encodingTry.m_frgbaColor2 = ColorFloatRGBA::ConvertFromRGB4((unsigned char)iRed2, (unsigned char)iGreen2, (unsigned char)iBlue2); + } + else + { + encodingTry.m_frgbaColor1 = ColorFloatRGBA::ConvertFromRGB4((unsigned char)iRed2, (unsigned char)iGreen2, (unsigned char)iBlue2); + encodingTry.m_frgbaColor2 = m_frgbaOriginalColor1_TAndH; + } + + encodingTry.TryT_BestSelectorCombination(); + + if (encodingTry.m_fError < m_fError) + { + m_mode = encodingTry.m_mode; + m_boolDiff = encodingTry.m_boolDiff; + m_boolFlip = encodingTry.m_boolFlip; + + m_frgbaColor1 = encodingTry.m_frgbaColor1; + m_frgbaColor2 = encodingTry.m_frgbaColor2; + m_uiCW1 = encodingTry.m_uiCW1; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_auiSelectors[uiPixel] = encodingTry.m_auiSelectors[uiPixel]; + m_afrgbaDecodedColors[uiPixel] = encodingTry.m_afrgbaDecodedColors[uiPixel]; + } + + m_fError = encodingTry.m_fError; + } + } + } + } + } + + // twiddle m_frgbaOriginalColor1_TAndH + for (int iRed1 = iMinRed1; iRed1 <= iMaxRed1; iRed1++) + { + for (int iGreen1 = iMinGreen1; iGreen1 <= iMaxGreen1; iGreen1++) + { + for (int iBlue1 = iMinBlue1; iBlue1 <= iMaxBlue1; iBlue1++) + { + for (unsigned int uiBaseColorSwaps = 0; uiBaseColorSwaps < 2; uiBaseColorSwaps++) + { + if (uiBaseColorSwaps == 0) + { + encodingTry.m_frgbaColor1 = ColorFloatRGBA::ConvertFromRGB4((unsigned char)iRed1, (unsigned char)iGreen1, (unsigned char)iBlue1); + encodingTry.m_frgbaColor2 = m_frgbaOriginalColor2_TAndH; + } + else + { + encodingTry.m_frgbaColor1 = m_frgbaOriginalColor2_TAndH; + encodingTry.m_frgbaColor2 = ColorFloatRGBA::ConvertFromRGB4((unsigned char)iRed1, (unsigned char)iGreen1, (unsigned char)iBlue1); + } + + encodingTry.TryT_BestSelectorCombination(); + + if (encodingTry.m_fError < m_fError) + { + m_mode = encodingTry.m_mode; + m_boolDiff = encodingTry.m_boolDiff; + m_boolFlip = encodingTry.m_boolFlip; + + m_frgbaColor1 = encodingTry.m_frgbaColor1; + m_frgbaColor2 = encodingTry.m_frgbaColor2; + m_uiCW1 = encodingTry.m_uiCW1; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_auiSelectors[uiPixel] = encodingTry.m_auiSelectors[uiPixel]; + m_afrgbaDecodedColors[uiPixel] = encodingTry.m_afrgbaDecodedColors[uiPixel]; + } + + m_fError = encodingTry.m_fError; + } + } + } + } + } + + } + + } + + // ---------------------------------------------------------------------------------------------------- + // find best selector combination for TryT + // called on an encodingTry + // + void Block4x4Encoding_RGB8A1::TryT_BestSelectorCombination(void) + { + + float fDistance = s_afTHDistanceTable[m_uiCW1]; + + unsigned int auiBestPixelSelectors[PIXELS]; + float afBestPixelErrors[PIXELS] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, + FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }; + ColorFloatRGBA afrgbaBestDecodedPixels[PIXELS]; + ColorFloatRGBA afrgbaDecodedPixel[SELECTORS]; + + assert(SELECTORS == 4); + afrgbaDecodedPixel[0] = m_frgbaColor1; + afrgbaDecodedPixel[1] = (m_frgbaColor2 + fDistance).ClampRGB(); + afrgbaDecodedPixel[2] = ColorFloatRGBA(); + afrgbaDecodedPixel[3] = (m_frgbaColor2 - fDistance).ClampRGB(); + + // try each selector + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + unsigned int uiMinSelector = 0; + unsigned int uiMaxSelector = SELECTORS - 1; + + if (m_pafrgbaSource[uiPixel].fA < 0.5f) + { + uiMinSelector = 2; + uiMaxSelector = 2; + } + + for (unsigned int uiSelector = uiMinSelector; uiSelector <= uiMaxSelector; uiSelector++) + { + float fPixelError = CalcPixelError(afrgbaDecodedPixel[uiSelector], m_afDecodedAlphas[uiPixel], + m_pafrgbaSource[uiPixel]); + + if (fPixelError < afBestPixelErrors[uiPixel]) + { + afBestPixelErrors[uiPixel] = fPixelError; + auiBestPixelSelectors[uiPixel] = uiSelector; + afrgbaBestDecodedPixels[uiPixel] = afrgbaDecodedPixel[uiSelector]; + } + } + } + + + // add up all of the pixel errors + float fBlockError = 0.0f; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + fBlockError += afBestPixelErrors[uiPixel]; + } + + if (fBlockError < m_fError) + { + m_fError = fBlockError; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_auiSelectors[uiPixel] = auiBestPixelSelectors[uiPixel]; + m_afrgbaDecodedColors[uiPixel] = afrgbaBestDecodedPixels[uiPixel]; + } + } + + } + + // ---------------------------------------------------------------------------------------------------- + // try encoding in H mode + // save this encoding if it improves the error + // + // since all pixels use the distance table, color1 and color2 can NOT be twiddled independently + // TWIDDLE_RADIUS of 2 is WAY too slow + // + void Block4x4Encoding_RGB8A1::TryH(unsigned int a_uiRadius) + { + Block4x4Encoding_RGB8A1 encodingTry = *this; + + // init "try" + { + encodingTry.m_mode = MODE_H; + encodingTry.m_boolDiff = true; + encodingTry.m_boolFlip = false; + encodingTry.m_fError = FLT_MAX; + } + + int iColor1Red = m_frgbaOriginalColor1_TAndH.IntRed(15.0f); + int iColor1Green = m_frgbaOriginalColor1_TAndH.IntGreen(15.0f); + int iColor1Blue = m_frgbaOriginalColor1_TAndH.IntBlue(15.0f); + + int iMinRed1 = iColor1Red - (int)a_uiRadius; + if (iMinRed1 < 0) + { + iMinRed1 = 0; + } + int iMaxRed1 = iColor1Red + (int)a_uiRadius; + if (iMaxRed1 > 15) + { + iMinRed1 = 15; + } + + int iMinGreen1 = iColor1Green - (int)a_uiRadius; + if (iMinGreen1 < 0) + { + iMinGreen1 = 0; + } + int iMaxGreen1 = iColor1Green + (int)a_uiRadius; + if (iMaxGreen1 > 15) + { + iMinGreen1 = 15; + } + + int iMinBlue1 = iColor1Blue - (int)a_uiRadius; + if (iMinBlue1 < 0) + { + iMinBlue1 = 0; + } + int iMaxBlue1 = iColor1Blue + (int)a_uiRadius; + if (iMaxBlue1 > 15) + { + iMinBlue1 = 15; + } + + int iColor2Red = m_frgbaOriginalColor2_TAndH.IntRed(15.0f); + int iColor2Green = m_frgbaOriginalColor2_TAndH.IntGreen(15.0f); + int iColor2Blue = m_frgbaOriginalColor2_TAndH.IntBlue(15.0f); + + int iMinRed2 = iColor2Red - (int)a_uiRadius; + if (iMinRed2 < 0) + { + iMinRed2 = 0; + } + int iMaxRed2 = iColor2Red + (int)a_uiRadius; + if (iMaxRed2 > 15) + { + iMinRed2 = 15; + } + + int iMinGreen2 = iColor2Green - (int)a_uiRadius; + if (iMinGreen2 < 0) + { + iMinGreen2 = 0; + } + int iMaxGreen2 = iColor2Green + (int)a_uiRadius; + if (iMaxGreen2 > 15) + { + iMinGreen2 = 15; + } + + int iMinBlue2 = iColor2Blue - (int)a_uiRadius; + if (iMinBlue2 < 0) + { + iMinBlue2 = 0; + } + int iMaxBlue2 = iColor2Blue + (int)a_uiRadius; + if (iMaxBlue2 > 15) + { + iMinBlue2 = 15; + } + + for (unsigned int uiDistance = 0; uiDistance < TH_DISTANCES; uiDistance++) + { + encodingTry.m_uiCW1 = uiDistance; + + // twiddle m_frgbaOriginalColor1_TAndH + for (int iRed1 = iMinRed1; iRed1 <= iMaxRed1; iRed1++) + { + for (int iGreen1 = iMinGreen1; iGreen1 <= iMaxGreen1; iGreen1++) + { + for (int iBlue1 = iMinBlue1; iBlue1 <= iMaxBlue1; iBlue1++) + { + encodingTry.m_frgbaColor1 = ColorFloatRGBA::ConvertFromRGB4((unsigned char)iRed1, (unsigned char)iGreen1, (unsigned char)iBlue1); + encodingTry.m_frgbaColor2 = m_frgbaOriginalColor2_TAndH; + + // if color1 == color2, H encoding issues can pop up, so abort + if (iRed1 == iColor2Red && iGreen1 == iColor2Green && iBlue1 == iColor2Blue) + { + continue; + } + + encodingTry.TryH_BestSelectorCombination(); + + if (encodingTry.m_fError < m_fError) + { + m_mode = encodingTry.m_mode; + m_boolDiff = encodingTry.m_boolDiff; + m_boolFlip = encodingTry.m_boolFlip; + + m_frgbaColor1 = encodingTry.m_frgbaColor1; + m_frgbaColor2 = encodingTry.m_frgbaColor2; + m_uiCW1 = encodingTry.m_uiCW1; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_auiSelectors[uiPixel] = encodingTry.m_auiSelectors[uiPixel]; + m_afrgbaDecodedColors[uiPixel] = encodingTry.m_afrgbaDecodedColors[uiPixel]; + } + + m_fError = encodingTry.m_fError; + } + } + } + } + + // twiddle m_frgbaOriginalColor2_TAndH + for (int iRed2 = iMinRed2; iRed2 <= iMaxRed2; iRed2++) + { + for (int iGreen2 = iMinGreen2; iGreen2 <= iMaxGreen2; iGreen2++) + { + for (int iBlue2 = iMinBlue2; iBlue2 <= iMaxBlue2; iBlue2++) + { + encodingTry.m_frgbaColor1 = m_frgbaOriginalColor1_TAndH; + encodingTry.m_frgbaColor2 = ColorFloatRGBA::ConvertFromRGB4((unsigned char)iRed2, (unsigned char)iGreen2, (unsigned char)iBlue2); + + // if color1 == color2, H encoding issues can pop up, so abort + if (iRed2 == iColor1Red && iGreen2 == iColor1Green && iBlue2 == iColor1Blue) + { + continue; + } + + encodingTry.TryH_BestSelectorCombination(); + + if (encodingTry.m_fError < m_fError) + { + m_mode = encodingTry.m_mode; + m_boolDiff = encodingTry.m_boolDiff; + m_boolFlip = encodingTry.m_boolFlip; + + m_frgbaColor1 = encodingTry.m_frgbaColor1; + m_frgbaColor2 = encodingTry.m_frgbaColor2; + m_uiCW1 = encodingTry.m_uiCW1; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_auiSelectors[uiPixel] = encodingTry.m_auiSelectors[uiPixel]; + m_afrgbaDecodedColors[uiPixel] = encodingTry.m_afrgbaDecodedColors[uiPixel]; + } + + m_fError = encodingTry.m_fError; + } + } + } + } + + } + + } + + // ---------------------------------------------------------------------------------------------------- + // find best selector combination for TryH + // called on an encodingTry + // + void Block4x4Encoding_RGB8A1::TryH_BestSelectorCombination(void) + { + + // abort if colors and CW will pose an encoding problem + { + unsigned int uiRed1 = (unsigned int)m_frgbaColor1.IntRed(255.0f); + unsigned int uiGreen1 = (unsigned int)m_frgbaColor1.IntGreen(255.0f); + unsigned int uiBlue1 = (unsigned int)m_frgbaColor1.IntBlue(255.0f); + unsigned int uiColorValue1 = (uiRed1 << 16) + (uiGreen1 << 8) + uiBlue1; + + unsigned int uiRed2 = (unsigned int)m_frgbaColor2.IntRed(255.0f); + unsigned int uiGreen2 = (unsigned int)m_frgbaColor2.IntGreen(255.0f); + unsigned int uiBlue2 = (unsigned int)m_frgbaColor2.IntBlue(255.0f); + unsigned int uiColorValue2 = (uiRed2 << 16) + (uiGreen2 << 8) + uiBlue2; + + unsigned int uiCWLsb = m_uiCW1 & 1; + + if ((uiColorValue1 >= (uiColorValue2 & uiCWLsb)) == 0 || + (uiColorValue1 < (uiColorValue2 & uiCWLsb)) == 1) + { + return; + } + } + + float fDistance = s_afTHDistanceTable[m_uiCW1]; + + unsigned int auiBestPixelSelectors[PIXELS]; + float afBestPixelErrors[PIXELS] = { FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, + FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX, FLT_MAX }; + ColorFloatRGBA afrgbaBestDecodedPixels[PIXELS]; + ColorFloatRGBA afrgbaDecodedPixel[SELECTORS]; + + assert(SELECTORS == 4); + afrgbaDecodedPixel[0] = (m_frgbaColor1 + fDistance).ClampRGB(); + afrgbaDecodedPixel[1] = (m_frgbaColor1 - fDistance).ClampRGB(); + afrgbaDecodedPixel[2] = ColorFloatRGBA();; + afrgbaDecodedPixel[3] = (m_frgbaColor2 - fDistance).ClampRGB(); + + + // try each selector + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + unsigned int uiMinSelector = 0; + unsigned int uiMaxSelector = SELECTORS - 1; + + if (m_pafrgbaSource[uiPixel].fA < 0.5f) + { + uiMinSelector = 2; + uiMaxSelector = 2; + } + + for (unsigned int uiSelector = uiMinSelector; uiSelector <= uiMaxSelector; uiSelector++) + { + float fPixelError = CalcPixelError(afrgbaDecodedPixel[uiSelector], m_afDecodedAlphas[uiPixel], + m_pafrgbaSource[uiPixel]); + + if (fPixelError < afBestPixelErrors[uiPixel]) + { + afBestPixelErrors[uiPixel] = fPixelError; + auiBestPixelSelectors[uiPixel] = uiSelector; + afrgbaBestDecodedPixels[uiPixel] = afrgbaDecodedPixel[uiSelector]; + } + } + } + + + // add up all of the pixel errors + float fBlockError = 0.0f; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + fBlockError += afBestPixelErrors[uiPixel]; + } + + if (fBlockError < m_fError) + { + m_fError = fBlockError; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_auiSelectors[uiPixel] = auiBestPixelSelectors[uiPixel]; + m_afrgbaDecodedColors[uiPixel] = afrgbaBestDecodedPixels[uiPixel]; + } + } + + } + + // ---------------------------------------------------------------------------------------------------- + // try version 1 of the degenerate search + // degenerate encodings use basecolor movement and a subset of the selectors to find useful encodings + // each subsequent version of the degenerate search uses more basecolor movement and is less likely to + // be successfull + // + void Block4x4Encoding_RGB8A1::TryDegenerates1(void) + { + + TryDifferential(m_boolMostLikelyFlip, 1, -2, 0); + TryDifferential(m_boolMostLikelyFlip, 1, 2, 0); + TryDifferential(m_boolMostLikelyFlip, 1, 0, 2); + TryDifferential(m_boolMostLikelyFlip, 1, 0, -2); + + } + + // ---------------------------------------------------------------------------------------------------- + // try version 2 of the degenerate search + // degenerate encodings use basecolor movement and a subset of the selectors to find useful encodings + // each subsequent version of the degenerate search uses more basecolor movement and is less likely to + // be successfull + // + void Block4x4Encoding_RGB8A1::TryDegenerates2(void) + { + + TryDifferential(!m_boolMostLikelyFlip, 1, -2, 0); + TryDifferential(!m_boolMostLikelyFlip, 1, 2, 0); + TryDifferential(!m_boolMostLikelyFlip, 1, 0, 2); + TryDifferential(!m_boolMostLikelyFlip, 1, 0, -2); + + } + + // ---------------------------------------------------------------------------------------------------- + // try version 3 of the degenerate search + // degenerate encodings use basecolor movement and a subset of the selectors to find useful encodings + // each subsequent version of the degenerate search uses more basecolor movement and is less likely to + // be successfull + // + void Block4x4Encoding_RGB8A1::TryDegenerates3(void) + { + + TryDifferential(m_boolMostLikelyFlip, 1, -2, -2); + TryDifferential(m_boolMostLikelyFlip, 1, -2, 2); + TryDifferential(m_boolMostLikelyFlip, 1, 2, -2); + TryDifferential(m_boolMostLikelyFlip, 1, 2, 2); + + } + + // ---------------------------------------------------------------------------------------------------- + // try version 4 of the degenerate search + // degenerate encodings use basecolor movement and a subset of the selectors to find useful encodings + // each subsequent version of the degenerate search uses more basecolor movement and is less likely to + // be successfull + // + void Block4x4Encoding_RGB8A1::TryDegenerates4(void) + { + + TryDifferential(m_boolMostLikelyFlip, 1, -4, 0); + TryDifferential(m_boolMostLikelyFlip, 1, 4, 0); + TryDifferential(m_boolMostLikelyFlip, 1, 0, 4); + TryDifferential(m_boolMostLikelyFlip, 1, 0, -4); + + } + + // ---------------------------------------------------------------------------------------------------- + // set the encoding bits based on encoding state + // + void Block4x4Encoding_RGB8A1::SetEncodingBits(void) + { + switch (m_mode) + { + case MODE_ETC1: + SetEncodingBits_ETC1(); + break; + + case MODE_T: + SetEncodingBits_T(); + break; + + case MODE_H: + SetEncodingBits_H(); + break; + + case MODE_PLANAR: + Block4x4Encoding_RGB8::SetEncodingBits_Planar(); + break; + + default: + assert(false); + } + } + + // ---------------------------------------------------------------------------------------------------- + // set the encoding bits based on encoding state if ETC1 mode + // + void Block4x4Encoding_RGB8A1::SetEncodingBits_ETC1(void) + { + + // there is no individual mode in RGB8A1 + assert(m_boolDiff); + + int iRed1 = m_frgbaColor1.IntRed(31.0f); + int iGreen1 = m_frgbaColor1.IntGreen(31.0f); + int iBlue1 = m_frgbaColor1.IntBlue(31.0f); + + int iRed2 = m_frgbaColor2.IntRed(31.0f); + int iGreen2 = m_frgbaColor2.IntGreen(31.0f); + int iBlue2 = m_frgbaColor2.IntBlue(31.0f); + + int iDRed2 = iRed2 - iRed1; + int iDGreen2 = iGreen2 - iGreen1; + int iDBlue2 = iBlue2 - iBlue1; + + assert(iDRed2 >= -4 && iDRed2 < 4); + assert(iDGreen2 >= -4 && iDGreen2 < 4); + assert(iDBlue2 >= -4 && iDBlue2 < 4); + + m_pencodingbitsRGB8->differential.red1 = iRed1; + m_pencodingbitsRGB8->differential.green1 = iGreen1; + m_pencodingbitsRGB8->differential.blue1 = iBlue1; + + m_pencodingbitsRGB8->differential.dred2 = iDRed2; + m_pencodingbitsRGB8->differential.dgreen2 = iDGreen2; + m_pencodingbitsRGB8->differential.dblue2 = iDBlue2; + + m_pencodingbitsRGB8->individual.cw1 = m_uiCW1; + m_pencodingbitsRGB8->individual.cw2 = m_uiCW2; + + SetEncodingBits_Selectors(); + + // in RGB8A1 encoding bits, opaque replaces differential + m_pencodingbitsRGB8->differential.diff = !m_boolPunchThroughPixels; + + m_pencodingbitsRGB8->individual.flip = m_boolFlip; + + } + + // ---------------------------------------------------------------------------------------------------- + // set the encoding bits based on encoding state if T mode + // + void Block4x4Encoding_RGB8A1::SetEncodingBits_T(void) + { + static const bool SANITY_CHECK = true; + + assert(m_mode == MODE_T); + assert(m_boolDiff == true); + + unsigned int uiRed1 = (unsigned int)m_frgbaColor1.IntRed(15.0f); + unsigned int uiGreen1 = (unsigned int)m_frgbaColor1.IntGreen(15.0f); + unsigned int uiBlue1 = (unsigned int)m_frgbaColor1.IntBlue(15.0f); + + unsigned int uiRed2 = (unsigned int)m_frgbaColor2.IntRed(15.0f); + unsigned int uiGreen2 = (unsigned int)m_frgbaColor2.IntGreen(15.0f); + unsigned int uiBlue2 = (unsigned int)m_frgbaColor2.IntBlue(15.0f); + + m_pencodingbitsRGB8->t.red1a = uiRed1 >> 2; + m_pencodingbitsRGB8->t.red1b = uiRed1; + m_pencodingbitsRGB8->t.green1 = uiGreen1; + m_pencodingbitsRGB8->t.blue1 = uiBlue1; + + m_pencodingbitsRGB8->t.red2 = uiRed2; + m_pencodingbitsRGB8->t.green2 = uiGreen2; + m_pencodingbitsRGB8->t.blue2 = uiBlue2; + + m_pencodingbitsRGB8->t.da = m_uiCW1 >> 1; + m_pencodingbitsRGB8->t.db = m_uiCW1; + + // in RGB8A1 encoding bits, opaque replaces differential + m_pencodingbitsRGB8->differential.diff = !m_boolPunchThroughPixels; + + Block4x4Encoding_ETC1::SetEncodingBits_Selectors(); + + // create an invalid R differential to trigger T mode + m_pencodingbitsRGB8->t.detect1 = 0; + m_pencodingbitsRGB8->t.detect2 = 0; + int iRed2 = (int)m_pencodingbitsRGB8->differential.red1 + (int)m_pencodingbitsRGB8->differential.dred2; + if (iRed2 >= 4) + { + m_pencodingbitsRGB8->t.detect1 = 7; + m_pencodingbitsRGB8->t.detect2 = 0; + } + else + { + m_pencodingbitsRGB8->t.detect1 = 0; + m_pencodingbitsRGB8->t.detect2 = 1; + } + + if (SANITY_CHECK) + { + iRed2 = (int)m_pencodingbitsRGB8->differential.red1 + (int)m_pencodingbitsRGB8->differential.dred2; + + // make sure red overflows + assert(iRed2 < 0 || iRed2 > 31); + } + + } + + // ---------------------------------------------------------------------------------------------------- + // set the encoding bits based on encoding state if H mode + // + // colors and selectors may need to swap in order to generate lsb of distance index + // + void Block4x4Encoding_RGB8A1::SetEncodingBits_H(void) + { + static const bool SANITY_CHECK = true; + + assert(m_mode == MODE_H); + assert(m_boolDiff == true); + + unsigned int uiRed1 = (unsigned int)m_frgbaColor1.IntRed(15.0f); + unsigned int uiGreen1 = (unsigned int)m_frgbaColor1.IntGreen(15.0f); + unsigned int uiBlue1 = (unsigned int)m_frgbaColor1.IntBlue(15.0f); + + unsigned int uiRed2 = (unsigned int)m_frgbaColor2.IntRed(15.0f); + unsigned int uiGreen2 = (unsigned int)m_frgbaColor2.IntGreen(15.0f); + unsigned int uiBlue2 = (unsigned int)m_frgbaColor2.IntBlue(15.0f); + + unsigned int uiColor1 = (uiRed1 << 16) + (uiGreen1 << 8) + uiBlue1; + unsigned int uiColor2 = (uiRed2 << 16) + (uiGreen2 << 8) + uiBlue2; + + bool boolOddDistance = m_uiCW1 & 1; + bool boolSwapColors = (uiColor1 < uiColor2) ^ !boolOddDistance; + + if (boolSwapColors) + { + m_pencodingbitsRGB8->h.red1 = uiRed2; + m_pencodingbitsRGB8->h.green1a = uiGreen2 >> 1; + m_pencodingbitsRGB8->h.green1b = uiGreen2; + m_pencodingbitsRGB8->h.blue1a = uiBlue2 >> 3; + m_pencodingbitsRGB8->h.blue1b = uiBlue2 >> 1; + m_pencodingbitsRGB8->h.blue1c = uiBlue2; + + m_pencodingbitsRGB8->h.red2 = uiRed1; + m_pencodingbitsRGB8->h.green2a = uiGreen1 >> 1; + m_pencodingbitsRGB8->h.green2b = uiGreen1; + m_pencodingbitsRGB8->h.blue2 = uiBlue1; + + m_pencodingbitsRGB8->h.da = m_uiCW1 >> 2; + m_pencodingbitsRGB8->h.db = m_uiCW1 >> 1; + } + else + { + m_pencodingbitsRGB8->h.red1 = uiRed1; + m_pencodingbitsRGB8->h.green1a = uiGreen1 >> 1; + m_pencodingbitsRGB8->h.green1b = uiGreen1; + m_pencodingbitsRGB8->h.blue1a = uiBlue1 >> 3; + m_pencodingbitsRGB8->h.blue1b = uiBlue1 >> 1; + m_pencodingbitsRGB8->h.blue1c = uiBlue1; + + m_pencodingbitsRGB8->h.red2 = uiRed2; + m_pencodingbitsRGB8->h.green2a = uiGreen2 >> 1; + m_pencodingbitsRGB8->h.green2b = uiGreen2; + m_pencodingbitsRGB8->h.blue2 = uiBlue2; + + m_pencodingbitsRGB8->h.da = m_uiCW1 >> 2; + m_pencodingbitsRGB8->h.db = m_uiCW1 >> 1; + } + + // in RGB8A1 encoding bits, opaque replaces differential + m_pencodingbitsRGB8->differential.diff = !m_boolPunchThroughPixels; + + Block4x4Encoding_ETC1::SetEncodingBits_Selectors(); + + if (boolSwapColors) + { + m_pencodingbitsRGB8->h.selectors ^= 0x0000FFFF; + } + + // create an invalid R differential to trigger T mode + m_pencodingbitsRGB8->h.detect1 = 0; + m_pencodingbitsRGB8->h.detect2 = 0; + m_pencodingbitsRGB8->h.detect3 = 0; + int iRed2 = (int)m_pencodingbitsRGB8->differential.red1 + (int)m_pencodingbitsRGB8->differential.dred2; + int iGreen2 = (int)m_pencodingbitsRGB8->differential.green1 + (int)m_pencodingbitsRGB8->differential.dgreen2; + if (iRed2 < 0 || iRed2 > 31) + { + m_pencodingbitsRGB8->h.detect1 = 1; + } + if (iGreen2 >= 4) + { + m_pencodingbitsRGB8->h.detect2 = 7; + m_pencodingbitsRGB8->h.detect3 = 0; + } + else + { + m_pencodingbitsRGB8->h.detect2 = 0; + m_pencodingbitsRGB8->h.detect3 = 1; + } + + if (SANITY_CHECK) + { + iRed2 = (int)m_pencodingbitsRGB8->differential.red1 + (int)m_pencodingbitsRGB8->differential.dred2; + iGreen2 = (int)m_pencodingbitsRGB8->differential.green1 + (int)m_pencodingbitsRGB8->differential.dgreen2; + + // make sure red doesn't overflow and green does + assert(iRed2 >= 0 && iRed2 <= 31); + assert(iGreen2 < 0 || iGreen2 > 31); + } + + } + + // #################################################################################################### + // Block4x4Encoding_RGB8A1_Opaque + // #################################################################################################### + + // ---------------------------------------------------------------------------------------------------- + // perform a single encoding iteration + // replace the encoding if a better encoding was found + // subsequent iterations generally take longer for each iteration + // set m_boolDone if encoding is perfect or encoding is finished based on a_fEffort + // + void Block4x4Encoding_RGB8A1_Opaque::PerformIteration(float a_fEffort) + { + assert(!m_boolPunchThroughPixels); + assert(!m_boolTransparent); + assert(!m_boolDone); + + switch (m_uiEncodingIterations) + { + case 0: + PerformFirstIteration(); + break; + + case 1: + Block4x4Encoding_ETC1::TryDifferential(m_boolMostLikelyFlip, 1, 0, 0); + break; + + case 2: + Block4x4Encoding_ETC1::TryDifferential(!m_boolMostLikelyFlip, 1, 0, 0); + break; + + case 3: + Block4x4Encoding_RGB8::TryPlanar(1); + break; + + case 4: + Block4x4Encoding_RGB8::TryTAndH(1); + if (a_fEffort <= 49.5f) + { + m_boolDone = true; + } + break; + + case 5: + Block4x4Encoding_ETC1::TryDegenerates1(); + if (a_fEffort <= 59.5f) + { + m_boolDone = true; + } + break; + + case 6: + Block4x4Encoding_ETC1::TryDegenerates2(); + if (a_fEffort <= 69.5f) + { + m_boolDone = true; + } + break; + + case 7: + Block4x4Encoding_ETC1::TryDegenerates3(); + if (a_fEffort <= 79.5f) + { + m_boolDone = true; + } + break; + + case 8: + Block4x4Encoding_ETC1::TryDegenerates4(); + m_boolDone = true; + break; + + default: + assert(0); + break; + } + + m_uiEncodingIterations++; + SetDoneIfPerfect(); + } + + // ---------------------------------------------------------------------------------------------------- + // find best initial encoding to ensure block has a valid encoding + // + void Block4x4Encoding_RGB8A1_Opaque::PerformFirstIteration(void) + { + + // set decoded alphas + // calculate alpha error + m_fError = 0.0f; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_afDecodedAlphas[uiPixel] = 1.0f; + + float fDeltaA = 1.0f - m_pafrgbaSource[uiPixel].fA; + m_fError += fDeltaA * fDeltaA; + } + + CalculateMostLikelyFlip(); + + m_fError = FLT_MAX; + + Block4x4Encoding_ETC1::TryDifferential(m_boolMostLikelyFlip, 0, 0, 0); + SetDoneIfPerfect(); + if (m_boolDone) + { + return; + } + Block4x4Encoding_ETC1::TryDifferential(!m_boolMostLikelyFlip, 0, 0, 0); + SetDoneIfPerfect(); + if (m_boolDone) + { + return; + } + Block4x4Encoding_RGB8::TryPlanar(0); + SetDoneIfPerfect(); + if (m_boolDone) + { + return; + } + Block4x4Encoding_RGB8::TryTAndH(0); + SetDoneIfPerfect(); + } + + // #################################################################################################### + // Block4x4Encoding_RGB8A1_Transparent + // #################################################################################################### + + // ---------------------------------------------------------------------------------------------------- + // perform a single encoding iteration + // replace the encoding if a better encoding was found + // subsequent iterations generally take longer for each iteration + // set m_boolDone if encoding is perfect or encoding is finished based on a_fEffort + // + void Block4x4Encoding_RGB8A1_Transparent::PerformIteration(float ) + { + assert(!m_boolOpaque); + assert(m_boolTransparent); + assert(!m_boolDone); + assert(m_uiEncodingIterations == 0); + + m_mode = MODE_ETC1; + m_boolDiff = true; + m_boolFlip = false; + + m_uiCW1 = 0; + m_uiCW2 = 0; + + m_frgbaColor1 = ColorFloatRGBA(); + m_frgbaColor2 = ColorFloatRGBA(); + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_auiSelectors[uiPixel] = TRANSPARENT_SELECTOR; + + m_afrgbaDecodedColors[uiPixel] = ColorFloatRGBA(); + m_afDecodedAlphas[uiPixel] = 0.0f; + } + + CalcBlockError(); + + m_boolDone = true; + m_uiEncodingIterations++; + + } + + // ---------------------------------------------------------------------------------------------------- + // +} diff --git a/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGB8A1.h b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGB8A1.h new file mode 100644 index 0000000..91b9355 --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGB8A1.h @@ -0,0 +1,129 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +#pragma once + +#include "EtcBlock4x4Encoding_RGB8.h" +#include "EtcErrorMetric.h" +#include "EtcBlock4x4EncodingBits.h" + +namespace Etc +{ + + // ################################################################################ + // Block4x4Encoding_RGB8A1 + // RGB8A1 if not completely opaque or transparent + // ################################################################################ + + class Block4x4Encoding_RGB8A1 : public Block4x4Encoding_RGB8 + { + public: + + static const unsigned int TRANSPARENT_SELECTOR = 2; + + Block4x4Encoding_RGB8A1(void); + virtual ~Block4x4Encoding_RGB8A1(void); + + virtual void InitFromSource(Block4x4 *a_pblockParent, + ColorFloatRGBA *a_pafrgbaSource, + unsigned char *a_paucEncodingBits, + ErrorMetric a_errormetric); + + virtual void InitFromEncodingBits(Block4x4 *a_pblockParent, + unsigned char *a_paucEncodingBits, + ColorFloatRGBA *a_pafrgbaSource, + ErrorMetric a_errormetric); + + virtual void PerformIteration(float a_fEffort); + + virtual void SetEncodingBits(void); + + void InitFromEncodingBits_ETC1(Block4x4 *a_pblockParent, + unsigned char *a_paucEncodingBits, + ColorFloatRGBA *a_pafrgbaSource, + ErrorMetric a_errormetric); + + void InitFromEncodingBits_T(void); + void InitFromEncodingBits_H(void); + + void PerformFirstIteration(void); + + void Decode_ETC1(void); + void DecodePixels_T(void); + void DecodePixels_H(void); + void SetEncodingBits_ETC1(void); + void SetEncodingBits_T(void); + void SetEncodingBits_H(void); + + protected: + + bool m_boolOpaque; // all source pixels have alpha >= 0.5 + bool m_boolTransparent; // all source pixels have alpha < 0.5 + bool m_boolPunchThroughPixels; // some source pixels have alpha < 0.5 + + static float s_aafCwOpaqueUnsetTable[CW_RANGES][SELECTORS]; + + private: + + void TryDifferential(bool a_boolFlip, unsigned int a_uiRadius, + int a_iGrayOffset1, int a_iGrayOffset2); + void TryDifferentialHalf(DifferentialTrys::Half *a_phalf); + + void TryT(unsigned int a_uiRadius); + void TryT_BestSelectorCombination(void); + void TryH(unsigned int a_uiRadius); + void TryH_BestSelectorCombination(void); + + void TryDegenerates1(void); + void TryDegenerates2(void); + void TryDegenerates3(void); + void TryDegenerates4(void); + + }; + + // ################################################################################ + // Block4x4Encoding_RGB8A1_Opaque + // RGB8A1 if all pixels have alpha==1 + // ################################################################################ + + class Block4x4Encoding_RGB8A1_Opaque : public Block4x4Encoding_RGB8A1 + { + public: + + virtual void PerformIteration(float a_fEffort); + + void PerformFirstIteration(void); + + private: + + }; + + // ################################################################################ + // Block4x4Encoding_RGB8A1_Transparent + // RGB8A1 if all pixels have alpha==0 + // ################################################################################ + + class Block4x4Encoding_RGB8A1_Transparent : public Block4x4Encoding_RGB8A1 + { + public: + + virtual void PerformIteration(float a_fEffort); + + private: + + }; + +} // namespace Etc diff --git a/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGBA8.cpp b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGBA8.cpp new file mode 100644 index 0000000..0ca531d --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGBA8.cpp @@ -0,0 +1,474 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +/* +EtcBlock4x4Encoding_RGBA8.cpp contains: + Block4x4Encoding_RGBA8 + Block4x4Encoding_RGBA8_Opaque + Block4x4Encoding_RGBA8_Transparent + +These encoders are used when targetting file format RGBA8. + +Block4x4Encoding_RGBA8_Opaque is used when all pixels in the 4x4 block are opaque +Block4x4Encoding_RGBA8_Transparent is used when all pixels in the 4x4 block are transparent +Block4x4Encoding_RGBA8 is used when there is a mixture of alphas in the 4x4 block + +*/ + +#include "EtcConfig.h" +#include "EtcBlock4x4Encoding_RGBA8.h" + +#include "EtcBlock4x4EncodingBits.h" +#include "EtcBlock4x4.h" + +#include +#include +#include +#include +#include + +namespace Etc +{ + + // #################################################################################################### + // Block4x4Encoding_RGBA8 + // #################################################################################################### + + float Block4x4Encoding_RGBA8::s_aafModifierTable[MODIFIER_TABLE_ENTRYS][ALPHA_SELECTORS] + { + { -3.0f / 255.0f, -6.0f / 255.0f, -9.0f / 255.0f, -15.0f / 255.0f, 2.0f / 255.0f, 5.0f / 255.0f, 8.0f / 255.0f, 14.0f / 255.0f }, + { -3.0f / 255.0f, -7.0f / 255.0f, -10.0f / 255.0f, -13.0f / 255.0f, 2.0f / 255.0f, 6.0f / 255.0f, 9.0f / 255.0f, 12.0f / 255.0f }, + { -2.0f / 255.0f, -5.0f / 255.0f, -8.0f / 255.0f, -13.0f / 255.0f, 1.0f / 255.0f, 4.0f / 255.0f, 7.0f / 255.0f, 12.0f / 255.0f }, + { -2.0f / 255.0f, -4.0f / 255.0f, -6.0f / 255.0f, -13.0f / 255.0f, 1.0f / 255.0f, 3.0f / 255.0f, 5.0f / 255.0f, 12.0f / 255.0f }, + + { -3.0f / 255.0f, -6.0f / 255.0f, -8.0f / 255.0f, -12.0f / 255.0f, 2.0f / 255.0f, 5.0f / 255.0f, 7.0f / 255.0f, 11.0f / 255.0f }, + { -3.0f / 255.0f, -7.0f / 255.0f, -9.0f / 255.0f, -11.0f / 255.0f, 2.0f / 255.0f, 6.0f / 255.0f, 8.0f / 255.0f, 10.0f / 255.0f }, + { -4.0f / 255.0f, -7.0f / 255.0f, -8.0f / 255.0f, -11.0f / 255.0f, 3.0f / 255.0f, 6.0f / 255.0f, 7.0f / 255.0f, 10.0f / 255.0f }, + { -3.0f / 255.0f, -5.0f / 255.0f, -8.0f / 255.0f, -11.0f / 255.0f, 2.0f / 255.0f, 4.0f / 255.0f, 7.0f / 255.0f, 10.0f / 255.0f }, + + { -2.0f / 255.0f, -6.0f / 255.0f, -8.0f / 255.0f, -10.0f / 255.0f, 1.0f / 255.0f, 5.0f / 255.0f, 7.0f / 255.0f, 9.0f / 255.0f }, + { -2.0f / 255.0f, -5.0f / 255.0f, -8.0f / 255.0f, -10.0f / 255.0f, 1.0f / 255.0f, 4.0f / 255.0f, 7.0f / 255.0f, 9.0f / 255.0f }, + { -2.0f / 255.0f, -4.0f / 255.0f, -8.0f / 255.0f, -10.0f / 255.0f, 1.0f / 255.0f, 3.0f / 255.0f, 7.0f / 255.0f, 9.0f / 255.0f }, + { -2.0f / 255.0f, -5.0f / 255.0f, -7.0f / 255.0f, -10.0f / 255.0f, 1.0f / 255.0f, 4.0f / 255.0f, 6.0f / 255.0f, 9.0f / 255.0f }, + + { -3.0f / 255.0f, -4.0f / 255.0f, -7.0f / 255.0f, -10.0f / 255.0f, 2.0f / 255.0f, 3.0f / 255.0f, 6.0f / 255.0f, 9.0f / 255.0f }, + { -1.0f / 255.0f, -2.0f / 255.0f, -3.0f / 255.0f, -10.0f / 255.0f, 0.0f / 255.0f, 1.0f / 255.0f, 2.0f / 255.0f, 9.0f / 255.0f }, + { -4.0f / 255.0f, -6.0f / 255.0f, -8.0f / 255.0f, -9.0f / 255.0f, 3.0f / 255.0f, 5.0f / 255.0f, 7.0f / 255.0f, 8.0f / 255.0f }, + { -3.0f / 255.0f, -5.0f / 255.0f, -7.0f / 255.0f, -9.0f / 255.0f, 2.0f / 255.0f, 4.0f / 255.0f, 6.0f / 255.0f, 8.0f / 255.0f } + }; + + // ---------------------------------------------------------------------------------------------------- + // + Block4x4Encoding_RGBA8::Block4x4Encoding_RGBA8(void) + { + + m_pencodingbitsA8 = nullptr; + + } + Block4x4Encoding_RGBA8::~Block4x4Encoding_RGBA8(void) {} + // ---------------------------------------------------------------------------------------------------- + // initialization prior to encoding + // a_pblockParent points to the block associated with this encoding + // a_errormetric is used to choose the best encoding + // a_pafrgbaSource points to a 4x4 block subset of the source image + // a_paucEncodingBits points to the final encoding bits + // + void Block4x4Encoding_RGBA8::InitFromSource(Block4x4 *a_pblockParent, + ColorFloatRGBA *a_pafrgbaSource, + unsigned char *a_paucEncodingBits, ErrorMetric a_errormetric) + { + Block4x4Encoding::Init(a_pblockParent, a_pafrgbaSource,a_errormetric); + + m_pencodingbitsA8 = (Block4x4EncodingBits_A8 *)a_paucEncodingBits; + m_pencodingbitsRGB8 = (Block4x4EncodingBits_RGB8 *)(a_paucEncodingBits + sizeof(Block4x4EncodingBits_A8)); + + } + + // ---------------------------------------------------------------------------------------------------- + // initialization from the encoding bits of a previous encoding + // a_pblockParent points to the block associated with this encoding + // a_errormetric is used to choose the best encoding + // a_pafrgbaSource points to a 4x4 block subset of the source image + // a_paucEncodingBits points to the final encoding bits of a previous encoding + // + void Block4x4Encoding_RGBA8::InitFromEncodingBits(Block4x4 *a_pblockParent, + unsigned char *a_paucEncodingBits, + ColorFloatRGBA *a_pafrgbaSource, + ErrorMetric a_errormetric) + { + + m_pencodingbitsA8 = (Block4x4EncodingBits_A8 *)a_paucEncodingBits; + m_pencodingbitsRGB8 = (Block4x4EncodingBits_RGB8 *)(a_paucEncodingBits + sizeof(Block4x4EncodingBits_A8)); + + // init RGB portion + Block4x4Encoding_RGB8::InitFromEncodingBits(a_pblockParent, + (unsigned char *) m_pencodingbitsRGB8, + a_pafrgbaSource, + a_errormetric); + + // init A8 portion + // has to be done after InitFromEncodingBits() + { + m_fBase = m_pencodingbitsA8->data.base / 255.0f; + m_fMultiplier = (float)m_pencodingbitsA8->data.multiplier; + m_uiModifierTableIndex = m_pencodingbitsA8->data.table; + + unsigned long long int ulliSelectorBits = 0; + ulliSelectorBits |= (unsigned long long int)m_pencodingbitsA8->data.selectors0 << 40; + ulliSelectorBits |= (unsigned long long int)m_pencodingbitsA8->data.selectors1 << 32; + ulliSelectorBits |= (unsigned long long int)m_pencodingbitsA8->data.selectors2 << 24; + ulliSelectorBits |= (unsigned long long int)m_pencodingbitsA8->data.selectors3 << 16; + ulliSelectorBits |= (unsigned long long int)m_pencodingbitsA8->data.selectors4 << 8; + ulliSelectorBits |= (unsigned long long int)m_pencodingbitsA8->data.selectors5; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + unsigned int uiShift = 45 - (3 * uiPixel); + m_auiAlphaSelectors[uiPixel] = (ulliSelectorBits >> uiShift) & (ALPHA_SELECTORS - 1); + } + + // decode the alphas + // calc alpha error + m_fError = 0.0f; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_afDecodedAlphas[uiPixel] = DecodePixelAlpha(m_fBase, m_fMultiplier, + m_uiModifierTableIndex, + m_auiAlphaSelectors[uiPixel]); + + float fDeltaAlpha = m_afDecodedAlphas[uiPixel] - m_pafrgbaSource[uiPixel].fA; + m_fError += fDeltaAlpha * fDeltaAlpha; + } + } + + // redo error calc to include alpha + CalcBlockError(); + + } + + // ---------------------------------------------------------------------------------------------------- + // perform a single encoding iteration + // replace the encoding if a better encoding was found + // subsequent iterations generally take longer for each iteration + // set m_boolDone if encoding is perfect or encoding is finished based on a_fEffort + // + // similar to Block4x4Encoding_RGB8_Base::Encode_RGB8(), but with alpha added + // + void Block4x4Encoding_RGBA8::PerformIteration(float a_fEffort) + { + assert(!m_boolDone); + + if (m_uiEncodingIterations == 0) + { + if (a_fEffort < 24.9f) + { + CalculateA8(0.0f); + } + else if (a_fEffort < 49.9f) + { + CalculateA8(1.0f); + } + else + { + CalculateA8(2.0f); + } + } + + Block4x4Encoding_RGB8::PerformIteration(a_fEffort); + + } + + // ---------------------------------------------------------------------------------------------------- + // find the best combination of base alpga, multiplier and selectors + // + // a_fRadius limits the range of base alpha to try + // + void Block4x4Encoding_RGBA8::CalculateA8(float a_fRadius) + { + + // find min/max alpha + float fMinAlpha = 1.0f; + float fMaxAlpha = 0.0f; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + float fAlpha = m_pafrgbaSource[uiPixel].fA; + + // ignore border pixels + if (isnan(fAlpha)) + { + continue; + } + + if (fAlpha < fMinAlpha) + { + fMinAlpha = fAlpha; + } + if (fAlpha > fMaxAlpha) + { + fMaxAlpha = fAlpha; + } + } + assert(fMinAlpha <= fMaxAlpha); + + float fAlphaRange = fMaxAlpha - fMinAlpha; + + // try each modifier table entry + m_fError = FLT_MAX; // artificially high value + for (unsigned int uiTableEntry = 0; uiTableEntry < MODIFIER_TABLE_ENTRYS; uiTableEntry++) + { + static const unsigned int MIN_VALUE_SELECTOR = 3; + static const unsigned int MAX_VALUE_SELECTOR = 7; + + float fTableEntryCenter = -s_aafModifierTable[uiTableEntry][MIN_VALUE_SELECTOR]; + + float fTableEntryRange = s_aafModifierTable[uiTableEntry][MAX_VALUE_SELECTOR] - + s_aafModifierTable[uiTableEntry][MIN_VALUE_SELECTOR]; + + float fCenterRatio = fTableEntryCenter / fTableEntryRange; + + float fCenter = fMinAlpha + fCenterRatio*fAlphaRange; + fCenter = roundf(255.0f * fCenter) / 255.0f; + + float fMinBase = fCenter - (a_fRadius / 255.0f); + if (fMinBase < 0.0f) + { + fMinBase = 0.0f; + } + + float fMaxBase = fCenter + (a_fRadius / 255.0f); + if (fMaxBase > 1.0f) + { + fMaxBase = 1.0f; + } + + for (float fBase = fMinBase; fBase <= fMaxBase; fBase += (0.999999f / 255.0f)) + { + + float fRangeMultiplier = roundf(fAlphaRange / fTableEntryRange); + + float fMinMultiplier = fRangeMultiplier - a_fRadius; + if (fMinMultiplier < 1.0f) + { + fMinMultiplier = 1.0f; + } + else if (fMinMultiplier > 15.0f) + { + fMinMultiplier = 15.0f; + } + + float fMaxMultiplier = fRangeMultiplier + a_fRadius; + if (fMaxMultiplier < 1.0f) + { + fMaxMultiplier = 1.0f; + } + else if (fMaxMultiplier > 15.0f) + { + fMaxMultiplier = 15.0f; + } + + for (float fMultiplier = fMinMultiplier; fMultiplier <= fMaxMultiplier; fMultiplier += 1.0f) + { + // find best selector for each pixel + unsigned int auiBestSelectors[PIXELS]; + float afBestAlphaError[PIXELS]; + float afBestDecodedAlphas[PIXELS]; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + float fBestPixelAlphaError = FLT_MAX; + for (unsigned int uiSelector = 0; uiSelector < ALPHA_SELECTORS; uiSelector++) + { + float fDecodedAlpha = DecodePixelAlpha(fBase, fMultiplier, uiTableEntry, uiSelector); + + // border pixels (NAN) should have zero error + float fPixelDeltaAlpha = isnan(m_pafrgbaSource[uiPixel].fA) ? + 0.0f : + fDecodedAlpha - m_pafrgbaSource[uiPixel].fA; + + float fPixelAlphaError = fPixelDeltaAlpha * fPixelDeltaAlpha; + + if (fPixelAlphaError < fBestPixelAlphaError) + { + fBestPixelAlphaError = fPixelAlphaError; + auiBestSelectors[uiPixel] = uiSelector; + afBestAlphaError[uiPixel] = fBestPixelAlphaError; + afBestDecodedAlphas[uiPixel] = fDecodedAlpha; + } + } + } + + float fBlockError = 0.0f; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + fBlockError += afBestAlphaError[uiPixel]; + } + + if (fBlockError < m_fError) + { + m_fError = fBlockError; + + m_fBase = fBase; + m_fMultiplier = fMultiplier; + m_uiModifierTableIndex = uiTableEntry; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_auiAlphaSelectors[uiPixel] = auiBestSelectors[uiPixel]; + m_afDecodedAlphas[uiPixel] = afBestDecodedAlphas[uiPixel]; + } + } + } + } + + } + + } + + // ---------------------------------------------------------------------------------------------------- + // set the encoding bits based on encoding state + // + void Block4x4Encoding_RGBA8::SetEncodingBits(void) + { + + // set the RGB8 portion + Block4x4Encoding_RGB8::SetEncodingBits(); + + // set the A8 portion + { + m_pencodingbitsA8->data.base = (unsigned char)roundf(255.0f * m_fBase); + m_pencodingbitsA8->data.table = m_uiModifierTableIndex; + m_pencodingbitsA8->data.multiplier = (unsigned char)roundf(m_fMultiplier); + + unsigned long long int ulliSelectorBits = 0; + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + unsigned int uiShift = 45 - (3 * uiPixel); + ulliSelectorBits |= ((unsigned long long int)m_auiAlphaSelectors[uiPixel]) << uiShift; + } + + m_pencodingbitsA8->data.selectors0 = ulliSelectorBits >> 40; + m_pencodingbitsA8->data.selectors1 = ulliSelectorBits >> 32; + m_pencodingbitsA8->data.selectors2 = ulliSelectorBits >> 24; + m_pencodingbitsA8->data.selectors3 = ulliSelectorBits >> 16; + m_pencodingbitsA8->data.selectors4 = ulliSelectorBits >> 8; + m_pencodingbitsA8->data.selectors5 = ulliSelectorBits; + } + + } + + // #################################################################################################### + // Block4x4Encoding_RGBA8_Opaque + // #################################################################################################### + + // ---------------------------------------------------------------------------------------------------- + // perform a single encoding iteration + // replace the encoding if a better encoding was found + // subsequent iterations generally take longer for each iteration + // set m_boolDone if encoding is perfect or encoding is finished based on a_fEffort + // + void Block4x4Encoding_RGBA8_Opaque::PerformIteration(float a_fEffort) + { + assert(!m_boolDone); + + if (m_uiEncodingIterations == 0) + { + m_fError = 0.0f; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_afDecodedAlphas[uiPixel] = 1.0f; + } + } + + Block4x4Encoding_RGB8::PerformIteration(a_fEffort); + + } + + // ---------------------------------------------------------------------------------------------------- + // set the encoding bits based on encoding state + // + void Block4x4Encoding_RGBA8_Opaque::SetEncodingBits(void) + { + + // set the RGB8 portion + Block4x4Encoding_RGB8::SetEncodingBits(); + + // set the A8 portion + m_pencodingbitsA8->data.base = 255; + m_pencodingbitsA8->data.table = 15; + m_pencodingbitsA8->data.multiplier = 15; + m_pencodingbitsA8->data.selectors0 = 0xFF; + m_pencodingbitsA8->data.selectors1 = 0xFF; + m_pencodingbitsA8->data.selectors2 = 0xFF; + m_pencodingbitsA8->data.selectors3 = 0xFF; + m_pencodingbitsA8->data.selectors4 = 0xFF; + m_pencodingbitsA8->data.selectors5 = 0xFF; + + } + + // #################################################################################################### + // Block4x4Encoding_RGBA8_Transparent + // #################################################################################################### + + // ---------------------------------------------------------------------------------------------------- + // perform a single encoding iteration + // replace the encoding if a better encoding was found + // subsequent iterations generally take longer for each iteration + // set m_boolDone if encoding is perfect or encoding is finished based on a_fEffort + // + void Block4x4Encoding_RGBA8_Transparent::PerformIteration(float ) + { + assert(!m_boolDone); + assert(m_uiEncodingIterations == 0); + + m_mode = MODE_ETC1; + m_boolDiff = true; + m_boolFlip = false; + + for (unsigned int uiPixel = 0; uiPixel < PIXELS; uiPixel++) + { + m_afrgbaDecodedColors[uiPixel] = ColorFloatRGBA(); + m_afDecodedAlphas[uiPixel] = 0.0f; + } + + m_fError = 0.0f; + + m_boolDone = true; + m_uiEncodingIterations++; + + } + + // ---------------------------------------------------------------------------------------------------- + // set the encoding bits based on encoding state + // + void Block4x4Encoding_RGBA8_Transparent::SetEncodingBits(void) + { + + Block4x4Encoding_RGB8::SetEncodingBits(); + + // set the A8 portion + m_pencodingbitsA8->data.base = 0; + m_pencodingbitsA8->data.table = 0; + m_pencodingbitsA8->data.multiplier = 1; + m_pencodingbitsA8->data.selectors0 = 0; + m_pencodingbitsA8->data.selectors1 = 0; + m_pencodingbitsA8->data.selectors2 = 0; + m_pencodingbitsA8->data.selectors3 = 0; + m_pencodingbitsA8->data.selectors4 = 0; + m_pencodingbitsA8->data.selectors5 = 0; + + } + + // ---------------------------------------------------------------------------------------------------- + // +} diff --git a/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGBA8.h b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGBA8.h new file mode 100644 index 0000000..9d21e90 --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcBlock4x4Encoding_RGBA8.h @@ -0,0 +1,121 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +#pragma once + +#include "EtcBlock4x4Encoding_RGB8.h" + +namespace Etc +{ + class Block4x4EncodingBits_A8; + + // ################################################################################ + // Block4x4Encoding_RGBA8 + // RGBA8 if not completely opaque or transparent + // ################################################################################ + + class Block4x4Encoding_RGBA8 : public Block4x4Encoding_RGB8 + { + public: + + Block4x4Encoding_RGBA8(void); + virtual ~Block4x4Encoding_RGBA8(void); + + virtual void InitFromSource(Block4x4 *a_pblockParent, + ColorFloatRGBA *a_pafrgbaSource, + unsigned char *a_paucEncodingBits, ErrorMetric a_errormetric); + + virtual void InitFromEncodingBits(Block4x4 *a_pblockParent, + unsigned char *a_paucEncodingBits, + ColorFloatRGBA *a_pafrgbaSource, + ErrorMetric a_errormetric); + + virtual void PerformIteration(float a_fEffort); + + virtual void SetEncodingBits(void); + + protected: + + static const unsigned int MODIFIER_TABLE_ENTRYS = 16; + static const unsigned int ALPHA_SELECTOR_BITS = 3; + static const unsigned int ALPHA_SELECTORS = 1 << ALPHA_SELECTOR_BITS; + + static float s_aafModifierTable[MODIFIER_TABLE_ENTRYS][ALPHA_SELECTORS]; + + void CalculateA8(float a_fRadius); + + Block4x4EncodingBits_A8 *m_pencodingbitsA8; // A8 portion of Block4x4EncodingBits_RGBA8 + + float m_fBase; + float m_fMultiplier; + unsigned int m_uiModifierTableIndex; + unsigned int m_auiAlphaSelectors[PIXELS]; + + private: + + inline float DecodePixelAlpha(float a_fBase, float a_fMultiplier, + unsigned int a_uiTableIndex, unsigned int a_uiSelector) + { + float fPixelAlpha = a_fBase + + a_fMultiplier*s_aafModifierTable[a_uiTableIndex][a_uiSelector]; + if (fPixelAlpha < 0.0f) + { + fPixelAlpha = 0.0f; + } + else if (fPixelAlpha > 1.0f) + { + fPixelAlpha = 1.0f; + } + + return fPixelAlpha; + } + + }; + + // ################################################################################ + // Block4x4Encoding_RGBA8_Opaque + // RGBA8 if all pixels have alpha==1 + // ################################################################################ + + class Block4x4Encoding_RGBA8_Opaque : public Block4x4Encoding_RGBA8 + { + public: + + virtual void PerformIteration(float a_fEffort); + + virtual void SetEncodingBits(void); + + }; + + // ################################################################################ + // Block4x4Encoding_RGBA8_Transparent + // RGBA8 if all pixels have alpha==0 + // ################################################################################ + + class Block4x4Encoding_RGBA8_Transparent : public Block4x4Encoding_RGBA8 + { + public: + + virtual void PerformIteration(float a_fEffort); + + virtual void SetEncodingBits(void); + + }; + + // ---------------------------------------------------------------------------------------------------- + // + +} // namespace Etc diff --git a/extern/EtcLib/EtcCodec/EtcDifferentialTrys.cpp b/extern/EtcLib/EtcCodec/EtcDifferentialTrys.cpp new file mode 100644 index 0000000..0fcc550 --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcDifferentialTrys.cpp @@ -0,0 +1,173 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +/* +EtcDifferentialTrys.cpp + +Gathers the results of the various encoding trys for both halves of a 4x4 block for Differential mode + +*/ + +#include "EtcConfig.h" +#include "EtcDifferentialTrys.h" + +#include + +namespace Etc +{ + + // ---------------------------------------------------------------------------------------------------- + // construct a list of trys (encoding attempts) + // + // a_frgbaColor1 is the basecolor for the first half + // a_frgbaColor2 is the basecolor for the second half + // a_pauiPixelMapping1 is the pixel order for the first half + // a_pauiPixelMapping2 is the pixel order for the second half + // a_uiRadius is the amount to vary the base colors + // + DifferentialTrys::DifferentialTrys(ColorFloatRGBA a_frgbaColor1, ColorFloatRGBA a_frgbaColor2, + const unsigned int *a_pauiPixelMapping1, + const unsigned int *a_pauiPixelMapping2, + unsigned int a_uiRadius, + int a_iGrayOffset1, int a_iGrayOffset2) + { + assert(a_uiRadius <= MAX_RADIUS); + + m_boolSeverelyBentColors = false; + + ColorFloatRGBA frgbaQuantizedColor1 = a_frgbaColor1.QuantizeR5G5B5(); + ColorFloatRGBA frgbaQuantizedColor2 = a_frgbaColor2.QuantizeR5G5B5(); + + // quantize base colors + // ensure that trys with a_uiRadius don't overflow + int iRed1 = MoveAwayFromEdge(frgbaQuantizedColor1.IntRed(31.0f)+a_iGrayOffset1, a_uiRadius); + int iGreen1 = MoveAwayFromEdge(frgbaQuantizedColor1.IntGreen(31.0f) + a_iGrayOffset1, a_uiRadius); + int iBlue1 = MoveAwayFromEdge(frgbaQuantizedColor1.IntBlue(31.0f) + a_iGrayOffset1, a_uiRadius); + int iRed2 = MoveAwayFromEdge(frgbaQuantizedColor2.IntRed(31.0f) + a_iGrayOffset2, a_uiRadius); + int iGreen2 = MoveAwayFromEdge(frgbaQuantizedColor2.IntGreen(31.0f) + a_iGrayOffset2, a_uiRadius); + int iBlue2 = MoveAwayFromEdge(frgbaQuantizedColor2.IntBlue(31.0f) + a_iGrayOffset2, a_uiRadius); + + int iDeltaRed = iRed2 - iRed1; + int iDeltaGreen = iGreen2 - iGreen1; + int iDeltaBlue = iBlue2 - iBlue1; + + // make sure components are within range + { + if (iDeltaRed > 3) + { + if (iDeltaRed > 7) + { + m_boolSeverelyBentColors = true; + } + + iRed1 += (iDeltaRed - 3) / 2; + iRed2 = iRed1 + 3; + iDeltaRed = 3; + } + else if (iDeltaRed < -4) + { + if (iDeltaRed < -8) + { + m_boolSeverelyBentColors = true; + } + + iRed1 += (iDeltaRed + 4) / 2; + iRed2 = iRed1 - 4; + iDeltaRed = -4; + } + assert(iRed1 >= (signed)(0 + a_uiRadius) && iRed1 <= (signed)(31 - a_uiRadius)); + assert(iRed2 >= (signed)(0 + a_uiRadius) && iRed2 <= (signed)(31 - a_uiRadius)); + assert(iDeltaRed >= -4 && iDeltaRed <= 3); + + if (iDeltaGreen > 3) + { + if (iDeltaGreen > 7) + { + m_boolSeverelyBentColors = true; + } + + iGreen1 += (iDeltaGreen - 3) / 2; + iGreen2 = iGreen1 + 3; + iDeltaGreen = 3; + } + else if (iDeltaGreen < -4) + { + if (iDeltaGreen < -8) + { + m_boolSeverelyBentColors = true; + } + + iGreen1 += (iDeltaGreen + 4) / 2; + iGreen2 = iGreen1 - 4; + iDeltaGreen = -4; + } + assert(iGreen1 >= (signed)(0 + a_uiRadius) && iGreen1 <= (signed)(31 - a_uiRadius)); + assert(iGreen2 >= (signed)(0 + a_uiRadius) && iGreen2 <= (signed)(31 - a_uiRadius)); + assert(iDeltaGreen >= -4 && iDeltaGreen <= 3); + + if (iDeltaBlue > 3) + { + if (iDeltaBlue > 7) + { + m_boolSeverelyBentColors = true; + } + + iBlue1 += (iDeltaBlue - 3) / 2; + iBlue2 = iBlue1 + 3; + iDeltaBlue = 3; + } + else if (iDeltaBlue < -4) + { + if (iDeltaBlue < -8) + { + m_boolSeverelyBentColors = true; + } + + iBlue1 += (iDeltaBlue + 4) / 2; + iBlue2 = iBlue1 - 4; + iDeltaBlue = -4; + } + assert(iBlue1 >= (signed)(0+a_uiRadius) && iBlue1 <= (signed)(31 - a_uiRadius)); + assert(iBlue2 >= (signed)(0 + a_uiRadius) && iBlue2 <= (signed)(31 - a_uiRadius)); + assert(iDeltaBlue >= -4 && iDeltaBlue <= 3); + } + + m_half1.Init(iRed1, iGreen1, iBlue1, a_pauiPixelMapping1, a_uiRadius); + m_half2.Init(iRed2, iGreen2, iBlue2, a_pauiPixelMapping2, a_uiRadius); + + } + + // ---------------------------------------------------------------------------------------------------- + // + void DifferentialTrys::Half::Init(int a_iRed, int a_iGreen, int a_iBlue, + const unsigned int *a_pauiPixelMapping, unsigned int a_uiRadius) + { + + m_iRed = a_iRed; + m_iGreen = a_iGreen; + m_iBlue = a_iBlue; + + m_pauiPixelMapping = a_pauiPixelMapping; + m_uiRadius = a_uiRadius; + + m_uiTrys = 0; + + } + + // ---------------------------------------------------------------------------------------------------- + // + +} // namespace Etc diff --git a/extern/EtcLib/EtcCodec/EtcDifferentialTrys.h b/extern/EtcLib/EtcCodec/EtcDifferentialTrys.h new file mode 100644 index 0000000..6b1cd9c --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcDifferentialTrys.h @@ -0,0 +1,97 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +#pragma once + +#include "EtcColorFloatRGBA.h" + +namespace Etc +{ + + class DifferentialTrys + { + public: + + static const unsigned int MAX_RADIUS = 2; + + DifferentialTrys(ColorFloatRGBA a_frgbaColor1, + ColorFloatRGBA a_frgbaColor2, + const unsigned int *a_pauiPixelMapping1, + const unsigned int *a_pauiPixelMapping2, + unsigned int a_uiRadius, + int a_iGrayOffset1, int a_iGrayOffset2); + + inline static int MoveAwayFromEdge(int a_i, int a_iDistance) + { + if (a_i < (0+ a_iDistance)) + { + return (0 + a_iDistance); + } + else if (a_i > (31- a_iDistance)) + { + return (31 - a_iDistance); + } + + return a_i; + } + + class Try + { + public : + static const unsigned int SELECTORS = 8; // per half + + int m_iRed; + int m_iGreen; + int m_iBlue; + unsigned int m_uiCW; + unsigned int m_auiSelectors[SELECTORS]; + float m_fError; + }; + + class Half + { + public: + + static const unsigned int MAX_TRYS = 125; + + void Init(int a_iRed, int a_iGreen, int a_iBlue, + const unsigned int *a_pauiPixelMapping, + unsigned int a_uiRadius); + + // center of trys + int m_iRed; + int m_iGreen; + int m_iBlue; + + const unsigned int *m_pauiPixelMapping; + unsigned int m_uiRadius; + + unsigned int m_uiTrys; + Try m_atry[MAX_TRYS]; + + Try *m_ptryBest; + }; + + Half m_half1; + Half m_half2; + + bool m_boolSeverelyBentColors; + }; + + // ---------------------------------------------------------------------------------------------------- + // + +} // namespace Etc diff --git a/extern/EtcLib/EtcCodec/EtcErrorMetric.h b/extern/EtcLib/EtcCodec/EtcErrorMetric.h new file mode 100644 index 0000000..29bb33b --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcErrorMetric.h @@ -0,0 +1,51 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +#pragma once + +namespace Etc +{ + + enum ErrorMetric + { + RGBA, + REC709, + NUMERIC, + NORMALXYZ, + // + ERROR_METRICS, + // + BT709 = REC709 + }; + + inline const char *ErrorMetricToString(ErrorMetric errorMetric) + { + switch (errorMetric) + { + case RGBA: + return "RGBA"; + case REC709: + return "REC709"; + case NUMERIC: + return "NUMERIC"; + case NORMALXYZ: + return "NORMALXYZ"; + case ERROR_METRICS: + default: + return "UNKNOWN"; + } + } +} // namespace Etc diff --git a/extern/EtcLib/EtcCodec/EtcIndividualTrys.cpp b/extern/EtcLib/EtcCodec/EtcIndividualTrys.cpp new file mode 100644 index 0000000..951edec --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcIndividualTrys.cpp @@ -0,0 +1,85 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +/* +EtcIndividualTrys.cpp + +Gathers the results of the various encoding trys for both halves of a 4x4 block for Individual mode + +*/ + +#include "EtcConfig.h" +#include "EtcIndividualTrys.h" + +#include + +namespace Etc +{ + + // ---------------------------------------------------------------------------------------------------- + // construct a list of trys (encoding attempts) + // + // a_frgbaColor1 is the basecolor for the first half + // a_frgbaColor2 is the basecolor for the second half + // a_pauiPixelMapping1 is the pixel order for the first half + // a_pauiPixelMapping2 is the pixel order for the second half + // a_uiRadius is the amount to vary the base colors + // + IndividualTrys::IndividualTrys(ColorFloatRGBA a_frgbaColor1, ColorFloatRGBA a_frgbaColor2, + const unsigned int *a_pauiPixelMapping1, + const unsigned int *a_pauiPixelMapping2, + unsigned int a_uiRadius) + { + assert(a_uiRadius <= MAX_RADIUS); + + ColorFloatRGBA frgbaQuantizedColor1 = a_frgbaColor1.QuantizeR4G4B4(); + ColorFloatRGBA frgbaQuantizedColor2 = a_frgbaColor2.QuantizeR4G4B4(); + + // quantize base colors + // ensure that trys with a_uiRadius don't overflow + int iRed1 = MoveAwayFromEdge(frgbaQuantizedColor1.IntRed(15.0f), a_uiRadius); + int iGreen1 = MoveAwayFromEdge(frgbaQuantizedColor1.IntGreen(15.0f), a_uiRadius); + int iBlue1 = MoveAwayFromEdge(frgbaQuantizedColor1.IntBlue(15.0f), a_uiRadius); + int iRed2 = MoveAwayFromEdge(frgbaQuantizedColor2.IntRed(15.0f), a_uiRadius); + int iGreen2 = MoveAwayFromEdge(frgbaQuantizedColor2.IntGreen(15.0f), a_uiRadius); + int iBlue2 = MoveAwayFromEdge(frgbaQuantizedColor2.IntBlue(15.0f), a_uiRadius); + + m_half1.Init(iRed1, iGreen1, iBlue1, a_pauiPixelMapping1, a_uiRadius); + m_half2.Init(iRed2, iGreen2, iBlue2, a_pauiPixelMapping2, a_uiRadius); + + } + + // ---------------------------------------------------------------------------------------------------- + // + void IndividualTrys::Half::Init(int a_iRed, int a_iGreen, int a_iBlue, + const unsigned int *a_pauiPixelMapping, unsigned int a_uiRadius) + { + + m_iRed = a_iRed; + m_iGreen = a_iGreen; + m_iBlue = a_iBlue; + + m_pauiPixelMapping = a_pauiPixelMapping; + m_uiRadius = a_uiRadius; + + m_uiTrys = 0; + + } + + // ---------------------------------------------------------------------------------------------------- + // + +} // namespace Etc diff --git a/extern/EtcLib/EtcCodec/EtcIndividualTrys.h b/extern/EtcLib/EtcCodec/EtcIndividualTrys.h new file mode 100644 index 0000000..49170d4 --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcIndividualTrys.h @@ -0,0 +1,95 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +#pragma once + +#include "EtcColorFloatRGBA.h" + +namespace Etc +{ + + class IndividualTrys + { + public: + + static const unsigned int MAX_RADIUS = 1; + + IndividualTrys(ColorFloatRGBA a_frgbaColor1, + ColorFloatRGBA a_frgbaColor2, + const unsigned int *a_pauiPixelMapping1, + const unsigned int *a_pauiPixelMapping2, + unsigned int a_uiRadius); + + inline static int MoveAwayFromEdge(int a_i, int a_iDistance) + { + if (a_i < (0+ a_iDistance)) + { + return (0 + a_iDistance); + } + else if (a_i > (15- a_iDistance)) + { + return (15 - a_iDistance); + } + + return a_i; + } + + class Try + { + public : + static const unsigned int SELECTORS = 8; // per half + + int m_iRed; + int m_iGreen; + int m_iBlue; + unsigned int m_uiCW; + unsigned int m_auiSelectors[SELECTORS]; + float m_fError; + }; + + class Half + { + public: + + static const unsigned int MAX_TRYS = 27; + + void Init(int a_iRed, int a_iGreen, int a_iBlue, + const unsigned int *a_pauiPixelMapping, + unsigned int a_uiRadius); + + // center of trys + int m_iRed; + int m_iGreen; + int m_iBlue; + + const unsigned int *m_pauiPixelMapping; + unsigned int m_uiRadius; + + unsigned int m_uiTrys; + Try m_atry[MAX_TRYS]; + + Try *m_ptryBest; + }; + + Half m_half1; + Half m_half2; + + }; + + // ---------------------------------------------------------------------------------------------------- + // + +} // namespace Etc diff --git a/extern/EtcLib/EtcCodec/EtcSortedBlockList.cpp b/extern/EtcLib/EtcCodec/EtcSortedBlockList.cpp new file mode 100644 index 0000000..7f4f56e --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcSortedBlockList.cpp @@ -0,0 +1,228 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +/* +EtcSortedBlockList.cpp + +SortedBlockList is a list of 4x4 blocks that can be used by the "effort" system to prioritize +the encoding of the 4x4 blocks. + +The sorting is done with buckets, where each bucket is an indication of how much error each 4x4 block has + +*/ + +#include "EtcConfig.h" +#include "EtcSortedBlockList.h" + +#include "EtcBlock4x4.h" + +#include +#include +#include + +namespace Etc +{ + + // ---------------------------------------------------------------------------------------------------- + // construct an empty list + // + // allocate enough memory to add all of the image's 4x4 blocks later + // allocate enough buckets to sort the blocks + // + SortedBlockList::SortedBlockList(unsigned int a_uiImageBlocks, unsigned int a_uiBuckets) + { + m_uiImageBlocks = a_uiImageBlocks; + m_iBuckets = (int)a_uiBuckets; + + m_uiAddedBlocks = 0; + m_uiSortedBlocks = 0; + m_palinkPool = new Link[m_uiImageBlocks]; + m_pabucket = new Bucket[m_iBuckets]; + m_fMaxError = 0.0f; + + InitBuckets(); + + } + + // ---------------------------------------------------------------------------------------------------- + // + SortedBlockList::~SortedBlockList(void) + { + delete[] m_palinkPool; + delete[] m_pabucket; + } + + // ---------------------------------------------------------------------------------------------------- + // add a 4x4 block to the list + // the 4x4 block will be sorted later + // + void SortedBlockList::AddBlock(Block4x4 *a_pblock) + { + assert(m_uiAddedBlocks < m_uiImageBlocks); + Link *plink = &m_palinkPool[m_uiAddedBlocks++]; + plink->Init(a_pblock); + } + + // ---------------------------------------------------------------------------------------------------- + // sort all of the 4x4 blocks that have been added to the list + // + // first, determine the maximum error, then assign an error range to each bucket + // next, determine which bucket each 4x4 block belongs to based on the 4x4 block's error + // add the 4x4 block to the appropriate bucket + // lastly, walk thru the buckets and add each bucket to a sorted linked list + // + // the resultant sorting is an approximate sorting from most to least error + // + void SortedBlockList::Sort(void) + { + assert(m_uiAddedBlocks == m_uiImageBlocks); + InitBuckets(); + + // find max block error + m_fMaxError = -1.0f; + + for (unsigned int uiLink = 0; uiLink < m_uiAddedBlocks; uiLink++) + { + Link *plinkBlock = &m_palinkPool[uiLink]; + + float fBlockError = plinkBlock->GetBlock()->GetError(); + if (fBlockError > m_fMaxError) + { + m_fMaxError = fBlockError; + } + } + // prevent divide by zero or divide by negative + if (m_fMaxError <= 0.0f) + { + m_fMaxError = 1.0f; + } + //used for debugging + //int numDone = 0; + // put all of the blocks with unfinished encodings into the appropriate bucket + m_uiSortedBlocks = 0; + for (unsigned int uiLink = 0; uiLink < m_uiAddedBlocks; uiLink++) + { + Link *plinkBlock = &m_palinkPool[uiLink]; + + // if the encoding is done, don't add it to the list + if (plinkBlock->GetBlock()->GetEncoding()->IsDone()) + { + //numDone++; + continue; + } + + // calculate the appropriate sort bucket + float fBlockError = plinkBlock->GetBlock()->GetError(); + int iBucket = (int) floorf(m_iBuckets * fBlockError / m_fMaxError); + // clamp to bucket index + iBucket = iBucket < 0 ? 0 : iBucket >= m_iBuckets ? m_iBuckets - 1 : iBucket; + + // add block to bucket + { + Bucket *pbucket = &m_pabucket[iBucket]; + if (pbucket->plinkLast) + { + pbucket->plinkLast->SetNext(plinkBlock); + pbucket->plinkLast = plinkBlock; + } + else + { + pbucket->plinkFirst = pbucket->plinkLast = plinkBlock; + } + plinkBlock->SetNext(nullptr); + } + + m_uiSortedBlocks++; + + if (0) + { + printf("%u: e=%.3f\n", uiLink, fBlockError); + Print(); + printf("\n\n\n"); + } + } + //printf("num blocks already done: %d\n",numDone); + //link the blocks together across buckets + m_plinkFirst = nullptr; + m_plinkLast = nullptr; + for (int iBucket = m_iBuckets - 1; iBucket >= 0; iBucket--) + { + Bucket *pbucket = &m_pabucket[iBucket]; + + if (pbucket->plinkFirst) + { + if (m_plinkFirst == nullptr) + { + m_plinkFirst = pbucket->plinkFirst; + } + else + { + assert(pbucket->plinkLast->GetNext() == nullptr); + m_plinkLast->SetNext(pbucket->plinkFirst); + } + + m_plinkLast = pbucket->plinkLast; + } + } + + + } + + // ---------------------------------------------------------------------------------------------------- + // clear all of the buckets. normally done in preparation for a sort + // + void SortedBlockList::InitBuckets(void) + { + for (int iBucket = 0; iBucket < m_iBuckets; iBucket++) + { + Bucket *pbucket = &m_pabucket[iBucket]; + + pbucket->plinkFirst = 0; + pbucket->plinkLast = 0; + } + } + + // ---------------------------------------------------------------------------------------------------- + // print out the list of sorted 4x4 blocks + // normally used for debugging + // + void SortedBlockList::Print(void) + { + for (int iBucket = m_iBuckets-1; iBucket >= 0; iBucket--) + { + Bucket *pbucket = &m_pabucket[iBucket]; + + unsigned int uiBlocks = 0; + for (Link *plink = pbucket->plinkFirst; plink != nullptr; plink = plink->GetNext() ) + { + uiBlocks++; + + if (plink == pbucket->plinkLast) + { + break; + } + } + + float fBucketError = m_fMaxError * iBucket / m_iBuckets; + float fBucketRMS = sqrtf(fBucketError / (4.0f*16.0f) ); + printf("%3d: e=%.3f rms=%.6f %u\n", iBucket, fBucketError, fBucketRMS, uiBlocks); + } + } + + // ---------------------------------------------------------------------------------------------------- + // + +} // namespace Etc diff --git a/extern/EtcLib/EtcCodec/EtcSortedBlockList.h b/extern/EtcLib/EtcCodec/EtcSortedBlockList.h new file mode 100644 index 0000000..8ebb978 --- /dev/null +++ b/extern/EtcLib/EtcCodec/EtcSortedBlockList.h @@ -0,0 +1,124 @@ +/* + * Copyright 2015 The Etc2Comp Authors. + * + * 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. + */ + +#pragma once + +namespace Etc +{ + class Block4x4; + + class SortedBlockList + { + public: + + class Link + { + public: + + inline void Init(Block4x4 *a_pblock) + { + m_pblock = a_pblock; + m_plinkNext = nullptr; + } + + inline Block4x4 * GetBlock(void) + { + return m_pblock; + } + + inline void SetNext(Link *a_plinkNext) + { + m_plinkNext = a_plinkNext; + } + + inline Link * GetNext(void) + { + return m_plinkNext; + } + + inline Link * Advance(unsigned int a_uiSteps = 1) + { + Link *plink = this; + + for (unsigned int uiStep = 0; uiStep < a_uiSteps; uiStep++) + { + if (plink == nullptr) + { + break; + } + + plink = plink->m_plinkNext; + } + + return plink; + } + + private: + + Block4x4 *m_pblock; + Link *m_plinkNext; + }; + + SortedBlockList(unsigned int a_uiImageBlocks, unsigned int a_uiBuckets); + ~SortedBlockList(void); + + void AddBlock(Block4x4 *a_pblock); + + void Sort(void); + + inline Link * GetLinkToFirstBlock(void) + { + return m_plinkFirst; + } + + inline unsigned int GetNumberOfAddedBlocks(void) + { + return m_uiAddedBlocks; + } + + inline unsigned int GetNumberOfSortedBlocks(void) + { + return m_uiSortedBlocks; + } + + void Print(void); + + private: + + void InitBuckets(void); + + class Bucket + { + public: + Link *plinkFirst; + Link *plinkLast; + }; + + unsigned int m_uiImageBlocks; + int m_iBuckets; + + unsigned int m_uiAddedBlocks; + unsigned int m_uiSortedBlocks; + Link *m_palinkPool; + Bucket *m_pabucket; + float m_fMaxError; + + Link *m_plinkFirst; + Link *m_plinkLast; + + }; + +} // namespace Etc diff --git a/extern/butteraugli/CMakeLists.txt b/extern/butteraugli/CMakeLists.txt new file mode 100644 index 0000000..da0d7da --- /dev/null +++ b/extern/butteraugli/CMakeLists.txt @@ -0,0 +1,7 @@ + +SET(BUTTERAUGLI_SRCS + butteraugli.cc + butteraugli.h) + +ADD_LIBRARY(butteraugli STATIC ${BUTTERAUGLI_SRCS}) + diff --git a/extern/butteraugli/butteraugli.cc b/extern/butteraugli/butteraugli.cc new file mode 100755 index 0000000..ebf7cc1 --- /dev/null +++ b/extern/butteraugli/butteraugli.cc @@ -0,0 +1,1588 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// 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. +// +// Author: Jyrki Alakuijala (jyrki.alakuijala@gmail.com) +// +// The physical architecture of butteraugli is based on the following naming +// convention: +// * Opsin - dynamics of the photosensitive chemicals in the retina +// with their immediate electrical processing +// * Xyb - hybrid opponent/trichromatic color space +// x is roughly red-subtract-green. +// y is yellow. +// b is blue. +// Xyb values are computed from Opsin mixing, not directly from rgb. +// * Mask - for visual masking +// * Hf - color modeling for spatially high-frequency features +// * Lf - color modeling for spatially low-frequency features +// * Diffmap - to cluster and build an image of error between the images +// * Blur - to hold the smoothing code + +#include "butteraugli.h" + +#include +#include +#include +#include +#include + +#include +#include + +// Restricted pointers speed up Convolution(); MSVC uses a different keyword. +#ifdef _MSC_VER +#define __restrict__ __restrict +#endif + +namespace butteraugli { + +void *CacheAligned::Allocate(const size_t bytes) { + char *const allocated = static_cast(malloc(bytes + kCacheLineSize)); + if (allocated == nullptr) { + return nullptr; + } + const uintptr_t misalignment = + reinterpret_cast(allocated) & (kCacheLineSize - 1); + // malloc is at least kPointerSize aligned, so we can store the "allocated" + // pointer immediately before the aligned memory. + assert(misalignment % kPointerSize == 0); + char *const aligned = allocated + kCacheLineSize - misalignment; + memcpy(aligned - kPointerSize, &allocated, kPointerSize); + return aligned; +} + +void CacheAligned::Free(void *aligned_pointer) { + if (aligned_pointer == nullptr) { + return; + } + char *const aligned = static_cast(aligned_pointer); + assert(reinterpret_cast(aligned) % kCacheLineSize == 0); + char *allocated; + memcpy(&allocated, aligned - kPointerSize, kPointerSize); + assert(allocated <= aligned - kPointerSize); + assert(allocated >= aligned - kCacheLineSize); + free(allocated); +} + +static inline bool IsNan(const float x) { + uint32_t bits; + memcpy(&bits, &x, sizeof(bits)); + const uint32_t bitmask_exp = 0x7F800000; + return (bits & bitmask_exp) == bitmask_exp && (bits & 0x7FFFFF); +} + +static inline bool IsNan(const double x) { + uint64_t bits; + memcpy(&bits, &x, sizeof(bits)); + return (0x7ff0000000000001ULL <= bits && bits <= 0x7fffffffffffffffULL) || + (0xfff0000000000001ULL <= bits && bits <= 0xffffffffffffffffULL); +} + +static inline void CheckImage(const ImageF &image, const char *name) { + for (size_t y = 0; y < image.ysize(); ++y) { + ConstRestrict row = image.Row(y); + for (size_t x = 0; x < image.xsize(); ++x) { + if (IsNan(row[x])) { + printf("Image %s @ %lu,%lu (of %lu,%lu)\n", name, x, y, image.xsize(), + image.ysize()); + exit(1); + } + } + } +} + +#if BUTTERAUGLI_ENABLE_CHECKS + +#define CHECK_NAN(x, str) \ + do { \ + if (IsNan(x)) { \ + printf("%d: %s\n", __LINE__, str); \ + abort(); \ + } \ + } while (0) + +#define CHECK_IMAGE(image, name) CheckImage(image, name) + +#else + +#define CHECK_NAN(x, str) +#define CHECK_IMAGE(image, name) + +#endif + + +static const double kInternalGoodQualityThreshold = 14.921561160295326; +static const double kGlobalScale = 1.0 / kInternalGoodQualityThreshold; + +inline double DotProduct(const double u[3], const double v[3]) { + return u[0] * v[0] + u[1] * v[1] + u[2] * v[2]; +} + +inline double DotProduct(const float u[3], const double v[3]) { + return u[0] * v[0] + u[1] * v[1] + u[2] * v[2]; +} + +// Computes a horizontal convolution and transposes the result. +static void Convolution(size_t xsize, size_t ysize, + size_t xstep, + size_t len, size_t offset, + const float* __restrict__ multipliers, + const float* __restrict__ inp, + double border_ratio, + float* __restrict__ result) { + PROFILER_FUNC; + double weight_no_border = 0; + for (int j = 0; j <= 2 * offset; ++j) { + weight_no_border += multipliers[j]; + } + for (size_t x = 0, ox = 0; x < xsize; x += xstep, ox++) { + int minx = x < offset ? 0 : x - offset; + int maxx = std::min(xsize, x + len - offset) - 1; + double weight = 0.0; + for (int j = minx; j <= maxx; ++j) { + weight += multipliers[j - x + offset]; + } + // Interpolate linearly between the no-border scaling and border scaling. + weight = (1.0 - border_ratio) * weight + border_ratio * weight_no_border; + double scale = 1.0 / weight; + for (size_t y = 0; y < ysize; ++y) { + double sum = 0.0; + for (int j = minx; j <= maxx; ++j) { + sum += inp[y * xsize + j] * multipliers[j - x + offset]; + } + result[ox * ysize + y] = sum * scale; + } + } +} + +void Blur(size_t xsize, size_t ysize, float* channel, double sigma, + double border_ratio) { + PROFILER_FUNC; + double m = 2.25; // Accuracy increases when m is increased. + const double scaler = -1.0 / (2 * sigma * sigma); + // For m = 9.0: exp(-scaler * diff * diff) < 2^ {-52} + const int diff = std::max(1, m * fabs(sigma)); + const int expn_size = 2 * diff + 1; + std::vector expn(expn_size); + for (int i = -diff; i <= diff; ++i) { + expn[i + diff] = exp(scaler * i * i); + } + const int xstep = std::max(1, int(sigma / 3)); + const int ystep = xstep; + int dxsize = (xsize + xstep - 1) / xstep; + int dysize = (ysize + ystep - 1) / ystep; + std::vector tmp(dxsize * ysize); + std::vector downsampled_output(dxsize * dysize); + Convolution(xsize, ysize, xstep, expn_size, diff, expn.data(), channel, + border_ratio, + tmp.data()); + Convolution(ysize, dxsize, ystep, expn_size, diff, expn.data(), tmp.data(), + border_ratio, + downsampled_output.data()); + for (int y = 0; y < ysize; y++) { + for (int x = 0; x < xsize; x++) { + // TODO: Use correct rounding. + channel[y * xsize + x] = + downsampled_output[(y / ystep) * dxsize + (x / xstep)]; + } + } +} + +// To change this to n, add the relevant FFTn function and kFFTnMapIndexTable. +constexpr size_t kBlockEdge = 8; +constexpr size_t kBlockSize = kBlockEdge * kBlockEdge; +constexpr size_t kBlockEdgeHalf = kBlockEdge / 2; +constexpr size_t kBlockHalf = kBlockEdge * kBlockEdgeHalf; + +// Contrast sensitivity related weights. +static const double *GetContrastSensitivityMatrix() { + static double csf8x8[kBlockHalf + kBlockEdgeHalf + 1] = { + 5.28270670524, + 0.0, + 0.0, + 0.0, + 0.3831134973, + 0.676303603859, + 3.58927792424, + 18.6104367002, + 18.6104367002, + 3.09093131948, + 1.0, + 0.498250875965, + 0.36198671102, + 0.308982169883, + 0.1312701920435, + 2.37370549629, + 3.58927792424, + 1.0, + 2.37370549629, + 0.991205724152, + 1.05178802919, + 0.627264168628, + 0.4, + 0.1312701920435, + 0.676303603859, + 0.498250875965, + 0.991205724152, + 0.5, + 0.3831134973, + 0.349686450518, + 0.627264168628, + 0.308982169883, + 0.3831134973, + 0.36198671102, + 1.05178802919, + 0.3831134973, + 0.12, + }; + return &csf8x8[0]; +} + +std::array MakeHighFreqColorDiffDx() { + std::array lut; + static const double off = 11.38708334481672; + static const double inc = 14.550189611520716; + lut[1] = off; + for (int i = 2; i < 21; ++i) { + lut[i] = lut[i - 1] + inc; + } + return lut; +} + +const double *GetHighFreqColorDiffDx() { + static const std::array kLut = MakeHighFreqColorDiffDx(); + return kLut.data(); +} + +std::array MakeHighFreqColorDiffDy() { + std::array lut; + static const double off = 1.4103373714040413; + static const double inc = 0.7084088867024; + lut[1] = off; + for (int i = 2; i < 21; ++i) { + lut[i] = lut[i - 1] + inc; + } + return lut; +} + +const double *GetHighFreqColorDiffDy() { + static const std::array kLut = MakeHighFreqColorDiffDy(); + return kLut.data(); +} + +std::array MakeLowFreqColorDiffDy() { + std::array lut; + static const double inc = 5.2511644570349185; + for (int i = 1; i < 21; ++i) { + lut[i] = lut[i - 1] + inc; + } + return lut; +} + +const double *GetLowFreqColorDiffDy() { + static const std::array kLut = MakeLowFreqColorDiffDy(); + return kLut.data(); +} + +inline double Interpolate(const double *array, int size, double sx) { + double ix = fabs(sx); + assert(ix < 10000); + int baseix = static_cast(ix); + double res; + if (baseix >= size - 1) { + res = array[size - 1]; + } else { + double mix = ix - baseix; + int nextix = baseix + 1; + res = array[baseix] + mix * (array[nextix] - array[baseix]); + } + if (sx < 0) res = -res; + return res; +} + +inline double InterpolateClampNegative(const double *array, + int size, double sx) { + if (sx < 0) { + sx = 0; + } + double ix = fabs(sx); + int baseix = static_cast(ix); + double res; + if (baseix >= size - 1) { + res = array[size - 1]; + } else { + double mix = ix - baseix; + int nextix = baseix + 1; + res = array[baseix] + mix * (array[nextix] - array[baseix]); + } + return res; +} + +void RgbToXyb(double r, double g, double b, + double *valx, double *valy, double *valz) { + static const double a0 = 1.01611726948; + static const double a1 = 0.982482243696; + static const double a2 = 1.43571362627; + static const double a3 = 0.896039849412; + *valx = a0 * r - a1 * g; + *valy = a2 * r + a3 * g; + *valz = b; +} + +static inline void XybToVals(double x, double y, double z, + double *valx, double *valy, double *valz) { + static const double xmul = 0.758304045695; + static const double ymul = 2.28148649801; + static const double zmul = 1.87816926918; + *valx = Interpolate(GetHighFreqColorDiffDx(), 21, x * xmul); + *valy = Interpolate(GetHighFreqColorDiffDy(), 21, y * ymul); + *valz = zmul * z; +} + +// Rough psychovisual distance to gray for low frequency colors. +static void XybLowFreqToVals(double x, double y, double z, + double *valx, double *valy, double *valz) { + static const double xmul = 6.64482198135; + static const double ymul = 0.837846224276; + static const double zmul = 7.34905756986; + static const double y_to_z_mul = 0.0812519812628; + z += y_to_z_mul * y; + *valz = z * zmul; + *valx = x * xmul; + *valy = Interpolate(GetLowFreqColorDiffDy(), 21, y * ymul); +} + +double RemoveRangeAroundZero(double v, double range) { + if (v >= -range && v < range) { + return 0; + } + if (v < 0) { + return v + range; + } else { + return v - range; + } +} + +void XybDiffLowFreqSquaredAccumulate(double r0, double g0, double b0, + double r1, double g1, double b1, + double factor, double res[3]) { + double valx0, valy0, valz0; + double valx1, valy1, valz1; + XybLowFreqToVals(r0, g0, b0, &valx0, &valy0, &valz0); + if (r1 == 0.0 && g1 == 0.0 && b1 == 0.0) { + PROFILER_ZONE("XybDiff r1=g1=b1=0"); + res[0] += factor * valx0 * valx0; + res[1] += factor * valy0 * valy0; + res[2] += factor * valz0 * valz0; + return; + } + XybLowFreqToVals(r1, g1, b1, &valx1, &valy1, &valz1); + // Approximate the distance of the colors by their respective distances + // to gray. + double valx = valx0 - valx1; + double valy = valy0 - valy1; + double valz = valz0 - valz1; + res[0] += factor * valx * valx; + res[1] += factor * valy * valy; + res[2] += factor * valz * valz; +} + +struct Complex { + public: + double real; + double imag; +}; + +inline double abssq(const Complex& c) { + return c.real * c.real + c.imag * c.imag; +} + +static void TransposeBlock(Complex data[kBlockSize]) { + for (int i = 0; i < kBlockEdge; i++) { + for (int j = 0; j < i; j++) { + std::swap(data[kBlockEdge * i + j], data[kBlockEdge * j + i]); + } + } +} + +// D. J. Bernstein's Fast Fourier Transform algorithm on 4 elements. +inline void FFT4(Complex* a) { + double t1, t2, t3, t4, t5, t6, t7, t8; + t5 = a[2].real; + t1 = a[0].real - t5; + t7 = a[3].real; + t5 += a[0].real; + t3 = a[1].real - t7; + t7 += a[1].real; + t8 = t5 + t7; + a[0].real = t8; + t5 -= t7; + a[1].real = t5; + t6 = a[2].imag; + t2 = a[0].imag - t6; + t6 += a[0].imag; + t5 = a[3].imag; + a[2].imag = t2 + t3; + t2 -= t3; + a[3].imag = t2; + t4 = a[1].imag - t5; + a[3].real = t1 + t4; + t1 -= t4; + a[2].real = t1; + t5 += a[1].imag; + a[0].imag = t6 + t5; + t6 -= t5; + a[1].imag = t6; +} + +static const double kSqrtHalf = 0.70710678118654752440084436210484903; + +// D. J. Bernstein's Fast Fourier Transform algorithm on 8 elements. +void FFT8(Complex* a) { + double t1, t2, t3, t4, t5, t6, t7, t8; + + t7 = a[4].imag; + t4 = a[0].imag - t7; + t7 += a[0].imag; + a[0].imag = t7; + + t8 = a[6].real; + t5 = a[2].real - t8; + t8 += a[2].real; + a[2].real = t8; + + t7 = a[6].imag; + a[6].imag = t4 - t5; + t4 += t5; + a[4].imag = t4; + + t6 = a[2].imag - t7; + t7 += a[2].imag; + a[2].imag = t7; + + t8 = a[4].real; + t3 = a[0].real - t8; + t8 += a[0].real; + a[0].real = t8; + + a[4].real = t3 - t6; + t3 += t6; + a[6].real = t3; + + t7 = a[5].real; + t3 = a[1].real - t7; + t7 += a[1].real; + a[1].real = t7; + + t8 = a[7].imag; + t6 = a[3].imag - t8; + t8 += a[3].imag; + a[3].imag = t8; + t1 = t3 - t6; + t3 += t6; + + t7 = a[5].imag; + t4 = a[1].imag - t7; + t7 += a[1].imag; + a[1].imag = t7; + + t8 = a[7].real; + t5 = a[3].real - t8; + t8 += a[3].real; + a[3].real = t8; + + t2 = t4 - t5; + t4 += t5; + + t6 = t1 - t4; + t8 = kSqrtHalf; + t6 *= t8; + a[5].real = a[4].real - t6; + t1 += t4; + t1 *= t8; + a[5].imag = a[4].imag - t1; + t6 += a[4].real; + a[4].real = t6; + t1 += a[4].imag; + a[4].imag = t1; + + t5 = t2 - t3; + t5 *= t8; + a[7].imag = a[6].imag - t5; + t2 += t3; + t2 *= t8; + a[7].real = a[6].real - t2; + t2 += a[6].real; + a[6].real = t2; + t5 += a[6].imag; + a[6].imag = t5; + + FFT4(a); + + // Reorder to the correct output order. + // TODO: Modify the above computation so that this is not needed. + Complex tmp = a[2]; + a[2] = a[3]; + a[3] = a[5]; + a[5] = a[7]; + a[7] = a[4]; + a[4] = a[1]; + a[1] = a[6]; + a[6] = tmp; +} + +// Same as FFT8, but all inputs are real. +// TODO: Since this does not need to be in-place, maybe there is a +// faster FFT than this one, which is derived from DJB's in-place complex FFT. +void RealFFT8(const double* in, Complex* out) { + double t1, t2, t3, t5, t6, t7, t8; + t8 = in[6]; + t5 = in[2] - t8; + t8 += in[2]; + out[2].real = t8; + out[6].imag = -t5; + out[4].imag = t5; + t8 = in[4]; + t3 = in[0] - t8; + t8 += in[0]; + out[0].real = t8; + out[4].real = t3; + out[6].real = t3; + t7 = in[5]; + t3 = in[1] - t7; + t7 += in[1]; + out[1].real = t7; + t8 = in[7]; + t5 = in[3] - t8; + t8 += in[3]; + out[3].real = t8; + t2 = -t5; + t6 = t3 - t5; + t8 = kSqrtHalf; + t6 *= t8; + out[5].real = out[4].real - t6; + t1 = t3 + t5; + t1 *= t8; + out[5].imag = out[4].imag - t1; + t6 += out[4].real; + out[4].real = t6; + t1 += out[4].imag; + out[4].imag = t1; + t5 = t2 - t3; + t5 *= t8; + out[7].imag = out[6].imag - t5; + t2 += t3; + t2 *= t8; + out[7].real = out[6].real - t2; + t2 += out[6].real; + out[6].real = t2; + t5 += out[6].imag; + out[6].imag = t5; + t5 = out[2].real; + t1 = out[0].real - t5; + t7 = out[3].real; + t5 += out[0].real; + t3 = out[1].real - t7; + t7 += out[1].real; + t8 = t5 + t7; + out[0].real = t8; + t5 -= t7; + out[1].real = t5; + out[2].imag = t3; + out[3].imag = -t3; + out[3].real = t1; + out[2].real = t1; + out[0].imag = 0; + out[1].imag = 0; + + // Reorder to the correct output order. + // TODO: Modify the above computation so that this is not needed. + Complex tmp = out[2]; + out[2] = out[3]; + out[3] = out[5]; + out[5] = out[7]; + out[7] = out[4]; + out[4] = out[1]; + out[1] = out[6]; + out[6] = tmp; +} + +// Fills in block[kBlockEdgeHalf..(kBlockHalf+kBlockEdgeHalf)], and leaves the +// rest unmodified. +void ButteraugliFFTSquared(double block[kBlockSize]) { + double global_mul = 0.000064; + Complex block_c[kBlockSize]; + assert(kBlockEdge == 8); + for (int y = 0; y < kBlockEdge; ++y) { + RealFFT8(block + y * kBlockEdge, block_c + y * kBlockEdge); + } + TransposeBlock(block_c); + double r0[kBlockEdge]; + double r1[kBlockEdge]; + for (int x = 0; x < kBlockEdge; ++x) { + r0[x] = block_c[x].real; + r1[x] = block_c[kBlockHalf + x].real; + } + RealFFT8(r0, block_c); + RealFFT8(r1, block_c + kBlockHalf); + for (int y = 1; y < kBlockEdgeHalf; ++y) { + FFT8(block_c + y * kBlockEdge); + } + for (int i = kBlockEdgeHalf; i < kBlockHalf + kBlockEdgeHalf + 1; ++i) { + block[i] = abssq(block_c[i]); + block[i] *= global_mul; + } +} + +// Computes 8x8 FFT of each channel of xyb0 and xyb1 and adds the total squared +// 3-dimensional xybdiff of the two blocks to diff_xyb_{dc,ac} and the average +// diff on the edges to diff_xyb_edge_dc. +void ButteraugliBlockDiff(double xyb0[3 * kBlockSize], + double xyb1[3 * kBlockSize], + double diff_xyb_dc[3], + double diff_xyb_ac[3], + double diff_xyb_edge_dc[3]) { + PROFILER_FUNC; + const double *csf8x8 = GetContrastSensitivityMatrix(); + + double avgdiff_xyb[3] = {0.0}; + double avgdiff_edge[3][4] = { {0.0} }; + for (int i = 0; i < 3 * kBlockSize; ++i) { + const double diff_xyb = xyb0[i] - xyb1[i]; + const int c = i / kBlockSize; + avgdiff_xyb[c] += diff_xyb / kBlockSize; + const int k = i % kBlockSize; + const int kx = k % kBlockEdge; + const int ky = k / kBlockEdge; + const int h_edge_idx = ky == 0 ? 1 : ky == 7 ? 3 : -1; + const int v_edge_idx = kx == 0 ? 0 : kx == 7 ? 2 : -1; + if (h_edge_idx >= 0) { + avgdiff_edge[c][h_edge_idx] += diff_xyb / kBlockEdge; + } + if (v_edge_idx >= 0) { + avgdiff_edge[c][v_edge_idx] += diff_xyb / kBlockEdge; + } + } + XybDiffLowFreqSquaredAccumulate(avgdiff_xyb[0], + avgdiff_xyb[1], + avgdiff_xyb[2], + 0, 0, 0, csf8x8[0], + diff_xyb_dc); + for (int i = 0; i < 4; ++i) { + XybDiffLowFreqSquaredAccumulate(avgdiff_edge[0][i], + avgdiff_edge[1][i], + avgdiff_edge[2][i], + 0, 0, 0, csf8x8[0], + diff_xyb_edge_dc); + } + + double* xyb_avg = xyb0; + double* xyb_halfdiff = xyb1; + for(int i = 0; i < 3 * kBlockSize; ++i) { + double avg = (xyb0[i] + xyb1[i])/2; + double halfdiff = (xyb0[i] - xyb1[i])/2; + xyb_avg[i] = avg; + xyb_halfdiff[i] = halfdiff; + } + double *y_avg = &xyb_avg[kBlockSize]; + double *x_halfdiff_squared = &xyb_halfdiff[0]; + double *y_halfdiff = &xyb_halfdiff[kBlockSize]; + double *z_halfdiff_squared = &xyb_halfdiff[2 * kBlockSize]; + ButteraugliFFTSquared(y_avg); + ButteraugliFFTSquared(x_halfdiff_squared); + ButteraugliFFTSquared(y_halfdiff); + ButteraugliFFTSquared(z_halfdiff_squared); + + static const double xmul = 64.8; + static const double ymul = 1.753123908348329; + static const double ymul2 = 1.51983458269; + static const double zmul = 2.4; + + for (size_t i = kBlockEdgeHalf; i < kBlockHalf + kBlockEdgeHalf + 1; ++i) { + double d = csf8x8[i]; + diff_xyb_ac[0] += d * xmul * x_halfdiff_squared[i]; + diff_xyb_ac[2] += d * zmul * z_halfdiff_squared[i]; + + y_avg[i] = sqrt(y_avg[i]); + y_halfdiff[i] = sqrt(y_halfdiff[i]); + double y0 = y_avg[i] - y_halfdiff[i]; + double y1 = y_avg[i] + y_halfdiff[i]; + // Remove the impact of small absolute values. + // This improves the behavior with flat noise. + static const double ylimit = 0.04; + y0 = RemoveRangeAroundZero(y0, ylimit); + y1 = RemoveRangeAroundZero(y1, ylimit); + if (y0 != y1) { + double valy0 = Interpolate(GetHighFreqColorDiffDy(), 21, y0 * ymul2); + double valy1 = Interpolate(GetHighFreqColorDiffDy(), 21, y1 * ymul2); + double valy = ymul * (valy0 - valy1); + diff_xyb_ac[1] += d * valy * valy; + } + } +} + +// Low frequency edge detectors. +// Two edge detectors are applied in each corner of the 8x8 square. +// The squared 3-dimensional error vector is added to diff_xyb. +void Butteraugli8x8CornerEdgeDetectorDiff( + const size_t pos_x, + const size_t pos_y, + const size_t xsize, + const size_t ysize, + const std::vector > &blurred0, + const std::vector > &blurred1, + double diff_xyb[3]) { + PROFILER_FUNC; + int local_count = 0; + double local_xyb[3] = { 0 }; + static const double w = 0.711100840192; + for (int k = 0; k < 4; ++k) { + size_t step = 3; + size_t offset[4][2] = { { 0, 0 }, { 0, 7 }, { 7, 0 }, { 7, 7 } }; + size_t x = pos_x + offset[k][0]; + size_t y = pos_y + offset[k][1]; + if (x >= step && x + step < xsize) { + size_t ix = y * xsize + (x - step); + size_t ix2 = ix + 2 * step; + XybDiffLowFreqSquaredAccumulate( + w * (blurred0[0][ix] - blurred0[0][ix2]), + w * (blurred0[1][ix] - blurred0[1][ix2]), + w * (blurred0[2][ix] - blurred0[2][ix2]), + w * (blurred1[0][ix] - blurred1[0][ix2]), + w * (blurred1[1][ix] - blurred1[1][ix2]), + w * (blurred1[2][ix] - blurred1[2][ix2]), + 1.0, local_xyb); + ++local_count; + } + if (y >= step && y + step < ysize) { + size_t ix = (y - step) * xsize + x; + size_t ix2 = ix + 2 * step * xsize; + XybDiffLowFreqSquaredAccumulate( + w * (blurred0[0][ix] - blurred0[0][ix2]), + w * (blurred0[1][ix] - blurred0[1][ix2]), + w * (blurred0[2][ix] - blurred0[2][ix2]), + w * (blurred1[0][ix] - blurred1[0][ix2]), + w * (blurred1[1][ix] - blurred1[1][ix2]), + w * (blurred1[2][ix] - blurred1[2][ix2]), + 1.0, local_xyb); + ++local_count; + } + } + static const double weight = 0.01617112696; + const double mul = weight * 8.0 / local_count; + for (int i = 0; i < 3; ++i) { + diff_xyb[i] += mul * local_xyb[i]; + } +} + +// https://en.wikipedia.org/wiki/Photopsin absordance modeling. +const double *GetOpsinAbsorbance() { + static const double kMix[12] = { + 0.348036746003, + 0.577814843137, + 0.0544556093735, + 0.774145581713, + 0.26922717275, + 0.767247733938, + 0.0366922708552, + 0.920130265014, + 0.0882062883536, + 0.158581714673, + 0.712857943858, + 10.6524069248, + }; + return &kMix[0]; +} + +void OpsinAbsorbance(const double in[3], double out[3]) { + const double *mix = GetOpsinAbsorbance(); + out[0] = mix[0] * in[0] + mix[1] * in[1] + mix[2] * in[2] + mix[3]; + out[1] = mix[4] * in[0] + mix[5] * in[1] + mix[6] * in[2] + mix[7]; + out[2] = mix[8] * in[0] + mix[9] * in[1] + mix[10] * in[2] + mix[11]; +} + +double GammaMinArg() { + double in[3] = { 0.0, 0.0, 0.0 }; + double out[3]; + OpsinAbsorbance(in, out); + return std::min(out[0], std::min(out[1], out[2])); +} + +double GammaMaxArg() { + double in[3] = { 255.0, 255.0, 255.0 }; + double out[3]; + OpsinAbsorbance(in, out); + return std::max(out[0], std::max(out[1], out[2])); +} + +ButteraugliComparator::ButteraugliComparator( + size_t xsize, size_t ysize, int step) + : xsize_(xsize), + ysize_(ysize), + num_pixels_(xsize * ysize), + step_(step), + res_xsize_((xsize + step - 1) / step), + res_ysize_((ysize + step - 1) / step) { + assert(step <= 4); +} + +void MaskHighIntensityChange( + size_t xsize, size_t ysize, + const std::vector > &c0, + const std::vector > &c1, + std::vector > &xyb0, + std::vector > &xyb1) { + PROFILER_FUNC; + for (int y = 0; y < ysize; ++y) { + for (int x = 0; x < xsize; ++x) { + int ix = y * xsize + x; + const double ave[3] = { + (c0[0][ix] + c1[0][ix]) * 0.5, + (c0[1][ix] + c1[1][ix]) * 0.5, + (c0[2][ix] + c1[2][ix]) * 0.5, + }; + double sqr_max_diff = -1; + { + int offset[4] = + { -1, 1, -static_cast(xsize), static_cast(xsize) }; + int border[4] = + { x == 0, x + 1 == xsize, y == 0, y + 1 == ysize }; + for (int dir = 0; dir < 4; ++dir) { + if (border[dir]) { + continue; + } + const int ix2 = ix + offset[dir]; + double diff = 0.5 * (c0[1][ix2] + c1[1][ix2]) - ave[1]; + diff *= diff; + if (sqr_max_diff < diff) { + sqr_max_diff = diff; + } + } + } + static const double kReductionX = 275.19165240059317; + static const double kReductionY = 18599.41286306991; + static const double kReductionZ = 410.8995306951065; + static const double kChromaBalance = 106.95800948271017; + double chroma_scale = kChromaBalance / (ave[1] + kChromaBalance); + + const double mix[3] = { + chroma_scale * kReductionX / (sqr_max_diff + kReductionX), + kReductionY / (sqr_max_diff + kReductionY), + chroma_scale * kReductionZ / (sqr_max_diff + kReductionZ), + }; + // Interpolate lineraly between the average color and the actual + // color -- to reduce the importance of this pixel. + for (int i = 0; i < 3; ++i) { + xyb0[i][ix] = mix[i] * c0[i][ix] + (1 - mix[i]) * ave[i]; + xyb1[i][ix] = mix[i] * c1[i][ix] + (1 - mix[i]) * ave[i]; + } + } + } +} + +double SimpleGamma(double v) { + static const double kGamma = 0.387494322593; + static const double limit = 43.01745241042018; + double bright = v - limit; + if (bright >= 0) { + static const double mul = 0.0383723643799; + v -= bright * mul; + } + static const double limit2 = 94.68634353321337; + double bright2 = v - limit2; + if (bright2 >= 0) { + static const double mul = 0.22885405968; + v -= bright2 * mul; + } + static const double offset = 0.156775786057; + static const double scale = 8.898059160493739; + double retval = scale * (offset + pow(v, kGamma)); + return retval; +} + +static inline double Gamma(double v) { + // return SimpleGamma(v); + return GammaPolynomial(v); +} + +void OpsinDynamicsImage(size_t xsize, size_t ysize, + std::vector > &rgb) { + PROFILER_FUNC; + std::vector > blurred = rgb; + static const double kSigma = 1.1; + for (int i = 0; i < 3; ++i) { + Blur(xsize, ysize, blurred[i].data(), kSigma, 0.0); + } + for (int i = 0; i < rgb[0].size(); ++i) { + double sensitivity[3]; + { + // Calculate sensitivity[3] based on the smoothed image gamma derivative. + double pre_rgb[3] = { blurred[0][i], blurred[1][i], blurred[2][i] }; + double pre_mixed[3]; + OpsinAbsorbance(pre_rgb, pre_mixed); + sensitivity[0] = Gamma(pre_mixed[0]) / pre_mixed[0]; + sensitivity[1] = Gamma(pre_mixed[1]) / pre_mixed[1]; + sensitivity[2] = Gamma(pre_mixed[2]) / pre_mixed[2]; + } + double cur_rgb[3] = { rgb[0][i], rgb[1][i], rgb[2][i] }; + double cur_mixed[3]; + OpsinAbsorbance(cur_rgb, cur_mixed); + cur_mixed[0] *= sensitivity[0]; + cur_mixed[1] *= sensitivity[1]; + cur_mixed[2] *= sensitivity[2]; + double x, y, z; + RgbToXyb(cur_mixed[0], cur_mixed[1], cur_mixed[2], &x, &y, &z); + rgb[0][i] = x; + rgb[1][i] = y; + rgb[2][i] = z; + } +} + +static void ScaleImage(double scale, std::vector *result) { + PROFILER_FUNC; + for (size_t i = 0; i < result->size(); ++i) { + (*result)[i] *= scale; + } +} + +// Making a cluster of local errors to be more impactful than +// just a single error. +void CalculateDiffmap(const size_t xsize, const size_t ysize, + const int step, + std::vector* diffmap) { + PROFILER_FUNC; + // Shift the diffmap more correctly above the pixels, from 2.5 pixels to 0.5 + // pixels distance over the original image. The border of 2 pixels on top and + // left side and 3 pixels on right and bottom side are zeroed, but these + // values have no meaning, they only exist to keep the result map the same + // size as the input images. + int s2 = (8 - step) / 2; + // Upsample and take square root. + std::vector diffmap_out(xsize * ysize); + const size_t res_xsize = (xsize + step - 1) / step; + for (size_t res_y = 0; res_y + 8 - step < ysize; res_y += step) { + for (size_t res_x = 0; res_x + 8 - step < xsize; res_x += step) { + size_t res_ix = (res_y * res_xsize + res_x) / step; + float orig_val = (*diffmap)[res_ix]; + constexpr float kInitialSlope = 100; + // TODO(b/29974893): Until that is fixed do not call sqrt on very small + // numbers. + double val = orig_val < (1.0 / (kInitialSlope * kInitialSlope)) + ? kInitialSlope * orig_val + : std::sqrt(orig_val); + for (size_t off_y = 0; off_y < step; ++off_y) { + for (size_t off_x = 0; off_x < step; ++off_x) { + diffmap_out[(res_y + off_y + s2) * xsize + res_x + off_x + s2] = val; + } + } + } + } + *diffmap = diffmap_out; + { + static const double kSigma = 8.8510880283; + static const double mul1 = 24.8235314874; + static const double scale = 1.0 / (1.0 + mul1); + const int s = 8 - step; + std::vector blurred((xsize - s) * (ysize - s)); + for (int y = 0; y < ysize - s; ++y) { + for (int x = 0; x < xsize - s; ++x) { + blurred[y * (xsize - s) + x] = (*diffmap)[(y + s2) * xsize + x + s2]; + } + } + static const double border_ratio = 0.03027655136; + Blur(xsize - s, ysize - s, blurred.data(), kSigma, border_ratio); + for (int y = 0; y < ysize - s; ++y) { + for (int x = 0; x < xsize - s; ++x) { + (*diffmap)[(y + s2) * xsize + x + s2] + += mul1 * blurred[y * (xsize - s) + x]; + } + } + ScaleImage(scale, diffmap); + } +} + +void ButteraugliComparator::Diffmap(const std::vector &rgb0_arg, + const std::vector &rgb1_arg, + ImageF &result) { + result = ImageF(xsize_, ysize_); + if (xsize_ < 8 || ysize_ < 8) return; + std::vector> rgb0_c = PackedFromPlanes(rgb0_arg); + std::vector> rgb1_c = PackedFromPlanes(rgb1_arg); + OpsinDynamicsImage(xsize_, ysize_, rgb0_c); + OpsinDynamicsImage(xsize_, ysize_, rgb1_c); + std::vector pg0 = PlanesFromPacked(xsize_, ysize_, rgb0_c); + std::vector pg1 = PlanesFromPacked(xsize_, ysize_, rgb1_c); + DiffmapOpsinDynamicsImage(pg0, pg1, result); +} + +void ButteraugliComparator::DiffmapOpsinDynamicsImage( + const std::vector &xyb0_arg, const std::vector &xyb1_arg, + ImageF &result) { + result = ImageF(xsize_, ysize_); + if (xsize_ < 8 || ysize_ < 8) return; + std::vector> xyb0 = PackedFromPlanes(xyb0_arg); + std::vector> xyb1 = PackedFromPlanes(xyb1_arg); + auto xyb0_c = xyb0; + auto xyb1_c = xyb1; + + MaskHighIntensityChange(xsize_, ysize_, xyb0_c, xyb1_c, xyb0, xyb1); + assert(8 <= xsize_); + for (int i = 0; i < 3; i++) { + assert(xyb0[i].size() == num_pixels_); + assert(xyb1[i].size() == num_pixels_); + } + std::vector > mask_xyb(3); + std::vector > mask_xyb_dc(3); + std::vector block_diff_dc(3 * res_xsize_ * res_ysize_); + std::vector block_diff_ac(3 * res_xsize_ * res_ysize_); + std::vector edge_detector_map(3 * res_xsize_ * res_ysize_); + std::vector packed_result; + BlockDiffMap(xyb0, xyb1, &block_diff_dc, &block_diff_ac); + EdgeDetectorMap(xyb0, xyb1, &edge_detector_map); + EdgeDetectorLowFreq(xyb0, xyb1, &block_diff_ac); + Mask(xyb0, xyb1, xsize_, ysize_, &mask_xyb, &mask_xyb_dc); + CombineChannels(mask_xyb, mask_xyb_dc, block_diff_dc, block_diff_ac, + edge_detector_map, &packed_result); + CalculateDiffmap(xsize_, ysize_, step_, &packed_result); + CopyFromPacked(packed_result, &result); +} + +void ButteraugliComparator::BlockDiffMap( + const std::vector > &xyb0, + const std::vector > &xyb1, + std::vector* block_diff_dc, + std::vector* block_diff_ac) { + PROFILER_FUNC; + for (size_t res_y = 0; res_y + (kBlockEdge - step_ - 1) < ysize_; + res_y += step_) { + for (size_t res_x = 0; res_x + (kBlockEdge - step_ - 1) < xsize_; + res_x += step_) { + size_t res_ix = (res_y * res_xsize_ + res_x) / step_; + size_t offset = (std::min(res_y, ysize_ - 8) * xsize_ + + std::min(res_x, xsize_ - 8)); + double block0[3 * kBlockEdge * kBlockEdge]; + double block1[3 * kBlockEdge * kBlockEdge]; + for (int i = 0; i < 3; ++i) { + double *m0 = &block0[i * kBlockEdge * kBlockEdge]; + double *m1 = &block1[i * kBlockEdge * kBlockEdge]; + for (size_t y = 0; y < kBlockEdge; y++) { + for (size_t x = 0; x < kBlockEdge; x++) { + m0[kBlockEdge * y + x] = xyb0[i][offset + y * xsize_ + x]; + m1[kBlockEdge * y + x] = xyb1[i][offset + y * xsize_ + x]; + } + } + } + double diff_xyb_dc[3] = { 0.0 }; + double diff_xyb_ac[3] = { 0.0 }; + double diff_xyb_edge_dc[3] = { 0.0 }; + ButteraugliBlockDiff(block0, block1, + diff_xyb_dc, diff_xyb_ac, diff_xyb_edge_dc); + for (int i = 0; i < 3; ++i) { + (*block_diff_dc)[3 * res_ix + i] = diff_xyb_dc[i]; + (*block_diff_ac)[3 * res_ix + i] = diff_xyb_ac[i]; + } + } + } +} + +void ButteraugliComparator::EdgeDetectorMap( + const std::vector > &xyb0, + const std::vector > &xyb1, + std::vector* edge_detector_map) { + PROFILER_FUNC; + static const double kSigma[3] = { + 1.5, + 0.586, + 0.4, + }; + std::vector > blurred0(xyb0); + std::vector > blurred1(xyb1); + for (int i = 0; i < 3; i++) { + Blur(xsize_, ysize_, blurred0[i].data(), kSigma[i], 0.0); + Blur(xsize_, ysize_, blurred1[i].data(), kSigma[i], 0.0); + } + for (size_t res_y = 0; res_y + (8 - step_) < ysize_; res_y += step_) { + for (size_t res_x = 0; res_x + (8 - step_) < xsize_; res_x += step_) { + size_t res_ix = (res_y * res_xsize_ + res_x) / step_; + double diff_xyb[3] = { 0.0 }; + Butteraugli8x8CornerEdgeDetectorDiff(std::min(res_x, xsize_ - 8), + std::min(res_y, ysize_ - 8), + xsize_, ysize_, + blurred0, blurred1, + diff_xyb); + for (int i = 0; i < 3; ++i) { + (*edge_detector_map)[3 * res_ix + i] = diff_xyb[i]; + } + } + } +} + +void ButteraugliComparator::EdgeDetectorLowFreq( + const std::vector > &xyb0, + const std::vector > &xyb1, + std::vector* block_diff_ac) { + PROFILER_FUNC; + static const double kSigma = 14; + static const double kMul = 10; + std::vector > blurred0(xyb0); + std::vector > blurred1(xyb1); + for (int i = 0; i < 3; i++) { + Blur(xsize_, ysize_, blurred0[i].data(), kSigma, 0.0); + Blur(xsize_, ysize_, blurred1[i].data(), kSigma, 0.0); + } + const int step = 8; + for (int y = 0; y + step < ysize_; y += step_) { + int resy = y / step_; + int resx = step / step_; + for (int x = 0; x + step < xsize_; x += step_, resx++) { + const int ix = y * xsize_ + x; + const int res_ix = resy * res_xsize_ + resx; + double diff[4][3]; + for (int i = 0; i < 3; ++i) { + int ix2 = ix + 8; + diff[0][i] = + ((blurred1[i][ix] - blurred0[i][ix]) + + (blurred0[i][ix2] - blurred1[i][ix2])); + ix2 = ix + 8 * xsize_; + diff[1][i] = + ((blurred1[i][ix] - blurred0[i][ix]) + + (blurred0[i][ix2] - blurred1[i][ix2])); + ix2 = ix + 6 * xsize_ + 6; + diff[2][i] = + ((blurred1[i][ix] - blurred0[i][ix]) + + (blurred0[i][ix2] - blurred1[i][ix2])); + ix2 = ix + 6 * xsize_ - 6; + diff[3][i] = x < step ? 0 : + ((blurred1[i][ix] - blurred0[i][ix]) + + (blurred0[i][ix2] - blurred1[i][ix2])); + } + double max_diff_xyb[3] = { 0 }; + for (int k = 0; k < 4; ++k) { + double diff_xyb[3] = { 0 }; + XybDiffLowFreqSquaredAccumulate(diff[k][0], diff[k][1], diff[k][2], + 0, 0, 0, 1.0, + diff_xyb); + for (int i = 0; i < 3; ++i) { + max_diff_xyb[i] = std::max(max_diff_xyb[i], diff_xyb[i]); + } + } + for (int i = 0; i < 3; ++i) { + (*block_diff_ac)[3 * res_ix + i] += kMul * max_diff_xyb[i]; + } + } + } +} + +void ButteraugliComparator::CombineChannels( + const std::vector >& mask_xyb, + const std::vector >& mask_xyb_dc, + const std::vector& block_diff_dc, + const std::vector& block_diff_ac, + const std::vector& edge_detector_map, + std::vector* result) { + PROFILER_FUNC; + result->resize(res_xsize_ * res_ysize_); + for (size_t res_y = 0; res_y + (8 - step_) < ysize_; res_y += step_) { + for (size_t res_x = 0; res_x + (8 - step_) < xsize_; res_x += step_) { + size_t res_ix = (res_y * res_xsize_ + res_x) / step_; + double mask[3]; + double dc_mask[3]; + for (int i = 0; i < 3; ++i) { + mask[i] = mask_xyb[i][(res_y + 3) * xsize_ + (res_x + 3)]; + dc_mask[i] = mask_xyb_dc[i][(res_y + 3) * xsize_ + (res_x + 3)]; + } + (*result)[res_ix] = + (DotProduct(&block_diff_dc[3 * res_ix], dc_mask) + + DotProduct(&block_diff_ac[3 * res_ix], mask) + + DotProduct(&edge_detector_map[3 * res_ix], mask)); + } + } +} + +double ButteraugliScoreFromDiffmap(const ImageF& diffmap) { + PROFILER_FUNC; + float retval = 0.0f; + for (size_t y = 0; y < diffmap.ysize(); ++y) { + ConstRestrict row = diffmap.Row(y); + for (size_t x = 0; x < diffmap.xsize(); ++x) { + retval = std::max(retval, row[x]); + } + } + return retval; +} + +static std::array MakeMask( + double extmul, double extoff, + double mul, double offset, + double scaler) { + std::array lut; + for (int i = 0; i < lut.size(); ++i) { + const double c = mul / ((0.01 * scaler * i) + offset); + lut[i] = 1.0 + extmul * (c + extoff); + assert(lut[i] >= 0.0); + lut[i] *= lut[i]; + } + return lut; +} + +double MaskX(double delta) { + PROFILER_FUNC; + static const double extmul = 0.975741017749; + static const double extoff = -4.25328244168; + static const double offset = 0.454909521427; + static const double scaler = 0.0738288224836; + static const double mul = 20.8029176447; + static const std::array lut = + MakeMask(extmul, extoff, mul, offset, scaler); + return InterpolateClampNegative(lut.data(), lut.size(), delta); +} + +double MaskY(double delta) { + PROFILER_FUNC; + static const double extmul = 0.373995618954; + static const double extoff = 1.5307267433; + static const double offset = 0.911952641929; + static const double scaler = 1.1731667845; + static const double mul = 16.2447033988; + static const std::array lut = + MakeMask(extmul, extoff, mul, offset, scaler); + return InterpolateClampNegative(lut.data(), lut.size(), delta); +} + +double MaskB(double delta) { + PROFILER_FUNC; + static const double extmul = 0.61582234137; + static const double extoff = -4.25376118646; + static const double offset = 1.05105070921; + static const double scaler = 0.47434643535; + static const double mul = 31.1444967089; + static const std::array lut = + MakeMask(extmul, extoff, mul, offset, scaler); + return InterpolateClampNegative(lut.data(), lut.size(), delta); +} + +double MaskDcX(double delta) { + PROFILER_FUNC; + static const double extmul = 1.79116943438; + static const double extoff = -3.86797479189; + static const double offset = 0.670960225853; + static const double scaler = 0.486575865525; + static const double mul = 20.4563479139; + static const std::array lut = + MakeMask(extmul, extoff, mul, offset, scaler); + return InterpolateClampNegative(lut.data(), lut.size(), delta); +} + +double MaskDcY(double delta) { + PROFILER_FUNC; + static const double extmul = 0.212223514236; + static const double extoff = -3.65647120524; + static const double offset = 1.73396799447; + static const double scaler = 0.170392660501; + static const double mul = 21.6566724788; + static const std::array lut = + MakeMask(extmul, extoff, mul, offset, scaler); + return InterpolateClampNegative(lut.data(), lut.size(), delta); +} + +double MaskDcB(double delta) { + PROFILER_FUNC; + static const double extmul = 0.349376011816; + static const double extoff = -0.894711072781; + static const double offset = 0.901647926679; + static const double scaler = 0.380086095024; + static const double mul = 18.0373825149; + static const std::array lut = + MakeMask(extmul, extoff, mul, offset, scaler); + return InterpolateClampNegative(lut.data(), lut.size(), delta); +} + +// Replaces values[x + y * xsize] with the minimum of the values in the +// square_size square with coordinates +// x - offset .. x + square_size - offset - 1, +// y - offset .. y + square_size - offset - 1. +void MinSquareVal(size_t square_size, size_t offset, + size_t xsize, size_t ysize, + float *values) { + PROFILER_FUNC; + // offset is not negative and smaller than square_size. + assert(offset < square_size); + std::vector tmp(xsize * ysize); + for (size_t y = 0; y < ysize; ++y) { + const size_t minh = offset > y ? 0 : y - offset; + const size_t maxh = std::min(ysize, y + square_size - offset); + for (size_t x = 0; x < xsize; ++x) { + double min = values[x + minh * xsize]; + for (size_t j = minh + 1; j < maxh; ++j) { + min = fmin(min, values[x + j * xsize]); + } + tmp[x + y * xsize] = min; + } + } + for (size_t x = 0; x < xsize; ++x) { + const size_t minw = offset > x ? 0 : x - offset; + const size_t maxw = std::min(xsize, x + square_size - offset); + for (size_t y = 0; y < ysize; ++y) { + double min = tmp[minw + y * xsize]; + for (size_t j = minw + 1; j < maxw; ++j) { + min = fmin(min, tmp[j + y * xsize]); + } + values[x + y * xsize] = min; + } + } +} + +// ===== Functions used by Mask only ===== +void Average5x5(int xsize, int ysize, std::vector* diffs) { + PROFILER_FUNC; + if (xsize < 4 || ysize < 4) { + // TODO: Make this work for small dimensions as well. + return; + } + static const float w = 0.679144890667; + static const float scale = 1.0 / (5.0 + 4 * w); + std::vector result = *diffs; + std::vector tmp0 = *diffs; + std::vector tmp1 = *diffs; + ScaleImage(w, &tmp1); + for (int y = 0; y < ysize; y++) { + const int row0 = y * xsize; + result[row0 + 1] += tmp0[row0]; + result[row0 + 0] += tmp0[row0 + 1]; + result[row0 + 2] += tmp0[row0 + 1]; + for (int x = 2; x < xsize - 2; ++x) { + result[row0 + x - 1] += tmp0[row0 + x]; + result[row0 + x + 1] += tmp0[row0 + x]; + } + result[row0 + xsize - 3] += tmp0[row0 + xsize - 2]; + result[row0 + xsize - 1] += tmp0[row0 + xsize - 2]; + result[row0 + xsize - 2] += tmp0[row0 + xsize - 1]; + if (y > 0) { + const int rowd1 = row0 - xsize; + result[rowd1 + 1] += tmp1[row0]; + result[rowd1 + 0] += tmp0[row0]; + for (int x = 1; x < xsize - 1; ++x) { + result[rowd1 + x + 1] += tmp1[row0 + x]; + result[rowd1 + x + 0] += tmp0[row0 + x]; + result[rowd1 + x - 1] += tmp1[row0 + x]; + } + result[rowd1 + xsize - 1] += tmp0[row0 + xsize - 1]; + result[rowd1 + xsize - 2] += tmp1[row0 + xsize - 1]; + } + if (y + 1 < ysize) { + const int rowu1 = row0 + xsize; + result[rowu1 + 1] += tmp1[row0]; + result[rowu1 + 0] += tmp0[row0]; + for (int x = 1; x < xsize - 1; ++x) { + result[rowu1 + x + 1] += tmp1[row0 + x]; + result[rowu1 + x + 0] += tmp0[row0 + x]; + result[rowu1 + x - 1] += tmp1[row0 + x]; + } + result[rowu1 + xsize - 1] += tmp0[row0 + xsize - 1]; + result[rowu1 + xsize - 2] += tmp1[row0 + xsize - 1]; + } + } + *diffs = result; + ScaleImage(scale, diffs); +} + +void DiffPrecompute( + const std::vector > &xyb0, + const std::vector > &xyb1, + size_t xsize, size_t ysize, + std::vector > *mask) { + PROFILER_FUNC; + mask->resize(3, std::vector(xyb0[0].size())); + double valsh0[3] = { 0.0 }; + double valsv0[3] = { 0.0 }; + double valsh1[3] = { 0.0 }; + double valsv1[3] = { 0.0 }; + int ix2; + for (size_t y = 0; y < ysize; ++y) { + for (size_t x = 0; x < xsize; ++x) { + size_t ix = x + xsize * y; + if (x + 1 < xsize) { + ix2 = ix + 1; + } else { + ix2 = ix - 1; + } + { + double x0 = (xyb0[0][ix] - xyb0[0][ix2]); + double y0 = (xyb0[1][ix] - xyb0[1][ix2]); + double z0 = (xyb0[2][ix] - xyb0[2][ix2]); + XybToVals(x0, y0, z0, &valsh0[0], &valsh0[1], &valsh0[2]); + double x1 = (xyb1[0][ix] - xyb1[0][ix2]); + double y1 = (xyb1[1][ix] - xyb1[1][ix2]); + double z1 = (xyb1[2][ix] - xyb1[2][ix2]); + XybToVals(x1, y1, z1, &valsh1[0], &valsh1[1], &valsh1[2]); + } + if (y + 1 < ysize) { + ix2 = ix + xsize; + } else { + ix2 = ix - xsize; + } + { + double x0 = (xyb0[0][ix] - xyb0[0][ix2]); + double y0 = (xyb0[1][ix] - xyb0[1][ix2]); + double z0 = (xyb0[2][ix] - xyb0[2][ix2]); + XybToVals(x0, y0, z0, &valsv0[0], &valsv0[1], &valsv0[2]); + double x1 = (xyb1[0][ix] - xyb1[0][ix2]); + double y1 = (xyb1[1][ix] - xyb1[1][ix2]); + double z1 = (xyb1[2][ix] - xyb1[2][ix2]); + XybToVals(x1, y1, z1, &valsv1[0], &valsv1[1], &valsv1[2]); + } + for (int i = 0; i < 3; ++i) { + double sup0 = fabs(valsh0[i]) + fabs(valsv0[i]); + double sup1 = fabs(valsh1[i]) + fabs(valsv1[i]); + double m = std::min(sup0, sup1); + (*mask)[i][ix] = m; + } + } + } +} + +void Mask(const std::vector > &xyb0, + const std::vector > &xyb1, + size_t xsize, size_t ysize, + std::vector > *mask, + std::vector > *mask_dc) { + PROFILER_FUNC; + mask->resize(3); + mask_dc->resize(3); + for (int i = 0; i < 3; ++i) { + (*mask)[i].resize(xsize * ysize); + (*mask_dc)[i].resize(xsize * ysize); + } + DiffPrecompute(xyb0, xyb1, xsize, ysize, mask); + for (int i = 0; i < 3; ++i) { + Average5x5(xsize, ysize, &(*mask)[i]); + MinSquareVal(4, 0, xsize, ysize, (*mask)[i].data()); + static const double sigma[3] = { + 9.65781083553, + 14.2644604355, + 4.53358927369, + }; + Blur(xsize, ysize, (*mask)[i].data(), sigma[i], 0.0); + } + static const double w00 = 232.206464018; + static const double w11 = 22.9455222245; + static const double w22 = 503.962310606; + + for (size_t y = 0; y < ysize; ++y) { + for (size_t x = 0; x < xsize; ++x) { + const size_t idx = y * xsize + x; + const double s0 = (*mask)[0][idx]; + const double s1 = (*mask)[1][idx]; + const double s2 = (*mask)[2][idx]; + const double p0 = w00 * s0; + const double p1 = w11 * s1; + const double p2 = w22 * s2; + + (*mask)[0][idx] = MaskX(p0); + (*mask)[1][idx] = MaskY(p1); + (*mask)[2][idx] = MaskB(p2); + (*mask_dc)[0][idx] = MaskDcX(p0); + (*mask_dc)[1][idx] = MaskDcY(p1); + (*mask_dc)[2][idx] = MaskDcB(p2); + } + } + for (int i = 0; i < 3; ++i) { + ScaleImage(kGlobalScale * kGlobalScale, &(*mask)[i]); + ScaleImage(kGlobalScale * kGlobalScale, &(*mask_dc)[i]); + } +} + +void ButteraugliDiffmap(const std::vector &rgb0_image, + const std::vector &rgb1_image, + ImageF &result_image) { + const size_t xsize = rgb0_image[0].xsize(); + const size_t ysize = rgb0_image[0].ysize(); + ButteraugliComparator butteraugli(xsize, ysize, 3); + butteraugli.Diffmap(rgb0_image, rgb1_image, result_image); +} + +bool ButteraugliInterface(const std::vector &rgb0, + const std::vector &rgb1, + ImageF &diffmap, + double &diffvalue) { + const size_t xsize = rgb0[0].xsize(); + const size_t ysize = rgb0[0].ysize(); + if (xsize < 1 || ysize < 1) { + // Butteraugli values for small (where xsize or ysize is smaller + // than 8 pixels) images are non-sensical, but most likely it is + // less disruptive to try to compute something than just give up. + return false; // No image. + } + for (int i = 1; i < 3; i++) { + if (rgb0[i].xsize() != xsize || rgb0[i].ysize() != ysize || + rgb1[i].xsize() != xsize || rgb1[i].ysize() != ysize) { + return false; // Image planes must have same dimensions. + } + } + if (xsize < 8 || ysize < 8) { + for (int y = 0; y < ysize; ++y) { + for (int x = 0; x < xsize; ++x) { + diffmap.Row(y)[x] = 0; + } + } + diffvalue = 0; + return true; + } + ButteraugliDiffmap(rgb0, rgb1, diffmap); + diffvalue = ButteraugliScoreFromDiffmap(diffmap); + return true; +} + +bool ButteraugliAdaptiveQuantization(size_t xsize, size_t ysize, + const std::vector > &rgb, std::vector &quant) { + if (xsize < 16 || ysize < 16) { + return false; // Butteraugli is undefined for small images. + } + size_t size = xsize * ysize; + + std::vector > scale_xyb(3); + std::vector > scale_xyb_dc(3); + Mask(rgb, rgb, xsize, ysize, &scale_xyb, &scale_xyb_dc); + quant.resize(size); + + // Mask gives us values in 3 color channels, but for now we take only + // the intensity channel. + for (size_t i = 0; i < size; i++) { + quant[i] = scale_xyb[1][i]; + } + return true; +} + +double ButteraugliFuzzyClass(double score) { + static const double fuzzy_width_up = 10.287189655; + static const double fuzzy_width_down = 6.97490803335; + static const double m0 = 2.0; + double fuzzy_width = score < 1.0 ? fuzzy_width_down : fuzzy_width_up; + return m0 / (1.0 + exp((score - 1.0) * fuzzy_width)); +} + +double ButteraugliFuzzyInverse(double seek) { + double pos = 0; + for (double range = 1.0; range >= 1e-10; range *= 0.5) { + double cur = ButteraugliFuzzyClass(pos); + if (cur < seek) { + pos -= range; + } else { + pos += range; + } + } + return pos; +} + +} // namespace butteraugli diff --git a/extern/butteraugli/butteraugli.h b/extern/butteraugli/butteraugli.h new file mode 100755 index 0000000..31824b8 --- /dev/null +++ b/extern/butteraugli/butteraugli.h @@ -0,0 +1,560 @@ +// Copyright 2016 Google Inc. All Rights Reserved. +// +// 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. +// +// Disclaimer: This is not an official Google product. +// +// Author: Jyrki Alakuijala (jyrki.alakuijala@gmail.com) + +#ifndef BUTTERAUGLI_BUTTERAUGLI_H_ +#define BUTTERAUGLI_BUTTERAUGLI_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef PROFILER_ENABLED +#define PROFILER_ENABLED 0 +#endif +#if PROFILER_ENABLED +#else +#define PROFILER_FUNC +#define PROFILER_ZONE(name) +#endif + +#define BUTTERAUGLI_ENABLE_CHECKS 0 + +// This is the main interface to butteraugli image similarity +// analysis function. + +namespace butteraugli { + +template +class Image; + +using Image8 = Image; +using ImageF = Image; +using ImageD = Image; + +// ButteraugliInterface defines the public interface for butteraugli. +// +// It calculates the difference between rgb0 and rgb1. +// +// rgb0 and rgb1 contain the images. rgb0[c][px] and rgb1[c][px] contains +// the red image for c == 0, green for c == 1, blue for c == 2. Location index +// px is calculated as y * xsize + x. +// +// Value of pixels of images rgb0 and rgb1 need to be represented as raw +// intensity. Most image formats store gamma corrected intensity in pixel +// values. This gamma correction has to be removed, by applying the following +// function: +// butteraugli_val = 255.0 * pow(png_val / 255.0, gamma); +// A typical value of gamma is 2.2. It is usually stored in the image header. +// Take care not to confuse that value with its inverse. The gamma value should +// be always greater than one. +// Butteraugli does not work as intended if the caller does not perform +// gamma correction. +// +// diffmap will contain an image of the size xsize * ysize, containing +// localized differences for values px (indexed with the px the same as rgb0 +// and rgb1). diffvalue will give a global score of similarity. +// +// A diffvalue smaller than kButteraugliGood indicates that images can be +// observed as the same image. +// diffvalue larger than kButteraugliBad indicates that a difference between +// the images can be observed. +// A diffvalue between kButteraugliGood and kButteraugliBad indicates that +// a subtle difference can be observed between the images. +// +// Returns true on success. + +bool ButteraugliInterface(const std::vector &rgb0, + const std::vector &rgb1, + ImageF &diffmap, + double &diffvalue); + +const double kButteraugliQuantLow = 0.26; +const double kButteraugliQuantHigh = 1.454; + +// Converts the butteraugli score into fuzzy class values that are continuous +// at the class boundary. The class boundary location is based on human +// raters, but the slope is arbitrary. Particularly, it does not reflect +// the expectation value of probabilities of the human raters. It is just +// expected that a smoother class boundary will allow for higher-level +// optimization algorithms to work faster. +// +// Returns 2.0 for a perfect match, and 1.0 for 'ok', 0.0 for bad. Because the +// scoring is fuzzy, a butteraugli score of 0.96 would return a class of +// around 1.9. +double ButteraugliFuzzyClass(double score); + +// Input values should be in range 0 (bad) to 2 (good). Use +// kButteraugliNormalization as normalization. +double ButteraugliFuzzyInverse(double seek); + +// Returns a map which can be used for adaptive quantization. Values can +// typically range from kButteraugliQuantLow to kButteraugliQuantHigh. Low +// values require coarse quantization (e.g. near random noise), high values +// require fine quantization (e.g. in smooth bright areas). +bool ButteraugliAdaptiveQuantization(size_t xsize, size_t ysize, + const std::vector > &rgb, std::vector &quant); + +// Implementation details, don't use anything below or your code will +// break in the future. + +#ifdef _MSC_VER +#define BUTTERAUGLI_RESTRICT __restrict +#else +#define BUTTERAUGLI_RESTRICT __restrict__ +#endif + +#ifdef _MSC_VER +#define BUTTERAUGLI_CACHE_ALIGNED_RETURN /* not supported */ +#else +#define BUTTERAUGLI_CACHE_ALIGNED_RETURN __attribute__((assume_aligned(64))) +#endif + +// Alias for unchangeable, non-aliased pointers. T is a pointer type, +// possibly to a const type. Example: ConstRestrict ptr = nullptr. +// The conventional syntax uint8_t* const RESTRICT is more confusing - it is +// not immediately obvious that the pointee is non-const. +template +using ConstRestrict = T const BUTTERAUGLI_RESTRICT; + +// Functions that depend on the cache line size. +class CacheAligned { + public: + static constexpr size_t kPointerSize = sizeof(void *); + static constexpr size_t kCacheLineSize = 64; + + // The aligned-return annotation is only allowed on function declarations. + static void *Allocate(const size_t bytes) BUTTERAUGLI_CACHE_ALIGNED_RETURN; + static void Free(void *aligned_pointer); +}; + +template +using CacheAlignedUniquePtrT = std::unique_ptr; + +using CacheAlignedUniquePtr = CacheAlignedUniquePtrT; + +template +static inline CacheAlignedUniquePtrT Allocate(const size_t entries) { + return CacheAlignedUniquePtrT( + static_cast>( + CacheAligned::Allocate(entries * sizeof(T))), + CacheAligned::Free); +} + +// Returns the smallest integer not less than "amount" that is divisible by +// "multiple", which must be a power of two. +template +static inline size_t Align(const size_t amount) { + static_assert(multiple != 0 && ((multiple & (multiple - 1)) == 0), + "Align<> argument must be a power of two"); + return (amount + multiple - 1) & ~(multiple - 1); +} + +// Single channel, contiguous (cache-aligned) rows separated by padding. +// T must be POD. +// +// Rationale: vectorization benefits from aligned operands - unaligned loads and +// especially stores are expensive when the address crosses cache line +// boundaries. Introducing padding after each row ensures the start of a row is +// aligned, and that row loops can process entire vectors (writes to the padding +// are allowed and ignored). +// +// We prefer a planar representation, where channels are stored as separate +// 2D arrays, because that simplifies vectorization (repeating the same +// operation on multiple adjacent components) without the complexity of a +// hybrid layout (8 R, 8 G, 8 B, ...). In particular, clients can easily iterate +// over all components in a row and Image requires no knowledge of the pixel +// format beyond the component type "T". The downside is that we duplicate the +// xsize/ysize members for each channel. +// +// This image layout could also be achieved with a vector and a row accessor +// function, but a class wrapper with support for "deleter" allows wrapping +// existing memory allocated by clients without copying the pixels. It also +// provides convenient accessors for xsize/ysize, which shortens function +// argument lists. Supports move-construction so it can be stored in containers. +template +class Image { + // Returns cache-aligned row stride, being careful to avoid 2K aliasing. + static size_t BytesPerRow(const size_t xsize) { + // Allow reading one extra AVX-2 vector on the right margin. + const size_t row_size = xsize * sizeof(T) + 32; + const size_t align = CacheAligned::kCacheLineSize; + size_t bytes_per_row = (row_size + align - 1) & ~(align - 1); + // During the lengthy window before writes are committed to memory, CPUs + // guard against read after write hazards by checking the address, but + // only the lower 11 bits. We avoid a false dependency between writes to + // consecutive rows by ensuring their sizes are not multiples of 2 KiB. + if (bytes_per_row % 2048 == 0) { + bytes_per_row += align; + } + return bytes_per_row; + } + + public: + using T = ComponentType; + + Image() : xsize_(0), ysize_(0), bytes_per_row_(0), bytes_(nullptr, Ignore) {} + + Image(const size_t xsize, const size_t ysize) + : xsize_(xsize), + ysize_(ysize), + bytes_per_row_(BytesPerRow(xsize)), + bytes_(Allocate(bytes_per_row_ * ysize)) {} + + Image(const size_t xsize, const size_t ysize, ConstRestrict bytes, + const size_t bytes_per_row) + : xsize_(xsize), + ysize_(ysize), + bytes_per_row_(bytes_per_row), + bytes_(bytes, Ignore) {} + + // Move constructor (required for returning Image from function) + Image(Image &&other) + : xsize_(other.xsize_), + ysize_(other.ysize_), + bytes_per_row_(other.bytes_per_row_), + bytes_(std::move(other.bytes_)) {} + + // Move assignment (required for std::vector) + Image &operator=(Image &&other) { + xsize_ = other.xsize_; + ysize_ = other.ysize_; + bytes_per_row_ = other.bytes_per_row_; + bytes_ = std::move(other.bytes_); + return *this; + } + + void Swap(Image &other) { + std::swap(xsize_, other.xsize_); + std::swap(ysize_, other.ysize_); + std::swap(bytes_per_row_, other.bytes_per_row_); + std::swap(bytes_, other.bytes_); + } + + // How many pixels. + size_t xsize() const { return xsize_; } + size_t ysize() const { return ysize_; } + + ConstRestrict Row(const size_t y) BUTTERAUGLI_CACHE_ALIGNED_RETURN { +#ifdef BUTTERAUGLI_ENABLE_CHECKS + if (y >= ysize_) { + printf("Row %zu out of bounds (ysize=%zu)\n", y, ysize_); + abort(); + } +#endif + return reinterpret_cast(bytes_.get() + y * bytes_per_row_); + } + + ConstRestrict Row(const size_t y) const + BUTTERAUGLI_CACHE_ALIGNED_RETURN { +#ifdef BUTTERAUGLI_ENABLE_CHECKS + if (y >= ysize_) { + printf("Const row %zu out of bounds (ysize=%zu)\n", y, ysize_); + abort(); + } +#endif + return reinterpret_cast(bytes_.get() + y * bytes_per_row_); + } + + // Raw access to byte contents, for interfacing with other libraries. + // Unsigned char instead of char to avoid surprises (sign extension). + ConstRestrict bytes() { return bytes_.get(); } + ConstRestrict bytes() const { return bytes_.get(); } + size_t bytes_per_row() const { return bytes_per_row_; } + + // Returns number of pixels (some of which are padding) per row. Useful for + // computing other rows via pointer arithmetic. + intptr_t PixelsPerRow() const { + static_assert(CacheAligned::kCacheLineSize % sizeof(T) == 0, + "Padding must be divisible by the pixel size."); + return static_cast(bytes_per_row_ / sizeof(T)); + } + + private: + // Deleter used when bytes are not owned. + static void Ignore(void *ptr) {} + + // (Members are non-const to enable assignment during move-assignment.) + size_t xsize_; // original intended pixels, not including any padding. + size_t ysize_; + size_t bytes_per_row_; // [bytes] including padding. + CacheAlignedUniquePtr bytes_; +}; + +// Returns newly allocated planes of the given dimensions. +template +static inline std::vector> CreatePlanes(const size_t xsize, + const size_t ysize, + const size_t num_planes) { + std::vector> planes; + planes.reserve(num_planes); + for (size_t i = 0; i < num_planes; ++i) { + planes.emplace_back(xsize, ysize); + } + return planes; +} + +// Returns a new image with the same dimensions and pixel values. +template +static inline Image CopyPixels(const Image &other) { + Image copy(other.xsize(), other.ysize()); + const void *BUTTERAUGLI_RESTRICT from = other.bytes(); + void *BUTTERAUGLI_RESTRICT to = copy.bytes(); + memcpy(to, from, other.ysize() * other.bytes_per_row()); + return copy; +} + +// Returns new planes with the same dimensions and pixel values. +template +static inline std::vector> CopyPlanes( + const std::vector> &planes) { + std::vector> copy; + copy.reserve(planes.size()); + for (const Image &plane : planes) { + copy.push_back(CopyPixels(plane)); + } + return copy; +} + +// Compacts a padded image into a preallocated packed vector. +template +static inline void CopyToPacked(const Image &from, std::vector *to) { + const size_t xsize = from.xsize(); + const size_t ysize = from.ysize(); +#if BUTTERAUGLI_ENABLE_CHECKS + if (to->size() < xsize * ysize) { + printf("%zu x %zu exceeds %zu capacity\n", xsize, ysize, to->size()); + abort(); + } +#endif + for (size_t y = 0; y < ysize; ++y) { + ConstRestrict row_from = from.Row(y); + ConstRestrict row_to = to->data() + y * xsize; + memcpy(row_to, row_from, xsize * sizeof(T)); + } +} + +// Expands a packed vector into a preallocated padded image. +template +static inline void CopyFromPacked(const std::vector &from, Image *to) { + const size_t xsize = to->xsize(); + const size_t ysize = to->ysize(); + assert(from.size() == xsize * ysize); + for (size_t y = 0; y < ysize; ++y) { + ConstRestrict row_from = from.data() + y * xsize; + ConstRestrict row_to = to->Row(y); + memcpy(row_to, row_from, xsize * sizeof(T)); + } +} + +template +static inline std::vector> PlanesFromPacked( + const size_t xsize, const size_t ysize, + const std::vector> &packed) { + std::vector> planes; + planes.reserve(packed.size()); + for (const std::vector &p : packed) { + planes.push_back(Image(xsize, ysize)); + CopyFromPacked(p, &planes.back()); + } + return planes; +} + +template +static inline std::vector> PackedFromPlanes( + const std::vector> &planes) { + assert(!planes.empty()); + const size_t num_pixels = planes[0].xsize() * planes[0].ysize(); + std::vector> packed; + packed.reserve(planes.size()); + for (const Image &image : planes) { + packed.push_back(std::vector(num_pixels)); + CopyToPacked(image, &packed.back()); + } + return packed; +} + +class ButteraugliComparator { + public: + ButteraugliComparator(size_t xsize, size_t ysize, int step); + + // Computes the butteraugli map between rgb0 and rgb1 and updates result. + void Diffmap(const std::vector &rgb0, + const std::vector &rgb1, + ImageF &result); + + // Same as above, but OpsinDynamicsImage() was already applied to + // rgb0 and rgb1. + void DiffmapOpsinDynamicsImage(const std::vector &rgb0, + const std::vector &rgb1, + ImageF &result); + + private: + void BlockDiffMap(const std::vector > &rgb0, + const std::vector > &rgb1, + std::vector* block_diff_dc, + std::vector* block_diff_ac); + + + void EdgeDetectorMap(const std::vector > &rgb0, + const std::vector > &rgb1, + std::vector* edge_detector_map); + + void EdgeDetectorLowFreq(const std::vector > &rgb0, + const std::vector > &rgb1, + std::vector* block_diff_ac); + + void CombineChannels(const std::vector >& scale_xyb, + const std::vector >& scale_xyb_dc, + const std::vector& block_diff_dc, + const std::vector& block_diff_ac, + const std::vector& edge_detector_map, + std::vector* result); + + const size_t xsize_; + const size_t ysize_; + const size_t num_pixels_; + const int step_; + const size_t res_xsize_; + const size_t res_ysize_; +}; + +void ButteraugliDiffmap(const std::vector &rgb0, + const std::vector &rgb1, + ImageF &diffmap); + +double ButteraugliScoreFromDiffmap(const ImageF& distmap); + +// Compute values of local frequency and dc masking based on the activity +// in the two images. +void Mask(const std::vector > &rgb0, + const std::vector > &rgb1, + size_t xsize, size_t ysize, + std::vector > *mask, + std::vector > *mask_dc); + +// Computes difference metrics for one 8x8 block. +void ButteraugliBlockDiff(double rgb0[192], + double rgb1[192], + double diff_xyb_dc[3], + double diff_xyb_ac[3], + double diff_xyb_edge_dc[3]); + +void OpsinAbsorbance(const double in[3], double out[3]); + +void OpsinDynamicsImage(size_t xsize, size_t ysize, + std::vector > &rgb); + +void MaskHighIntensityChange( + size_t xsize, size_t ysize, + const std::vector > &c0, + const std::vector > &c1, + std::vector > &rgb0, + std::vector > &rgb1); + +void Blur(size_t xsize, size_t ysize, float* channel, double sigma, + double border_ratio = 0.0); + +void RgbToXyb(double r, double g, double b, + double *valx, double *valy, double *valz); + +double SimpleGamma(double v); + +double GammaMinArg(); +double GammaMaxArg(); + +// Polynomial evaluation via Clenshaw's scheme (similar to Horner's). +// Template enables compile-time unrolling of the recursion, but must reside +// outside of a class due to the specialization. +template +static inline void ClenshawRecursion(const double x, const double *coefficients, + double *b1, double *b2) { + const double x_b1 = x * (*b1); + const double t = (x_b1 + x_b1) - (*b2) + coefficients[INDEX]; + *b2 = *b1; + *b1 = t; + + ClenshawRecursion(x, coefficients, b1, b2); +} + +// Base case +template <> +inline void ClenshawRecursion<0>(const double x, const double *coefficients, + double *b1, double *b2) { + const double x_b1 = x * (*b1); + // The final iteration differs - no 2 * x_b1 here. + *b1 = x_b1 - (*b2) + coefficients[0]; +} + +// Rational polynomial := dividing two polynomial evaluations. These are easier +// to find than minimax polynomials. +struct RationalPolynomial { + template + static double EvaluatePolynomial(const double x, + const double (&coefficients)[N]) { + double b1 = 0.0; + double b2 = 0.0; + ClenshawRecursion(x, coefficients, &b1, &b2); + return b1; + } + + // Evaluates the polynomial at x (in [min_value, max_value]). + inline double operator()(const float x) const { + // First normalize to [0, 1]. + const double x01 = (x - min_value) / (max_value - min_value); + // And then to [-1, 1] domain of Chebyshev polynomials. + const double xc = 2.0 * x01 - 1.0; + + const double yp = EvaluatePolynomial(xc, p); + const double yq = EvaluatePolynomial(xc, q); + if (yq == 0.0) return 0.0; + return static_cast(yp / yq); + } + + // Domain of the polynomials; they are undefined elsewhere. + double min_value; + double max_value; + + // Coefficients of T_n (Chebyshev polynomials of the first kind). + // Degree 5/5 is a compromise between accuracy (0.1%) and numerical stability. + double p[5 + 1]; + double q[5 + 1]; +}; + +static inline float GammaPolynomial(float value) { + // Generated by gamma_polynomial.m from equispaced x/gamma(x) samples. + static const RationalPolynomial r = { + 0.770000000000000, 274.579999999999984, + { + 881.979476556478289, 1496.058452015812463, 908.662212739659481, + 373.566100223287378, 85.840860336314364, 6.683258861509244, + }, + { + 12.262350348616792, 20.557285797683576, 12.161463238367844, + 4.711532733641639, 0.899112889751053, 0.035662329617191, + }}; + return r(value); +} + +} // namespace butteraugli + +#endif // BUTTERAUGLI_BUTTERAUGLI_H_ diff --git a/extern/etcpack/CMakeLists.txt b/extern/etcpack/CMakeLists.txt new file mode 100644 index 0000000..2690373 --- /dev/null +++ b/extern/etcpack/CMakeLists.txt @@ -0,0 +1,10 @@ + +SET(ETCPACK_SRCS + etcdec.cxx + etcpack.cxx + image.cxx + image.h) + +add_definitions("-DPGMOUT=0") +ADD_LIBRARY(etcpack STATIC ${ETCPACK_SRCS}) + diff --git a/extern/etcpack/etcdec.cxx b/extern/etcpack/etcdec.cxx new file mode 100755 index 0000000..4d9cfd6 --- /dev/null +++ b/extern/etcpack/etcdec.cxx @@ -0,0 +1,1842 @@ +/** + +@~English +@page licensing Licensing + +@section etcdec etcdec.cxx License + +etcdec.cxx is made available under the terms and conditions of the following +License Agreement. + +Software License Agreement + +PLEASE REVIEW THE FOLLOWING TERMS AND CONDITIONS PRIOR TO USING THE +ERICSSON TEXTURE COMPRESSION CODEC SOFTWARE (THE "SOFTWARE"). THE USE +OF THE SOFTWARE IS SUBJECT TO THE TERMS AND CONDITIONS OF THE +FOLLOWING SOFTWARE LICENSE AGREEMENT (THE "SLA"). IF YOU DO NOT ACCEPT +SUCH TERMS AND CONDITIONS YOU MAY NOT USE THE SOFTWARE. + +Subject to the terms and conditions of the SLA, the licensee of the +Software (the "Licensee") hereby, receives a non-exclusive, +non-transferable, limited, free-of-charge, perpetual and worldwide +license, to copy, use, distribute and modify the Software, but only +for the purpose of developing, manufacturing, selling, using and +distributing products including the Software in binary form, which +products are used for compression and/or decompression according to +the Khronos standard specifications OpenGL, OpenGL ES and +WebGL. Notwithstanding anything of the above, Licensee may distribute +[etcdec.cxx] in source code form provided (i) it is in unmodified +form; and (ii) it is included in software owned by Licensee. + +If Licensee institutes, or threatens to institute, patent litigation +against Ericsson or Ericsson's affiliates for using the Software for +developing, having developed, manufacturing, having manufactured, +selling, offer for sale, importing, using, leasing, operating, +repairing and/or distributing products (i) within the scope of the +Khronos framework; or (ii) using software or other intellectual +property rights owned by Ericsson or its affiliates and provided under +the Khronos framework, Ericsson shall have the right to terminate this +SLA with immediate effect. Moreover, if Licensee institutes, or +threatens to institute, patent litigation against any other licensee +of the Software for using the Software in products within the scope of +the Khronos framework, Ericsson shall have the right to terminate this +SLA with immediate effect. However, should Licensee institute, or +threaten to institute, patent litigation against any other licensee of +the Software based on such other licensee's use of any other software +together with the Software, then Ericsson shall have no right to +terminate this SLA. + +This SLA does not transfer to Licensee any ownership to any Ericsson +or third party intellectual property rights. All rights not expressly +granted by Ericsson under this SLA are hereby expressly +reserved. Furthermore, nothing in this SLA shall be construed as a +right to use or sell products in a manner which conveys or purports to +convey whether explicitly, by principles of implied license, or +otherwise, any rights to any third party, under any patent of Ericsson +or of Ericsson's affiliates covering or relating to any combination of +the Software with any other software or product (not licensed +hereunder) where the right applies specifically to the combination and +not to the software or product itself. + +THE SOFTWARE IS PROVIDED "AS IS". ERICSSON MAKES NO REPRESENTATIONS OF +ANY KIND, EXTENDS NO WARRANTIES OR CONDITIONS OF ANY KIND, EITHER +EXPRESS, IMPLIED OR STATUTORY; INCLUDING, BUT NOT LIMITED TO, EXPRESS, +IMPLIED OR STATUTORY WARRANTIES OR CONDITIONS OF TITLE, +MERCHANTABILITY, SATISFACTORY QUALITY, SUITABILITY, AND FITNESS FOR A +PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE +OF THE SOFTWARE IS WITH THE LICENSEE. SHOULD THE SOFTWARE PROVE +DEFECTIVE, THE LICENSEE ASSUMES THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. ERICSSON MAKES NO WARRANTY THAT THE MANUFACTURE, +SALE, OFFERING FOR SALE, DISTRIBUTION, LEASE, USE OR IMPORTATION UNDER +THE SLA WILL BE FREE FROM INFRINGEMENT OF PATENTS, COPYRIGHTS OR OTHER +INTELLECTUAL PROPERTY RIGHTS OF OTHERS, AND THE VALIDITY OF THE +LICENSE AND THE SLA ARE SUBJECT TO LICENSEE'S SOLE RESPONSIBILITY TO +MAKE SUCH DETERMINATION AND ACQUIRE SUCH LICENSES AS MAY BE NECESSARY +WITH RESPECT TO PATENTS, COPYRIGHT AND OTHER INTELLECTUAL PROPERTY OF +THIRD PARTIES. + +THE LICENSEE ACKNOWLEDGES AND ACCEPTS THAT THE SOFTWARE (I) IS NOT +LICENSED FOR; (II) IS NOT DESIGNED FOR OR INTENDED FOR; AND (III) MAY +NOT BE USED FOR; ANY MISSION CRITICAL APPLICATIONS SUCH AS, BUT NOT +LIMITED TO OPERATION OF NUCLEAR OR HEALTHCARE COMPUTER SYSTEMS AND/OR +NETWORKS, AIRCRAFT OR TRAIN CONTROL AND/OR COMMUNICATION SYSTEMS OR +ANY OTHER COMPUTER SYSTEMS AND/OR NETWORKS OR CONTROL AND/OR +COMMUNICATION SYSTEMS ALL IN WHICH CASE THE FAILURE OF THE SOFTWARE +COULD LEAD TO DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL, MATERIAL OR +ENVIRONMENTAL DAMAGE. LICENSEE'S RIGHTS UNDER THIS LICENSE WILL +TERMINATE AUTOMATICALLY AND IMMEDIATELY WITHOUT NOTICE IF LICENSEE +FAILS TO COMPLY WITH THIS PARAGRAPH. + +IN NO EVENT SHALL ERICSSON BE LIABLE FOR ANY DAMAGES WHATSOEVER, +INCLUDING BUT NOT LIMITED TO PERSONAL INJURY, ANY GENERAL, SPECIAL, +INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF OR IN +CONNECTION WITH THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING +BUT NOT LIMITED TO LOSS OF PROFITS, BUSINESS INTERUPTIONS, OR ANY +OTHER COMMERCIAL DAMAGES OR LOSSES, LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY THE LICENSEE OR THIRD +PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER +SOFTWARE) REGARDLESS OF THE THEORY OF LIABILITY (CONTRACT, TORT, OR +OTHERWISE), EVEN IF THE LICENSEE OR ANY OTHER PARTY HAS BEEN ADVISED +OF THE POSSIBILITY OF SUCH DAMAGES. + +Licensee acknowledges that "ERICSSON ///" is the corporate trademark +of Telefonaktiebolaget LM Ericsson and that both "Ericsson" and the +figure "///" are important features of the trade names of +Telefonaktiebolaget LM Ericsson. Nothing contained in these terms and +conditions shall be deemed to grant Licensee any right, title or +interest in the word "Ericsson" or the figure "///". No delay or +omission by Ericsson to exercise any right or power shall impair any +such right or power to be construed to be a waiver thereof. Consent by +Ericsson to, or waiver of, a breach by the Licensee shall not +constitute consent to, waiver of, or excuse for any other different or +subsequent breach. + +This SLA shall be governed by the substantive law of Sweden. Any +dispute, controversy or claim arising out of or in connection with +this SLA, or the breach, termination or invalidity thereof, shall be +submitted to the exclusive jurisdiction of the Swedish Courts. + +*/ + +//// etcpack v2.74 +//// +//// NO WARRANTY +//// +//// BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE THE PROGRAM IS PROVIDED +//// "AS IS". ERICSSON MAKES NO REPRESENTATIONS OF ANY KIND, EXTENDS NO +//// WARRANTIES OR CONDITIONS OF ANY KIND; EITHER EXPRESS, IMPLIED OR +//// STATUTORY; INCLUDING, BUT NOT LIMITED TO, EXPRESS, IMPLIED OR +//// STATUTORY WARRANTIES OR CONDITIONS OF TITLE, MERCHANTABILITY, +//// SATISFACTORY QUALITY, SUITABILITY AND FITNESS FOR A PARTICULAR +//// PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +//// PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME +//// THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. ERICSSON +//// MAKES NO WARRANTY THAT THE MANUFACTURE, SALE, OFFERING FOR SALE, +//// DISTRIBUTION, LEASE, USE OR IMPORTATION UNDER THE LICENSE WILL BE FREE +//// FROM INFRINGEMENT OF PATENTS, COPYRIGHTS OR OTHER INTELLECTUAL +//// PROPERTY RIGHTS OF OTHERS, AND THE VALIDITY OF THE LICENSE IS SUBJECT +//// TO YOUR SOLE RESPONSIBILITY TO MAKE SUCH DETERMINATION AND ACQUIRE +//// SUCH LICENSES AS MAY BE NECESSARY WITH RESPECT TO PATENTS, COPYRIGHT +//// AND OTHER INTELLECTUAL PROPERTY OF THIRD PARTIES. +//// +//// FOR THE AVOIDANCE OF DOUBT THE PROGRAM (I) IS NOT LICENSED FOR; (II) +//// IS NOT DESIGNED FOR OR INTENDED FOR; AND (III) MAY NOT BE USED FOR; +//// ANY MISSION CRITICAL APPLICATIONS SUCH AS, BUT NOT LIMITED TO +//// OPERATION OF NUCLEAR OR HEALTHCARE COMPUTER SYSTEMS AND/OR NETWORKS, +//// AIRCRAFT OR TRAIN CONTROL AND/OR COMMUNICATION SYSTEMS OR ANY OTHER +//// COMPUTER SYSTEMS AND/OR NETWORKS OR CONTROL AND/OR COMMUNICATION +//// SYSTEMS ALL IN WHICH CASE THE FAILURE OF THE PROGRAM COULD LEAD TO +//// DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL, MATERIAL OR ENVIRONMENTAL +//// DAMAGE. YOUR RIGHTS UNDER THIS LICENSE WILL TERMINATE AUTOMATICALLY +//// AND IMMEDIATELY WITHOUT NOTICE IF YOU FAIL TO COMPLY WITH THIS +//// PARAGRAPH. +//// +//// IN NO EVENT WILL ERICSSON, BE LIABLE FOR ANY DAMAGES WHATSOEVER, +//// INCLUDING BUT NOT LIMITED TO PERSONAL INJURY, ANY GENERAL, SPECIAL, +//// INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN +//// CONNECTION WITH THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +//// NOT LIMITED TO LOSS OF PROFITS, BUSINESS INTERUPTIONS, OR ANY OTHER +//// COMMERCIAL DAMAGES OR LOSSES, LOSS OF DATA OR DATA BEING RENDERED +//// INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF +//// THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS) REGARDLESS OF THE +//// THEORY OF LIABILITY (CONTRACT, TORT OR OTHERWISE), EVEN IF SUCH HOLDER +//// OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +//// +//// (C) Ericsson AB 2005-2013. All Rights Reserved. +//// + +#include +#include + +// Typedefs +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef short int16; + +// Macros to help with bit extraction/insertion +#define SHIFT(size,startpos) ((startpos)-(size)+1) +#define MASK(size, startpos) (((2<<(size-1))-1) << SHIFT(size,startpos)) +#define PUTBITS( dest, data, size, startpos) dest = ((dest & ~MASK(size, startpos)) | ((data << SHIFT(size, startpos)) & MASK(size,startpos))) +#define SHIFTHIGH(size, startpos) (((startpos)-32)-(size)+1) +#define MASKHIGH(size, startpos) (((1<<(size))-1) << SHIFTHIGH(size,startpos)) +#define PUTBITSHIGH(dest, data, size, startpos) dest = ((dest & ~MASKHIGH(size, startpos)) | ((data << SHIFTHIGH(size, startpos)) & MASKHIGH(size,startpos))) +#define GETBITS(source, size, startpos) (( (source) >> ((startpos)-(size)+1) ) & ((1<<(size)) -1)) +#define GETBITSHIGH(source, size, startpos) (( (source) >> (((startpos)-32)-(size)+1) ) & ((1<<(size)) -1)) +#ifndef PGMOUT +#define PGMOUT 1 +#endif +// Thumb macros and definitions +#define R_BITS59T 4 +#define G_BITS59T 4 +#define B_BITS59T 4 +#define R_BITS58H 4 +#define G_BITS58H 4 +#define B_BITS58H 4 +#define MAXIMUM_ERROR (255*255*16*1000) +#define R 0 +#define G 1 +#define B 2 +#define BLOCKHEIGHT 4 +#define BLOCKWIDTH 4 +#define BINPOW(power) (1<<(power)) +#define TABLE_BITS_59T 3 +#define TABLE_BITS_58H 3 + +// Helper Macros +#define CLAMP(ll,x,ul) (((x)<(ll)) ? (ll) : (((x)>(ul)) ? (ul) : (x))) +#define JAS_ROUND(x) (((x) < 0.0 ) ? ((int)((x)-0.5)) : ((int)((x)+0.5))) + +#define RED_CHANNEL(img,width,x,y,channels) img[channels*(y*width+x)+0] +#define GREEN_CHANNEL(img,width,x,y,channels) img[channels*(y*width+x)+1] +#define BLUE_CHANNEL(img,width,x,y,channels) img[channels*(y*width+x)+2] +#define ALPHA_CHANNEL(img,width,x,y,channels) img[channels*(y*width+x)+3] + + +// Global tables +static uint8 table59T[8] = {3,6,11,16,23,32,41,64}; // 3-bit table for the 59 bit T-mode +static uint8 table58H[8] = {3,6,11,16,23,32,41,64}; // 3-bit table for the 58 bit H-mode +static int compressParams[16][4] = {{-8, -2, 2, 8}, {-8, -2, 2, 8}, {-17, -5, 5, 17}, {-17, -5, 5, 17}, {-29, -9, 9, 29}, {-29, -9, 9, 29}, {-42, -13, 13, 42}, {-42, -13, 13, 42}, {-60, -18, 18, 60}, {-60, -18, 18, 60}, {-80, -24, 24, 80}, {-80, -24, 24, 80}, {-106, -33, 33, 106}, {-106, -33, 33, 106}, {-183, -47, 47, 183}, {-183, -47, 47, 183}}; +static int unscramble[4] = {2, 3, 1, 0}; +int alphaTableInitialized = 0; +int alphaTable[256][8]; +int alphaBase[16][4] = { + {-15,-9,-6,-3}, + {-13,-10,-7,-3}, + {-13,-8,-5,-2}, + {-13,-6,-4,-2}, + {-12,-8,-6,-3}, + {-11,-9,-7,-3}, + {-11,-8,-7,-4}, + {-11,-8,-5,-3}, + { -10,-8,-6,-2}, + { -10,-8,-5,-2}, + { -10,-8,-4,-2}, + { -10,-7,-5,-2}, + { -10,-7,-4,-3}, + { -10,-3,-2, -1}, + { -9,-8,-6,-4}, + { -9,-7,-5,-3} + }; + +// Global variables +int formatSigned = 0; + +// Enums + enum{PATTERN_H = 0, + PATTERN_T = 1}; + + +// Code used to create the valtab +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void setupAlphaTable() +{ + if(alphaTableInitialized) + return; + alphaTableInitialized = 1; + + //read table used for alpha compression + int buf; + for(int i = 16; i<32; i++) + { + for(int j=0; j<8; j++) + { + buf=alphaBase[i-16][3-j%4]; + if(j<4) + alphaTable[i][j]=buf; + else + alphaTable[i][j]=(-buf-1); + } + } + + //beyond the first 16 values, the rest of the table is implicit.. so calculate that! + for(int i=0; i<256; i++) + { + //fill remaining slots in table with multiples of the first ones. + int mul = i/16; + int old = 16+i%16; + for(int j = 0; j<8; j++) + { + alphaTable[i][j]=alphaTable[old][j]*mul; + //note: we don't do clamping here, though we could, because we'll be clamped afterwards anyway. + } + } +} + +// Read a word in big endian style +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void read_big_endian_2byte_word(unsigned short *blockadr, FILE *f) +{ + uint8 bytes[2]; + unsigned short block; + + fread(&bytes[0], 1, 1, f); + fread(&bytes[1], 1, 1, f); + + block = 0; + block |= bytes[0]; + block = block << 8; + block |= bytes[1]; + + blockadr[0] = block; +} + +// Read a word in big endian style +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void read_big_endian_4byte_word(unsigned int *blockadr, FILE *f) +{ + uint8 bytes[4]; + unsigned int block; + + fread(&bytes[0], 1, 1, f); + fread(&bytes[1], 1, 1, f); + fread(&bytes[2], 1, 1, f); + fread(&bytes[3], 1, 1, f); + + block = 0; + block |= bytes[0]; + block = block << 8; + block |= bytes[1]; + block = block << 8; + block |= bytes[2]; + block = block << 8; + block |= bytes[3]; + + blockadr[0] = block; +} + +// The format stores the bits for the three extra modes in a roundabout way to be able to +// fit them without increasing the bit rate. This function converts them into something +// that is easier to work with. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void unstuff57bits(unsigned int planar_word1, unsigned int planar_word2, unsigned int &planar57_word1, unsigned int &planar57_word2) +{ + // Get bits from twotimer configuration for 57 bits + // + // Go to this bit layout: + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // ----------------------------------------------------------------------------------------------- + // |R0 |G01G02 |B01B02 ;B03 |RH1 |RH2|GH | + // ----------------------------------------------------------------------------------------------- + // + // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // ----------------------------------------------------------------------------------------------- + // |BH |RV |GV |BV | not used | + // ----------------------------------------------------------------------------------------------- + // + // From this: + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // ------------------------------------------------------------------------------------------------ + // |//|R0 |G01|/|G02 |B01|/ // //|B02 |//|B03 |RH1 |df|RH2| + // ------------------------------------------------------------------------------------------------ + // + // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // ----------------------------------------------------------------------------------------------- + // |GH |BH |RV |GV |BV | + // ----------------------------------------------------------------------------------------------- + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | dcol 2 | base col1 | dcol 2 | base col 1 | dcol 2 | table | table |diff|flip| + // | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + + uint8 RO, GO1, GO2, BO1, BO2, BO3, RH1, RH2, GH, BH, RV, GV, BV; + + RO = GETBITSHIGH( planar_word1, 6, 62); + GO1 = GETBITSHIGH( planar_word1, 1, 56); + GO2 = GETBITSHIGH( planar_word1, 6, 54); + BO1 = GETBITSHIGH( planar_word1, 1, 48); + BO2 = GETBITSHIGH( planar_word1, 2, 44); + BO3 = GETBITSHIGH( planar_word1, 3, 41); + RH1 = GETBITSHIGH( planar_word1, 5, 38); + RH2 = GETBITSHIGH( planar_word1, 1, 32); + GH = GETBITS( planar_word2, 7, 31); + BH = GETBITS( planar_word2, 6, 24); + RV = GETBITS( planar_word2, 6, 18); + GV = GETBITS( planar_word2, 7, 12); + BV = GETBITS( planar_word2, 6, 5); + + planar57_word1 = 0; planar57_word2 = 0; + PUTBITSHIGH( planar57_word1, RO, 6, 63); + PUTBITSHIGH( planar57_word1, GO1, 1, 57); + PUTBITSHIGH( planar57_word1, GO2, 6, 56); + PUTBITSHIGH( planar57_word1, BO1, 1, 50); + PUTBITSHIGH( planar57_word1, BO2, 2, 49); + PUTBITSHIGH( planar57_word1, BO3, 3, 47); + PUTBITSHIGH( planar57_word1, RH1, 5, 44); + PUTBITSHIGH( planar57_word1, RH2, 1, 39); + PUTBITSHIGH( planar57_word1, GH, 7, 38); + PUTBITS( planar57_word2, BH, 6, 31); + PUTBITS( planar57_word2, RV, 6, 25); + PUTBITS( planar57_word2, GV, 7, 19); + PUTBITS( planar57_word2, BV, 6, 12); +} + +// The format stores the bits for the three extra modes in a roundabout way to be able to +// fit them without increasing the bit rate. This function converts them into something +// that is easier to work with. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void unstuff58bits(unsigned int thumbH_word1, unsigned int thumbH_word2, unsigned int &thumbH58_word1, unsigned int &thumbH58_word2) +{ + // Go to this layout: + // + // |63 62 61 60 59 58|57 56 55 54 53 52 51|50 49|48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33|32 | + // |-------empty-----|part0---------------|part1|part2------------------------------------------|part3| + // + // from this: + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------| + // |//|part0 |// // //|part1|//|part2 |df|part3| + // --------------------------------------------------------------------------------------------------| + + unsigned int part0, part1, part2, part3; + + // move parts + part0 = GETBITSHIGH( thumbH_word1, 7, 62); + part1 = GETBITSHIGH( thumbH_word1, 2, 52); + part2 = GETBITSHIGH( thumbH_word1,16, 49); + part3 = GETBITSHIGH( thumbH_word1, 1, 32); + thumbH58_word1 = 0; + PUTBITSHIGH( thumbH58_word1, part0, 7, 57); + PUTBITSHIGH( thumbH58_word1, part1, 2, 50); + PUTBITSHIGH( thumbH58_word1, part2, 16, 48); + PUTBITSHIGH( thumbH58_word1, part3, 1, 32); + + thumbH58_word2 = thumbH_word2; +} + +// The format stores the bits for the three extra modes in a roundabout way to be able to +// fit them without increasing the bit rate. This function converts them into something +// that is easier to work with. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void unstuff59bits(unsigned int thumbT_word1, unsigned int thumbT_word2, unsigned int &thumbT59_word1, unsigned int &thumbT59_word2) +{ + // Get bits from twotimer configuration 59 bits. + // + // Go to this bit layout: + // + // |63 62 61 60 59|58 57 56 55|54 53 52 51|50 49 48 47|46 45 44 43|42 41 40 39|38 37 36 35|34 33 32| + // |----empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|--dist--| + // + // |31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| + // |----------------------------------------index bits---------------------------------------------| + // + // + // From this: + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // ----------------------------------------------------------------------------------------------- + // |// // //|R0a |//|R0b |G0 |B0 |R1 |G1 |B1 |da |df|db| + // ----------------------------------------------------------------------------------------------- + // + // |31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| + // |----------------------------------------index bits---------------------------------------------| + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // ----------------------------------------------------------------------------------------------- + // | base col1 | dcol 2 | base col1 | dcol 2 | base col 1 | dcol 2 | table | table |df|fp| + // | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | B1' (5 bits) | dB2 | cw 1 | cw 2 |bt|bt| + // ------------------------------------------------------------------------------------------------ + + uint8 R0a; + + // Fix middle part + thumbT59_word1 = thumbT_word1 >> 1; + // Fix db (lowest bit of d) + PUTBITSHIGH( thumbT59_word1, thumbT_word1, 1, 32); + // Fix R0a (top two bits of R0) + R0a = GETBITSHIGH( thumbT_word1, 2, 60); + PUTBITSHIGH( thumbT59_word1, R0a, 2, 58); + + // Zero top part (not needed) + PUTBITSHIGH( thumbT59_word1, 0, 5, 63); + + thumbT59_word2 = thumbT_word2; +} + +// The color bits are expanded to the full color +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void decompressColor(int R_B, int G_B, int B_B, uint8 (colors_RGB444)[2][3], uint8 (colors)[2][3]) +{ + // The color should be retrieved as: + // + // c = round(255/(r_bits^2-1))*comp_color + // + // This is similar to bit replication + // + // Note -- this code only work for bit replication from 4 bits and up --- 3 bits needs + // two copy operations. + + colors[0][R] = (colors_RGB444[0][R] << (8 - R_B)) | (colors_RGB444[0][R] >> (R_B - (8-R_B)) ); + colors[0][G] = (colors_RGB444[0][G] << (8 - G_B)) | (colors_RGB444[0][G] >> (G_B - (8-G_B)) ); + colors[0][B] = (colors_RGB444[0][B] << (8 - B_B)) | (colors_RGB444[0][B] >> (B_B - (8-B_B)) ); + colors[1][R] = (colors_RGB444[1][R] << (8 - R_B)) | (colors_RGB444[1][R] >> (R_B - (8-R_B)) ); + colors[1][G] = (colors_RGB444[1][G] << (8 - G_B)) | (colors_RGB444[1][G] >> (G_B - (8-G_B)) ); + colors[1][B] = (colors_RGB444[1][B] << (8 - B_B)) | (colors_RGB444[1][B] >> (B_B - (8-B_B)) ); +} + +void calculatePaintColors59T(uint8 d, uint8 p, uint8 (colors)[2][3], uint8 (possible_colors)[4][3]) +{ + ////////////////////////////////////////////// + // + // C3 C1 C4----C1---C2 + // | | | + // | | | + // |-------| | + // | | | + // | | | + // C4 C2 C3 + // + ////////////////////////////////////////////// + + // C4 + possible_colors[3][R] = CLAMP(0,colors[1][R] - table59T[d],255); + possible_colors[3][G] = CLAMP(0,colors[1][G] - table59T[d],255); + possible_colors[3][B] = CLAMP(0,colors[1][B] - table59T[d],255); + + if (p == PATTERN_T) + { + // C3 + possible_colors[0][R] = colors[0][R]; + possible_colors[0][G] = colors[0][G]; + possible_colors[0][B] = colors[0][B]; + // C2 + possible_colors[1][R] = CLAMP(0,colors[1][R] + table59T[d],255); + possible_colors[1][G] = CLAMP(0,colors[1][G] + table59T[d],255); + possible_colors[1][B] = CLAMP(0,colors[1][B] + table59T[d],255); + // C1 + possible_colors[2][R] = colors[1][R]; + possible_colors[2][G] = colors[1][G]; + possible_colors[2][B] = colors[1][B]; + + } + else + { + printf("Invalid pattern. Terminating"); + exit(1); + } +} +// Decompress a T-mode block (simple packing) +// Simple 59T packing: +//|63 62 61 60 59|58 57 56 55|54 53 52 51|50 49 48 47|46 45 44 43|42 41 40 39|38 37 36 35|34 33 32| +//|----empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|--dist--| +// +//|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| +//|----------------------------------------index bits---------------------------------------------| +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void decompressBlockTHUMB59Tc(unsigned int block_part1, unsigned int block_part2, uint8 *img,int width,int height,int startx,int starty, int channels) +{ + uint8 colorsRGB444[2][3]; + uint8 colors[2][3]; + uint8 paint_colors[4][3]; + uint8 distance; + uint8 block_mask[4][4]; + + // First decode left part of block. + colorsRGB444[0][R]= GETBITSHIGH(block_part1, 4, 58); + colorsRGB444[0][G]= GETBITSHIGH(block_part1, 4, 54); + colorsRGB444[0][B]= GETBITSHIGH(block_part1, 4, 50); + + colorsRGB444[1][R]= GETBITSHIGH(block_part1, 4, 46); + colorsRGB444[1][G]= GETBITSHIGH(block_part1, 4, 42); + colorsRGB444[1][B]= GETBITSHIGH(block_part1, 4, 38); + + distance = GETBITSHIGH(block_part1, TABLE_BITS_59T, 34); + + // Extend the two colors to RGB888 + decompressColor(R_BITS59T, G_BITS59T, B_BITS59T, colorsRGB444, colors); + calculatePaintColors59T(distance, PATTERN_T, colors, paint_colors); + + // Choose one of the four paint colors for each texel + for (uint8 x = 0; x < BLOCKWIDTH; ++x) + { + for (uint8 y = 0; y < BLOCKHEIGHT; ++y) + { + //block_mask[x][y] = GETBITS(block_part2,2,31-(y*4+x)*2); + block_mask[x][y] = GETBITS(block_part2,1,(y+x*4)+16)<<1; + block_mask[x][y] |= GETBITS(block_part2,1,(y+x*4)); + img[channels*((starty+y)*width+startx+x)+R] = + CLAMP(0,paint_colors[block_mask[x][y]][R],255); // RED + img[channels*((starty+y)*width+startx+x)+G] = + CLAMP(0,paint_colors[block_mask[x][y]][G],255); // GREEN + img[channels*((starty+y)*width+startx+x)+B] = + CLAMP(0,paint_colors[block_mask[x][y]][B],255); // BLUE + } + } +} + +void decompressBlockTHUMB59T(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty) +{ + decompressBlockTHUMB59Tc(block_part1, block_part2, img, width, height, startx, starty, 3); +} + +// Calculate the paint colors from the block colors +// using a distance d and one of the H- or T-patterns. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void calculatePaintColors58H(uint8 d, uint8 p, uint8 (colors)[2][3], uint8 (possible_colors)[4][3]) +{ + + ////////////////////////////////////////////// + // + // C3 C1 C4----C1---C2 + // | | | + // | | | + // |-------| | + // | | | + // | | | + // C4 C2 C3 + // + ////////////////////////////////////////////// + + // C4 + possible_colors[3][R] = CLAMP(0,colors[1][R] - table58H[d],255); + possible_colors[3][G] = CLAMP(0,colors[1][G] - table58H[d],255); + possible_colors[3][B] = CLAMP(0,colors[1][B] - table58H[d],255); + + if (p == PATTERN_H) + { + // C1 + possible_colors[0][R] = CLAMP(0,colors[0][R] + table58H[d],255); + possible_colors[0][G] = CLAMP(0,colors[0][G] + table58H[d],255); + possible_colors[0][B] = CLAMP(0,colors[0][B] + table58H[d],255); + // C2 + possible_colors[1][R] = CLAMP(0,colors[0][R] - table58H[d],255); + possible_colors[1][G] = CLAMP(0,colors[0][G] - table58H[d],255); + possible_colors[1][B] = CLAMP(0,colors[0][B] - table58H[d],255); + // C3 + possible_colors[2][R] = CLAMP(0,colors[1][R] + table58H[d],255); + possible_colors[2][G] = CLAMP(0,colors[1][G] + table58H[d],255); + possible_colors[2][B] = CLAMP(0,colors[1][B] + table58H[d],255); + } + else + { + printf("Invalid pattern. Terminating"); + exit(1); + } +} + +// Decompress an H-mode block +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void decompressBlockTHUMB58Hc(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty, int channels) +{ + unsigned int col0, col1; + uint8 colors[2][3]; + uint8 colorsRGB444[2][3]; + uint8 paint_colors[4][3]; + uint8 distance; + uint8 block_mask[4][4]; + + // First decode left part of block. + colorsRGB444[0][R]= GETBITSHIGH(block_part1, 4, 57); + colorsRGB444[0][G]= GETBITSHIGH(block_part1, 4, 53); + colorsRGB444[0][B]= GETBITSHIGH(block_part1, 4, 49); + + colorsRGB444[1][R]= GETBITSHIGH(block_part1, 4, 45); + colorsRGB444[1][G]= GETBITSHIGH(block_part1, 4, 41); + colorsRGB444[1][B]= GETBITSHIGH(block_part1, 4, 37); + + distance = 0; + distance = (GETBITSHIGH(block_part1, 2, 33)) << 1; + + col0 = GETBITSHIGH(block_part1, 12, 57); + col1 = GETBITSHIGH(block_part1, 12, 45); + + if(col0 >= col1) + { + distance |= 1; + } + + // Extend the two colors to RGB888 + decompressColor(R_BITS58H, G_BITS58H, B_BITS58H, colorsRGB444, colors); + + calculatePaintColors58H(distance, PATTERN_H, colors, paint_colors); + + // Choose one of the four paint colors for each texel + for (uint8 x = 0; x < BLOCKWIDTH; ++x) + { + for (uint8 y = 0; y < BLOCKHEIGHT; ++y) + { + //block_mask[x][y] = GETBITS(block_part2,2,31-(y*4+x)*2); + block_mask[x][y] = GETBITS(block_part2,1,(y+x*4)+16)<<1; + block_mask[x][y] |= GETBITS(block_part2,1,(y+x*4)); + img[channels*((starty+y)*width+startx+x)+R] = + CLAMP(0,paint_colors[block_mask[x][y]][R],255); // RED + img[channels*((starty+y)*width+startx+x)+G] = + CLAMP(0,paint_colors[block_mask[x][y]][G],255); // GREEN + img[channels*((starty+y)*width+startx+x)+B] = + CLAMP(0,paint_colors[block_mask[x][y]][B],255); // BLUE + } + } +} +void decompressBlockTHUMB58H(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty) +{ + decompressBlockTHUMB58Hc(block_part1, block_part2, img, width, height, startx, starty, 3); +} + +// Decompress the planar mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void decompressBlockPlanar57c(unsigned int compressed57_1, unsigned int compressed57_2, uint8 *img, int width, int height, int startx, int starty, int channels) +{ + uint8 colorO[3], colorH[3], colorV[3]; + + colorO[0] = GETBITSHIGH( compressed57_1, 6, 63); + colorO[1] = GETBITSHIGH( compressed57_1, 7, 57); + colorO[2] = GETBITSHIGH( compressed57_1, 6, 50); + colorH[0] = GETBITSHIGH( compressed57_1, 6, 44); + colorH[1] = GETBITSHIGH( compressed57_1, 7, 38); + colorH[2] = GETBITS( compressed57_2, 6, 31); + colorV[0] = GETBITS( compressed57_2, 6, 25); + colorV[1] = GETBITS( compressed57_2, 7, 19); + colorV[2] = GETBITS( compressed57_2, 6, 12); + + colorO[0] = (colorO[0] << 2) | (colorO[0] >> 4); + colorO[1] = (colorO[1] << 1) | (colorO[1] >> 6); + colorO[2] = (colorO[2] << 2) | (colorO[2] >> 4); + + colorH[0] = (colorH[0] << 2) | (colorH[0] >> 4); + colorH[1] = (colorH[1] << 1) | (colorH[1] >> 6); + colorH[2] = (colorH[2] << 2) | (colorH[2] >> 4); + + colorV[0] = (colorV[0] << 2) | (colorV[0] >> 4); + colorV[1] = (colorV[1] << 1) | (colorV[1] >> 6); + colorV[2] = (colorV[2] << 2) | (colorV[2] >> 4); + + int xx, yy; + + for( xx=0; xx<4; xx++) + { + for( yy=0; yy<4; yy++) + { + img[channels*width*(starty+yy) + channels*(startx+xx) + 0] = CLAMP(0, ((xx*(colorH[0]-colorO[0]) + yy*(colorV[0]-colorO[0]) + 4*colorO[0] + 2) >> 2),255); + img[channels*width*(starty+yy) + channels*(startx+xx) + 1] = CLAMP(0, ((xx*(colorH[1]-colorO[1]) + yy*(colorV[1]-colorO[1]) + 4*colorO[1] + 2) >> 2),255); + img[channels*width*(starty+yy) + channels*(startx+xx) + 2] = CLAMP(0, ((xx*(colorH[2]-colorO[2]) + yy*(colorV[2]-colorO[2]) + 4*colorO[2] + 2) >> 2),255); + + //Equivalent method + /*img[channels*width*(starty+yy) + channels*(startx+xx) + 0] = (int)CLAMP(0, JAS_ROUND((xx*(colorH[0]-colorO[0])/4.0 + yy*(colorV[0]-colorO[0])/4.0 + colorO[0])), 255); + img[channels*width*(starty+yy) + channels*(startx+xx) + 1] = (int)CLAMP(0, JAS_ROUND((xx*(colorH[1]-colorO[1])/4.0 + yy*(colorV[1]-colorO[1])/4.0 + colorO[1])), 255); + img[channels*width*(starty+yy) + channels*(startx+xx) + 2] = (int)CLAMP(0, JAS_ROUND((xx*(colorH[2]-colorO[2])/4.0 + yy*(colorV[2]-colorO[2])/4.0 + colorO[2])), 255);*/ + + } + } +} +void decompressBlockPlanar57(unsigned int compressed57_1, unsigned int compressed57_2, uint8 *img, int width, int height, int startx, int starty) +{ + decompressBlockPlanar57c(compressed57_1, compressed57_2, img, width, height, startx, starty, 3); +} +// Decompress an ETC1 block (or ETC2 using individual or differential mode). +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void decompressBlockDiffFlipC(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty, int channels) +{ + uint8 avg_color[3], enc_color1[3], enc_color2[3]; + signed char diff[3]; + int table; + int index,shift; + int r,g,b; + int diffbit; + int flipbit; + + diffbit = (GETBITSHIGH(block_part1, 1, 33)); + flipbit = (GETBITSHIGH(block_part1, 1, 32)); + + if( !diffbit ) + { + // We have diffbit = 0. + + // First decode left part of block. + avg_color[0]= GETBITSHIGH(block_part1, 4, 63); + avg_color[1]= GETBITSHIGH(block_part1, 4, 55); + avg_color[2]= GETBITSHIGH(block_part1, 4, 47); + + // Here, we should really multiply by 17 instead of 16. This can + // be done by just copying the four lower bits to the upper ones + // while keeping the lower bits. + avg_color[0] |= (avg_color[0] <<4); + avg_color[1] |= (avg_color[1] <<4); + avg_color[2] |= (avg_color[2] <<4); + + table = GETBITSHIGH(block_part1, 3, 39) << 1; + + unsigned int pixel_indices_MSB, pixel_indices_LSB; + + pixel_indices_MSB = GETBITS(block_part2, 16, 31); + pixel_indices_LSB = GETBITS(block_part2, 16, 15); + + if( (flipbit) == 0 ) + { + // We should not flip + shift = 0; + for(int x=startx; x> shift) & 1) << 1; + index |= ((pixel_indices_LSB >> shift) & 1); + shift++; + index=unscramble[index]; + + r=RED_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[0]+compressParams[table][index],255); + g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255); + b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255); + } + } + } + else + { + // We should flip + shift = 0; + for(int x=startx; x> shift) & 1) << 1; + index |= ((pixel_indices_LSB >> shift) & 1); + shift++; + index=unscramble[index]; + + r=RED_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[0]+compressParams[table][index],255); + g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255); + b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255); + } + shift+=2; + } + } + + // Now decode other part of block. + avg_color[0]= GETBITSHIGH(block_part1, 4, 59); + avg_color[1]= GETBITSHIGH(block_part1, 4, 51); + avg_color[2]= GETBITSHIGH(block_part1, 4, 43); + + // Here, we should really multiply by 17 instead of 16. This can + // be done by just copying the four lower bits to the upper ones + // while keeping the lower bits. + avg_color[0] |= (avg_color[0] <<4); + avg_color[1] |= (avg_color[1] <<4); + avg_color[2] |= (avg_color[2] <<4); + + table = GETBITSHIGH(block_part1, 3, 36) << 1; + pixel_indices_MSB = GETBITS(block_part2, 16, 31); + pixel_indices_LSB = GETBITS(block_part2, 16, 15); + + if( (flipbit) == 0 ) + { + // We should not flip + shift=8; + for(int x=startx+2; x> shift) & 1) << 1; + index |= ((pixel_indices_LSB >> shift) & 1); + shift++; + index=unscramble[index]; + + r=RED_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[0]+compressParams[table][index],255); + g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255); + b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255); + } + } + } + else + { + // We should flip + shift=2; + for(int x=startx; x> shift) & 1) << 1; + index |= ((pixel_indices_LSB >> shift) & 1); + shift++; + index=unscramble[index]; + + r=RED_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[0]+compressParams[table][index],255); + g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255); + b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255); + } + shift += 2; + } + } + } + else + { + // We have diffbit = 1. + +// 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 +// --------------------------------------------------------------------------------------------------- +// | base col1 | dcol 2 | base col1 | dcol 2 | base col 1 | dcol 2 | table | table |diff|flip| +// | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit | +// --------------------------------------------------------------------------------------------------- +// +// +// c) bit layout in bits 31 through 0 (in both cases) +// +// 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 +// -------------------------------------------------------------------------------------------------- +// | most significant pixel index bits | least significant pixel index bits | +// | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a | +// -------------------------------------------------------------------------------------------------- + + // First decode left part of block. + enc_color1[0]= GETBITSHIGH(block_part1, 5, 63); + enc_color1[1]= GETBITSHIGH(block_part1, 5, 55); + enc_color1[2]= GETBITSHIGH(block_part1, 5, 47); + + // Expand from 5 to 8 bits + avg_color[0] = (enc_color1[0] <<3) | (enc_color1[0] >> 2); + avg_color[1] = (enc_color1[1] <<3) | (enc_color1[1] >> 2); + avg_color[2] = (enc_color1[2] <<3) | (enc_color1[2] >> 2); + + table = GETBITSHIGH(block_part1, 3, 39) << 1; + + unsigned int pixel_indices_MSB, pixel_indices_LSB; + + pixel_indices_MSB = GETBITS(block_part2, 16, 31); + pixel_indices_LSB = GETBITS(block_part2, 16, 15); + + if( (flipbit) == 0 ) + { + // We should not flip + shift = 0; + for(int x=startx; x> shift) & 1) << 1; + index |= ((pixel_indices_LSB >> shift) & 1); + shift++; + index=unscramble[index]; + + r=RED_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[0]+compressParams[table][index],255); + g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255); + b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255); + } + } + } + else + { + // We should flip + shift = 0; + for(int x=startx; x> shift) & 1) << 1; + index |= ((pixel_indices_LSB >> shift) & 1); + shift++; + index=unscramble[index]; + + r=RED_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[0]+compressParams[table][index],255); + g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255); + b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255); + } + shift+=2; + } + } + + // Now decode right part of block. + diff[0]= GETBITSHIGH(block_part1, 3, 58); + diff[1]= GETBITSHIGH(block_part1, 3, 50); + diff[2]= GETBITSHIGH(block_part1, 3, 42); + + // Extend sign bit to entire byte. + diff[0] = (diff[0] << 5); + diff[1] = (diff[1] << 5); + diff[2] = (diff[2] << 5); + diff[0] = diff[0] >> 5; + diff[1] = diff[1] >> 5; + diff[2] = diff[2] >> 5; + + // Calculale second color + enc_color2[0]= enc_color1[0] + diff[0]; + enc_color2[1]= enc_color1[1] + diff[1]; + enc_color2[2]= enc_color1[2] + diff[2]; + + // Expand from 5 to 8 bits + avg_color[0] = (enc_color2[0] <<3) | (enc_color2[0] >> 2); + avg_color[1] = (enc_color2[1] <<3) | (enc_color2[1] >> 2); + avg_color[2] = (enc_color2[2] <<3) | (enc_color2[2] >> 2); + + table = GETBITSHIGH(block_part1, 3, 36) << 1; + pixel_indices_MSB = GETBITS(block_part2, 16, 31); + pixel_indices_LSB = GETBITS(block_part2, 16, 15); + + if( (flipbit) == 0 ) + { + // We should not flip + shift=8; + for(int x=startx+2; x> shift) & 1) << 1; + index |= ((pixel_indices_LSB >> shift) & 1); + shift++; + index=unscramble[index]; + + r=RED_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[0]+compressParams[table][index],255); + g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255); + b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255); + } + } + } + else + { + // We should flip + shift=2; + for(int x=startx; x> shift) & 1) << 1; + index |= ((pixel_indices_LSB >> shift) & 1); + shift++; + index=unscramble[index]; + + r=RED_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[0]+compressParams[table][index],255); + g=GREEN_CHANNEL(img,width,x,y,channels)=CLAMP(0,avg_color[1]+compressParams[table][index],255); + b=BLUE_CHANNEL(img,width,x,y,channels) =CLAMP(0,avg_color[2]+compressParams[table][index],255); + } + shift += 2; + } + } + } +} +void decompressBlockDiffFlip(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty) +{ + decompressBlockDiffFlipC(block_part1, block_part2, img, width, height, startx, starty, 3); +} + +// Decompress an ETC2 RGB block +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void decompressBlockETC2c(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty, int channels) +{ + int diffbit; + signed char color1[3]; + signed char diff[3]; + signed char red, green, blue; + + diffbit = (GETBITSHIGH(block_part1, 1, 33)); + + if( diffbit ) + { + // We have diffbit = 1; + + // Base color + color1[0]= GETBITSHIGH(block_part1, 5, 63); + color1[1]= GETBITSHIGH(block_part1, 5, 55); + color1[2]= GETBITSHIGH(block_part1, 5, 47); + + // Diff color + diff[0]= GETBITSHIGH(block_part1, 3, 58); + diff[1]= GETBITSHIGH(block_part1, 3, 50); + diff[2]= GETBITSHIGH(block_part1, 3, 42); + + // Extend sign bit to entire byte. + diff[0] = (diff[0] << 5); + diff[1] = (diff[1] << 5); + diff[2] = (diff[2] << 5); + diff[0] = diff[0] >> 5; + diff[1] = diff[1] >> 5; + diff[2] = diff[2] >> 5; + + red = color1[0] + diff[0]; + green = color1[1] + diff[1]; + blue = color1[2] + diff[2]; + + if(red < 0 || red > 31) + { + unsigned int block59_part1, block59_part2; + unstuff59bits(block_part1, block_part2, block59_part1, block59_part2); + decompressBlockTHUMB59Tc(block59_part1, block59_part2, img, width, height, startx, starty, channels); + } + else if (green < 0 || green > 31) + { + unsigned int block58_part1, block58_part2; + unstuff58bits(block_part1, block_part2, block58_part1, block58_part2); + decompressBlockTHUMB58Hc(block58_part1, block58_part2, img, width, height, startx, starty, channels); + } + else if(blue < 0 || blue > 31) + { + unsigned int block57_part1, block57_part2; + + unstuff57bits(block_part1, block_part2, block57_part1, block57_part2); + decompressBlockPlanar57c(block57_part1, block57_part2, img, width, height, startx, starty, channels); + } + else + { + decompressBlockDiffFlipC(block_part1, block_part2, img, width, height, startx, starty, channels); + } + } + else + { + // We have diffbit = 0; + decompressBlockDiffFlipC(block_part1, block_part2, img, width, height, startx, starty, channels); + } +} +void decompressBlockETC2(unsigned int block_part1, unsigned int block_part2, uint8 *img, int width, int height, int startx, int starty) +{ + decompressBlockETC2c(block_part1, block_part2, img, width, height, startx, starty, 3); +} +// Decompress an ETC2 block with punchthrough alpha +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void decompressBlockDifferentialWithAlphaC(unsigned int block_part1, unsigned int block_part2, uint8* img, uint8* alpha, int width, int height, int startx, int starty, int channelsRGB) +{ + + uint8 avg_color[3], enc_color1[3], enc_color2[3]; + signed char diff[3]; + int table; + int index,shift; + int r,g,b; + int diffbit; + int flipbit; + int channelsA; + + if(channelsRGB == 3) + { + // We will decode the alpha data to a separate memory area. + channelsA = 1; + } + else + { + // We will decode the RGB data and the alpha data to the same memory area, + // interleaved as RGBA. + channelsA = 4; + alpha = &img[0+3]; + } + + //the diffbit now encodes whether or not the entire alpha channel is 255. + diffbit = (GETBITSHIGH(block_part1, 1, 33)); + flipbit = (GETBITSHIGH(block_part1, 1, 32)); + + // First decode left part of block. + enc_color1[0]= GETBITSHIGH(block_part1, 5, 63); + enc_color1[1]= GETBITSHIGH(block_part1, 5, 55); + enc_color1[2]= GETBITSHIGH(block_part1, 5, 47); + + // Expand from 5 to 8 bits + avg_color[0] = (enc_color1[0] <<3) | (enc_color1[0] >> 2); + avg_color[1] = (enc_color1[1] <<3) | (enc_color1[1] >> 2); + avg_color[2] = (enc_color1[2] <<3) | (enc_color1[2] >> 2); + + table = GETBITSHIGH(block_part1, 3, 39) << 1; + + unsigned int pixel_indices_MSB, pixel_indices_LSB; + + pixel_indices_MSB = GETBITS(block_part2, 16, 31); + pixel_indices_LSB = GETBITS(block_part2, 16, 15); + + if( (flipbit) == 0 ) + { + // We should not flip + shift = 0; + for(int x=startx; x> shift) & 1) << 1; + index |= ((pixel_indices_LSB >> shift) & 1); + shift++; + index=unscramble[index]; + + int mod = compressParams[table][index]; + if(diffbit==0&&(index==1||index==2)) + { + mod=0; + } + + r=RED_CHANNEL(img,width,x,y,channelsRGB) =CLAMP(0,avg_color[0]+mod,255); + g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=CLAMP(0,avg_color[1]+mod,255); + b=BLUE_CHANNEL(img,width,x,y,channelsRGB) =CLAMP(0,avg_color[2]+mod,255); + if(diffbit==0&&index==1) + { + alpha[(y*width+x)*channelsA]=0; + r=RED_CHANNEL(img,width,x,y,channelsRGB)=0; + g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=0; + b=BLUE_CHANNEL(img,width,x,y,channelsRGB)=0; + } + else + { + alpha[(y*width+x)*channelsA]=255; + } + + } + } + } + else + { + // We should flip + shift = 0; + for(int x=startx; x> shift) & 1) << 1; + index |= ((pixel_indices_LSB >> shift) & 1); + shift++; + index=unscramble[index]; + int mod = compressParams[table][index]; + if(diffbit==0&&(index==1||index==2)) + { + mod=0; + } + r=RED_CHANNEL(img,width,x,y,channelsRGB) =CLAMP(0,avg_color[0]+mod,255); + g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=CLAMP(0,avg_color[1]+mod,255); + b=BLUE_CHANNEL(img,width,x,y,channelsRGB) =CLAMP(0,avg_color[2]+mod,255); + if(diffbit==0&&index==1) + { + alpha[(y*width+x)*channelsA]=0; + r=RED_CHANNEL(img,width,x,y,channelsRGB)=0; + g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=0; + b=BLUE_CHANNEL(img,width,x,y,channelsRGB)=0; + } + else + { + alpha[(y*width+x)*channelsA]=255; + } + } + shift+=2; + } + } + // Now decode right part of block. + diff[0]= GETBITSHIGH(block_part1, 3, 58); + diff[1]= GETBITSHIGH(block_part1, 3, 50); + diff[2]= GETBITSHIGH(block_part1, 3, 42); + + // Extend sign bit to entire byte. + diff[0] = (diff[0] << 5); + diff[1] = (diff[1] << 5); + diff[2] = (diff[2] << 5); + diff[0] = diff[0] >> 5; + diff[1] = diff[1] >> 5; + diff[2] = diff[2] >> 5; + + // Calculate second color + enc_color2[0]= enc_color1[0] + diff[0]; + enc_color2[1]= enc_color1[1] + diff[1]; + enc_color2[2]= enc_color1[2] + diff[2]; + + // Expand from 5 to 8 bits + avg_color[0] = (enc_color2[0] <<3) | (enc_color2[0] >> 2); + avg_color[1] = (enc_color2[1] <<3) | (enc_color2[1] >> 2); + avg_color[2] = (enc_color2[2] <<3) | (enc_color2[2] >> 2); + + table = GETBITSHIGH(block_part1, 3, 36) << 1; + pixel_indices_MSB = GETBITS(block_part2, 16, 31); + pixel_indices_LSB = GETBITS(block_part2, 16, 15); + + if( (flipbit) == 0 ) + { + // We should not flip + shift=8; + for(int x=startx+2; x> shift) & 1) << 1; + index |= ((pixel_indices_LSB >> shift) & 1); + shift++; + index=unscramble[index]; + int mod = compressParams[table][index]; + if(diffbit==0&&(index==1||index==2)) + { + mod=0; + } + + r=RED_CHANNEL(img,width,x,y,channelsRGB) =CLAMP(0,avg_color[0]+mod,255); + g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=CLAMP(0,avg_color[1]+mod,255); + b=BLUE_CHANNEL(img,width,x,y,channelsRGB) =CLAMP(0,avg_color[2]+mod,255); + if(diffbit==0&&index==1) + { + alpha[(y*width+x)*channelsA]=0; + r=RED_CHANNEL(img,width,x,y,channelsRGB)=0; + g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=0; + b=BLUE_CHANNEL(img,width,x,y,channelsRGB)=0; + } + else + { + alpha[(y*width+x)*channelsA]=255; + } + } + } + } + else + { + // We should flip + shift=2; + for(int x=startx; x> shift) & 1) << 1; + index |= ((pixel_indices_LSB >> shift) & 1); + shift++; + index=unscramble[index]; + int mod = compressParams[table][index]; + if(diffbit==0&&(index==1||index==2)) + { + mod=0; + } + + r=RED_CHANNEL(img,width,x,y,channelsRGB) =CLAMP(0,avg_color[0]+mod,255); + g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=CLAMP(0,avg_color[1]+mod,255); + b=BLUE_CHANNEL(img,width,x,y,channelsRGB) =CLAMP(0,avg_color[2]+mod,255); + if(diffbit==0&&index==1) + { + alpha[(y*width+x)*channelsA]=0; + r=RED_CHANNEL(img,width,x,y,channelsRGB)=0; + g=GREEN_CHANNEL(img,width,x,y,channelsRGB)=0; + b=BLUE_CHANNEL(img,width,x,y,channelsRGB)=0; + } + else + { + alpha[(y*width+x)*channelsA]=255; + } + } + shift += 2; + } + } +} +void decompressBlockDifferentialWithAlpha(unsigned int block_part1, unsigned int block_part2, uint8* img, uint8* alpha, int width, int height, int startx, int starty) +{ + decompressBlockDifferentialWithAlphaC(block_part1, block_part2, img, alpha, width, height, startx, starty, 3); +} + + +// similar to regular decompression, but alpha channel is set to 0 if pixel index is 2, otherwise 255. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void decompressBlockTHUMB59TAlphaC(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alpha, int width, int height, int startx, int starty, int channelsRGB) +{ + + uint8 colorsRGB444[2][3]; + uint8 colors[2][3]; + uint8 paint_colors[4][3]; + uint8 distance; + uint8 block_mask[4][4]; + int channelsA; + + if(channelsRGB == 3) + { + // We will decode the alpha data to a separate memory area. + channelsA = 1; + } + else + { + // We will decode the RGB data and the alpha data to the same memory area, + // interleaved as RGBA. + channelsA = 4; + alpha = &img[0+3]; + } + + // First decode left part of block. + colorsRGB444[0][R]= GETBITSHIGH(block_part1, 4, 58); + colorsRGB444[0][G]= GETBITSHIGH(block_part1, 4, 54); + colorsRGB444[0][B]= GETBITSHIGH(block_part1, 4, 50); + + colorsRGB444[1][R]= GETBITSHIGH(block_part1, 4, 46); + colorsRGB444[1][G]= GETBITSHIGH(block_part1, 4, 42); + colorsRGB444[1][B]= GETBITSHIGH(block_part1, 4, 38); + + distance = GETBITSHIGH(block_part1, TABLE_BITS_59T, 34); + + // Extend the two colors to RGB888 + decompressColor(R_BITS59T, G_BITS59T, B_BITS59T, colorsRGB444, colors); + calculatePaintColors59T(distance, PATTERN_T, colors, paint_colors); + + // Choose one of the four paint colors for each texel + for (uint8 x = 0; x < BLOCKWIDTH; ++x) + { + for (uint8 y = 0; y < BLOCKHEIGHT; ++y) + { + //block_mask[x][y] = GETBITS(block_part2,2,31-(y*4+x)*2); + block_mask[x][y] = GETBITS(block_part2,1,(y+x*4)+16)<<1; + block_mask[x][y] |= GETBITS(block_part2,1,(y+x*4)); + img[channelsRGB*((starty+y)*width+startx+x)+R] = + CLAMP(0,paint_colors[block_mask[x][y]][R],255); // RED + img[channelsRGB*((starty+y)*width+startx+x)+G] = + CLAMP(0,paint_colors[block_mask[x][y]][G],255); // GREEN + img[channelsRGB*((starty+y)*width+startx+x)+B] = + CLAMP(0,paint_colors[block_mask[x][y]][B],255); // BLUE + if(block_mask[x][y]==2) + { + alpha[channelsA*(x+startx+(y+starty)*width)]=0; + img[channelsRGB*((starty+y)*width+startx+x)+R] =0; + img[channelsRGB*((starty+y)*width+startx+x)+G] =0; + img[channelsRGB*((starty+y)*width+startx+x)+B] =0; + } + else + alpha[channelsA*(x+startx+(y+starty)*width)]=255; + } + } +} +void decompressBlockTHUMB59TAlpha(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alpha, int width, int height, int startx, int starty) +{ + decompressBlockTHUMB59TAlphaC(block_part1, block_part2, img, alpha, width, height, startx, starty, 3); +} + + +// Decompress an H-mode block with alpha +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void decompressBlockTHUMB58HAlphaC(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alpha, int width, int height, int startx, int starty, int channelsRGB) +{ + unsigned int col0, col1; + uint8 colors[2][3]; + uint8 colorsRGB444[2][3]; + uint8 paint_colors[4][3]; + uint8 distance; + uint8 block_mask[4][4]; + int channelsA; + + if(channelsRGB == 3) + { + // We will decode the alpha data to a separate memory area. + channelsA = 1; + } + else + { + // We will decode the RGB data and the alpha data to the same memory area, + // interleaved as RGBA. + channelsA = 4; + alpha = &img[0+3]; + } + + // First decode left part of block. + colorsRGB444[0][R]= GETBITSHIGH(block_part1, 4, 57); + colorsRGB444[0][G]= GETBITSHIGH(block_part1, 4, 53); + colorsRGB444[0][B]= GETBITSHIGH(block_part1, 4, 49); + + colorsRGB444[1][R]= GETBITSHIGH(block_part1, 4, 45); + colorsRGB444[1][G]= GETBITSHIGH(block_part1, 4, 41); + colorsRGB444[1][B]= GETBITSHIGH(block_part1, 4, 37); + + distance = 0; + distance = (GETBITSHIGH(block_part1, 2, 33)) << 1; + + col0 = GETBITSHIGH(block_part1, 12, 57); + col1 = GETBITSHIGH(block_part1, 12, 45); + + if(col0 >= col1) + { + distance |= 1; + } + + // Extend the two colors to RGB888 + decompressColor(R_BITS58H, G_BITS58H, B_BITS58H, colorsRGB444, colors); + + calculatePaintColors58H(distance, PATTERN_H, colors, paint_colors); + + // Choose one of the four paint colors for each texel + for (uint8 x = 0; x < BLOCKWIDTH; ++x) + { + for (uint8 y = 0; y < BLOCKHEIGHT; ++y) + { + //block_mask[x][y] = GETBITS(block_part2,2,31-(y*4+x)*2); + block_mask[x][y] = GETBITS(block_part2,1,(y+x*4)+16)<<1; + block_mask[x][y] |= GETBITS(block_part2,1,(y+x*4)); + img[channelsRGB*((starty+y)*width+startx+x)+R] = + CLAMP(0,paint_colors[block_mask[x][y]][R],255); // RED + img[channelsRGB*((starty+y)*width+startx+x)+G] = + CLAMP(0,paint_colors[block_mask[x][y]][G],255); // GREEN + img[channelsRGB*((starty+y)*width+startx+x)+B] = + CLAMP(0,paint_colors[block_mask[x][y]][B],255); // BLUE + + if(block_mask[x][y]==2) + { + alpha[channelsA*(x+startx+(y+starty)*width)]=0; + img[channelsRGB*((starty+y)*width+startx+x)+R] =0; + img[channelsRGB*((starty+y)*width+startx+x)+G] =0; + img[channelsRGB*((starty+y)*width+startx+x)+B] =0; + } + else + alpha[channelsA*(x+startx+(y+starty)*width)]=255; + } + } +} +void decompressBlockTHUMB58HAlpha(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alpha, int width, int height, int startx, int starty) +{ + decompressBlockTHUMB58HAlphaC(block_part1, block_part2, img, alpha, width, height, startx, starty, 3); +} +// Decompression function for ETC2_RGBA1 format. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void decompressBlockETC21BitAlphaC(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alphaimg, int width, int height, int startx, int starty, int channelsRGB) +{ + int diffbit; + signed char color1[3]; + signed char diff[3]; + signed char red, green, blue; + int channelsA; + + if(channelsRGB == 3) + { + // We will decode the alpha data to a separate memory area. + channelsA = 1; + } + else + { + // We will decode the RGB data and the alpha data to the same memory area, + // interleaved as RGBA. + channelsA = 4; + alphaimg = &img[0+3]; + } + + diffbit = (GETBITSHIGH(block_part1, 1, 33)); + + if( diffbit ) + { + // We have diffbit = 1, meaning no transparent pixels. regular decompression. + + // Base color + color1[0]= GETBITSHIGH(block_part1, 5, 63); + color1[1]= GETBITSHIGH(block_part1, 5, 55); + color1[2]= GETBITSHIGH(block_part1, 5, 47); + + // Diff color + diff[0]= GETBITSHIGH(block_part1, 3, 58); + diff[1]= GETBITSHIGH(block_part1, 3, 50); + diff[2]= GETBITSHIGH(block_part1, 3, 42); + + // Extend sign bit to entire byte. + diff[0] = (diff[0] << 5); + diff[1] = (diff[1] << 5); + diff[2] = (diff[2] << 5); + diff[0] = diff[0] >> 5; + diff[1] = diff[1] >> 5; + diff[2] = diff[2] >> 5; + + red = color1[0] + diff[0]; + green = color1[1] + diff[1]; + blue = color1[2] + diff[2]; + + if(red < 0 || red > 31) + { + unsigned int block59_part1, block59_part2; + unstuff59bits(block_part1, block_part2, block59_part1, block59_part2); + decompressBlockTHUMB59Tc(block59_part1, block59_part2, img, width, height, startx, starty, channelsRGB); + } + else if (green < 0 || green > 31) + { + unsigned int block58_part1, block58_part2; + unstuff58bits(block_part1, block_part2, block58_part1, block58_part2); + decompressBlockTHUMB58Hc(block58_part1, block58_part2, img, width, height, startx, starty, channelsRGB); + } + else if(blue < 0 || blue > 31) + { + unsigned int block57_part1, block57_part2; + + unstuff57bits(block_part1, block_part2, block57_part1, block57_part2); + decompressBlockPlanar57c(block57_part1, block57_part2, img, width, height, startx, starty, channelsRGB); + } + else + { + decompressBlockDifferentialWithAlphaC(block_part1, block_part2, img, alphaimg, width, height, startx, starty, channelsRGB); + } + for(int x=startx; x> 5; + diff[1] = diff[1] >> 5; + diff[2] = diff[2] >> 5; + + red = color1[0] + diff[0]; + green = color1[1] + diff[1]; + blue = color1[2] + diff[2]; + if(red < 0 || red > 31) + { + unsigned int block59_part1, block59_part2; + unstuff59bits(block_part1, block_part2, block59_part1, block59_part2); + decompressBlockTHUMB59TAlphaC(block59_part1, block59_part2, img, alphaimg, width, height, startx, starty, channelsRGB); + } + else if(green < 0 || green > 31) + { + unsigned int block58_part1, block58_part2; + unstuff58bits(block_part1, block_part2, block58_part1, block58_part2); + decompressBlockTHUMB58HAlphaC(block58_part1, block58_part2, img, alphaimg, width, height, startx, starty, channelsRGB); + } + else if(blue < 0 || blue > 31) + { + unsigned int block57_part1, block57_part2; + + unstuff57bits(block_part1, block_part2, block57_part1, block57_part2); + decompressBlockPlanar57c(block57_part1, block57_part2, img, width, height, startx, starty, channelsRGB); + for(int x=startx; xtopos) + return ((1<>(frompos-topos); + return ((1<255) + val=255; + return val; +} + +// Decodes tha alpha component in a block coded with GL_COMPRESSED_RGBA8_ETC2_EAC. +// Note that this decoding is slightly different from that of GL_COMPRESSED_R11_EAC. +// However, a hardware decoder can share gates between the two formats as explained +// in the specification under GL_COMPRESSED_R11_EAC. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void decompressBlockAlphaC(uint8* data, uint8* img, int width, int height, int ix, int iy, int channels) +{ + int alpha = data[0]; + int table = data[1]; + + int bit=0; + int byte=2; + //extract an alpha value for each pixel. + for(int x=0; x<4; x++) + { + for(int y=0; y<4; y++) + { + //Extract table index + int index=0; + for(int bitpos=0; bitpos<3; bitpos++) + { + index|=getbit(data[byte],7-bit,2-bitpos); + bit++; + if(bit>7) + { + bit=0; + byte++; + } + } + img[(ix+x+(iy+y)*width)*channels]=clamp(alpha +alphaTable[table][index]); + } + } +} +void decompressBlockAlpha(uint8* data, uint8* img, int width, int height, int ix, int iy) +{ + decompressBlockAlphaC(data, img, width, height, ix, iy, 1); +} + +// Does decompression and then immediately converts from 11 bit signed to a 16-bit format. +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +int16 get16bits11signed(int base, int table, int mul, int index) +{ + int elevenbase = base-128; + if(elevenbase==-128) + elevenbase=-127; + elevenbase*=8; + //i want the positive value here + int tabVal = -alphaBase[table][3-index%4]-1; + //and the sign, please + int sign = 1-(index/4); + + if(sign) + tabVal=tabVal+1; + int elevenTabVal = tabVal*8; + + if(mul!=0) + elevenTabVal*=mul; + else + elevenTabVal/=8; + + if(sign) + elevenTabVal=-elevenTabVal; + + //calculate sum + int elevenbits = elevenbase+elevenTabVal; + + //clamp.. + if(elevenbits>=1024) + elevenbits=1023; + else if(elevenbits<-1023) + elevenbits=-1023; + //this is the value we would actually output.. + //but there aren't any good 11-bit file or uncompressed GL formats + //so we extend to 15 bits signed. + sign = elevenbits<0; + elevenbits=abs(elevenbits); + int16 fifteenbits = (elevenbits<<5)+(elevenbits>>5); + int16 sixteenbits=fifteenbits; + + if(sign) + sixteenbits=-sixteenbits; + + return sixteenbits; +} + +// Does decompression and then immediately converts from 11 bit signed to a 16-bit format +// Calculates the 11 bit value represented by base, table, mul and index, and extends it to 16 bits. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +uint16 get16bits11bits(int base, int table, int mul, int index) +{ + int elevenbase = base*8+4; + + //i want the positive value here + int tabVal = -alphaBase[table][3-index%4]-1; + //and the sign, please + int sign = 1-(index/4); + + if(sign) + tabVal=tabVal+1; + int elevenTabVal = tabVal*8; + + if(mul!=0) + elevenTabVal*=mul; + else + elevenTabVal/=8; + + if(sign) + elevenTabVal=-elevenTabVal; + + //calculate sum + int elevenbits = elevenbase+elevenTabVal; + + //clamp.. + if(elevenbits>=256*8) + elevenbits=256*8-1; + else if(elevenbits<0) + elevenbits=0; + //elevenbits now contains the 11 bit alpha value as defined in the spec. + + //extend to 16 bits before returning, since we don't have any good 11-bit file formats. + uint16 sixteenbits = (elevenbits<<5)+(elevenbits>>6); + + return sixteenbits; +} + +// Decompresses a block using one of the GL_COMPRESSED_R11_EAC or GL_COMPRESSED_SIGNED_R11_EAC-formats +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void decompressBlockAlpha16bitC(uint8* data, uint8* img, int width, int height, int ix, int iy, int channels) +{ + int alpha = data[0]; + int table = data[1]; + + if(formatSigned) + { + //if we have a signed format, the base value is given as a signed byte. We convert it to (0-255) here, + //so more code can be shared with the unsigned mode. + alpha = *((signed char*)(&data[0])); + alpha = alpha+128; + } + + int bit=0; + int byte=2; + //extract an alpha value for each pixel. + for(int x=0; x<4; x++) + { + for(int y=0; y<4; y++) + { + //Extract table index + int index=0; + for(int bitpos=0; bitpos<3; bitpos++) + { + index|=getbit(data[byte],7-bit,2-bitpos); + bit++; + if(bit>7) + { + bit=0; + byte++; + } + } + int windex = channels*(2*(ix+x+(iy+y)*width)); +#if !PGMOUT + if(formatSigned) + { + *(int16 *)&img[windex] = get16bits11signed(alpha,(table%16),(table/16),index); + } + else + { + *(uint16 *)&img[windex] = get16bits11bits(alpha,(table%16),(table/16),index); + } +#else + //make data compatible with the .pgm format. See the comment in compressBlockAlpha16() for details. + uint16 uSixteen; + if (formatSigned) + { + //the pgm-format only allows unsigned images, + //so we add 2^15 to get a 16-bit value. + uSixteen = get16bits11signed(alpha,(table%16),(table/16),index) + 256*128; + } + else + { + uSixteen = get16bits11bits(alpha,(table%16),(table/16),index); + } + //byte swap for pgm + img[windex] = uSixteen/256; + img[windex+1] = uSixteen%256; +#endif + + } + } +} + +void decompressBlockAlpha16bit(uint8* data, uint8* img, int width, int height, int ix, int iy) +{ + decompressBlockAlpha16bitC(data, img, width, height, ix, iy, 1); +} diff --git a/extern/etcpack/etcpack.cxx b/extern/etcpack/etcpack.cxx new file mode 100755 index 0000000..6448b67 --- /dev/null +++ b/extern/etcpack/etcpack.cxx @@ -0,0 +1,16091 @@ +//// etcpack v2.74 +//// +//// NO WARRANTY +//// +//// BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE THE PROGRAM IS PROVIDED +//// "AS IS". ERICSSON MAKES NO REPRESENTATIONS OF ANY KIND, EXTENDS NO +//// WARRANTIES OR CONDITIONS OF ANY KIND; EITHER EXPRESS, IMPLIED OR +//// STATUTORY; INCLUDING, BUT NOT LIMITED TO, EXPRESS, IMPLIED OR +//// STATUTORY WARRANTIES OR CONDITIONS OF TITLE, MERCHANTABILITY, +//// SATISFACTORY QUALITY, SUITABILITY AND FITNESS FOR A PARTICULAR +//// PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +//// PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME +//// THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. ERICSSON +//// MAKES NO WARRANTY THAT THE MANUFACTURE, SALE, OFFERING FOR SALE, +//// DISTRIBUTION, LEASE, USE OR IMPORTATION UNDER THE LICENSE WILL BE FREE +//// FROM INFRINGEMENT OF PATENTS, COPYRIGHTS OR OTHER INTELLECTUAL +//// PROPERTY RIGHTS OF OTHERS, AND THE VALIDITY OF THE LICENSE IS SUBJECT +//// TO YOUR SOLE RESPONSIBILITY TO MAKE SUCH DETERMINATION AND ACQUIRE +//// SUCH LICENSES AS MAY BE NECESSARY WITH RESPECT TO PATENTS, COPYRIGHT +//// AND OTHER INTELLECTUAL PROPERTY OF THIRD PARTIES. +//// +//// FOR THE AVOIDANCE OF DOUBT THE PROGRAM (I) IS NOT LICENSED FOR; (II) +//// IS NOT DESIGNED FOR OR INTENDED FOR; AND (III) MAY NOT BE USED FOR; +//// ANY MISSION CRITICAL APPLICATIONS SUCH AS, BUT NOT LIMITED TO +//// OPERATION OF NUCLEAR OR HEALTHCARE COMPUTER SYSTEMS AND/OR NETWORKS, +//// AIRCRAFT OR TRAIN CONTROL AND/OR COMMUNICATION SYSTEMS OR ANY OTHER +//// COMPUTER SYSTEMS AND/OR NETWORKS OR CONTROL AND/OR COMMUNICATION +//// SYSTEMS ALL IN WHICH CASE THE FAILURE OF THE PROGRAM COULD LEAD TO +//// DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL, MATERIAL OR ENVIRONMENTAL +//// DAMAGE. YOUR RIGHTS UNDER THIS LICENSE WILL TERMINATE AUTOMATICALLY +//// AND IMMEDIATELY WITHOUT NOTICE IF YOU FAIL TO COMPLY WITH THIS +//// PARAGRAPH. +//// +//// IN NO EVENT WILL ERICSSON, BE LIABLE FOR ANY DAMAGES WHATSOEVER, +//// INCLUDING BUT NOT LIMITED TO PERSONAL INJURY, ANY GENERAL, SPECIAL, +//// INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN +//// CONNECTION WITH THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +//// NOT LIMITED TO LOSS OF PROFITS, BUSINESS INTERUPTIONS, OR ANY OTHER +//// COMMERCIAL DAMAGES OR LOSSES, LOSS OF DATA OR DATA BEING RENDERED +//// INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF +//// THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS) REGARDLESS OF THE +//// THEORY OF LIABILITY (CONTRACT, TORT OR OTHERWISE), EVEN IF SUCH HOLDER +//// OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +//// +//// (C) Ericsson AB 2005-2013. All Rights Reserved. +//// + +#include +#include +#include +#include +#include +#include +#include "image.h" + +// Typedefs +typedef unsigned char uint8; +typedef unsigned short uint16; +typedef short int16; + +// Functions needed for decrompession ---- in etcdec.cxx +void read_big_endian_2byte_word(unsigned short *blockadr, FILE *f); +void read_big_endian_4byte_word(unsigned int *blockadr, FILE *f); +void unstuff57bits(unsigned int planar_word1, unsigned int planar_word2, unsigned int &planar57_word1, unsigned int &planar57_word2); +void unstuff59bits(unsigned int thumbT_word1, unsigned int thumbT_word2, unsigned int &thumbT59_word1, unsigned int &thumbT59_word2); +void unstuff58bits(unsigned int thumbH_word1, unsigned int thumbH_word2, unsigned int &thumbH58_word1, unsigned int &thumbH58_word2); +void decompressColor(int R_B, int G_B, int B_B, uint8 (colors_RGB444)[2][3], uint8 (colors)[2][3]); +void calculatePaintColors59T(uint8 d, uint8 p, uint8 (colors)[2][3], uint8 (possible_colors)[4][3]); +void calculatePaintColors58H(uint8 d, uint8 p, uint8 (colors)[2][3], uint8 (possible_colors)[4][3]); +void decompressBlockTHUMB59T(unsigned int block_part1, unsigned int block_part2, uint8 *img,int width,int height,int startx,int starty); +void decompressBlockTHUMB58H(unsigned int block_part1, unsigned int block_part2, uint8 *img,int width,int height,int startx,int starty); +void decompressBlockPlanar57(unsigned int compressed57_1, unsigned int compressed57_2, uint8 *img,int width,int height,int startx,int starty); +void decompressBlockDiffFlip(unsigned int block_part1, unsigned int block_part2, uint8 *img,int width,int height,int startx,int starty); +void decompressBlockETC2(unsigned int block_part1, unsigned int block_part2, uint8 *img,int width,int height,int startx,int starty); +void decompressBlockDifferentialWithAlpha(unsigned int block_part1,unsigned int block_part2, uint8* img, uint8* alpha, int width, int height, int startx, int starty); +void decompressBlockETC21BitAlpha(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alphaimg, int width,int height,int startx,int starty); +void decompressBlockTHUMB58HAlpha(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alpha,int width,int height,int startx,int starty); +void decompressBlockTHUMB59TAlpha(unsigned int block_part1, unsigned int block_part2, uint8 *img, uint8* alpha,int width,int height,int startx,int starty); +uint8 getbit(uint8 input, int frompos, int topos); +int clamp(int val); +void decompressBlockAlpha(uint8* data,uint8* img,int width,int height,int ix,int iy); +uint16 get16bits11bits(int base, int table, int mul, int index); +void decompressBlockAlpha16bit(uint8* data,uint8* img,int width,int height,int ix,int iy); +int16 get16bits11signed(int base, int table, int mul, int index); +void setupAlphaTable(); + + +// This source code is quite long. You can make it shorter by not including the +// code doing the exhaustive code. Then the -slow modes will not work, but the +// code will be approximately half the number of lines of code. +// Then the lines between "exhaustive code starts here" and "exhaustive code ends here" +// can then be removed. +#define EXHAUSTIVE_CODE_ACTIVE 1 + +// Remove warnings for unsafe functions such as strcpy +#pragma warning(disable : 4996) +// Remove warnings for conversions between different time variables +#pragma warning(disable : 4244) +// Remove warnings for negative or too big shifts +//#pragma warning(disable : 4293) + +#define CLAMP(ll,x,ul) (((x)<(ll)) ? (ll) : (((x)>(ul)) ? (ul) : (x))) +// The below code works as CLAMP(0, x, 255) if x < 255 +#define CLAMP_LEFT_ZERO(x) ((~(((int)(x))>>31))&(x)) +// The below code works as CLAMP(0, x, 255) if x is in [0,511] +#define CLAMP_RIGHT_255(x) (((( ((((int)(x))<<23)>>31) ))|(x))&0x000000ff) + +#define SQUARE(x) ((x)*(x)) +#define JAS_ROUND(x) (((x) < 0.0 ) ? ((int)((x)-0.5)) : ((int)((x)+0.5))) +#define JAS_MIN(a,b) ((a) < (b) ? (a) : (b)) +#define JAS_MAX(a,b) ((a) > (b) ? (a) : (b)) + +// The error metric Wr Wg Wb should be definied so that Wr^2 + Wg^2 + Wb^2 = 1. +// Hence it is easier to first define the squared values and derive the weights +// as their square-roots. + +#define PERCEPTUAL_WEIGHT_R_SQUARED 0.299 +#define PERCEPTUAL_WEIGHT_G_SQUARED 0.587 +#define PERCEPTUAL_WEIGHT_B_SQUARED 0.114 + +#define PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000 299 +#define PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000 587 +#define PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000 114 + +#define RED(img,width,x,y) img[3*(y*width+x)+0] +#define GREEN(img,width,x,y) img[3*(y*width+x)+1] +#define BLUE(img,width,x,y) img[3*(y*width+x)+2] + +#define SHIFT(size,startpos) ((startpos)-(size)+1) +#define MASK(size, startpos) (((2<<(size-1))-1) << SHIFT(size,startpos)) +#define PUTBITS( dest, data, size, startpos) dest = ((dest & ~MASK(size, startpos)) | ((data << SHIFT(size, startpos)) & MASK(size,startpos))) +#define SHIFTHIGH(size, startpos) (((startpos)-32)-(size)+1) +#define MASKHIGH(size, startpos) (((1<<(size))-1) << SHIFTHIGH(size,startpos)) +#define PUTBITSHIGH(dest, data, size, startpos) dest = ((dest & ~MASKHIGH(size, startpos)) | ((data << SHIFTHIGH(size, startpos)) & MASKHIGH(size,startpos))) +#define GETBITS(source, size, startpos) (( (source) >> ((startpos)-(size)+1) ) & ((1<<(size)) -1)) +#define GETBITSHIGH(source, size, startpos) (( (source) >> (((startpos)-32)-(size)+1) ) & ((1<<(size)) -1)) + +// Thumb macros and definitions +#define R_BITS59T 4 +#define G_BITS59T 4 +#define B_BITS59T 4 +#define R_BITS58H 4 +#define G_BITS58H 4 +#define B_BITS58H 4 +#define MAXIMUM_ERROR (255*255*16*1000) +#define R 0 +#define G 1 +#define B 2 +#define BLOCKHEIGHT 4 +#define BLOCKWIDTH 4 +#define BINPOW(power) (1<<(power)) +//#define RADIUS 2 +#define TABLE_BITS_59T 3 +#define TABLE_BITS_58H 3 + +// Global tables +static uint8 table59T[8] = {3,6,11,16,23,32,41,64}; // 3-bit table for the 59 bit T-mode +static uint8 table58H[8] = {3,6,11,16,23,32,41,64}; // 3-bit table for the 58 bit H-mode +uint8 weight[3] = {1,1,1}; // Color weight + +// Enums +static enum{PATTERN_H = 0, + PATTERN_T = 1}; + +static enum{MODE_ETC1, MODE_THUMB_T, MODE_THUMB_H, MODE_PLANAR}; +// The ETC2 package of codecs includes the following codecs: +// +// codec enum +// -------------------------------------------------------- +// GL_COMPRESSED_R11_EAC 0x9270 +// GL_COMPRESSED_SIGNED_R11_EAC 0x9271 +// GL_COMPRESSED_RG11_EAC 0x9272 +// GL_COMPRESSED_SIGNED_RG11_EAC 0x9273 +// GL_COMPRESSED_RGB8_ETC2 0x9274 +// GL_COMPRESSED_SRGB8_ETC2 0x9275 +// GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9276 +// GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9277 +// GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +// GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC 0x9279 +// +// The older codec ETC1 is not included in the package +// GL_ETC1_RGB8_OES 0x8d64 +// but since ETC2 is backwards compatible an ETC1 texture can +// be decoded using the RGB8_ETC2 enum (0x9274) +// +// In a PKM-file, the codecs are stored using the following identifiers +// +// identifier value codec +// -------------------------------------------------------------------- +// ETC1_RGB_NO_MIPMAPS 0 GL_ETC1_RGB8_OES +// ETC2PACKAGE_RGB_NO_MIPMAPS 1 GL_COMPRESSED_RGB8_ETC2 +// ETC2PACKAGE_RGBA_NO_MIPMAPS_OLD 2, not used - +// ETC2PACKAGE_RGBA_NO_MIPMAPS 3 GL_COMPRESSED_RGBA8_ETC2_EAC +// ETC2PACKAGE_RGBA1_NO_MIPMAPS 4 GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 +// ETC2PACKAGE_R_NO_MIPMAPS 5 GL_COMPRESSED_R11_EAC +// ETC2PACKAGE_RG_NO_MIPMAPS 6 GL_COMPRESSED_RG11_EAC +// ETC2PACKAGE_R_SIGNED_NO_MIPMAPS 7 GL_COMPRESSED_SIGNED_R11_EAC +// ETC2PACKAGE_RG_SIGNED_NO_MIPMAPS 8 GL_COMPRESSED_SIGNED_RG11_EAC +// +// In the code, the identifiers are not always used strictly. For instance, the +// identifier ETC2PACKAGE_R_NO_MIPMAPS is sometimes used for both the unsigned +// (GL_COMPRESSED_R11_EAC) and signed (GL_COMPRESSED_SIGNED_R11_EAC) version of +// the codec. +// +static enum{ETC1_RGB_NO_MIPMAPS,ETC2PACKAGE_RGB_NO_MIPMAPS,ETC2PACKAGE_RGBA_NO_MIPMAPS_OLD,ETC2PACKAGE_RGBA_NO_MIPMAPS,ETC2PACKAGE_RGBA1_NO_MIPMAPS,ETC2PACKAGE_R_NO_MIPMAPS,ETC2PACKAGE_RG_NO_MIPMAPS,ETC2PACKAGE_R_SIGNED_NO_MIPMAPS,ETC2PACKAGE_RG_SIGNED_NO_MIPMAPS,ETC2PACKAGE_sRGB_NO_MIPMAPS,ETC2PACKAGE_sRGBA_NO_MIPMAPS,ETC2PACKAGE_sRGBA1_NO_MIPMAPS}; +static enum {MODE_COMPRESS, MODE_UNCOMPRESS, MODE_PSNR}; +static enum {SPEED_SLOW, SPEED_FAST, SPEED_MEDIUM}; +static enum {METRIC_PERCEPTUAL, METRIC_NONPERCEPTUAL}; +static enum {CODEC_ETC, CODEC_ETC2}; + +int mode = MODE_COMPRESS; +int speed = SPEED_FAST; +int metric = METRIC_PERCEPTUAL; +int codec = CODEC_ETC2; +int format = ETC2PACKAGE_RGB_NO_MIPMAPS; +int verbose = true; +extern int formatSigned; +int ktxFile=0; +bool first_time_message = true; + +static int scramble[4] = {3, 2, 0, 1}; +static int unscramble[4] = {2, 3, 1, 0}; + +typedef struct KTX_header_t +{ + uint8 identifier[12]; + unsigned int endianness; + unsigned int glType; + unsigned int glTypeSize; + unsigned int glFormat; + unsigned int glInternalFormat; + unsigned int glBaseInternalFormat; + unsigned int pixelWidth; + unsigned int pixelHeight; + unsigned int pixelDepth; + unsigned int numberOfArrayElements; + unsigned int numberOfFaces; + unsigned int numberOfMipmapLevels; + unsigned int bytesOfKeyValueData; +} +KTX_header; +#define KTX_IDENTIFIER_REF { 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A } + +#define KTX_ENDIAN_REF (0x04030201) +#define KTX_ENDIAN_REF_REV (0x01020304) + +static enum {GL_R=0x1903,GL_RG=0x8227,GL_RGB=0x1907,GL_RGBA=0x1908}; +#define GL_SRGB 0x8C40 +#define GL_SRGB8 0x8C41 +#define GL_SRGB8_ALPHA8 0x8C43 +#define GL_ETC1_RGB8_OES 0x8d64 +#define GL_COMPRESSED_R11_EAC 0x9270 +#define GL_COMPRESSED_SIGNED_R11_EAC 0x9271 +#define GL_COMPRESSED_RG11_EAC 0x9272 +#define GL_COMPRESSED_SIGNED_RG11_EAC 0x9273 +#define GL_COMPRESSED_RGB8_ETC2 0x9274 +#define GL_COMPRESSED_SRGB8_ETC2 0x9275 +#define GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9276 +#define GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 0x9277 +#define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278 +#define GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC 0x9279 + + +int ktx_identifier[] = KTX_IDENTIFIER_REF; + + +//converts indices from |a0|a1|e0|e1|i0|i1|m0|m1|b0|b1|f0|f1|j0|j1|n0|n1|c0|c1|g0|g1|k0|k1|o0|o1|d0|d1|h0|h1|l0|l1|p0|p1| previously used by T- and H-modes +// into |p0|o0|n0|m0|l0|k0|j0|i0|h0|g0|f0|e0|d0|c0|b0|a0|p1|o1|n1|m1|l1|k1|j1|i1|h1|g1|f1|e1|d1|c1|b1|a1| which should be used for all modes. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +int indexConversion(int pixelIndices) +{ + int correctIndices = 0; + int LSB[4][4]; + int MSB[4][4]; + int shift=0; + for(int y=3; y>=0; y--) + { + for(int x=3; x>=0; x--) + { + LSB[x][y] = (pixelIndices>>shift)&1; + shift++; + MSB[x][y] = (pixelIndices>>shift)&1; + shift++; + } + } + shift=0; + for(int x=0; x<4; x++) + { + for(int y=0; y<4; y++) + { + correctIndices|=(LSB[x][y]<=0) // find file name extension + { + if(src[q]=='.') break; + q--; + } + if(q<0) + return -1; + else + return q; +} + +// Read source file. Does conversion if file format is not .ppm. +// Will expand file to be divisible by four in the x- and y- dimension. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +bool readSrcFile(char *filename,uint8 *&img,int &width,int &height, int &expandedwidth, int &expandedheight) +{ + int w1,h1; + int wdiv4, hdiv4; + char str[255]; + + + // Delete temp file if it exists. + if(fileExist("tmp.ppm")) + { + sprintf(str, "del tmp.ppm\n"); + system(str); + } + + int q = find_pos_of_extension(filename); + if(!strcmp(&filename[q],".ppm")) + { + // Already a .ppm file. Just copy. + sprintf(str,"copy %s tmp.ppm \n", filename); + printf("Copying source file to tmp.ppm\n", filename); + } + else + { + // Converting from other format to .ppm + // + // Use your favorite command line image converter program, + // for instance Image Magick. Just make sure the syntax can + // be written as below: + // + // C:\imconv source.jpg dest.ppm + // + sprintf(str,"imconv %s tmp.ppm\n", filename); + printf("Converting source file from %s to .ppm\n", filename); + } + // Execute system call + system(str); + + int bitrate=8; + if(format==ETC2PACKAGE_RG_NO_MIPMAPS) + bitrate=16; + if(fReadPPM("tmp.ppm",w1,h1,img,bitrate)) + { + width=w1; + height=h1; + system("del tmp.ppm"); + + // Width must be divisible by 4 and height must be + // divisible by 4. Otherwise, we will expand the image + + wdiv4 = width / 4; + hdiv4 = height / 4; + + expandedwidth = width; + expandedheight = height; + + if( !(wdiv4 * 4 == width) ) + { + printf(" Width = %d is not divisible by four... ", width); + printf(" expanding image in x-dir... "); + if(expandToWidthDivByFour(img, width, height, expandedwidth, expandedheight,bitrate)) + { + printf("OK.\n"); + } + else + { + printf("\n Error: could not expand image\n"); + return false; + } + } + if( !(hdiv4 * 4 == height)) + { + printf(" Height = %d is not divisible by four... ", height); + printf(" expanding image in y-dir..."); + if(expandToHeightDivByFour(img, expandedwidth, height, expandedwidth, expandedheight,bitrate)) + { + printf("OK.\n"); + } + else + { + printf("\n Error: could not expand image\n"); + return false; + } + } + if(!(expandedwidth == width && expandedheight == height)) + printf("Active pixels: %dx%d. Expanded image: %dx%d\n",width,height,expandedwidth,expandedheight); + return true; + } + else + { + printf("Could not read tmp.ppm file\n"); + exit(1); + } + return false; + +} + +// Reads a file without expanding it to be divisible by 4. +// Is used when doing PSNR calculation between two files. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +bool readSrcFileNoExpand(char *filename,uint8 *&img,int &width,int &height) +{ + int w1,h1; + char str[255]; + + + // Delete temp file if it exists. + if(fileExist("tmp.ppm")) + { + sprintf(str, "del tmp.ppm\n"); + system(str); + } + + + int q = find_pos_of_extension(filename); + if(!strcmp(&filename[q],".ppm")) + { + // Already a .ppm file. Just copy. + sprintf(str,"copy %s tmp.ppm \n", filename); + printf("Copying source file to tmp.ppm\n", filename); + } + else + { + // Converting from other format to .ppm + // + // Use your favorite command line image converter program, + // for instance Image Magick. Just make sure the syntax can + // be written as below: + // + // C:\imconv source.jpg dest.ppm + // + sprintf(str,"imconv %s tmp.ppm\n", filename); +// printf("Converting source file from %s to .ppm\n", filename); + } + // Execute system call + system(str); + + if(fReadPPM("tmp.ppm",w1,h1,img,8)) + { + width=w1; + height=h1; + system("del tmp.ppm"); + + return true; + } + return false; +} + +// Parses the arguments from the command line. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void readArguments(int argc,char *argv[],char* src,char *dst) +{ + int q; + + //new code!! do this in a more nicer way! + bool srcfound=false,dstfound=false; + for(int i=1; i> 1), 1, i); + PUTBITS( pixel_indices_LSB, (pixel_indices & 1) , 1, i); + + i++; + + // In order to simplify hardware, the table {-12, -4, 4, 12} is indexed {11, 10, 00, 01} + // so that first bit is sign bit and the other bit is size bit (4 or 12). + // This means that we have to scramble the bits before storing them. + sum_error+=min_error; + } + } + + *pixel_indices_MSBp = pixel_indices_MSB; + *pixel_indices_LSBp = pixel_indices_LSB; + return sum_error; +} + +#define MAXERR1000 1000*255*255*16 + +// Finds all pixel indices for a 2x4 block using perceptual weighting of error. +// Done using fixed poinit arithmetics where weights are multiplied by 1000. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int compressBlockWithTable2x4percep1000(uint8 *img,int width,int height,int startx,int starty,uint8 *avg_color,int table,unsigned int *pixel_indices_MSBp, unsigned int *pixel_indices_LSBp) +{ + uint8 orig[3],approx[3]; + unsigned int pixel_indices_MSB=0, pixel_indices_LSB=0, pixel_indices = 0; + unsigned int sum_error=0; + int q, i; + + i = 0; + for(int x=startx; x> 1), 1, i); + PUTBITS( pixel_indices_LSB, (pixel_indices & 1) , 1, i); + + i++; + + // In order to simplify hardware, the table {-12, -4, 4, 12} is indexed {11, 10, 00, 01} + // so that first bit is sign bit and the other bit is size bit (4 or 12). + // This means that we have to scramble the bits before storing them. + + + sum_error+=min_error; + } + + } + + *pixel_indices_MSBp = pixel_indices_MSB; + *pixel_indices_LSBp = pixel_indices_LSB; + + return sum_error; +} + +// Finds all pixel indices for a 2x4 block using perceptual weighting of error. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +float compressBlockWithTable2x4percep(uint8 *img,int width,int height,int startx,int starty,uint8 *avg_color,int table,unsigned int *pixel_indices_MSBp, unsigned int *pixel_indices_LSBp) +{ + uint8 orig[3],approx[3]; + unsigned int pixel_indices_MSB=0, pixel_indices_LSB=0, pixel_indices = 0; + float sum_error=0; + int q, i; + + double wR2 = PERCEPTUAL_WEIGHT_R_SQUARED; + double wG2 = PERCEPTUAL_WEIGHT_G_SQUARED; + double wB2 = PERCEPTUAL_WEIGHT_B_SQUARED; + + i = 0; + for(int x=startx; x> 1), 1, i); + PUTBITS( pixel_indices_LSB, (pixel_indices & 1) , 1, i); + + i++; + + // In order to simplify hardware, the table {-12, -4, 4, 12} is indexed {11, 10, 00, 01} + // so that first bit is sign bit and the other bit is size bit (4 or 12). + // This means that we have to scramble the bits before storing them. + + sum_error+=min_error; + } + } + + *pixel_indices_MSBp = pixel_indices_MSB; + *pixel_indices_LSBp = pixel_indices_LSB; + + return sum_error; +} + +// Finds all pixel indices for a 4x2 block. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +int compressBlockWithTable4x2(uint8 *img,int width,int height,int startx,int starty,uint8 *avg_color,int table,unsigned int *pixel_indices_MSBp, unsigned int *pixel_indices_LSBp) +{ + uint8 orig[3],approx[3]; + unsigned int pixel_indices_MSB=0, pixel_indices_LSB=0, pixel_indices = 0; + int sum_error=0; + int q; + int i; + + i = 0; + for(int x=startx; x> 1), 1, i); + PUTBITS( pixel_indices_LSB, (pixel_indices & 1) , 1, i); + i++; + + // In order to simplify hardware, the table {-12, -4, 4, 12} is indexed {11, 10, 00, 01} + // so that first bit is sign bit and the other bit is size bit (4 or 12). + // This means that we have to scramble the bits before storing them. + + sum_error+=min_error; + } + i+=2; + } + + *pixel_indices_MSBp = pixel_indices_MSB; + *pixel_indices_LSBp = pixel_indices_LSB; + + return sum_error; +} + +// Finds all pixel indices for a 4x2 block using perceptual weighting of error. +// Done using fixed point arithmetics where 1000 corresponds to 1.0. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int compressBlockWithTable4x2percep1000(uint8 *img,int width,int height,int startx,int starty,uint8 *avg_color,int table,unsigned int *pixel_indices_MSBp, unsigned int *pixel_indices_LSBp) +{ + uint8 orig[3],approx[3]; + unsigned int pixel_indices_MSB=0, pixel_indices_LSB=0, pixel_indices = 0; + unsigned int sum_error=0; + int q; + int i; + + i = 0; + for(int x=startx; x> 1), 1, i); + PUTBITS( pixel_indices_LSB, (pixel_indices & 1) , 1, i); + i++; + + // In order to simplify hardware, the table {-12, -4, 4, 12} is indexed {11, 10, 00, 01} + // so that first bit is sign bit and the other bit is size bit (4 or 12). + // This means that we have to scramble the bits before storing them. + + sum_error+=min_error; + } + i+=2; + + } + + *pixel_indices_MSBp = pixel_indices_MSB; + *pixel_indices_LSBp = pixel_indices_LSB; + + return sum_error; +} + +// Finds all pixel indices for a 4x2 block using perceptual weighting of error. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +float compressBlockWithTable4x2percep(uint8 *img,int width,int height,int startx,int starty,uint8 *avg_color,int table,unsigned int *pixel_indices_MSBp, unsigned int *pixel_indices_LSBp) +{ + uint8 orig[3],approx[3]; + unsigned int pixel_indices_MSB=0, pixel_indices_LSB=0, pixel_indices = 0; + float sum_error=0; + int q; + int i; + float wR2 = (float) PERCEPTUAL_WEIGHT_R_SQUARED; + float wG2 = (float) PERCEPTUAL_WEIGHT_G_SQUARED; + float wB2 = (float) PERCEPTUAL_WEIGHT_B_SQUARED; + + i = 0; + for(int x=startx; x> 1), 1, i); + PUTBITS( pixel_indices_LSB, (pixel_indices & 1) , 1, i); + i++; + + // In order to simplify hardware, the table {-12, -4, 4, 12} is indexed {11, 10, 00, 01} + // so that first bit is sign bit and the other bit is size bit (4 or 12). + // This means that we have to scramble the bits before storing them. + + sum_error+=min_error; + } + i+=2; + } + + *pixel_indices_MSBp = pixel_indices_MSB; + *pixel_indices_LSBp = pixel_indices_LSB; + + return sum_error; +} + +// Table for fast implementation of clamping to the interval [0,255] followed by addition of 255. +const int clamp_table_plus_255[768] = {0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, 0+255, + 0+255, 1+255, 2+255, 3+255, 4+255, 5+255, 6+255, 7+255, 8+255, 9+255, 10+255, 11+255, 12+255, 13+255, 14+255, 15+255, 16+255, 17+255, 18+255, 19+255, 20+255, 21+255, 22+255, 23+255, 24+255, 25+255, 26+255, 27+255, 28+255, 29+255, 30+255, 31+255, 32+255, 33+255, 34+255, 35+255, 36+255, 37+255, 38+255, 39+255, 40+255, 41+255, 42+255, 43+255, 44+255, 45+255, 46+255, 47+255, 48+255, 49+255, 50+255, 51+255, 52+255, 53+255, 54+255, 55+255, 56+255, 57+255, 58+255, 59+255, 60+255, 61+255, 62+255, 63+255, 64+255, 65+255, 66+255, 67+255, 68+255, 69+255, 70+255, 71+255, 72+255, 73+255, 74+255, 75+255, 76+255, 77+255, 78+255, 79+255, 80+255, 81+255, 82+255, 83+255, 84+255, 85+255, 86+255, 87+255, 88+255, 89+255, 90+255, 91+255, 92+255, 93+255, 94+255, 95+255, 96+255, 97+255, 98+255, 99+255, 100+255, 101+255, 102+255, 103+255, 104+255, 105+255, 106+255, 107+255, 108+255, 109+255, 110+255, 111+255, 112+255, 113+255, 114+255, 115+255, 116+255, 117+255, 118+255, 119+255, 120+255, 121+255, 122+255, 123+255, 124+255, 125+255, 126+255, 127+255, 128+255, 129+255, 130+255, 131+255, 132+255, 133+255, 134+255, 135+255, 136+255, 137+255, 138+255, 139+255, 140+255, 141+255, 142+255, 143+255, 144+255, 145+255, 146+255, 147+255, 148+255, 149+255, 150+255, 151+255, 152+255, 153+255, 154+255, 155+255, 156+255, 157+255, 158+255, 159+255, 160+255, 161+255, 162+255, 163+255, 164+255, 165+255, 166+255, 167+255, 168+255, 169+255, 170+255, 171+255, 172+255, 173+255, 174+255, 175+255, 176+255, 177+255, 178+255, 179+255, 180+255, 181+255, 182+255, 183+255, 184+255, 185+255, 186+255, 187+255, 188+255, 189+255, 190+255, 191+255, 192+255, 193+255, 194+255, 195+255, 196+255, 197+255, 198+255, 199+255, 200+255, 201+255, 202+255, 203+255, 204+255, 205+255, 206+255, 207+255, 208+255, 209+255, 210+255, 211+255, + 212+255, 213+255, 214+255, 215+255, 216+255, 217+255, 218+255, 219+255, 220+255, 221+255, 222+255, 223+255, 224+255, 225+255, 226+255, 227+255, 228+255, 229+255, 230+255, 231+255, 232+255, 233+255, 234+255, 235+255, 236+255, 237+255, 238+255, 239+255, 240+255, 241+255, 242+255, 243+255, 244+255, 245+255, 246+255, 247+255, 248+255, 249+255, 250+255, 251+255, 252+255, 253+255, 254+255, 255+255, + 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, + 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255, 255+255}; + +// Table for fast implementationi of clamping to the interval [0,255] +const int clamp_table[768] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255}; + +// Table for fast implementation of squaring for numbers in the interval [-255, 255] +const unsigned int square_table[511] = {65025, 64516, 64009, 63504, 63001, 62500, 62001, 61504, 61009, 60516, 60025, 59536, 59049, 58564, 58081, 57600, + 57121, 56644, 56169, 55696, 55225, 54756, 54289, 53824, 53361, 52900, 52441, 51984, 51529, 51076, 50625, 50176, + 49729, 49284, 48841, 48400, 47961, 47524, 47089, 46656, 46225, 45796, 45369, 44944, 44521, 44100, 43681, 43264, + 42849, 42436, 42025, 41616, 41209, 40804, 40401, 40000, 39601, 39204, 38809, 38416, 38025, 37636, 37249, 36864, + 36481, 36100, 35721, 35344, 34969, 34596, 34225, 33856, 33489, 33124, 32761, 32400, 32041, 31684, 31329, 30976, + 30625, 30276, 29929, 29584, 29241, 28900, 28561, 28224, 27889, 27556, 27225, 26896, 26569, 26244, 25921, 25600, + 25281, 24964, 24649, 24336, 24025, 23716, 23409, 23104, 22801, 22500, 22201, 21904, 21609, 21316, 21025, 20736, + 20449, 20164, 19881, 19600, 19321, 19044, 18769, 18496, 18225, 17956, 17689, 17424, 17161, 16900, 16641, 16384, + 16129, 15876, 15625, 15376, 15129, 14884, 14641, 14400, 14161, 13924, 13689, 13456, 13225, 12996, 12769, 12544, + 12321, 12100, 11881, 11664, 11449, 11236, 11025, 10816, 10609, 10404, 10201, 10000, 9801, 9604, 9409, 9216, + 9025, 8836, 8649, 8464, 8281, 8100, 7921, 7744, 7569, 7396, 7225, 7056, 6889, 6724, 6561, 6400, + 6241, 6084, 5929, 5776, 5625, 5476, 5329, 5184, 5041, 4900, 4761, 4624, 4489, 4356, 4225, 4096, + 3969, 3844, 3721, 3600, 3481, 3364, 3249, 3136, 3025, 2916, 2809, 2704, 2601, 2500, 2401, 2304, + 2209, 2116, 2025, 1936, 1849, 1764, 1681, 1600, 1521, 1444, 1369, 1296, 1225, 1156, 1089, 1024, + 961, 900, 841, 784, 729, 676, 625, 576, 529, 484, 441, 400, 361, 324, 289, 256, + 225, 196, 169, 144, 121, 100, 81, 64, 49, 36, 25, 16, 9, 4, 1, + 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, + 256, 289, 324, 361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900, 961, + 1024, 1089, 1156, 1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936, 2025, 2116, 2209, + 2304, 2401, 2500, 2601, 2704, 2809, 2916, 3025, 3136, 3249, 3364, 3481, 3600, 3721, 3844, 3969, + 4096, 4225, 4356, 4489, 4624, 4761, 4900, 5041, 5184, 5329, 5476, 5625, 5776, 5929, 6084, 6241, + 6400, 6561, 6724, 6889, 7056, 7225, 7396, 7569, 7744, 7921, 8100, 8281, 8464, 8649, 8836, 9025, + 9216, 9409, 9604, 9801, 10000, 10201, 10404, 10609, 10816, 11025, 11236, 11449, 11664, 11881, 12100, 12321, + 12544, 12769, 12996, 13225, 13456, 13689, 13924, 14161, 14400, 14641, 14884, 15129, 15376, 15625, 15876, 16129, + 16384, 16641, 16900, 17161, 17424, 17689, 17956, 18225, 18496, 18769, 19044, 19321, 19600, 19881, 20164, 20449, + 20736, 21025, 21316, 21609, 21904, 22201, 22500, 22801, 23104, 23409, 23716, 24025, 24336, 24649, 24964, 25281, + 25600, 25921, 26244, 26569, 26896, 27225, 27556, 27889, 28224, 28561, 28900, 29241, 29584, 29929, 30276, 30625, + 30976, 31329, 31684, 32041, 32400, 32761, 33124, 33489, 33856, 34225, 34596, 34969, 35344, 35721, 36100, 36481, + 36864, 37249, 37636, 38025, 38416, 38809, 39204, 39601, 40000, 40401, 40804, 41209, 41616, 42025, 42436, 42849, + 43264, 43681, 44100, 44521, 44944, 45369, 45796, 46225, 46656, 47089, 47524, 47961, 48400, 48841, 49284, 49729, + 50176, 50625, 51076, 51529, 51984, 52441, 52900, 53361, 53824, 54289, 54756, 55225, 55696, 56169, 56644, 57121, + 57600, 58081, 58564, 59049, 59536, 60025, 60516, 61009, 61504, 62001, 62500, 63001, 63504, 64009, 64516, 65025}; + +// Abbreviated variable names to make below tables smaller in source code size +#define KR PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000 +#define KG PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000 +#define KB PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000 + +// Table for fast implementation of squaring for numbers in the interval [-255, 255] multiplied by the perceptual weight for red. +const unsigned int square_table_percep_red[511] = { + 65025*KR, 64516*KR, 64009*KR, 63504*KR, 63001*KR, 62500*KR, 62001*KR, 61504*KR, 61009*KR, 60516*KR, 60025*KR, 59536*KR, 59049*KR, 58564*KR, 58081*KR, 57600*KR, + 57121*KR, 56644*KR, 56169*KR, 55696*KR, 55225*KR, 54756*KR, 54289*KR, 53824*KR, 53361*KR, 52900*KR, 52441*KR, 51984*KR, 51529*KR, 51076*KR, 50625*KR, 50176*KR, + 49729*KR, 49284*KR, 48841*KR, 48400*KR, 47961*KR, 47524*KR, 47089*KR, 46656*KR, 46225*KR, 45796*KR, 45369*KR, 44944*KR, 44521*KR, 44100*KR, 43681*KR, 43264*KR, + 42849*KR, 42436*KR, 42025*KR, 41616*KR, 41209*KR, 40804*KR, 40401*KR, 40000*KR, 39601*KR, 39204*KR, 38809*KR, 38416*KR, 38025*KR, 37636*KR, 37249*KR, 36864*KR, + 36481*KR, 36100*KR, 35721*KR, 35344*KR, 34969*KR, 34596*KR, 34225*KR, 33856*KR, 33489*KR, 33124*KR, 32761*KR, 32400*KR, 32041*KR, 31684*KR, 31329*KR, 30976*KR, + 30625*KR, 30276*KR, 29929*KR, 29584*KR, 29241*KR, 28900*KR, 28561*KR, 28224*KR, 27889*KR, 27556*KR, 27225*KR, 26896*KR, 26569*KR, 26244*KR, 25921*KR, 25600*KR, + 25281*KR, 24964*KR, 24649*KR, 24336*KR, 24025*KR, 23716*KR, 23409*KR, 23104*KR, 22801*KR, 22500*KR, 22201*KR, 21904*KR, 21609*KR, 21316*KR, 21025*KR, 20736*KR, + 20449*KR, 20164*KR, 19881*KR, 19600*KR, 19321*KR, 19044*KR, 18769*KR, 18496*KR, 18225*KR, 17956*KR, 17689*KR, 17424*KR, 17161*KR, 16900*KR, 16641*KR, 16384*KR, + 16129*KR, 15876*KR, 15625*KR, 15376*KR, 15129*KR, 14884*KR, 14641*KR, 14400*KR, 14161*KR, 13924*KR, 13689*KR, 13456*KR, 13225*KR, 12996*KR, 12769*KR, 12544*KR, + 12321*KR, 12100*KR, 11881*KR, 11664*KR, 11449*KR, 11236*KR, 11025*KR, 10816*KR, 10609*KR, 10404*KR, 10201*KR, 10000*KR, 9801*KR, 9604*KR, 9409*KR, 9216*KR, + 9025*KR, 8836*KR, 8649*KR, 8464*KR, 8281*KR, 8100*KR, 7921*KR, 7744*KR, 7569*KR, 7396*KR, 7225*KR, 7056*KR, 6889*KR, 6724*KR, 6561*KR, 6400*KR, + 6241*KR, 6084*KR, 5929*KR, 5776*KR, 5625*KR, 5476*KR, 5329*KR, 5184*KR, 5041*KR, 4900*KR, 4761*KR, 4624*KR, 4489*KR, 4356*KR, 4225*KR, 4096*KR, + 3969*KR, 3844*KR, 3721*KR, 3600*KR, 3481*KR, 3364*KR, 3249*KR, 3136*KR, 3025*KR, 2916*KR, 2809*KR, 2704*KR, 2601*KR, 2500*KR, 2401*KR, 2304*KR, + 2209*KR, 2116*KR, 2025*KR, 1936*KR, 1849*KR, 1764*KR, 1681*KR, 1600*KR, 1521*KR, 1444*KR, 1369*KR, 1296*KR, 1225*KR, 1156*KR, 1089*KR, 1024*KR, + 961*KR, 900*KR, 841*KR, 784*KR, 729*KR, 676*KR, 625*KR, 576*KR, 529*KR, 484*KR, 441*KR, 400*KR, 361*KR, 324*KR, 289*KR, 256*KR, + 225*KR, 196*KR, 169*KR, 144*KR, 121*KR, 100*KR, 81*KR, 64*KR, 49*KR, 36*KR, 25*KR, 16*KR, 9*KR, 4*KR, 1*KR, + 0*KR, 1*KR, 4*KR, 9*KR, 16*KR, 25*KR, 36*KR, 49*KR, 64*KR, 81*KR, 100*KR, 121*KR, 144*KR, 169*KR, 196*KR, 225*KR, + 256*KR, 289*KR, 324*KR, 361*KR, 400*KR, 441*KR, 484*KR, 529*KR, 576*KR, 625*KR, 676*KR, 729*KR, 784*KR, 841*KR, 900*KR, 961*KR, + 1024*KR, 1089*KR, 1156*KR, 1225*KR, 1296*KR, 1369*KR, 1444*KR, 1521*KR, 1600*KR, 1681*KR, 1764*KR, 1849*KR, 1936*KR, 2025*KR, 2116*KR, 2209*KR, + 2304*KR, 2401*KR, 2500*KR, 2601*KR, 2704*KR, 2809*KR, 2916*KR, 3025*KR, 3136*KR, 3249*KR, 3364*KR, 3481*KR, 3600*KR, 3721*KR, 3844*KR, 3969*KR, + 4096*KR, 4225*KR, 4356*KR, 4489*KR, 4624*KR, 4761*KR, 4900*KR, 5041*KR, 5184*KR, 5329*KR, 5476*KR, 5625*KR, 5776*KR, 5929*KR, 6084*KR, 6241*KR, + 6400*KR, 6561*KR, 6724*KR, 6889*KR, 7056*KR, 7225*KR, 7396*KR, 7569*KR, 7744*KR, 7921*KR, 8100*KR, 8281*KR, 8464*KR, 8649*KR, 8836*KR, 9025*KR, + 9216*KR, 9409*KR, 9604*KR, 9801*KR, 10000*KR, 10201*KR, 10404*KR, 10609*KR, 10816*KR, 11025*KR, 11236*KR, 11449*KR, 11664*KR, 11881*KR, 12100*KR, 12321*KR, + 12544*KR, 12769*KR, 12996*KR, 13225*KR, 13456*KR, 13689*KR, 13924*KR, 14161*KR, 14400*KR, 14641*KR, 14884*KR, 15129*KR, 15376*KR, 15625*KR, 15876*KR, 16129*KR, + 16384*KR, 16641*KR, 16900*KR, 17161*KR, 17424*KR, 17689*KR, 17956*KR, 18225*KR, 18496*KR, 18769*KR, 19044*KR, 19321*KR, 19600*KR, 19881*KR, 20164*KR, 20449*KR, + 20736*KR, 21025*KR, 21316*KR, 21609*KR, 21904*KR, 22201*KR, 22500*KR, 22801*KR, 23104*KR, 23409*KR, 23716*KR, 24025*KR, 24336*KR, 24649*KR, 24964*KR, 25281*KR, + 25600*KR, 25921*KR, 26244*KR, 26569*KR, 26896*KR, 27225*KR, 27556*KR, 27889*KR, 28224*KR, 28561*KR, 28900*KR, 29241*KR, 29584*KR, 29929*KR, 30276*KR, 30625*KR, + 30976*KR, 31329*KR, 31684*KR, 32041*KR, 32400*KR, 32761*KR, 33124*KR, 33489*KR, 33856*KR, 34225*KR, 34596*KR, 34969*KR, 35344*KR, 35721*KR, 36100*KR, 36481*KR, + 36864*KR, 37249*KR, 37636*KR, 38025*KR, 38416*KR, 38809*KR, 39204*KR, 39601*KR, 40000*KR, 40401*KR, 40804*KR, 41209*KR, 41616*KR, 42025*KR, 42436*KR, 42849*KR, + 43264*KR, 43681*KR, 44100*KR, 44521*KR, 44944*KR, 45369*KR, 45796*KR, 46225*KR, 46656*KR, 47089*KR, 47524*KR, 47961*KR, 48400*KR, 48841*KR, 49284*KR, 49729*KR, + 50176*KR, 50625*KR, 51076*KR, 51529*KR, 51984*KR, 52441*KR, 52900*KR, 53361*KR, 53824*KR, 54289*KR, 54756*KR, 55225*KR, 55696*KR, 56169*KR, 56644*KR, 57121*KR, + 57600*KR, 58081*KR, 58564*KR, 59049*KR, 59536*KR, 60025*KR, 60516*KR, 61009*KR, 61504*KR, 62001*KR, 62500*KR, 63001*KR, 63504*KR, 64009*KR, 64516*KR, 65025*KR}; + +// Table for fast implementation of squaring for numbers in the interval [-255, 255] multiplied by the perceptual weight for green. +const unsigned int square_table_percep_green[511] = { + 65025*KG, 64516*KG, 64009*KG, 63504*KG, 63001*KG, 62500*KG, 62001*KG, 61504*KG, 61009*KG, 60516*KG, 60025*KG, 59536*KG, 59049*KG, 58564*KG, 58081*KG, 57600*KG, + 57121*KG, 56644*KG, 56169*KG, 55696*KG, 55225*KG, 54756*KG, 54289*KG, 53824*KG, 53361*KG, 52900*KG, 52441*KG, 51984*KG, 51529*KG, 51076*KG, 50625*KG, 50176*KG, + 49729*KG, 49284*KG, 48841*KG, 48400*KG, 47961*KG, 47524*KG, 47089*KG, 46656*KG, 46225*KG, 45796*KG, 45369*KG, 44944*KG, 44521*KG, 44100*KG, 43681*KG, 43264*KG, + 42849*KG, 42436*KG, 42025*KG, 41616*KG, 41209*KG, 40804*KG, 40401*KG, 40000*KG, 39601*KG, 39204*KG, 38809*KG, 38416*KG, 38025*KG, 37636*KG, 37249*KG, 36864*KG, + 36481*KG, 36100*KG, 35721*KG, 35344*KG, 34969*KG, 34596*KG, 34225*KG, 33856*KG, 33489*KG, 33124*KG, 32761*KG, 32400*KG, 32041*KG, 31684*KG, 31329*KG, 30976*KG, + 30625*KG, 30276*KG, 29929*KG, 29584*KG, 29241*KG, 28900*KG, 28561*KG, 28224*KG, 27889*KG, 27556*KG, 27225*KG, 26896*KG, 26569*KG, 26244*KG, 25921*KG, 25600*KG, + 25281*KG, 24964*KG, 24649*KG, 24336*KG, 24025*KG, 23716*KG, 23409*KG, 23104*KG, 22801*KG, 22500*KG, 22201*KG, 21904*KG, 21609*KG, 21316*KG, 21025*KG, 20736*KG, + 20449*KG, 20164*KG, 19881*KG, 19600*KG, 19321*KG, 19044*KG, 18769*KG, 18496*KG, 18225*KG, 17956*KG, 17689*KG, 17424*KG, 17161*KG, 16900*KG, 16641*KG, 16384*KG, + 16129*KG, 15876*KG, 15625*KG, 15376*KG, 15129*KG, 14884*KG, 14641*KG, 14400*KG, 14161*KG, 13924*KG, 13689*KG, 13456*KG, 13225*KG, 12996*KG, 12769*KG, 12544*KG, + 12321*KG, 12100*KG, 11881*KG, 11664*KG, 11449*KG, 11236*KG, 11025*KG, 10816*KG, 10609*KG, 10404*KG, 10201*KG, 10000*KG, 9801*KG, 9604*KG, 9409*KG, 9216*KG, + 9025*KG, 8836*KG, 8649*KG, 8464*KG, 8281*KG, 8100*KG, 7921*KG, 7744*KG, 7569*KG, 7396*KG, 7225*KG, 7056*KG, 6889*KG, 6724*KG, 6561*KG, 6400*KG, + 6241*KG, 6084*KG, 5929*KG, 5776*KG, 5625*KG, 5476*KG, 5329*KG, 5184*KG, 5041*KG, 4900*KG, 4761*KG, 4624*KG, 4489*KG, 4356*KG, 4225*KG, 4096*KG, + 3969*KG, 3844*KG, 3721*KG, 3600*KG, 3481*KG, 3364*KG, 3249*KG, 3136*KG, 3025*KG, 2916*KG, 2809*KG, 2704*KG, 2601*KG, 2500*KG, 2401*KG, 2304*KG, + 2209*KG, 2116*KG, 2025*KG, 1936*KG, 1849*KG, 1764*KG, 1681*KG, 1600*KG, 1521*KG, 1444*KG, 1369*KG, 1296*KG, 1225*KG, 1156*KG, 1089*KG, 1024*KG, + 961*KG, 900*KG, 841*KG, 784*KG, 729*KG, 676*KG, 625*KG, 576*KG, 529*KG, 484*KG, 441*KG, 400*KG, 361*KG, 324*KG, 289*KG, 256*KG, + 225*KG, 196*KG, 169*KG, 144*KG, 121*KG, 100*KG, 81*KG, 64*KG, 49*KG, 36*KG, 25*KG, 16*KG, 9*KG, 4*KG, 1*KG, + 0*KG, 1*KG, 4*KG, 9*KG, 16*KG, 25*KG, 36*KG, 49*KG, 64*KG, 81*KG, 100*KG, 121*KG, 144*KG, 169*KG, 196*KG, 225*KG, + 256*KG, 289*KG, 324*KG, 361*KG, 400*KG, 441*KG, 484*KG, 529*KG, 576*KG, 625*KG, 676*KG, 729*KG, 784*KG, 841*KG, 900*KG, 961*KG, + 1024*KG, 1089*KG, 1156*KG, 1225*KG, 1296*KG, 1369*KG, 1444*KG, 1521*KG, 1600*KG, 1681*KG, 1764*KG, 1849*KG, 1936*KG, 2025*KG, 2116*KG, 2209*KG, + 2304*KG, 2401*KG, 2500*KG, 2601*KG, 2704*KG, 2809*KG, 2916*KG, 3025*KG, 3136*KG, 3249*KG, 3364*KG, 3481*KG, 3600*KG, 3721*KG, 3844*KG, 3969*KG, + 4096*KG, 4225*KG, 4356*KG, 4489*KG, 4624*KG, 4761*KG, 4900*KG, 5041*KG, 5184*KG, 5329*KG, 5476*KG, 5625*KG, 5776*KG, 5929*KG, 6084*KG, 6241*KG, + 6400*KG, 6561*KG, 6724*KG, 6889*KG, 7056*KG, 7225*KG, 7396*KG, 7569*KG, 7744*KG, 7921*KG, 8100*KG, 8281*KG, 8464*KG, 8649*KG, 8836*KG, 9025*KG, + 9216*KG, 9409*KG, 9604*KG, 9801*KG, 10000*KG, 10201*KG, 10404*KG, 10609*KG, 10816*KG, 11025*KG, 11236*KG, 11449*KG, 11664*KG, 11881*KG, 12100*KG, 12321*KG, + 12544*KG, 12769*KG, 12996*KG, 13225*KG, 13456*KG, 13689*KG, 13924*KG, 14161*KG, 14400*KG, 14641*KG, 14884*KG, 15129*KG, 15376*KG, 15625*KG, 15876*KG, 16129*KG, + 16384*KG, 16641*KG, 16900*KG, 17161*KG, 17424*KG, 17689*KG, 17956*KG, 18225*KG, 18496*KG, 18769*KG, 19044*KG, 19321*KG, 19600*KG, 19881*KG, 20164*KG, 20449*KG, + 20736*KG, 21025*KG, 21316*KG, 21609*KG, 21904*KG, 22201*KG, 22500*KG, 22801*KG, 23104*KG, 23409*KG, 23716*KG, 24025*KG, 24336*KG, 24649*KG, 24964*KG, 25281*KG, + 25600*KG, 25921*KG, 26244*KG, 26569*KG, 26896*KG, 27225*KG, 27556*KG, 27889*KG, 28224*KG, 28561*KG, 28900*KG, 29241*KG, 29584*KG, 29929*KG, 30276*KG, 30625*KG, + 30976*KG, 31329*KG, 31684*KG, 32041*KG, 32400*KG, 32761*KG, 33124*KG, 33489*KG, 33856*KG, 34225*KG, 34596*KG, 34969*KG, 35344*KG, 35721*KG, 36100*KG, 36481*KG, + 36864*KG, 37249*KG, 37636*KG, 38025*KG, 38416*KG, 38809*KG, 39204*KG, 39601*KG, 40000*KG, 40401*KG, 40804*KG, 41209*KG, 41616*KG, 42025*KG, 42436*KG, 42849*KG, + 43264*KG, 43681*KG, 44100*KG, 44521*KG, 44944*KG, 45369*KG, 45796*KG, 46225*KG, 46656*KG, 47089*KG, 47524*KG, 47961*KG, 48400*KG, 48841*KG, 49284*KG, 49729*KG, + 50176*KG, 50625*KG, 51076*KG, 51529*KG, 51984*KG, 52441*KG, 52900*KG, 53361*KG, 53824*KG, 54289*KG, 54756*KG, 55225*KG, 55696*KG, 56169*KG, 56644*KG, 57121*KG, + 57600*KG, 58081*KG, 58564*KG, 59049*KG, 59536*KG, 60025*KG, 60516*KG, 61009*KG, 61504*KG, 62001*KG, 62500*KG, 63001*KG, 63504*KG, 64009*KG, 64516*KG, 65025*KG}; + +// Table for fast implementation of squaring for numbers in the interval [-255, 255] multiplied by the perceptual weight for blue. +const unsigned int square_table_percep_blue[511] = { + 65025*KB, 64516*KB, 64009*KB, 63504*KB, 63001*KB, 62500*KB, 62001*KB, 61504*KB, 61009*KB, 60516*KB, 60025*KB, 59536*KB, 59049*KB, 58564*KB, 58081*KB, 57600*KB, + 57121*KB, 56644*KB, 56169*KB, 55696*KB, 55225*KB, 54756*KB, 54289*KB, 53824*KB, 53361*KB, 52900*KB, 52441*KB, 51984*KB, 51529*KB, 51076*KB, 50625*KB, 50176*KB, + 49729*KB, 49284*KB, 48841*KB, 48400*KB, 47961*KB, 47524*KB, 47089*KB, 46656*KB, 46225*KB, 45796*KB, 45369*KB, 44944*KB, 44521*KB, 44100*KB, 43681*KB, 43264*KB, + 42849*KB, 42436*KB, 42025*KB, 41616*KB, 41209*KB, 40804*KB, 40401*KB, 40000*KB, 39601*KB, 39204*KB, 38809*KB, 38416*KB, 38025*KB, 37636*KB, 37249*KB, 36864*KB, + 36481*KB, 36100*KB, 35721*KB, 35344*KB, 34969*KB, 34596*KB, 34225*KB, 33856*KB, 33489*KB, 33124*KB, 32761*KB, 32400*KB, 32041*KB, 31684*KB, 31329*KB, 30976*KB, + 30625*KB, 30276*KB, 29929*KB, 29584*KB, 29241*KB, 28900*KB, 28561*KB, 28224*KB, 27889*KB, 27556*KB, 27225*KB, 26896*KB, 26569*KB, 26244*KB, 25921*KB, 25600*KB, + 25281*KB, 24964*KB, 24649*KB, 24336*KB, 24025*KB, 23716*KB, 23409*KB, 23104*KB, 22801*KB, 22500*KB, 22201*KB, 21904*KB, 21609*KB, 21316*KB, 21025*KB, 20736*KB, + 20449*KB, 20164*KB, 19881*KB, 19600*KB, 19321*KB, 19044*KB, 18769*KB, 18496*KB, 18225*KB, 17956*KB, 17689*KB, 17424*KB, 17161*KB, 16900*KB, 16641*KB, 16384*KB, + 16129*KB, 15876*KB, 15625*KB, 15376*KB, 15129*KB, 14884*KB, 14641*KB, 14400*KB, 14161*KB, 13924*KB, 13689*KB, 13456*KB, 13225*KB, 12996*KB, 12769*KB, 12544*KB, + 12321*KB, 12100*KB, 11881*KB, 11664*KB, 11449*KB, 11236*KB, 11025*KB, 10816*KB, 10609*KB, 10404*KB, 10201*KB, 10000*KB, 9801*KB, 9604*KB, 9409*KB, 9216*KB, + 9025*KB, 8836*KB, 8649*KB, 8464*KB, 8281*KB, 8100*KB, 7921*KB, 7744*KB, 7569*KB, 7396*KB, 7225*KB, 7056*KB, 6889*KB, 6724*KB, 6561*KB, 6400*KB, + 6241*KB, 6084*KB, 5929*KB, 5776*KB, 5625*KB, 5476*KB, 5329*KB, 5184*KB, 5041*KB, 4900*KB, 4761*KB, 4624*KB, 4489*KB, 4356*KB, 4225*KB, 4096*KB, + 3969*KB, 3844*KB, 3721*KB, 3600*KB, 3481*KB, 3364*KB, 3249*KB, 3136*KB, 3025*KB, 2916*KB, 2809*KB, 2704*KB, 2601*KB, 2500*KB, 2401*KB, 2304*KB, + 2209*KB, 2116*KB, 2025*KB, 1936*KB, 1849*KB, 1764*KB, 1681*KB, 1600*KB, 1521*KB, 1444*KB, 1369*KB, 1296*KB, 1225*KB, 1156*KB, 1089*KB, 1024*KB, + 961*KB, 900*KB, 841*KB, 784*KB, 729*KB, 676*KB, 625*KB, 576*KB, 529*KB, 484*KB, 441*KB, 400*KB, 361*KB, 324*KB, 289*KB, 256*KB, + 225*KB, 196*KB, 169*KB, 144*KB, 121*KB, 100*KB, 81*KB, 64*KB, 49*KB, 36*KB, 25*KB, 16*KB, 9*KB, 4*KB, 1*KB, + 0*KB, 1*KB, 4*KB, 9*KB, 16*KB, 25*KB, 36*KB, 49*KB, 64*KB, 81*KB, 100*KB, 121*KB, 144*KB, 169*KB, 196*KB, 225*KB, + 256*KB, 289*KB, 324*KB, 361*KB, 400*KB, 441*KB, 484*KB, 529*KB, 576*KB, 625*KB, 676*KB, 729*KB, 784*KB, 841*KB, 900*KB, 961*KB, + 1024*KB, 1089*KB, 1156*KB, 1225*KB, 1296*KB, 1369*KB, 1444*KB, 1521*KB, 1600*KB, 1681*KB, 1764*KB, 1849*KB, 1936*KB, 2025*KB, 2116*KB, 2209*KB, + 2304*KB, 2401*KB, 2500*KB, 2601*KB, 2704*KB, 2809*KB, 2916*KB, 3025*KB, 3136*KB, 3249*KB, 3364*KB, 3481*KB, 3600*KB, 3721*KB, 3844*KB, 3969*KB, + 4096*KB, 4225*KB, 4356*KB, 4489*KB, 4624*KB, 4761*KB, 4900*KB, 5041*KB, 5184*KB, 5329*KB, 5476*KB, 5625*KB, 5776*KB, 5929*KB, 6084*KB, 6241*KB, + 6400*KB, 6561*KB, 6724*KB, 6889*KB, 7056*KB, 7225*KB, 7396*KB, 7569*KB, 7744*KB, 7921*KB, 8100*KB, 8281*KB, 8464*KB, 8649*KB, 8836*KB, 9025*KB, + 9216*KB, 9409*KB, 9604*KB, 9801*KB, 10000*KB, 10201*KB, 10404*KB, 10609*KB, 10816*KB, 11025*KB, 11236*KB, 11449*KB, 11664*KB, 11881*KB, 12100*KB, 12321*KB, + 12544*KB, 12769*KB, 12996*KB, 13225*KB, 13456*KB, 13689*KB, 13924*KB, 14161*KB, 14400*KB, 14641*KB, 14884*KB, 15129*KB, 15376*KB, 15625*KB, 15876*KB, 16129*KB, + 16384*KB, 16641*KB, 16900*KB, 17161*KB, 17424*KB, 17689*KB, 17956*KB, 18225*KB, 18496*KB, 18769*KB, 19044*KB, 19321*KB, 19600*KB, 19881*KB, 20164*KB, 20449*KB, + 20736*KB, 21025*KB, 21316*KB, 21609*KB, 21904*KB, 22201*KB, 22500*KB, 22801*KB, 23104*KB, 23409*KB, 23716*KB, 24025*KB, 24336*KB, 24649*KB, 24964*KB, 25281*KB, + 25600*KB, 25921*KB, 26244*KB, 26569*KB, 26896*KB, 27225*KB, 27556*KB, 27889*KB, 28224*KB, 28561*KB, 28900*KB, 29241*KB, 29584*KB, 29929*KB, 30276*KB, 30625*KB, + 30976*KB, 31329*KB, 31684*KB, 32041*KB, 32400*KB, 32761*KB, 33124*KB, 33489*KB, 33856*KB, 34225*KB, 34596*KB, 34969*KB, 35344*KB, 35721*KB, 36100*KB, 36481*KB, + 36864*KB, 37249*KB, 37636*KB, 38025*KB, 38416*KB, 38809*KB, 39204*KB, 39601*KB, 40000*KB, 40401*KB, 40804*KB, 41209*KB, 41616*KB, 42025*KB, 42436*KB, 42849*KB, + 43264*KB, 43681*KB, 44100*KB, 44521*KB, 44944*KB, 45369*KB, 45796*KB, 46225*KB, 46656*KB, 47089*KB, 47524*KB, 47961*KB, 48400*KB, 48841*KB, 49284*KB, 49729*KB, + 50176*KB, 50625*KB, 51076*KB, 51529*KB, 51984*KB, 52441*KB, 52900*KB, 53361*KB, 53824*KB, 54289*KB, 54756*KB, 55225*KB, 55696*KB, 56169*KB, 56644*KB, 57121*KB, + 57600*KB, 58081*KB, 58564*KB, 59049*KB, 59536*KB, 60025*KB, 60516*KB, 61009*KB, 61504*KB, 62001*KB, 62500*KB, 63001*KB, 63504*KB, 64009*KB, 64516*KB, 65025*KB}; + +// Find the best table to use for a 2x4 area by testing all. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +int tryalltables_3bittable2x4(uint8 *img,int width,int height,int startx,int starty,uint8 *avg_color, unsigned int &best_table,unsigned int &best_pixel_indices_MSB, unsigned int &best_pixel_indices_LSB) +{ + int min_error = 3*255*255*16; + int q; + int err; + unsigned int pixel_indices_MSB, pixel_indices_LSB; + + for(q=0;q<16;q+=2) // try all the 8 tables. + { + err=compressBlockWithTable2x4(img,width,height,startx,starty,avg_color,q,&pixel_indices_MSB, &pixel_indices_LSB); + + if(err> 1; + } + } + return min_error; +} + +// Find the best table to use for a 2x4 area by testing all. +// Uses perceptual weighting. +// Uses fixed point implementation where 1000 equals 1.0 +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int tryalltables_3bittable2x4percep1000(uint8 *img,int width,int height,int startx,int starty,uint8 *avg_color, unsigned int &best_table,unsigned int &best_pixel_indices_MSB, unsigned int &best_pixel_indices_LSB) +{ + unsigned int min_error = MAXERR1000; + int q; + unsigned int err; + unsigned int pixel_indices_MSB, pixel_indices_LSB; + + for(q=0;q<16;q+=2) // try all the 8 tables. + { + + err=compressBlockWithTable2x4percep1000(img,width,height,startx,starty,avg_color,q,&pixel_indices_MSB, &pixel_indices_LSB); + + if(err> 1; + + } + } + return min_error; +} + +// Find the best table to use for a 2x4 area by testing all. +// Uses perceptual weighting. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +int tryalltables_3bittable2x4percep(uint8 *img,int width,int height,int startx,int starty,uint8 *avg_color, unsigned int &best_table,unsigned int &best_pixel_indices_MSB, unsigned int &best_pixel_indices_LSB) +{ + float min_error = 3*255*255*16; + int q; + float err; + unsigned int pixel_indices_MSB, pixel_indices_LSB; + + for(q=0;q<16;q+=2) // try all the 8 tables. + { + err=compressBlockWithTable2x4percep(img,width,height,startx,starty,avg_color,q,&pixel_indices_MSB, &pixel_indices_LSB); + + if(err> 1; + } + } + return (int) min_error; +} + +// Find the best table to use for a 4x2 area by testing all. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +int tryalltables_3bittable4x2(uint8 *img,int width,int height,int startx,int starty,uint8 *avg_color, unsigned int &best_table,unsigned int &best_pixel_indices_MSB, unsigned int &best_pixel_indices_LSB) +{ + int min_error = 3*255*255*16; + int q; + int err; + unsigned int pixel_indices_MSB, pixel_indices_LSB; + + for(q=0;q<16;q+=2) // try all the 8 tables. + { + err=compressBlockWithTable4x2(img,width,height,startx,starty,avg_color,q,&pixel_indices_MSB, &pixel_indices_LSB); + + if(err> 1; + } + } + return min_error; +} + +// Find the best table to use for a 4x2 area by testing all. +// Uses perceptual weighting. +// Uses fixed point implementation where 1000 equals 1.0 +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int tryalltables_3bittable4x2percep1000(uint8 *img,int width,int height,int startx,int starty,uint8 *avg_color, unsigned int &best_table,unsigned int &best_pixel_indices_MSB, unsigned int &best_pixel_indices_LSB) +{ + unsigned int min_error = MAXERR1000; + int q; + unsigned int err; + unsigned int pixel_indices_MSB, pixel_indices_LSB; + + for(q=0;q<16;q+=2) // try all the 8 tables. + { + err=compressBlockWithTable4x2percep1000(img,width,height,startx,starty,avg_color,q,&pixel_indices_MSB, &pixel_indices_LSB); + + if(err> 1; + } + } + return min_error; +} + +// Find the best table to use for a 4x2 area by testing all. +// Uses perceptual weighting. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +int tryalltables_3bittable4x2percep(uint8 *img,int width,int height,int startx,int starty,uint8 *avg_color, unsigned int &best_table,unsigned int &best_pixel_indices_MSB, unsigned int &best_pixel_indices_LSB) +{ + float min_error = 3*255*255*16; + int q; + float err; + unsigned int pixel_indices_MSB, pixel_indices_LSB; + + for(q=0;q<16;q+=2) // try all the 8 tables. + { + err=compressBlockWithTable4x2percep(img,width,height,startx,starty,avg_color,q,&pixel_indices_MSB, &pixel_indices_LSB); + + if(err> 1; + } + } + return (int) min_error; +} + +// The below code quantizes a float RGB value to RGB444. +// +// The format often allows a pixel to completely compensate an intensity error of the base +// color. Hence the closest RGB444 point may not be the best, and the code below uses +// this fact to find a better RGB444 color as the base color. +// +// (See the presentation http://www.jacobstrom.com/publications/PACKMAN.ppt for more info.) +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void quantize444ColorCombined(float *avg_col_in, int *enc_color, uint8 *avg_color) +{ + float dr, dg, db; + float kr, kg, kb; + float wR2, wG2, wB2; + uint8 low_color[3]; + uint8 high_color[3]; + float min_error=255*255*8*3; + float lowhightable[8]; + unsigned int best_table=0; + unsigned int best_index=0; + int q; + float kval = (float) (255.0/15.0); + + // These are the values that we want to have: + float red_average, green_average, blue_average; + + int red_4bit_low, green_4bit_low, blue_4bit_low; + int red_4bit_high, green_4bit_high, blue_4bit_high; + + // These are the values that we approximate with: + int red_low, green_low, blue_low; + int red_high, green_high, blue_high; + + red_average = avg_col_in[0]; + green_average = avg_col_in[1]; + blue_average = avg_col_in[2]; + + // Find the 5-bit reconstruction levels red_low, red_high + // so that red_average is in interval [red_low, red_high]. + // (The same with green and blue.) + + red_4bit_low = (int) (red_average/kval); + green_4bit_low = (int) (green_average/kval); + blue_4bit_low = (int) (blue_average/kval); + + red_4bit_high = CLAMP(0, red_4bit_low + 1, 15); + green_4bit_high = CLAMP(0, green_4bit_low + 1, 15); + blue_4bit_high = CLAMP(0, blue_4bit_low + 1, 15); + + red_low = (red_4bit_low << 4) | (red_4bit_low >> 0); + green_low = (green_4bit_low << 4) | (green_4bit_low >> 0); + blue_low = (blue_4bit_low << 4) | (blue_4bit_low >> 0); + + red_high = (red_4bit_high << 4) | (red_4bit_high >> 0); + green_high = (green_4bit_high << 4) | (green_4bit_high >> 0); + blue_high = (blue_4bit_high << 4) | (blue_4bit_high >> 0); + + kr = (float)red_high - (float)red_low; + kg = (float)green_high - (float)green_low; + kb = (float)blue_high - (float)blue_low; + + // Note that dr, dg, and db are all negative. + dr = red_low - red_average; + dg = green_low - green_average; + db = blue_low - blue_average; + + // Use straight (nonperceptive) weights. + wR2 = (float) 1.0; + wG2 = (float) 1.0; + wB2 = (float) 1.0; + + lowhightable[0] = wR2*wG2*SQUARE( (dr+ 0) - (dg+ 0) ) + wR2*wB2*SQUARE( (dr+ 0) - (db+ 0) ) + wG2*wB2*SQUARE( (dg+ 0) - (db+ 0) ); + lowhightable[1] = wR2*wG2*SQUARE( (dr+kr) - (dg+ 0) ) + wR2*wB2*SQUARE( (dr+kr) - (db+ 0) ) + wG2*wB2*SQUARE( (dg+ 0) - (db+ 0) ); + lowhightable[2] = wR2*wG2*SQUARE( (dr+ 0) - (dg+kg) ) + wR2*wB2*SQUARE( (dr+ 0) - (db+ 0) ) + wG2*wB2*SQUARE( (dg+kg) - (db+ 0) ); + lowhightable[3] = wR2*wG2*SQUARE( (dr+ 0) - (dg+ 0) ) + wR2*wB2*SQUARE( (dr+ 0) - (db+kb) ) + wG2*wB2*SQUARE( (dg+ 0) - (db+kb) ); + lowhightable[4] = wR2*wG2*SQUARE( (dr+kr) - (dg+kg) ) + wR2*wB2*SQUARE( (dr+kr) - (db+ 0) ) + wG2*wB2*SQUARE( (dg+kg) - (db+ 0) ); + lowhightable[5] = wR2*wG2*SQUARE( (dr+kr) - (dg+ 0) ) + wR2*wB2*SQUARE( (dr+kr) - (db+kb) ) + wG2*wB2*SQUARE( (dg+ 0) - (db+kb) ); + lowhightable[6] = wR2*wG2*SQUARE( (dr+ 0) - (dg+kg) ) + wR2*wB2*SQUARE( (dr+ 0) - (db+kb) ) + wG2*wB2*SQUARE( (dg+kg) - (db+kb) ); + lowhightable[7] = wR2*wG2*SQUARE( (dr+kr) - (dg+kg) ) + wR2*wB2*SQUARE( (dr+kr) - (db+kb) ) + wG2*wB2*SQUARE( (dg+kg) - (db+kb) ); + + float min_value = lowhightable[0]; + int min_index = 0; + + for(q = 1; q<8; q++) + { + if(lowhightable[q] < min_value) + { + min_value = lowhightable[q]; + min_index = q; + } + } + + float drh = red_high-red_average; + float dgh = green_high-green_average; + float dbh = blue_high-blue_average; + + low_color[0] = red_4bit_low; + low_color[1] = green_4bit_low; + low_color[2] = blue_4bit_low; + + high_color[0] = red_4bit_high; + high_color[1] = green_4bit_high; + high_color[2] = blue_4bit_high; + + switch(min_index) + { + case 0: + // Since the step size is always 17 in RGB444 format (15*17=255), + // kr = kg = kb = 17, which means that case 0 and case 7 will + // always have equal projected error. Choose the one that is + // closer to the desired color. + if(dr*dr + dg*dg + db*db > 3*8*8) + { + enc_color[0] = high_color[0]; + enc_color[1] = high_color[1]; + enc_color[2] = high_color[2]; + } + else + { + enc_color[0] = low_color[0]; + enc_color[1] = low_color[1]; + enc_color[2] = low_color[2]; + } + break; + case 1: + enc_color[0] = high_color[0]; + enc_color[1] = low_color[1]; + enc_color[2] = low_color[2]; + break; + case 2: + enc_color[0] = low_color[0]; + enc_color[1] = high_color[1]; + enc_color[2] = low_color[2]; + break; + case 3: + enc_color[0] = low_color[0]; + enc_color[1] = low_color[1]; + enc_color[2] = high_color[2]; + break; + case 4: + enc_color[0] = high_color[0]; + enc_color[1] = high_color[1]; + enc_color[2] = low_color[2]; + break; + case 5: + enc_color[0] = high_color[0]; + enc_color[1] = low_color[1]; + enc_color[2] = high_color[2]; + break; + case 6: + enc_color[0] = low_color[0]; + enc_color[1] = high_color[1]; + enc_color[2] = high_color[2]; + break; + case 7: + if(dr*dr + dg*dg + db*db > 3*8*8) + { + enc_color[0] = high_color[0]; + enc_color[1] = high_color[1]; + enc_color[2] = high_color[2]; + } + else + { + enc_color[0] = low_color[0]; + enc_color[1] = low_color[1]; + enc_color[2] = low_color[2]; + } + break; + } + // Expand 5-bit encoded color to 8-bit color + avg_color[0] = (enc_color[0] << 3) | (enc_color[0] >> 2); + avg_color[1] = (enc_color[1] << 3) | (enc_color[1] >> 2); + avg_color[2] = (enc_color[2] << 3) | (enc_color[2] >> 2); +} + +// The below code quantizes a float RGB value to RGB555. +// +// The format often allows a pixel to completely compensate an intensity error of the base +// color. Hence the closest RGB555 point may not be the best, and the code below uses +// this fact to find a better RGB555 color as the base color. +// +// (See the presentation http://www.jacobstrom.com/publications/PACKMAN.ppt for more info.) +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void quantize555ColorCombined(float *avg_col_in, int *enc_color, uint8 *avg_color) +{ + float dr, dg, db; + float kr, kg, kb; + float wR2, wG2, wB2; + uint8 low_color[3]; + uint8 high_color[3]; + float min_error=255*255*8*3; + float lowhightable[8]; + unsigned int best_table=0; + unsigned int best_index=0; + int q; + float kval = (float) (255.0/31.0); + + // These are the values that we want to have: + float red_average, green_average, blue_average; + + int red_5bit_low, green_5bit_low, blue_5bit_low; + int red_5bit_high, green_5bit_high, blue_5bit_high; + + // These are the values that we approximate with: + int red_low, green_low, blue_low; + int red_high, green_high, blue_high; + + red_average = avg_col_in[0]; + green_average = avg_col_in[1]; + blue_average = avg_col_in[2]; + + // Find the 5-bit reconstruction levels red_low, red_high + // so that red_average is in interval [red_low, red_high]. + // (The same with green and blue.) + + red_5bit_low = (int) (red_average/kval); + green_5bit_low = (int) (green_average/kval); + blue_5bit_low = (int) (blue_average/kval); + + red_5bit_high = CLAMP(0, red_5bit_low + 1, 31); + green_5bit_high = CLAMP(0, green_5bit_low + 1, 31); + blue_5bit_high = CLAMP(0, blue_5bit_low + 1, 31); + + red_low = (red_5bit_low << 3) | (red_5bit_low >> 2); + green_low = (green_5bit_low << 3) | (green_5bit_low >> 2); + blue_low = (blue_5bit_low << 3) | (blue_5bit_low >> 2); + + red_high = (red_5bit_high << 3) | (red_5bit_high >> 2); + green_high = (green_5bit_high << 3) | (green_5bit_high >> 2); + blue_high = (blue_5bit_high << 3) | (blue_5bit_high >> 2); + + kr = (float)red_high - (float)red_low; + kg = (float)green_high - (float)green_low; + kb = (float)blue_high - (float)blue_low; + + // Note that dr, dg, and db are all negative. + dr = red_low - red_average; + dg = green_low - green_average; + db = blue_low - blue_average; + + // Use straight (nonperceptive) weights. + wR2 = (float) 1.0; + wG2 = (float) 1.0; + wB2 = (float) 1.0; + + lowhightable[0] = wR2*wG2*SQUARE( (dr+ 0) - (dg+ 0) ) + wR2*wB2*SQUARE( (dr+ 0) - (db+ 0) ) + wG2*wB2*SQUARE( (dg+ 0) - (db+ 0) ); + lowhightable[1] = wR2*wG2*SQUARE( (dr+kr) - (dg+ 0) ) + wR2*wB2*SQUARE( (dr+kr) - (db+ 0) ) + wG2*wB2*SQUARE( (dg+ 0) - (db+ 0) ); + lowhightable[2] = wR2*wG2*SQUARE( (dr+ 0) - (dg+kg) ) + wR2*wB2*SQUARE( (dr+ 0) - (db+ 0) ) + wG2*wB2*SQUARE( (dg+kg) - (db+ 0) ); + lowhightable[3] = wR2*wG2*SQUARE( (dr+ 0) - (dg+ 0) ) + wR2*wB2*SQUARE( (dr+ 0) - (db+kb) ) + wG2*wB2*SQUARE( (dg+ 0) - (db+kb) ); + lowhightable[4] = wR2*wG2*SQUARE( (dr+kr) - (dg+kg) ) + wR2*wB2*SQUARE( (dr+kr) - (db+ 0) ) + wG2*wB2*SQUARE( (dg+kg) - (db+ 0) ); + lowhightable[5] = wR2*wG2*SQUARE( (dr+kr) - (dg+ 0) ) + wR2*wB2*SQUARE( (dr+kr) - (db+kb) ) + wG2*wB2*SQUARE( (dg+ 0) - (db+kb) ); + lowhightable[6] = wR2*wG2*SQUARE( (dr+ 0) - (dg+kg) ) + wR2*wB2*SQUARE( (dr+ 0) - (db+kb) ) + wG2*wB2*SQUARE( (dg+kg) - (db+kb) ); + lowhightable[7] = wR2*wG2*SQUARE( (dr+kr) - (dg+kg) ) + wR2*wB2*SQUARE( (dr+kr) - (db+kb) ) + wG2*wB2*SQUARE( (dg+kg) - (db+kb) ); + + float min_value = lowhightable[0]; + int min_index = 0; + + for(q = 1; q<8; q++) + { + if(lowhightable[q] < min_value) + { + min_value = lowhightable[q]; + min_index = q; + } + } + + float drh = red_high-red_average; + float dgh = green_high-green_average; + float dbh = blue_high-blue_average; + + low_color[0] = red_5bit_low; + low_color[1] = green_5bit_low; + low_color[2] = blue_5bit_low; + + high_color[0] = red_5bit_high; + high_color[1] = green_5bit_high; + high_color[2] = blue_5bit_high; + + switch(min_index) + { + case 0: + enc_color[0] = low_color[0]; + enc_color[1] = low_color[1]; + enc_color[2] = low_color[2]; + break; + case 1: + enc_color[0] = high_color[0]; + enc_color[1] = low_color[1]; + enc_color[2] = low_color[2]; + break; + case 2: + enc_color[0] = low_color[0]; + enc_color[1] = high_color[1]; + enc_color[2] = low_color[2]; + break; + case 3: + enc_color[0] = low_color[0]; + enc_color[1] = low_color[1]; + enc_color[2] = high_color[2]; + break; + case 4: + enc_color[0] = high_color[0]; + enc_color[1] = high_color[1]; + enc_color[2] = low_color[2]; + break; + case 5: + enc_color[0] = high_color[0]; + enc_color[1] = low_color[1]; + enc_color[2] = high_color[2]; + break; + case 6: + enc_color[0] = low_color[0]; + enc_color[1] = high_color[1]; + enc_color[2] = high_color[2]; + break; + case 7: + enc_color[0] = high_color[0]; + enc_color[1] = high_color[1]; + enc_color[2] = high_color[2]; + break; + } + + // Expand 5-bit encoded color to 8-bit color + avg_color[0] = (enc_color[0] << 3) | (enc_color[0] >> 2); + avg_color[1] = (enc_color[1] << 3) | (enc_color[1] >> 2); + avg_color[2] = (enc_color[2] << 3) | (enc_color[2] >> 2); + +} + +// The below code quantizes a float RGB value to RGB444. +// +// The format often allows a pixel to completely compensate an intensity error of the base +// color. Hence the closest RGB444 point may not be the best, and the code below uses +// this fact to find a better RGB444 color as the base color. +// +// (See the presentation http://www.jacobstrom.com/publications/PACKMAN.ppt for more info.) +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void quantize444ColorCombinedPerceptual(float *avg_col_in, int *enc_color, uint8 *avg_color) +{ + float dr, dg, db; + float kr, kg, kb; + float wR2, wG2, wB2; + uint8 low_color[3]; + uint8 high_color[3]; + float min_error=255*255*8*3; + float lowhightable[8]; + unsigned int best_table=0; + unsigned int best_index=0; + int q; + float kval = (float) (255.0/15.0); + + // These are the values that we want to have: + float red_average, green_average, blue_average; + + int red_4bit_low, green_4bit_low, blue_4bit_low; + int red_4bit_high, green_4bit_high, blue_4bit_high; + + // These are the values that we approximate with: + int red_low, green_low, blue_low; + int red_high, green_high, blue_high; + + red_average = avg_col_in[0]; + green_average = avg_col_in[1]; + blue_average = avg_col_in[2]; + + // Find the 5-bit reconstruction levels red_low, red_high + // so that red_average is in interval [red_low, red_high]. + // (The same with green and blue.) + + red_4bit_low = (int) (red_average/kval); + green_4bit_low = (int) (green_average/kval); + blue_4bit_low = (int) (blue_average/kval); + + red_4bit_high = CLAMP(0, red_4bit_low + 1, 15); + green_4bit_high = CLAMP(0, green_4bit_low + 1, 15); + blue_4bit_high = CLAMP(0, blue_4bit_low + 1, 15); + + red_low = (red_4bit_low << 4) | (red_4bit_low >> 0); + green_low = (green_4bit_low << 4) | (green_4bit_low >> 0); + blue_low = (blue_4bit_low << 4) | (blue_4bit_low >> 0); + + red_high = (red_4bit_high << 4) | (red_4bit_high >> 0); + green_high = (green_4bit_high << 4) | (green_4bit_high >> 0); + blue_high = (blue_4bit_high << 4) | (blue_4bit_high >> 0); + + low_color[0] = red_4bit_low; + low_color[1] = green_4bit_low; + low_color[2] = blue_4bit_low; + + high_color[0] = red_4bit_high; + high_color[1] = green_4bit_high; + high_color[2] = blue_4bit_high; + + kr = (float)red_high - (float)red_low; + kg = (float)green_high - (float)green_low; + kb = (float)blue_high- (float)blue_low; + + // Note that dr, dg, and db are all negative. + dr = red_low - red_average; + dg = green_low - green_average; + db = blue_low - blue_average; + + // Perceptual weights to use + wR2 = (float) PERCEPTUAL_WEIGHT_R_SQUARED; + wG2 = (float) PERCEPTUAL_WEIGHT_G_SQUARED; + wB2 = (float) PERCEPTUAL_WEIGHT_B_SQUARED; + + lowhightable[0] = wR2*wG2*SQUARE( (dr+ 0) - (dg+ 0) ) + wR2*wB2*SQUARE( (dr+ 0) - (db+ 0) ) + wG2*wB2*SQUARE( (dg+ 0) - (db+ 0) ); + lowhightable[1] = wR2*wG2*SQUARE( (dr+kr) - (dg+ 0) ) + wR2*wB2*SQUARE( (dr+kr) - (db+ 0) ) + wG2*wB2*SQUARE( (dg+ 0) - (db+ 0) ); + lowhightable[2] = wR2*wG2*SQUARE( (dr+ 0) - (dg+kg) ) + wR2*wB2*SQUARE( (dr+ 0) - (db+ 0) ) + wG2*wB2*SQUARE( (dg+kg) - (db+ 0) ); + lowhightable[3] = wR2*wG2*SQUARE( (dr+ 0) - (dg+ 0) ) + wR2*wB2*SQUARE( (dr+ 0) - (db+kb) ) + wG2*wB2*SQUARE( (dg+ 0) - (db+kb) ); + lowhightable[4] = wR2*wG2*SQUARE( (dr+kr) - (dg+kg) ) + wR2*wB2*SQUARE( (dr+kr) - (db+ 0) ) + wG2*wB2*SQUARE( (dg+kg) - (db+ 0) ); + lowhightable[5] = wR2*wG2*SQUARE( (dr+kr) - (dg+ 0) ) + wR2*wB2*SQUARE( (dr+kr) - (db+kb) ) + wG2*wB2*SQUARE( (dg+ 0) - (db+kb) ); + lowhightable[6] = wR2*wG2*SQUARE( (dr+ 0) - (dg+kg) ) + wR2*wB2*SQUARE( (dr+ 0) - (db+kb) ) + wG2*wB2*SQUARE( (dg+kg) - (db+kb) ); + lowhightable[7] = wR2*wG2*SQUARE( (dr+kr) - (dg+kg) ) + wR2*wB2*SQUARE( (dr+kr) - (db+kb) ) + wG2*wB2*SQUARE( (dg+kg) - (db+kb) ); + + float min_value = lowhightable[0]; + int min_index = 0; + + for(q = 1; q<8; q++) + { + if(lowhightable[q] < min_value) + { + min_value = lowhightable[q]; + min_index = q; + } + } + + float drh = red_high-red_average; + float dgh = green_high-green_average; + float dbh = blue_high-blue_average; + + switch(min_index) + { + case 0: + enc_color[0] = low_color[0]; + enc_color[1] = low_color[1]; + enc_color[2] = low_color[2]; + break; + case 1: + enc_color[0] = high_color[0]; + enc_color[1] = low_color[1]; + enc_color[2] = low_color[2]; + break; + case 2: + enc_color[0] = low_color[0]; + enc_color[1] = high_color[1]; + enc_color[2] = low_color[2]; + break; + case 3: + enc_color[0] = low_color[0]; + enc_color[1] = low_color[1]; + enc_color[2] = high_color[2]; + break; + case 4: + enc_color[0] = high_color[0]; + enc_color[1] = high_color[1]; + enc_color[2] = low_color[2]; + break; + case 5: + enc_color[0] = high_color[0]; + enc_color[1] = low_color[1]; + enc_color[2] = high_color[2]; + break; + case 6: + enc_color[0] = low_color[0]; + enc_color[1] = high_color[1]; + enc_color[2] = high_color[2]; + break; + case 7: + enc_color[0] = high_color[0]; + enc_color[1] = high_color[1]; + enc_color[2] = high_color[2]; + break; + } + + // Expand encoded color to eight bits + avg_color[0] = (enc_color[0] << 4) | enc_color[0]; + avg_color[1] = (enc_color[1] << 4) | enc_color[1]; + avg_color[2] = (enc_color[2] << 4) | enc_color[2]; +} + +// The below code quantizes a float RGB value to RGB555. +// +// The format often allows a pixel to completely compensate an intensity error of the base +// color. Hence the closest RGB555 point may not be the best, and the code below uses +// this fact to find a better RGB555 color as the base color. +// +// (See the presentation http://www.jacobstrom.com/publications/PACKMAN.ppt for more info.) +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void quantize555ColorCombinedPerceptual(float *avg_col_in, int *enc_color, uint8 *avg_color) +{ + float dr, dg, db; + float kr, kg, kb; + float wR2, wG2, wB2; + uint8 low_color[3]; + uint8 high_color[3]; + float min_error=255*255*8*3; + float lowhightable[8]; + unsigned int best_table=0; + unsigned int best_index=0; + int q; + float kval = (float) (255.0/31.0); + + // These are the values that we want to have: + float red_average, green_average, blue_average; + + int red_5bit_low, green_5bit_low, blue_5bit_low; + int red_5bit_high, green_5bit_high, blue_5bit_high; + + // These are the values that we approximate with: + int red_low, green_low, blue_low; + int red_high, green_high, blue_high; + + red_average = avg_col_in[0]; + green_average = avg_col_in[1]; + blue_average = avg_col_in[2]; + + // Find the 5-bit reconstruction levels red_low, red_high + // so that red_average is in interval [red_low, red_high]. + // (The same with green and blue.) + + red_5bit_low = (int) (red_average/kval); + green_5bit_low = (int) (green_average/kval); + blue_5bit_low = (int) (blue_average/kval); + + red_5bit_high = CLAMP(0, red_5bit_low + 1, 31); + green_5bit_high = CLAMP(0, green_5bit_low + 1, 31); + blue_5bit_high = CLAMP(0, blue_5bit_low + 1, 31); + + red_low = (red_5bit_low << 3) | (red_5bit_low >> 2); + green_low = (green_5bit_low << 3) | (green_5bit_low >> 2); + blue_low = (blue_5bit_low << 3) | (blue_5bit_low >> 2); + + red_high = (red_5bit_high << 3) | (red_5bit_high >> 2); + green_high = (green_5bit_high << 3) | (green_5bit_high >> 2); + blue_high = (blue_5bit_high << 3) | (blue_5bit_high >> 2); + + low_color[0] = red_5bit_low; + low_color[1] = green_5bit_low; + low_color[2] = blue_5bit_low; + + high_color[0] = red_5bit_high; + high_color[1] = green_5bit_high; + high_color[2] = blue_5bit_high; + + kr = (float)red_high - (float)red_low; + kg = (float)green_high - (float)green_low; + kb = (float)blue_high - (float)blue_low; + + // Note that dr, dg, and db are all negative. + dr = red_low - red_average; + dg = green_low - green_average; + db = blue_low - blue_average; + + // Perceptual weights to use + wR2 = (float) PERCEPTUAL_WEIGHT_R_SQUARED; + wG2 = (float) PERCEPTUAL_WEIGHT_G_SQUARED; + wB2 = (float) PERCEPTUAL_WEIGHT_B_SQUARED; + + lowhightable[0] = wR2*wG2*SQUARE( (dr+ 0) - (dg+ 0) ) + wR2*wB2*SQUARE( (dr+ 0) - (db+ 0) ) + wG2*wB2*SQUARE( (dg+ 0) - (db+ 0) ); + lowhightable[1] = wR2*wG2*SQUARE( (dr+kr) - (dg+ 0) ) + wR2*wB2*SQUARE( (dr+kr) - (db+ 0) ) + wG2*wB2*SQUARE( (dg+ 0) - (db+ 0) ); + lowhightable[2] = wR2*wG2*SQUARE( (dr+ 0) - (dg+kg) ) + wR2*wB2*SQUARE( (dr+ 0) - (db+ 0) ) + wG2*wB2*SQUARE( (dg+kg) - (db+ 0) ); + lowhightable[3] = wR2*wG2*SQUARE( (dr+ 0) - (dg+ 0) ) + wR2*wB2*SQUARE( (dr+ 0) - (db+kb) ) + wG2*wB2*SQUARE( (dg+ 0) - (db+kb) ); + lowhightable[4] = wR2*wG2*SQUARE( (dr+kr) - (dg+kg) ) + wR2*wB2*SQUARE( (dr+kr) - (db+ 0) ) + wG2*wB2*SQUARE( (dg+kg) - (db+ 0) ); + lowhightable[5] = wR2*wG2*SQUARE( (dr+kr) - (dg+ 0) ) + wR2*wB2*SQUARE( (dr+kr) - (db+kb) ) + wG2*wB2*SQUARE( (dg+ 0) - (db+kb) ); + lowhightable[6] = wR2*wG2*SQUARE( (dr+ 0) - (dg+kg) ) + wR2*wB2*SQUARE( (dr+ 0) - (db+kb) ) + wG2*wB2*SQUARE( (dg+kg) - (db+kb) ); + lowhightable[7] = wR2*wG2*SQUARE( (dr+kr) - (dg+kg) ) + wR2*wB2*SQUARE( (dr+kr) - (db+kb) ) + wG2*wB2*SQUARE( (dg+kg) - (db+kb) ); + + float min_value = lowhightable[0]; + int min_index = 0; + + for(q = 1; q<8; q++) + { + if(lowhightable[q] < min_value) + { + min_value = lowhightable[q]; + min_index = q; + } + } + + float drh = red_high-red_average; + float dgh = green_high-green_average; + float dbh = blue_high-blue_average; + + switch(min_index) + { + case 0: + enc_color[0] = low_color[0]; + enc_color[1] = low_color[1]; + enc_color[2] = low_color[2]; + break; + case 1: + enc_color[0] = high_color[0]; + enc_color[1] = low_color[1]; + enc_color[2] = low_color[2]; + break; + case 2: + enc_color[0] = low_color[0]; + enc_color[1] = high_color[1]; + enc_color[2] = low_color[2]; + break; + case 3: + enc_color[0] = low_color[0]; + enc_color[1] = low_color[1]; + enc_color[2] = high_color[2]; + break; + case 4: + enc_color[0] = high_color[0]; + enc_color[1] = high_color[1]; + enc_color[2] = low_color[2]; + break; + case 5: + enc_color[0] = high_color[0]; + enc_color[1] = low_color[1]; + enc_color[2] = high_color[2]; + break; + case 6: + enc_color[0] = low_color[0]; + enc_color[1] = high_color[1]; + enc_color[2] = high_color[2]; + break; + case 7: + enc_color[0] = high_color[0]; + enc_color[1] = high_color[1]; + enc_color[2] = high_color[2]; + break; + } + + // Expand 5-bit encoded color to 8-bit color + avg_color[0] = (enc_color[0] << 3) | (enc_color[0] >> 2); + avg_color[1] = (enc_color[1] << 3) | (enc_color[1] >> 2); + avg_color[2] = (enc_color[2] << 3) | (enc_color[2] >> 2); +} + +// Compresses the block using only the individual mode in ETC1/ETC2 using the average color as the base color. +// Uses a perceptual error metric. +// Uses fixed point arithmetics where 1000 equals 1.0 +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int compressBlockOnlyIndividualAveragePerceptual1000(uint8 *img,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2, int *best_enc_color1, int*best_enc_color2, int &best_flip, unsigned int &best_err_upper, unsigned int &best_err_lower, unsigned int &best_err_left, unsigned int &best_err_right, int *best_color_upper, int *best_color_lower, int *best_color_left, int *best_color_right) +{ + unsigned int compressed1_norm, compressed2_norm; + unsigned int compressed1_flip, compressed2_flip; + uint8 avg_color_quant1[3], avg_color_quant2[3]; + + float avg_color_float1[3],avg_color_float2[3]; + int enc_color1[3], enc_color2[3]; + unsigned int best_table_indices1=0, best_table_indices2=0; + unsigned int best_table1=0, best_table2=0; + int diffbit; + + unsigned int norm_err=0; + unsigned int flip_err=0; + unsigned int best_err; + + // First try normal blocks 2x4: + + computeAverageColor2x4noQuantFloat(img,width,height,startx,starty,avg_color_float1); + computeAverageColor2x4noQuantFloat(img,width,height,startx+2,starty,avg_color_float2); + + enc_color1[0] = int( JAS_ROUND(15.0*avg_color_float1[0]/255.0) ); + enc_color1[1] = int( JAS_ROUND(15.0*avg_color_float1[1]/255.0) ); + enc_color1[2] = int( JAS_ROUND(15.0*avg_color_float1[2]/255.0) ); + enc_color2[0] = int( JAS_ROUND(15.0*avg_color_float2[0]/255.0) ); + enc_color2[1] = int( JAS_ROUND(15.0*avg_color_float2[1]/255.0) ); + enc_color2[2] = int( JAS_ROUND(15.0*avg_color_float2[2]/255.0) ); + + diffbit = 0; + + avg_color_quant1[0] = enc_color1[0] << 4 | (enc_color1[0] ); + avg_color_quant1[1] = enc_color1[1] << 4 | (enc_color1[1] ); + avg_color_quant1[2] = enc_color1[2] << 4 | (enc_color1[2] ); + avg_color_quant2[0] = enc_color2[0] << 4 | (enc_color2[0] ); + avg_color_quant2[1] = enc_color2[1] << 4 | (enc_color2[1] ); + avg_color_quant2[2] = enc_color2[2] << 4 | (enc_color2[2] ); + + // Pack bits into the first word. + + // ETC1_RGB8_OES: + // + // a) bit layout in bits 63 through 32 if diffbit = 0 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | base col2 | base col1 | base col2 | base col1 | base col2 | table | table |diff|flip| + // | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // b) bit layout in bits 63 through 32 if diffbit = 1 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | dcol 2 | base col1 | dcol 2 | base col 1 | dcol 2 | table | table |diff|flip| + // | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // c) bit layout in bits 31 through 0 (in both cases) + // + // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // -------------------------------------------------------------------------------------------------- + // | most significant pixel index bits | least significant pixel index bits | + // | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a | + // -------------------------------------------------------------------------------------------------- + + compressed1_norm = 0; + PUTBITSHIGH( compressed1_norm, diffbit, 1, 33); + PUTBITSHIGH( compressed1_norm, enc_color1[0], 4, 63); + PUTBITSHIGH( compressed1_norm, enc_color1[1], 4, 55); + PUTBITSHIGH( compressed1_norm, enc_color1[2], 4, 47); + PUTBITSHIGH( compressed1_norm, enc_color2[0], 4, 59); + PUTBITSHIGH( compressed1_norm, enc_color2[1], 4, 51); + PUTBITSHIGH( compressed1_norm, enc_color2[1], 4, 43); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + best_enc_color1[0] = enc_color1[0]; + best_enc_color1[1] = enc_color1[1]; + best_enc_color1[2] = enc_color1[2]; + best_enc_color2[0] = enc_color2[0]; + best_enc_color2[1] = enc_color2[1]; + best_enc_color2[2] = enc_color2[2]; + + best_color_left[0] = enc_color1[0]; + best_color_left[1] = enc_color1[1]; + best_color_left[2] = enc_color1[2]; + best_color_right[0] = enc_color2[0]; + best_color_right[1] = enc_color2[1]; + best_color_right[2] = enc_color2[2]; + + norm_err = 0; + + // left part of block + best_err_left = tryalltables_3bittable2x4percep1000(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + norm_err = best_err_left; + + // right part of block + best_err_right = tryalltables_3bittable2x4percep1000(img,width,height,startx+2,starty,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + norm_err += best_err_right; + + PUTBITSHIGH( compressed1_norm, best_table1, 3, 39); + PUTBITSHIGH( compressed1_norm, best_table2, 3, 36); + PUTBITSHIGH( compressed1_norm, 0, 1, 32); + + compressed2_norm = 0; + PUTBITS( compressed2_norm, (best_pixel_indices1_MSB ), 8, 23); + PUTBITS( compressed2_norm, (best_pixel_indices2_MSB ), 8, 31); + PUTBITS( compressed2_norm, (best_pixel_indices1_LSB ), 8, 7); + PUTBITS( compressed2_norm, (best_pixel_indices2_LSB ), 8, 15); + + // Now try flipped blocks 4x2: + + computeAverageColor4x2noQuantFloat(img,width,height,startx,starty,avg_color_float1); + computeAverageColor4x2noQuantFloat(img,width,height,startx,starty+2,avg_color_float2); + + // First test if avg_color1 is similar enough to avg_color2 so that + // we can use differential coding of colors. + + enc_color1[0] = int( JAS_ROUND(15.0*avg_color_float1[0]/255.0) ); + enc_color1[1] = int( JAS_ROUND(15.0*avg_color_float1[1]/255.0) ); + enc_color1[2] = int( JAS_ROUND(15.0*avg_color_float1[2]/255.0) ); + enc_color2[0] = int( JAS_ROUND(15.0*avg_color_float2[0]/255.0) ); + enc_color2[1] = int( JAS_ROUND(15.0*avg_color_float2[1]/255.0) ); + enc_color2[2] = int( JAS_ROUND(15.0*avg_color_float2[2]/255.0) ); + + best_color_upper[0] = enc_color1[0]; + best_color_upper[1] = enc_color1[1]; + best_color_upper[2] = enc_color1[2]; + best_color_lower[0] = enc_color2[0]; + best_color_lower[1] = enc_color2[1]; + best_color_lower[2] = enc_color2[2]; + + diffbit = 0; + + avg_color_quant1[0] = enc_color1[0] << 4 | (enc_color1[0] ); + avg_color_quant1[1] = enc_color1[1] << 4 | (enc_color1[1] ); + avg_color_quant1[2] = enc_color1[2] << 4 | (enc_color1[2] ); + avg_color_quant2[0] = enc_color2[0] << 4 | (enc_color2[0] ); + avg_color_quant2[1] = enc_color2[1] << 4 | (enc_color2[1] ); + avg_color_quant2[2] = enc_color2[2] << 4 | (enc_color2[2] ); + + // Pack bits into the first word. + + compressed1_flip = 0; + PUTBITSHIGH( compressed1_flip, diffbit, 1, 33); + PUTBITSHIGH( compressed1_flip, enc_color1[0], 4, 63); + PUTBITSHIGH( compressed1_flip, enc_color1[1], 4, 55); + PUTBITSHIGH( compressed1_flip, enc_color1[2], 4, 47); + PUTBITSHIGH( compressed1_flip, enc_color2[0], 4, 49); + PUTBITSHIGH( compressed1_flip, enc_color2[1], 4, 51); + PUTBITSHIGH( compressed1_flip, enc_color2[2], 4, 43); + + // upper part of block + best_err_upper = tryalltables_3bittable4x2percep1000(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + flip_err = best_err_upper; + // lower part of block + best_err_lower = tryalltables_3bittable4x2percep1000(img,width,height,startx,starty+2,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + flip_err += best_err_lower; + + PUTBITSHIGH( compressed1_flip, best_table1, 3, 39); + PUTBITSHIGH( compressed1_flip, best_table2, 3, 36); + PUTBITSHIGH( compressed1_flip, 1, 1, 32); + + best_pixel_indices1_MSB |= (best_pixel_indices2_MSB << 2); + best_pixel_indices1_LSB |= (best_pixel_indices2_LSB << 2); + + compressed2_flip = ((best_pixel_indices1_MSB & 0xffff) << 16) | (best_pixel_indices1_LSB & 0xffff); + + // Now lets see which is the best table to use. Only 8 tables are possible. + + if(norm_err <= flip_err) + { + compressed1 = compressed1_norm | 0; + compressed2 = compressed2_norm; + best_err = norm_err; + best_flip = 0; + } + else + { + compressed1 = compressed1_flip | 1; + compressed2 = compressed2_flip; + best_err = flip_err; + best_enc_color1[0] = enc_color1[0]; + best_enc_color1[1] = enc_color1[1]; + best_enc_color1[2] = enc_color1[2]; + best_enc_color2[0] = enc_color2[0]; + best_enc_color2[1] = enc_color2[1]; + best_enc_color2[2] = enc_color2[2]; + best_flip = 1; + } + return best_err; +} + +// Compresses the block using only the individual mode in ETC1/ETC2 using the average color as the base color. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +int compressBlockOnlyIndividualAverage(uint8 *img,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2, int *best_enc_color1, int*best_enc_color2, int &best_flip, unsigned int &best_err_upper, unsigned int &best_err_lower, unsigned int &best_err_left, unsigned int &best_err_right, int *best_color_upper, int *best_color_lower, int *best_color_left, int *best_color_right) +{ + unsigned int compressed1_norm, compressed2_norm; + unsigned int compressed1_flip, compressed2_flip; + uint8 avg_color_quant1[3], avg_color_quant2[3]; + + float avg_color_float1[3],avg_color_float2[3]; + int enc_color1[3], enc_color2[3]; + int min_error=255*255*8*3; + unsigned int best_table_indices1=0, best_table_indices2=0; + unsigned int best_table1=0, best_table2=0; + int diffbit; + + int norm_err=0; + int flip_err=0; + int best_err; + + // First try normal blocks 2x4: + + computeAverageColor2x4noQuantFloat(img,width,height,startx,starty,avg_color_float1); + computeAverageColor2x4noQuantFloat(img,width,height,startx+2,starty,avg_color_float2); + + enc_color1[0] = int( JAS_ROUND(15.0*avg_color_float1[0]/255.0) ); + enc_color1[1] = int( JAS_ROUND(15.0*avg_color_float1[1]/255.0) ); + enc_color1[2] = int( JAS_ROUND(15.0*avg_color_float1[2]/255.0) ); + enc_color2[0] = int( JAS_ROUND(15.0*avg_color_float2[0]/255.0) ); + enc_color2[1] = int( JAS_ROUND(15.0*avg_color_float2[1]/255.0) ); + enc_color2[2] = int( JAS_ROUND(15.0*avg_color_float2[2]/255.0) ); + + diffbit = 0; + + avg_color_quant1[0] = enc_color1[0] << 4 | (enc_color1[0] ); + avg_color_quant1[1] = enc_color1[1] << 4 | (enc_color1[1] ); + avg_color_quant1[2] = enc_color1[2] << 4 | (enc_color1[2] ); + avg_color_quant2[0] = enc_color2[0] << 4 | (enc_color2[0] ); + avg_color_quant2[1] = enc_color2[1] << 4 | (enc_color2[1] ); + avg_color_quant2[2] = enc_color2[2] << 4 | (enc_color2[2] ); + + // Pack bits into the first word. + + // ETC1_RGB8_OES: + // + // a) bit layout in bits 63 through 32 if diffbit = 0 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | base col2 | base col1 | base col2 | base col1 | base col2 | table | table |diff|flip| + // | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // b) bit layout in bits 63 through 32 if diffbit = 1 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | dcol 2 | base col1 | dcol 2 | base col 1 | dcol 2 | table | table |diff|flip| + // | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // c) bit layout in bits 31 through 0 (in both cases) + // + // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // -------------------------------------------------------------------------------------------------- + // | most significant pixel index bits | least significant pixel index bits | + // | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a | + // -------------------------------------------------------------------------------------------------- + + compressed1_norm = 0; + PUTBITSHIGH( compressed1_norm, diffbit, 1, 33); + PUTBITSHIGH( compressed1_norm, enc_color1[0], 4, 63); + PUTBITSHIGH( compressed1_norm, enc_color1[1], 4, 55); + PUTBITSHIGH( compressed1_norm, enc_color1[2], 4, 47); + PUTBITSHIGH( compressed1_norm, enc_color2[0], 4, 59); + PUTBITSHIGH( compressed1_norm, enc_color2[1], 4, 51); + PUTBITSHIGH( compressed1_norm, enc_color2[1], 4, 43); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + best_enc_color1[0] = enc_color1[0]; + best_enc_color1[1] = enc_color1[1]; + best_enc_color1[2] = enc_color1[2]; + best_enc_color2[0] = enc_color2[0]; + best_enc_color2[1] = enc_color2[1]; + best_enc_color2[2] = enc_color2[2]; + best_color_left[0] = enc_color1[0]; + best_color_left[1] = enc_color1[1]; + best_color_left[2] = enc_color1[2]; + best_color_right[0] = enc_color2[0]; + best_color_right[1] = enc_color2[1]; + best_color_right[2] = enc_color2[2]; + + norm_err = 0; + + // left part of block + best_err_left = tryalltables_3bittable2x4(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + norm_err = best_err_left; + + // right part of block + best_err_right = tryalltables_3bittable2x4(img,width,height,startx+2,starty,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + norm_err += best_err_right; + + PUTBITSHIGH( compressed1_norm, best_table1, 3, 39); + PUTBITSHIGH( compressed1_norm, best_table2, 3, 36); + PUTBITSHIGH( compressed1_norm, 0, 1, 32); + + compressed2_norm = 0; + PUTBITS( compressed2_norm, (best_pixel_indices1_MSB ), 8, 23); + PUTBITS( compressed2_norm, (best_pixel_indices2_MSB ), 8, 31); + PUTBITS( compressed2_norm, (best_pixel_indices1_LSB ), 8, 7); + PUTBITS( compressed2_norm, (best_pixel_indices2_LSB ), 8, 15); + + + // Now try flipped blocks 4x2: + + computeAverageColor4x2noQuantFloat(img,width,height,startx,starty,avg_color_float1); + computeAverageColor4x2noQuantFloat(img,width,height,startx,starty+2,avg_color_float2); + + // First test if avg_color1 is similar enough to avg_color2 so that + // we can use differential coding of colors. + + enc_color1[0] = int( JAS_ROUND(15.0*avg_color_float1[0]/255.0) ); + enc_color1[1] = int( JAS_ROUND(15.0*avg_color_float1[1]/255.0) ); + enc_color1[2] = int( JAS_ROUND(15.0*avg_color_float1[2]/255.0) ); + enc_color2[0] = int( JAS_ROUND(15.0*avg_color_float2[0]/255.0) ); + enc_color2[1] = int( JAS_ROUND(15.0*avg_color_float2[1]/255.0) ); + enc_color2[2] = int( JAS_ROUND(15.0*avg_color_float2[2]/255.0) ); + + best_color_upper[0] = enc_color1[0]; + best_color_upper[1] = enc_color1[1]; + best_color_upper[2] = enc_color1[2]; + best_color_lower[0] = enc_color2[0]; + best_color_lower[1] = enc_color2[1]; + best_color_lower[2] = enc_color2[2]; + + diffbit = 0; + + avg_color_quant1[0] = enc_color1[0] << 4 | (enc_color1[0] ); + avg_color_quant1[1] = enc_color1[1] << 4 | (enc_color1[1] ); + avg_color_quant1[2] = enc_color1[2] << 4 | (enc_color1[2] ); + avg_color_quant2[0] = enc_color2[0] << 4 | (enc_color2[0] ); + avg_color_quant2[1] = enc_color2[1] << 4 | (enc_color2[1] ); + avg_color_quant2[2] = enc_color2[2] << 4 | (enc_color2[2] ); + + // Pack bits into the first word. + + compressed1_flip = 0; + PUTBITSHIGH( compressed1_flip, diffbit, 1, 33); + PUTBITSHIGH( compressed1_flip, enc_color1[0], 4, 63); + PUTBITSHIGH( compressed1_flip, enc_color1[1], 4, 55); + PUTBITSHIGH( compressed1_flip, enc_color1[2], 4, 47); + PUTBITSHIGH( compressed1_flip, enc_color2[0], 4, 49); + PUTBITSHIGH( compressed1_flip, enc_color2[1], 4, 51); + PUTBITSHIGH( compressed1_flip, enc_color2[2], 4, 43); + + // upper part of block + best_err_upper = tryalltables_3bittable4x2(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + flip_err = best_err_upper; + // lower part of block + best_err_lower = tryalltables_3bittable4x2(img,width,height,startx,starty+2,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + flip_err += best_err_lower; + + PUTBITSHIGH( compressed1_flip, best_table1, 3, 39); + PUTBITSHIGH( compressed1_flip, best_table2, 3, 36); + PUTBITSHIGH( compressed1_flip, 1, 1, 32); + + best_pixel_indices1_MSB |= (best_pixel_indices2_MSB << 2); + best_pixel_indices1_LSB |= (best_pixel_indices2_LSB << 2); + + compressed2_flip = ((best_pixel_indices1_MSB & 0xffff) << 16) | (best_pixel_indices1_LSB & 0xffff); + + // Now lets see which is the best table to use. Only 8 tables are possible. + + if(norm_err <= flip_err) + { + compressed1 = compressed1_norm | 0; + compressed2 = compressed2_norm; + best_err = norm_err; + best_flip = 0; + } + else + { + compressed1 = compressed1_flip | 1; + compressed2 = compressed2_flip; + best_err = flip_err; + best_enc_color1[0] = enc_color1[0]; + best_enc_color1[1] = enc_color1[1]; + best_enc_color1[2] = enc_color1[2]; + best_enc_color2[0] = enc_color2[0]; + best_enc_color2[1] = enc_color2[1]; + best_enc_color2[2] = enc_color2[2]; + best_flip = 1; + } + return best_err; +} + +// Compresses the block using either the individual or differential mode in ETC1/ETC2 +// Uses the average color as the base color in each half-block. +// Tries both flipped and unflipped. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void compressBlockDiffFlipAverage(uint8 *img,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2) +{ + unsigned int compressed1_norm, compressed2_norm; + unsigned int compressed1_flip, compressed2_flip; + uint8 avg_color_quant1[3], avg_color_quant2[3]; + + float avg_color_float1[3],avg_color_float2[3]; + int enc_color1[3], enc_color2[3], diff[3]; + int min_error=255*255*8*3; + unsigned int best_table_indices1=0, best_table_indices2=0; + unsigned int best_table1=0, best_table2=0; + int diffbit; + + int norm_err=0; + int flip_err=0; + + // First try normal blocks 2x4: + computeAverageColor2x4noQuantFloat(img,width,height,startx,starty,avg_color_float1); + computeAverageColor2x4noQuantFloat(img,width,height,startx+2,starty,avg_color_float2); + + // First test if avg_color1 is similar enough to avg_color2 so that + // we can use differential coding of colors. + + float eps; + + enc_color1[0] = int( JAS_ROUND(31.0*avg_color_float1[0]/255.0) ); + enc_color1[1] = int( JAS_ROUND(31.0*avg_color_float1[1]/255.0) ); + enc_color1[2] = int( JAS_ROUND(31.0*avg_color_float1[2]/255.0) ); + enc_color2[0] = int( JAS_ROUND(31.0*avg_color_float2[0]/255.0) ); + enc_color2[1] = int( JAS_ROUND(31.0*avg_color_float2[1]/255.0) ); + enc_color2[2] = int( JAS_ROUND(31.0*avg_color_float2[2]/255.0) ); + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + if( (diff[0] >= -4) && (diff[0] <= 3) && (diff[1] >= -4) && (diff[1] <= 3) && (diff[2] >= -4) && (diff[2] <= 3) ) + { + diffbit = 1; + + // The difference to be coded: + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + avg_color_quant1[0] = enc_color1[0] << 3 | (enc_color1[0] >> 2); + avg_color_quant1[1] = enc_color1[1] << 3 | (enc_color1[1] >> 2); + avg_color_quant1[2] = enc_color1[2] << 3 | (enc_color1[2] >> 2); + avg_color_quant2[0] = enc_color2[0] << 3 | (enc_color2[0] >> 2); + avg_color_quant2[1] = enc_color2[1] << 3 | (enc_color2[1] >> 2); + avg_color_quant2[2] = enc_color2[2] << 3 | (enc_color2[2] >> 2); + + // Pack bits into the first word. + + // ETC1_RGB8_OES: + // + // a) bit layout in bits 63 through 32 if diffbit = 0 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | base col2 | base col1 | base col2 | base col1 | base col2 | table | table |diff|flip| + // | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // b) bit layout in bits 63 through 32 if diffbit = 1 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | dcol 2 | base col1 | dcol 2 | base col 1 | dcol 2 | table | table |diff|flip| + // | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // c) bit layout in bits 31 through 0 (in both cases) + // + // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // -------------------------------------------------------------------------------------------------- + // | most significant pixel index bits | least significant pixel index bits | + // | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a | + // -------------------------------------------------------------------------------------------------- + + compressed1_norm = 0; + PUTBITSHIGH( compressed1_norm, diffbit, 1, 33); + PUTBITSHIGH( compressed1_norm, enc_color1[0], 5, 63); + PUTBITSHIGH( compressed1_norm, enc_color1[1], 5, 55); + PUTBITSHIGH( compressed1_norm, enc_color1[2], 5, 47); + PUTBITSHIGH( compressed1_norm, diff[0], 3, 58); + PUTBITSHIGH( compressed1_norm, diff[1], 3, 50); + PUTBITSHIGH( compressed1_norm, diff[2], 3, 42); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + norm_err = 0; + + // left part of block + norm_err = tryalltables_3bittable2x4(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + + // right part of block + norm_err += tryalltables_3bittable2x4(img,width,height,startx+2,starty,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_norm, best_table1, 3, 39); + PUTBITSHIGH( compressed1_norm, best_table2, 3, 36); + PUTBITSHIGH( compressed1_norm, 0, 1, 32); + + compressed2_norm = 0; + PUTBITS( compressed2_norm, (best_pixel_indices1_MSB ), 8, 23); + PUTBITS( compressed2_norm, (best_pixel_indices2_MSB ), 8, 31); + PUTBITS( compressed2_norm, (best_pixel_indices1_LSB ), 8, 7); + PUTBITS( compressed2_norm, (best_pixel_indices2_LSB ), 8, 15); + } + else + { + diffbit = 0; + // The difference is bigger than what fits in 555 plus delta-333, so we will have + // to deal with 444 444. + + eps = (float) 0.0001; + + enc_color1[0] = int( ((float) avg_color_float1[0] / (17.0)) +0.5 + eps); + enc_color1[1] = int( ((float) avg_color_float1[1] / (17.0)) +0.5 + eps); + enc_color1[2] = int( ((float) avg_color_float1[2] / (17.0)) +0.5 + eps); + enc_color2[0] = int( ((float) avg_color_float2[0] / (17.0)) +0.5 + eps); + enc_color2[1] = int( ((float) avg_color_float2[1] / (17.0)) +0.5 + eps); + enc_color2[2] = int( ((float) avg_color_float2[2] / (17.0)) +0.5 + eps); + avg_color_quant1[0] = enc_color1[0] << 4 | enc_color1[0]; + avg_color_quant1[1] = enc_color1[1] << 4 | enc_color1[1]; + avg_color_quant1[2] = enc_color1[2] << 4 | enc_color1[2]; + avg_color_quant2[0] = enc_color2[0] << 4 | enc_color2[0]; + avg_color_quant2[1] = enc_color2[1] << 4 | enc_color2[1]; + avg_color_quant2[2] = enc_color2[2] << 4 | enc_color2[2]; + + // Pack bits into the first word. + + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | base col2 | base col1 | base col2 | base col1 | base col2 | table | table |diff|flip| + // | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + + compressed1_norm = 0; + PUTBITSHIGH( compressed1_norm, diffbit, 1, 33); + PUTBITSHIGH( compressed1_norm, enc_color1[0], 4, 63); + PUTBITSHIGH( compressed1_norm, enc_color1[1], 4, 55); + PUTBITSHIGH( compressed1_norm, enc_color1[2], 4, 47); + PUTBITSHIGH( compressed1_norm, enc_color2[0], 4, 59); + PUTBITSHIGH( compressed1_norm, enc_color2[1], 4, 51); + PUTBITSHIGH( compressed1_norm, enc_color2[2], 4, 43); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + // left part of block + norm_err = tryalltables_3bittable2x4(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + + // right part of block + norm_err += tryalltables_3bittable2x4(img,width,height,startx+2,starty,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_norm, best_table1, 3, 39); + PUTBITSHIGH( compressed1_norm, best_table2, 3, 36); + PUTBITSHIGH( compressed1_norm, 0, 1, 32); + + compressed2_norm = 0; + PUTBITS( compressed2_norm, (best_pixel_indices1_MSB ), 8, 23); + PUTBITS( compressed2_norm, (best_pixel_indices2_MSB ), 8, 31); + PUTBITS( compressed2_norm, (best_pixel_indices1_LSB ), 8, 7); + PUTBITS( compressed2_norm, (best_pixel_indices2_LSB ), 8, 15); + } + + // Now try flipped blocks 4x2: + + computeAverageColor4x2noQuantFloat(img,width,height,startx,starty,avg_color_float1); + computeAverageColor4x2noQuantFloat(img,width,height,startx,starty+2,avg_color_float2); + + // First test if avg_color1 is similar enough to avg_color2 so that + // we can use differential coding of colors. + + enc_color1[0] = int( JAS_ROUND(31.0*avg_color_float1[0]/255.0) ); + enc_color1[1] = int( JAS_ROUND(31.0*avg_color_float1[1]/255.0) ); + enc_color1[2] = int( JAS_ROUND(31.0*avg_color_float1[2]/255.0) ); + enc_color2[0] = int( JAS_ROUND(31.0*avg_color_float2[0]/255.0) ); + enc_color2[1] = int( JAS_ROUND(31.0*avg_color_float2[1]/255.0) ); + enc_color2[2] = int( JAS_ROUND(31.0*avg_color_float2[2]/255.0) ); + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + if( (diff[0] >= -4) && (diff[0] <= 3) && (diff[1] >= -4) && (diff[1] <= 3) && (diff[2] >= -4) && (diff[2] <= 3) ) + { + diffbit = 1; + + // The difference to be coded: + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + avg_color_quant1[0] = enc_color1[0] << 3 | (enc_color1[0] >> 2); + avg_color_quant1[1] = enc_color1[1] << 3 | (enc_color1[1] >> 2); + avg_color_quant1[2] = enc_color1[2] << 3 | (enc_color1[2] >> 2); + avg_color_quant2[0] = enc_color2[0] << 3 | (enc_color2[0] >> 2); + avg_color_quant2[1] = enc_color2[1] << 3 | (enc_color2[1] >> 2); + avg_color_quant2[2] = enc_color2[2] << 3 | (enc_color2[2] >> 2); + + // Pack bits into the first word. + + compressed1_flip = 0; + PUTBITSHIGH( compressed1_flip, diffbit, 1, 33); + PUTBITSHIGH( compressed1_flip, enc_color1[0], 5, 63); + PUTBITSHIGH( compressed1_flip, enc_color1[1], 5, 55); + PUTBITSHIGH( compressed1_flip, enc_color1[2], 5, 47); + PUTBITSHIGH( compressed1_flip, diff[0], 3, 58); + PUTBITSHIGH( compressed1_flip, diff[1], 3, 50); + PUTBITSHIGH( compressed1_flip, diff[2], 3, 42); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + // upper part of block + flip_err = tryalltables_3bittable4x2(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + // lower part of block + flip_err += tryalltables_3bittable4x2(img,width,height,startx,starty+2,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_flip, best_table1, 3, 39); + PUTBITSHIGH( compressed1_flip, best_table2, 3, 36); + PUTBITSHIGH( compressed1_flip, 1, 1, 32); + + best_pixel_indices1_MSB |= (best_pixel_indices2_MSB << 2); + best_pixel_indices1_LSB |= (best_pixel_indices2_LSB << 2); + + compressed2_flip = ((best_pixel_indices1_MSB & 0xffff) << 16) | (best_pixel_indices1_LSB & 0xffff); + } + else + { + diffbit = 0; + // The difference is bigger than what fits in 555 plus delta-333, so we will have + // to deal with 444 444. + eps = (float) 0.0001; + + enc_color1[0] = int( ((float) avg_color_float1[0] / (17.0)) +0.5 + eps); + enc_color1[1] = int( ((float) avg_color_float1[1] / (17.0)) +0.5 + eps); + enc_color1[2] = int( ((float) avg_color_float1[2] / (17.0)) +0.5 + eps); + enc_color2[0] = int( ((float) avg_color_float2[0] / (17.0)) +0.5 + eps); + enc_color2[1] = int( ((float) avg_color_float2[1] / (17.0)) +0.5 + eps); + enc_color2[2] = int( ((float) avg_color_float2[2] / (17.0)) +0.5 + eps); + + avg_color_quant1[0] = enc_color1[0] << 4 | enc_color1[0]; + avg_color_quant1[1] = enc_color1[1] << 4 | enc_color1[1]; + avg_color_quant1[2] = enc_color1[2] << 4 | enc_color1[2]; + avg_color_quant2[0] = enc_color2[0] << 4 | enc_color2[0]; + avg_color_quant2[1] = enc_color2[1] << 4 | enc_color2[1]; + avg_color_quant2[2] = enc_color2[2] << 4 | enc_color2[2]; + + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | base col2 | base col1 | base col2 | base col1 | base col2 | table | table |diff|flip| + // | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + + + // Pack bits into the first word. + + compressed1_flip = 0; + PUTBITSHIGH( compressed1_flip, diffbit, 1, 33); + PUTBITSHIGH( compressed1_flip, enc_color1[0], 4, 63); + PUTBITSHIGH( compressed1_flip, enc_color1[1], 4, 55); + PUTBITSHIGH( compressed1_flip, enc_color1[2], 4, 47); + PUTBITSHIGH( compressed1_flip, enc_color2[0], 4, 59); + PUTBITSHIGH( compressed1_flip, enc_color2[1], 4, 51); + PUTBITSHIGH( compressed1_flip, enc_color2[2], 4, 43); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + // upper part of block + flip_err = tryalltables_3bittable4x2(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + // lower part of block + flip_err += tryalltables_3bittable4x2(img,width,height,startx,starty+2,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_flip, best_table1, 3, 39); + PUTBITSHIGH( compressed1_flip, best_table2, 3, 36); + PUTBITSHIGH( compressed1_flip, 1, 1, 32); + + best_pixel_indices1_MSB |= (best_pixel_indices2_MSB << 2); + best_pixel_indices1_LSB |= (best_pixel_indices2_LSB << 2); + + compressed2_flip = ((best_pixel_indices1_MSB & 0xffff) << 16) | (best_pixel_indices1_LSB & 0xffff); + } + + // Now lets see which is the best table to use. Only 8 tables are possible. + + if(norm_err <= flip_err) + { + compressed1 = compressed1_norm | 0; + compressed2 = compressed2_norm; + } + else + { + compressed1 = compressed1_flip | 1; + compressed2 = compressed2_flip; + } +} + +// Compresses the block using only the differential mode in ETC1/ETC2 +// Uses the average color as the base color in each half-block. +// If average colors are too different, use the average color of the entire block in both half-blocks. +// Tries both flipped and unflipped. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +int compressBlockOnlyDiffFlipAverage(uint8 *img,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2, int *best_enc_color1, int*best_enc_color2, int &best_flip) +{ + unsigned int compressed1_norm, compressed2_norm; + unsigned int compressed1_flip, compressed2_flip; + uint8 avg_color_quant1[3], avg_color_quant2[3]; + + float avg_color_float1[3],avg_color_float2[3]; + int enc_color1[3], enc_color2[3], diff[3]; + int min_error=255*255*8*3; + unsigned int best_table_indices1=0, best_table_indices2=0; + unsigned int best_table1=0, best_table2=0; + int diffbit; + + int norm_err=0; + int flip_err=0; + int best_err; + + // First try normal blocks 2x4: + + computeAverageColor2x4noQuantFloat(img,width,height,startx,starty,avg_color_float1); + computeAverageColor2x4noQuantFloat(img,width,height,startx+2,starty,avg_color_float2); + + // First test if avg_color1 is similar enough to avg_color2 so that + // we can use differential coding of colors. + + enc_color1[0] = int( JAS_ROUND(31.0*avg_color_float1[0]/255.0) ); + enc_color1[1] = int( JAS_ROUND(31.0*avg_color_float1[1]/255.0) ); + enc_color1[2] = int( JAS_ROUND(31.0*avg_color_float1[2]/255.0) ); + enc_color2[0] = int( JAS_ROUND(31.0*avg_color_float2[0]/255.0) ); + enc_color2[1] = int( JAS_ROUND(31.0*avg_color_float2[1]/255.0) ); + enc_color2[2] = int( JAS_ROUND(31.0*avg_color_float2[2]/255.0) ); + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + if( !((diff[0] >= -4) && (diff[0] <= 3) && (diff[1] >= -4) && (diff[1] <= 3) && (diff[2] >= -4) && (diff[2] <= 3)) ) + { + // The colors are too different. Use the same color in both blocks. + enc_color1[0] = int( JAS_ROUND(31.0*((avg_color_float1[0]+avg_color_float2[0])/2.0)/255.0) ); + enc_color1[1] = int( JAS_ROUND(31.0*((avg_color_float1[1]+avg_color_float2[1])/2.0)/255.0) ); + enc_color1[2] = int( JAS_ROUND(31.0*((avg_color_float1[2]+avg_color_float2[2])/2.0)/255.0) ); + enc_color2[0] = enc_color1[0]; + enc_color2[1] = enc_color1[1]; + enc_color2[2] = enc_color1[2]; + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + } + + diffbit = 1; + + // The difference to be coded: + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + avg_color_quant1[0] = enc_color1[0] << 3 | (enc_color1[0] >> 2); + avg_color_quant1[1] = enc_color1[1] << 3 | (enc_color1[1] >> 2); + avg_color_quant1[2] = enc_color1[2] << 3 | (enc_color1[2] >> 2); + avg_color_quant2[0] = enc_color2[0] << 3 | (enc_color2[0] >> 2); + avg_color_quant2[1] = enc_color2[1] << 3 | (enc_color2[1] >> 2); + avg_color_quant2[2] = enc_color2[2] << 3 | (enc_color2[2] >> 2); + + // Pack bits into the first word. + + // ETC1_RGB8_OES: + // + // a) bit layout in bits 63 through 32 if diffbit = 0 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | base col2 | base col1 | base col2 | base col1 | base col2 | table | table |diff|flip| + // | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // b) bit layout in bits 63 through 32 if diffbit = 1 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | dcol 2 | base col1 | dcol 2 | base col 1 | dcol 2 | table | table |diff|flip| + // | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // c) bit layout in bits 31 through 0 (in both cases) + // + // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // -------------------------------------------------------------------------------------------------- + // | most significant pixel index bits | least significant pixel index bits | + // | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a | + // -------------------------------------------------------------------------------------------------- + + compressed1_norm = 0; + PUTBITSHIGH( compressed1_norm, diffbit, 1, 33); + PUTBITSHIGH( compressed1_norm, enc_color1[0], 5, 63); + PUTBITSHIGH( compressed1_norm, enc_color1[1], 5, 55); + PUTBITSHIGH( compressed1_norm, enc_color1[2], 5, 47); + PUTBITSHIGH( compressed1_norm, diff[0], 3, 58); + PUTBITSHIGH( compressed1_norm, diff[1], 3, 50); + PUTBITSHIGH( compressed1_norm, diff[2], 3, 42); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + best_enc_color1[0] = enc_color1[0]; + best_enc_color1[1] = enc_color1[1]; + best_enc_color1[2] = enc_color1[2]; + best_enc_color2[0] = enc_color2[0]; + best_enc_color2[1] = enc_color2[1]; + best_enc_color2[2] = enc_color2[2]; + + norm_err = 0; + + // left part of block + norm_err = tryalltables_3bittable2x4(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + + // right part of block + norm_err += tryalltables_3bittable2x4(img,width,height,startx+2,starty,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_norm, best_table1, 3, 39); + PUTBITSHIGH( compressed1_norm, best_table2, 3, 36); + PUTBITSHIGH( compressed1_norm, 0, 1, 32); + + compressed2_norm = 0; + PUTBITS( compressed2_norm, (best_pixel_indices1_MSB ), 8, 23); + PUTBITS( compressed2_norm, (best_pixel_indices2_MSB ), 8, 31); + PUTBITS( compressed2_norm, (best_pixel_indices1_LSB ), 8, 7); + PUTBITS( compressed2_norm, (best_pixel_indices2_LSB ), 8, 15); + + // Now try flipped blocks 4x2: + + computeAverageColor4x2noQuantFloat(img,width,height,startx,starty,avg_color_float1); + computeAverageColor4x2noQuantFloat(img,width,height,startx,starty+2,avg_color_float2); + + // First test if avg_color1 is similar enough to avg_color2 so that + // we can use differential coding of colors. + + enc_color1[0] = int( JAS_ROUND(31.0*avg_color_float1[0]/255.0) ); + enc_color1[1] = int( JAS_ROUND(31.0*avg_color_float1[1]/255.0) ); + enc_color1[2] = int( JAS_ROUND(31.0*avg_color_float1[2]/255.0) ); + enc_color2[0] = int( JAS_ROUND(31.0*avg_color_float2[0]/255.0) ); + enc_color2[1] = int( JAS_ROUND(31.0*avg_color_float2[1]/255.0) ); + enc_color2[2] = int( JAS_ROUND(31.0*avg_color_float2[2]/255.0) ); + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + if( !((diff[0] >= -4) && (diff[0] <= 3) && (diff[1] >= -4) && (diff[1] <= 3) && (diff[2] >= -4) && (diff[2] <= 3)) ) + { + // The colors are too different. Use the same color in both blocks. + enc_color1[0] = int( JAS_ROUND(31.0*((avg_color_float1[0]+avg_color_float2[0])/2.0)/255.0) ); + enc_color1[1] = int( JAS_ROUND(31.0*((avg_color_float1[1]+avg_color_float2[1])/2.0)/255.0) ); + enc_color1[2] = int( JAS_ROUND(31.0*((avg_color_float1[2]+avg_color_float2[2])/2.0)/255.0) ); + enc_color2[0] = enc_color1[0]; + enc_color2[1] = enc_color1[1]; + enc_color2[2] = enc_color1[2]; + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + } + diffbit = 1; + + // The difference to be coded: + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + avg_color_quant1[0] = enc_color1[0] << 3 | (enc_color1[0] >> 2); + avg_color_quant1[1] = enc_color1[1] << 3 | (enc_color1[1] >> 2); + avg_color_quant1[2] = enc_color1[2] << 3 | (enc_color1[2] >> 2); + avg_color_quant2[0] = enc_color2[0] << 3 | (enc_color2[0] >> 2); + avg_color_quant2[1] = enc_color2[1] << 3 | (enc_color2[1] >> 2); + avg_color_quant2[2] = enc_color2[2] << 3 | (enc_color2[2] >> 2); + + // Pack bits into the first word. + + compressed1_flip = 0; + PUTBITSHIGH( compressed1_flip, diffbit, 1, 33); + PUTBITSHIGH( compressed1_flip, enc_color1[0], 5, 63); + PUTBITSHIGH( compressed1_flip, enc_color1[1], 5, 55); + PUTBITSHIGH( compressed1_flip, enc_color1[2], 5, 47); + PUTBITSHIGH( compressed1_flip, diff[0], 3, 58); + PUTBITSHIGH( compressed1_flip, diff[1], 3, 50); + PUTBITSHIGH( compressed1_flip, diff[2], 3, 42); + + // upper part of block + flip_err = tryalltables_3bittable4x2(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + // lower part of block + flip_err += tryalltables_3bittable4x2(img,width,height,startx,starty+2,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_flip, best_table1, 3, 39); + PUTBITSHIGH( compressed1_flip, best_table2, 3, 36); + PUTBITSHIGH( compressed1_flip, 1, 1, 32); + + best_pixel_indices1_MSB |= (best_pixel_indices2_MSB << 2); + best_pixel_indices1_LSB |= (best_pixel_indices2_LSB << 2); + + compressed2_flip = ((best_pixel_indices1_MSB & 0xffff) << 16) | (best_pixel_indices1_LSB & 0xffff); + + // Now lets see which is the best table to use. Only 8 tables are possible. + + if(norm_err <= flip_err) + { + compressed1 = compressed1_norm | 0; + compressed2 = compressed2_norm; + best_err = norm_err; + best_flip = 0; + } + else + { + compressed1 = compressed1_flip | 1; + compressed2 = compressed2_flip; + best_err = flip_err; + best_enc_color1[0] = enc_color1[0]; + best_enc_color1[1] = enc_color1[1]; + best_enc_color1[2] = enc_color1[2]; + best_enc_color2[0] = enc_color2[0]; + best_enc_color2[1] = enc_color2[1]; + best_enc_color2[2] = enc_color2[2]; + best_flip = 1; + } + return best_err; +} + +// Compresses the block using only the differential mode in ETC1/ETC2 +// Uses the average color as the base color in each half-block. +// If average colors are too different, use the average color of the entire block in both half-blocks. +// Tries both flipped and unflipped. +// Uses fixed point arithmetics where 1000 represents 1.0. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int compressBlockOnlyDiffFlipAveragePerceptual1000(uint8 *img,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2) +{ + unsigned int compressed1_norm, compressed2_norm; + unsigned int compressed1_flip, compressed2_flip; + uint8 avg_color_quant1[3], avg_color_quant2[3]; + + float avg_color_float1[3],avg_color_float2[3]; + int enc_color1[3], enc_color2[3], diff[3]; + unsigned int min_error=MAXERR1000; + unsigned int best_table_indices1=0, best_table_indices2=0; + unsigned int best_table1=0, best_table2=0; + int diffbit; + + int norm_err=0; + int flip_err=0; + + // First try normal blocks 2x4: + + computeAverageColor2x4noQuantFloat(img,width,height,startx,starty,avg_color_float1); + computeAverageColor2x4noQuantFloat(img,width,height,startx+2,starty,avg_color_float2); + + // First test if avg_color1 is similar enough to avg_color2 so that + // we can use differential coding of colors. + + enc_color1[0] = int( JAS_ROUND(31.0*avg_color_float1[0]/255.0) ); + enc_color1[1] = int( JAS_ROUND(31.0*avg_color_float1[1]/255.0) ); + enc_color1[2] = int( JAS_ROUND(31.0*avg_color_float1[2]/255.0) ); + enc_color2[0] = int( JAS_ROUND(31.0*avg_color_float2[0]/255.0) ); + enc_color2[1] = int( JAS_ROUND(31.0*avg_color_float2[1]/255.0) ); + enc_color2[2] = int( JAS_ROUND(31.0*avg_color_float2[2]/255.0) ); + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + if( !((diff[0] >= -4) && (diff[0] <= 3) && (diff[1] >= -4) && (diff[1] <= 3) && (diff[2] >= -4) && (diff[2] <= 3)) ) + { + enc_color1[0] = (enc_color1[0] + enc_color2[0]) >> 1; + enc_color1[1] = (enc_color1[1] + enc_color2[1]) >> 1; + enc_color1[2] = (enc_color1[2] + enc_color2[2]) >> 1; + + enc_color2[0] = enc_color1[0]; + enc_color2[1] = enc_color1[1]; + enc_color2[2] = enc_color1[2]; + + } + + { + diffbit = 1; + + // The difference to be coded: + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + avg_color_quant1[0] = enc_color1[0] << 3 | (enc_color1[0] >> 2); + avg_color_quant1[1] = enc_color1[1] << 3 | (enc_color1[1] >> 2); + avg_color_quant1[2] = enc_color1[2] << 3 | (enc_color1[2] >> 2); + avg_color_quant2[0] = enc_color2[0] << 3 | (enc_color2[0] >> 2); + avg_color_quant2[1] = enc_color2[1] << 3 | (enc_color2[1] >> 2); + avg_color_quant2[2] = enc_color2[2] << 3 | (enc_color2[2] >> 2); + + // Pack bits into the first word. + + // ETC1_RGB8_OES: + // + // a) bit layout in bits 63 through 32 if diffbit = 0 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | base col2 | base col1 | base col2 | base col1 | base col2 | table | table |diff|flip| + // | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // b) bit layout in bits 63 through 32 if diffbit = 1 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | dcol 2 | base col1 | dcol 2 | base col 1 | dcol 2 | table | table |diff|flip| + // | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // c) bit layout in bits 31 through 0 (in both cases) + // + // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // -------------------------------------------------------------------------------------------------- + // | most significant pixel index bits | least significant pixel index bits | + // | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a | + // -------------------------------------------------------------------------------------------------- + + compressed1_norm = 0; + PUTBITSHIGH( compressed1_norm, diffbit, 1, 33); + PUTBITSHIGH( compressed1_norm, enc_color1[0], 5, 63); + PUTBITSHIGH( compressed1_norm, enc_color1[1], 5, 55); + PUTBITSHIGH( compressed1_norm, enc_color1[2], 5, 47); + PUTBITSHIGH( compressed1_norm, diff[0], 3, 58); + PUTBITSHIGH( compressed1_norm, diff[1], 3, 50); + PUTBITSHIGH( compressed1_norm, diff[2], 3, 42); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + norm_err = 0; + + // left part of block + norm_err = tryalltables_3bittable2x4percep1000(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + + // right part of block + norm_err += tryalltables_3bittable2x4percep1000(img,width,height,startx+2,starty,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_norm, best_table1, 3, 39); + PUTBITSHIGH( compressed1_norm, best_table2, 3, 36); + PUTBITSHIGH( compressed1_norm, 0, 1, 32); + + compressed2_norm = 0; + PUTBITS( compressed2_norm, (best_pixel_indices1_MSB ), 8, 23); + PUTBITS( compressed2_norm, (best_pixel_indices2_MSB ), 8, 31); + PUTBITS( compressed2_norm, (best_pixel_indices1_LSB ), 8, 7); + PUTBITS( compressed2_norm, (best_pixel_indices2_LSB ), 8, 15); + + } + // Now try flipped blocks 4x2: + + computeAverageColor4x2noQuantFloat(img,width,height,startx,starty,avg_color_float1); + computeAverageColor4x2noQuantFloat(img,width,height,startx,starty+2,avg_color_float2); + + // First test if avg_color1 is similar enough to avg_color2 so that + // we can use differential coding of colors. + + enc_color1[0] = int( JAS_ROUND(31.0*avg_color_float1[0]/255.0) ); + enc_color1[1] = int( JAS_ROUND(31.0*avg_color_float1[1]/255.0) ); + enc_color1[2] = int( JAS_ROUND(31.0*avg_color_float1[2]/255.0) ); + enc_color2[0] = int( JAS_ROUND(31.0*avg_color_float2[0]/255.0) ); + enc_color2[1] = int( JAS_ROUND(31.0*avg_color_float2[1]/255.0) ); + enc_color2[2] = int( JAS_ROUND(31.0*avg_color_float2[2]/255.0) ); + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + if( !((diff[0] >= -4) && (diff[0] <= 3) && (diff[1] >= -4) && (diff[1] <= 3) && (diff[2] >= -4) && (diff[2] <= 3)) ) + { + enc_color1[0] = (enc_color1[0] + enc_color2[0]) >> 1; + enc_color1[1] = (enc_color1[1] + enc_color2[1]) >> 1; + enc_color1[2] = (enc_color1[2] + enc_color2[2]) >> 1; + + enc_color2[0] = enc_color1[0]; + enc_color2[1] = enc_color1[1]; + enc_color2[2] = enc_color1[2]; + } + + { + diffbit = 1; + + // The difference to be coded: + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + avg_color_quant1[0] = enc_color1[0] << 3 | (enc_color1[0] >> 2); + avg_color_quant1[1] = enc_color1[1] << 3 | (enc_color1[1] >> 2); + avg_color_quant1[2] = enc_color1[2] << 3 | (enc_color1[2] >> 2); + avg_color_quant2[0] = enc_color2[0] << 3 | (enc_color2[0] >> 2); + avg_color_quant2[1] = enc_color2[1] << 3 | (enc_color2[1] >> 2); + avg_color_quant2[2] = enc_color2[2] << 3 | (enc_color2[2] >> 2); + + // Pack bits into the first word. + + compressed1_flip = 0; + PUTBITSHIGH( compressed1_flip, diffbit, 1, 33); + PUTBITSHIGH( compressed1_flip, enc_color1[0], 5, 63); + PUTBITSHIGH( compressed1_flip, enc_color1[1], 5, 55); + PUTBITSHIGH( compressed1_flip, enc_color1[2], 5, 47); + PUTBITSHIGH( compressed1_flip, diff[0], 3, 58); + PUTBITSHIGH( compressed1_flip, diff[1], 3, 50); + PUTBITSHIGH( compressed1_flip, diff[2], 3, 42); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + // upper part of block + flip_err = tryalltables_3bittable4x2percep1000(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + // lower part of block + flip_err += tryalltables_3bittable4x2percep1000(img,width,height,startx,starty+2,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_flip, best_table1, 3, 39); + PUTBITSHIGH( compressed1_flip, best_table2, 3, 36); + PUTBITSHIGH( compressed1_flip, 1, 1, 32); + + best_pixel_indices1_MSB |= (best_pixel_indices2_MSB << 2); + best_pixel_indices1_LSB |= (best_pixel_indices2_LSB << 2); + + compressed2_flip = ((best_pixel_indices1_MSB & 0xffff) << 16) | (best_pixel_indices1_LSB & 0xffff); + } + unsigned int best_err; + + if(norm_err <= flip_err) + { + compressed1 = compressed1_norm | 0; + compressed2 = compressed2_norm; + best_err = norm_err; + } + else + { + compressed1 = compressed1_flip | 1; + compressed2 = compressed2_flip; + best_err = flip_err; + } + return best_err; +} + +// Compresses the block using both the individual and the differential mode in ETC1/ETC2 +// Uses the average color as the base color in each half-block. +// Uses a perceptual error metric. +// Tries both flipped and unflipped. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double compressBlockDiffFlipAveragePerceptual(uint8 *img,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2) +{ + unsigned int compressed1_norm, compressed2_norm; + unsigned int compressed1_flip, compressed2_flip; + uint8 avg_color_quant1[3], avg_color_quant2[3]; + + float avg_color_float1[3],avg_color_float2[3]; + int enc_color1[3], enc_color2[3], diff[3]; + int min_error=255*255*8*3; + unsigned int best_table_indices1=0, best_table_indices2=0; + unsigned int best_table1=0, best_table2=0; + int diffbit; + + int norm_err=0; + int flip_err=0; + + // First try normal blocks 2x4: + + computeAverageColor2x4noQuantFloat(img,width,height,startx,starty,avg_color_float1); + computeAverageColor2x4noQuantFloat(img,width,height,startx+2,starty,avg_color_float2); + + // First test if avg_color1 is similar enough to avg_color2 so that + // we can use differential coding of colors. + + float eps; + + enc_color1[0] = int( JAS_ROUND(31.0*avg_color_float1[0]/255.0) ); + enc_color1[1] = int( JAS_ROUND(31.0*avg_color_float1[1]/255.0) ); + enc_color1[2] = int( JAS_ROUND(31.0*avg_color_float1[2]/255.0) ); + enc_color2[0] = int( JAS_ROUND(31.0*avg_color_float2[0]/255.0) ); + enc_color2[1] = int( JAS_ROUND(31.0*avg_color_float2[1]/255.0) ); + enc_color2[2] = int( JAS_ROUND(31.0*avg_color_float2[2]/255.0) ); + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + if( (diff[0] >= -4) && (diff[0] <= 3) && (diff[1] >= -4) && (diff[1] <= 3) && (diff[2] >= -4) && (diff[2] <= 3) ) + { + diffbit = 1; + + // The difference to be coded: + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + avg_color_quant1[0] = enc_color1[0] << 3 | (enc_color1[0] >> 2); + avg_color_quant1[1] = enc_color1[1] << 3 | (enc_color1[1] >> 2); + avg_color_quant1[2] = enc_color1[2] << 3 | (enc_color1[2] >> 2); + avg_color_quant2[0] = enc_color2[0] << 3 | (enc_color2[0] >> 2); + avg_color_quant2[1] = enc_color2[1] << 3 | (enc_color2[1] >> 2); + avg_color_quant2[2] = enc_color2[2] << 3 | (enc_color2[2] >> 2); + + // Pack bits into the first word. + + // ETC1_RGB8_OES: + // + // a) bit layout in bits 63 through 32 if diffbit = 0 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | base col2 | base col1 | base col2 | base col1 | base col2 | table | table |diff|flip| + // | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // b) bit layout in bits 63 through 32 if diffbit = 1 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | dcol 2 | base col1 | dcol 2 | base col 1 | dcol 2 | table | table |diff|flip| + // | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // c) bit layout in bits 31 through 0 (in both cases) + // + // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // -------------------------------------------------------------------------------------------------- + // | most significant pixel index bits | least significant pixel index bits | + // | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a | + // -------------------------------------------------------------------------------------------------- + + compressed1_norm = 0; + PUTBITSHIGH( compressed1_norm, diffbit, 1, 33); + PUTBITSHIGH( compressed1_norm, enc_color1[0], 5, 63); + PUTBITSHIGH( compressed1_norm, enc_color1[1], 5, 55); + PUTBITSHIGH( compressed1_norm, enc_color1[2], 5, 47); + PUTBITSHIGH( compressed1_norm, diff[0], 3, 58); + PUTBITSHIGH( compressed1_norm, diff[1], 3, 50); + PUTBITSHIGH( compressed1_norm, diff[2], 3, 42); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + norm_err = 0; + + // left part of block + norm_err = tryalltables_3bittable2x4percep(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + + // right part of block + norm_err += tryalltables_3bittable2x4percep(img,width,height,startx+2,starty,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_norm, best_table1, 3, 39); + PUTBITSHIGH( compressed1_norm, best_table2, 3, 36); + PUTBITSHIGH( compressed1_norm, 0, 1, 32); + + compressed2_norm = 0; + PUTBITS( compressed2_norm, (best_pixel_indices1_MSB ), 8, 23); + PUTBITS( compressed2_norm, (best_pixel_indices2_MSB ), 8, 31); + PUTBITS( compressed2_norm, (best_pixel_indices1_LSB ), 8, 7); + PUTBITS( compressed2_norm, (best_pixel_indices2_LSB ), 8, 15); + } + else + { + diffbit = 0; + // The difference is bigger than what fits in 555 plus delta-333, so we will have + // to deal with 444 444. + + eps = (float) 0.0001; + + enc_color1[0] = int( ((float) avg_color_float1[0] / (17.0)) +0.5 + eps); + enc_color1[1] = int( ((float) avg_color_float1[1] / (17.0)) +0.5 + eps); + enc_color1[2] = int( ((float) avg_color_float1[2] / (17.0)) +0.5 + eps); + enc_color2[0] = int( ((float) avg_color_float2[0] / (17.0)) +0.5 + eps); + enc_color2[1] = int( ((float) avg_color_float2[1] / (17.0)) +0.5 + eps); + enc_color2[2] = int( ((float) avg_color_float2[2] / (17.0)) +0.5 + eps); + avg_color_quant1[0] = enc_color1[0] << 4 | enc_color1[0]; + avg_color_quant1[1] = enc_color1[1] << 4 | enc_color1[1]; + avg_color_quant1[2] = enc_color1[2] << 4 | enc_color1[2]; + avg_color_quant2[0] = enc_color2[0] << 4 | enc_color2[0]; + avg_color_quant2[1] = enc_color2[1] << 4 | enc_color2[1]; + avg_color_quant2[2] = enc_color2[2] << 4 | enc_color2[2]; + + // Pack bits into the first word. + + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | base col2 | base col1 | base col2 | base col1 | base col2 | table | table |diff|flip| + // | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + + compressed1_norm = 0; + PUTBITSHIGH( compressed1_norm, diffbit, 1, 33); + PUTBITSHIGH( compressed1_norm, enc_color1[0], 4, 63); + PUTBITSHIGH( compressed1_norm, enc_color1[1], 4, 55); + PUTBITSHIGH( compressed1_norm, enc_color1[2], 4, 47); + PUTBITSHIGH( compressed1_norm, enc_color2[0], 4, 59); + PUTBITSHIGH( compressed1_norm, enc_color2[1], 4, 51); + PUTBITSHIGH( compressed1_norm, enc_color2[2], 4, 43); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + // left part of block + norm_err = tryalltables_3bittable2x4percep(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + + // right part of block + norm_err += tryalltables_3bittable2x4percep(img,width,height,startx+2,starty,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_norm, best_table1, 3, 39); + PUTBITSHIGH( compressed1_norm, best_table2, 3, 36); + PUTBITSHIGH( compressed1_norm, 0, 1, 32); + + compressed2_norm = 0; + PUTBITS( compressed2_norm, (best_pixel_indices1_MSB ), 8, 23); + PUTBITS( compressed2_norm, (best_pixel_indices2_MSB ), 8, 31); + PUTBITS( compressed2_norm, (best_pixel_indices1_LSB ), 8, 7); + PUTBITS( compressed2_norm, (best_pixel_indices2_LSB ), 8, 15); + } + + // Now try flipped blocks 4x2: + + computeAverageColor4x2noQuantFloat(img,width,height,startx,starty,avg_color_float1); + computeAverageColor4x2noQuantFloat(img,width,height,startx,starty+2,avg_color_float2); + + // First test if avg_color1 is similar enough to avg_color2 so that + // we can use differential coding of colors. + + enc_color1[0] = int( JAS_ROUND(31.0*avg_color_float1[0]/255.0) ); + enc_color1[1] = int( JAS_ROUND(31.0*avg_color_float1[1]/255.0) ); + enc_color1[2] = int( JAS_ROUND(31.0*avg_color_float1[2]/255.0) ); + enc_color2[0] = int( JAS_ROUND(31.0*avg_color_float2[0]/255.0) ); + enc_color2[1] = int( JAS_ROUND(31.0*avg_color_float2[1]/255.0) ); + enc_color2[2] = int( JAS_ROUND(31.0*avg_color_float2[2]/255.0) ); + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + if( (diff[0] >= -4) && (diff[0] <= 3) && (diff[1] >= -4) && (diff[1] <= 3) && (diff[2] >= -4) && (diff[2] <= 3) ) + { + diffbit = 1; + + // The difference to be coded: + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + avg_color_quant1[0] = enc_color1[0] << 3 | (enc_color1[0] >> 2); + avg_color_quant1[1] = enc_color1[1] << 3 | (enc_color1[1] >> 2); + avg_color_quant1[2] = enc_color1[2] << 3 | (enc_color1[2] >> 2); + avg_color_quant2[0] = enc_color2[0] << 3 | (enc_color2[0] >> 2); + avg_color_quant2[1] = enc_color2[1] << 3 | (enc_color2[1] >> 2); + avg_color_quant2[2] = enc_color2[2] << 3 | (enc_color2[2] >> 2); + + // Pack bits into the first word. + + compressed1_flip = 0; + PUTBITSHIGH( compressed1_flip, diffbit, 1, 33); + PUTBITSHIGH( compressed1_flip, enc_color1[0], 5, 63); + PUTBITSHIGH( compressed1_flip, enc_color1[1], 5, 55); + PUTBITSHIGH( compressed1_flip, enc_color1[2], 5, 47); + PUTBITSHIGH( compressed1_flip, diff[0], 3, 58); + PUTBITSHIGH( compressed1_flip, diff[1], 3, 50); + PUTBITSHIGH( compressed1_flip, diff[2], 3, 42); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + // upper part of block + flip_err = tryalltables_3bittable4x2percep(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + // lower part of block + flip_err += tryalltables_3bittable4x2percep(img,width,height,startx,starty+2,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_flip, best_table1, 3, 39); + PUTBITSHIGH( compressed1_flip, best_table2, 3, 36); + PUTBITSHIGH( compressed1_flip, 1, 1, 32); + + best_pixel_indices1_MSB |= (best_pixel_indices2_MSB << 2); + best_pixel_indices1_LSB |= (best_pixel_indices2_LSB << 2); + + compressed2_flip = ((best_pixel_indices1_MSB & 0xffff) << 16) | (best_pixel_indices1_LSB & 0xffff); + } + else + { + diffbit = 0; + // The difference is bigger than what fits in 555 plus delta-333, so we will have + // to deal with 444 444. + eps = (float) 0.0001; + + enc_color1[0] = int( ((float) avg_color_float1[0] / (17.0)) +0.5 + eps); + enc_color1[1] = int( ((float) avg_color_float1[1] / (17.0)) +0.5 + eps); + enc_color1[2] = int( ((float) avg_color_float1[2] / (17.0)) +0.5 + eps); + enc_color2[0] = int( ((float) avg_color_float2[0] / (17.0)) +0.5 + eps); + enc_color2[1] = int( ((float) avg_color_float2[1] / (17.0)) +0.5 + eps); + enc_color2[2] = int( ((float) avg_color_float2[2] / (17.0)) +0.5 + eps); + + avg_color_quant1[0] = enc_color1[0] << 4 | enc_color1[0]; + avg_color_quant1[1] = enc_color1[1] << 4 | enc_color1[1]; + avg_color_quant1[2] = enc_color1[2] << 4 | enc_color1[2]; + avg_color_quant2[0] = enc_color2[0] << 4 | enc_color2[0]; + avg_color_quant2[1] = enc_color2[1] << 4 | enc_color2[1]; + avg_color_quant2[2] = enc_color2[2] << 4 | enc_color2[2]; + + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | base col2 | base col1 | base col2 | base col1 | base col2 | table | table |diff|flip| + // | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + + // Pack bits into the first word. + + compressed1_flip = 0; + PUTBITSHIGH( compressed1_flip, diffbit, 1, 33); + PUTBITSHIGH( compressed1_flip, enc_color1[0], 4, 63); + PUTBITSHIGH( compressed1_flip, enc_color1[1], 4, 55); + PUTBITSHIGH( compressed1_flip, enc_color1[2], 4, 47); + PUTBITSHIGH( compressed1_flip, enc_color2[0], 4, 59); + PUTBITSHIGH( compressed1_flip, enc_color2[1], 4, 51); + PUTBITSHIGH( compressed1_flip, enc_color2[2], 4, 43); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + // upper part of block + flip_err = tryalltables_3bittable4x2percep(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + // lower part of block + flip_err += tryalltables_3bittable4x2percep(img,width,height,startx,starty+2,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_flip, best_table1, 3, 39); + PUTBITSHIGH( compressed1_flip, best_table2, 3, 36); + PUTBITSHIGH( compressed1_flip, 1, 1, 32); + + best_pixel_indices1_MSB |= (best_pixel_indices2_MSB << 2); + best_pixel_indices1_LSB |= (best_pixel_indices2_LSB << 2); + + compressed2_flip = ((best_pixel_indices1_MSB & 0xffff) << 16) | (best_pixel_indices1_LSB & 0xffff); + } + + // Now lets see which is the best table to use. Only 8 tables are possible. + + double best_err; + + if(norm_err <= flip_err) + { + compressed1 = compressed1_norm | 0; + compressed2 = compressed2_norm; + best_err = norm_err; + } + else + { + compressed1 = compressed1_flip | 1; + compressed2 = compressed2_flip; + best_err = flip_err; + } + return best_err; +} + +// This is our structure for matrix data +struct dMatrix +{ + int width; // The number of coloumns in the matrix + int height; // The number of rows in the matrix + double *data; // The matrix data in row order +}; + +// Multiplies two matrices +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +dMatrix *multiplyMatrices( dMatrix *Amat, dMatrix *Bmat) +{ + int xx,yy, q; + dMatrix *resmatrix; + + if(Amat->width != Bmat->height) + { + printf("Cannot multiply matrices -- dimensions do not agree.\n"); + exit(1); + } + + // Allocate space for result + resmatrix = (dMatrix*) malloc(sizeof(dMatrix)); + resmatrix->width = Bmat->width; + resmatrix->height = Amat->height; + resmatrix->data = (double*) malloc(sizeof(double)*(resmatrix->width)*(resmatrix->height)); + + for(yy = 0; yyheight; yy++) + for(xx = 0; xxwidth; xx++) + for(q=0, resmatrix->data[yy*resmatrix->width+xx] = 0.0; qwidth; q++) + resmatrix->data[yy*resmatrix->width+xx] += Amat->data[yy*Amat->width + q] * Bmat->data[q*Bmat->width+xx]; + + return(resmatrix); + +} + +// Transposes a matrix +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void transposeMatrix( dMatrix *mat) +{ + int xx, yy, zz; + double *temp; + int newwidth, newheight; + + temp = (double*) malloc (sizeof(double)*(mat->width)*(mat->height)); + + for(zz = 0; zz<((mat->width)*(mat->height)); zz++) + temp[zz] = mat->data[zz]; + + newwidth = mat->height; + newheight= mat->width; + + for(yy = 0; yydata[yy*newwidth+xx] = temp[xx*(mat->width)+yy]; + + mat->height = newheight; + mat->width = newwidth; + free(temp); +} + +// In the planar mode in ETC2, the block can be partitioned as follows: +// +// O A A A H +// B D1 D3 C3 +// B D2 C2 D5 +// B C1 D4 D6 +// V +// Here A-pixels, B-pixels and C-pixels only depend on two values. For instance, B-pixels only depend on O and V. +// This can be used to quickly rule out combinations of colors. +// Here we calculate the minimum error for the block if we know the red component for O and V. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcBBBred(uint8 *block, int colorO, int colorV) +{ + colorO = (colorO << 2) | (colorO >> 4); + colorV = (colorV << 2) | (colorV >> 4); + + unsigned int error = 0; + + // Now first column: B B B + /* unroll loop for( yy=0; (yy<4) && (error <= best_error_sofar); yy++)*/ + { + error = error + square_table[(block[4*4 + 0] - clamp_table[ ((((colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*4*2 + 0] - clamp_table[ (((((colorV-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*4*3 + 0] - clamp_table[ (((3*(colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + } + + return error; +} + +// Calculating the minimum error for the block if we know the red component for H and V. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcCCCred(uint8 *block, int colorH, int colorV) +{ + colorH = (colorH << 2) | (colorH >> 4); + colorV = (colorV << 2) | (colorV >> 4); + + unsigned int error=0; + + error = error + square_table[(block[4*4*3 + 4 + 0] - clamp_table[ (((colorH + 3*colorV)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*4*2 + 4*2 + 0] - clamp_table[ (((2*colorH + 2*colorV)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*4 + 4*3 + 0] - clamp_table[ (((3*colorH + colorV)+2)>>2) + 255])+255]; + + return error; +} + +// Calculating the minimum error for the block if we know the red component for O and H. +// Uses perceptual error metric. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcLowestPossibleRedOHperceptual(uint8 *block, int colorO, int colorH, unsigned int best_error_sofar) +{ + colorO = (colorO << 2) | (colorO >> 4); + colorH = (colorH << 2) | (colorH >> 4); + + unsigned int error; + + error = square_table_percep_red[(block[0] - colorO) + 255]; + error = error + square_table_percep_red[(block[4] - clamp_table[ ((( (colorH-colorO) + 4*colorO)+2)>>2) + 255])+255]; + if(error <= best_error_sofar) + { + error = error + square_table_percep_red[(block[4*2] - clamp_table[ ((( ((colorH-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table_percep_red[(block[4*3] - clamp_table[ ((( 3*(colorH-colorO) + 4*colorO)+2)>>2) + 255])+255]; + } + + return error; +} + +// Calculating the minimum error for the block (in planar mode) if we know the red component for O and H. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcLowestPossibleRedOH(uint8 *block, int colorO, int colorH, unsigned int best_error_sofar) +{ + colorO = (colorO << 2) | (colorO >> 4); + colorH = (colorH << 2) | (colorH >> 4); + + unsigned int error; + + error = square_table[(block[0] - colorO) + 255]; + error = error + square_table[(block[4] - clamp_table[ ((( (colorH-colorO) + 4*colorO)+2)>>2) + 255])+255]; + if(error <= best_error_sofar) + { + error = error + square_table[(block[4*2] - clamp_table[ ((( ((colorH-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*3] - clamp_table[ ((( 3*(colorH-colorO) + 4*colorO)+2)>>2) + 255])+255]; + } + + return error; +} + +// Calculating the minimum error for the block (in planar mode) if we know the red component for O and H and V. +// Uses perceptual error metric. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcErrorPlanarOnlyRedPerceptual(uint8 *block, int colorO, int colorH, int colorV, unsigned int lowest_possible_error, unsigned int BBBvalue, unsigned int CCCvalue, unsigned int best_error_sofar) +{ + colorO = (colorO << 2) | (colorO >> 4); + colorH = (colorH << 2) | (colorH >> 4); + colorV = (colorV << 2) | (colorV >> 4); + + unsigned int error; + + // The block can be partitioned into: O A A A + // B D1 D3 C3 + // B D2 C2 D5 + // B C1 D4 D6 + int xpart_times_4; + + // The first part: O A A A. It equals lowest_possible_error previously calculated. + // lowest_possible_error is OAAA, BBBvalue is BBB and CCCvalue is C1C2C3. + error = lowest_possible_error + BBBvalue + CCCvalue; + + // The remaining pixels to cover are D1 through D6. + if(error <= best_error_sofar) + { + // Second column: D1 D2 but not C1 + xpart_times_4 = (colorH-colorO); + error = error + square_table_percep_red[(block[4*4 + 4 + 0] - clamp_table[ (((xpart_times_4 + (colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table_percep_red[(block[4*4*2 + 4 + 0] - clamp_table[ (((xpart_times_4 + ((colorV-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + // Third column: D3 notC2 D4 + xpart_times_4 = (colorH-colorO) << 1; + error = error + square_table_percep_red[(block[4*4 + 4*2 + 0] - clamp_table[ (((xpart_times_4 + (colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + if(error <= best_error_sofar) + { + error = error + square_table_percep_red[(block[4*4*3 + 4*2 + 0] - clamp_table[ (((xpart_times_4 + 3*(colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + // Forth column: notC3 D5 D6 + xpart_times_4 = 3*(colorH-colorO); + error = error + square_table_percep_red[(block[4*4*2 + 4*3 + 0] - clamp_table[ (((xpart_times_4 + ((colorV-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table_percep_red[(block[4*4*3 + 4*3 + 0] - clamp_table[ (((xpart_times_4 + 3*(colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + } + } + return error; +} + +// Calculating the minimum error for the block (in planar mode) if we know the red component for O and H and V. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcErrorPlanarOnlyRed(uint8 *block, int colorO, int colorH, int colorV, unsigned int lowest_possible_error, unsigned int BBBvalue, unsigned int CCCvalue, unsigned int best_error_sofar) +{ + colorO = (colorO << 2) | (colorO >> 4); + colorH = (colorH << 2) | (colorH >> 4); + colorV = (colorV << 2) | (colorV >> 4); + + unsigned int error; + + // The block can be partitioned into: O A A A + // B D1 D3 C3 + // B D2 C2 D5 + // B C1 D4 D6 + int xpart_times_4; + + // The first part: O A A A. It equals lowest_possible_error previously calculated. + // lowest_possible_error is OAAA, BBBvalue is BBB and CCCvalue is C1C2C3. + error = lowest_possible_error + BBBvalue + CCCvalue; + + // The remaining pixels to cover are D1 through D6. + if(error <= best_error_sofar) + { + // Second column: D1 D2 but not C1 + xpart_times_4 = (colorH-colorO); + error = error + square_table[(block[4*4 + 4 + 0] - clamp_table[ (((xpart_times_4 + (colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*4*2 + 4 + 0] - clamp_table[ (((xpart_times_4 + ((colorV-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + // Third column: D3 notC2 D4 + xpart_times_4 = (colorH-colorO) << 1; + error = error + square_table[(block[4*4 + 4*2 + 0] - clamp_table[ (((xpart_times_4 + (colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + if(error <= best_error_sofar) + { + error = error + square_table[(block[4*4*3 + 4*2 + 0] - clamp_table[ (((xpart_times_4 + 3*(colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + // Forth column: notC3 D5 D6 + xpart_times_4 = 3*(colorH-colorO); + error = error + square_table[(block[4*4*2 + 4*3 + 0] - clamp_table[ (((xpart_times_4 + ((colorV-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*4*3 + 4*3 + 0] - clamp_table[ (((xpart_times_4 + 3*(colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + } + } + return error; +} + +// Calculating the minimum error for the block (in planar mode) if we know the red component for O and H. +// Uses perceptual error metrics. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcLowestPossibleGreenOHperceptual(uint8 *block, int colorO, int colorH, unsigned int best_error_sofar) +{ + colorO = (colorO << 1) | (colorO >> 6); + colorH = (colorH << 1) | (colorH >> 6); + + unsigned int error; + + error = square_table_percep_green[(block[1] - colorO) + 255]; + error = error + square_table_percep_green[(block[4 + 1] - clamp_table[ ((( (colorH-colorO) + 4*colorO)+2)>>2) + 255])+255]; + if(error <= best_error_sofar) + { + error = error + square_table_percep_green[(block[4*2 + 1] - clamp_table[ ((( ((colorH-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table_percep_green[(block[4*3 + 1] - clamp_table[ ((( 3*(colorH-colorO) + 4*colorO)+2)>>2) + 255])+255]; + } + return error; +} + +// Calculating the minimum error for the block (in planar mode) if we know the red component for O and H. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcLowestPossibleGreenOH(uint8 *block, int colorO, int colorH, unsigned int best_error_sofar) +{ + colorO = (colorO << 1) | (colorO >> 6); + colorH = (colorH << 1) | (colorH >> 6); + + unsigned int error; + + error = square_table[(block[1] - colorO) + 255]; + error = error + square_table[(block[4 + 1] - clamp_table[ ((( (colorH-colorO) + 4*colorO)+2)>>2) + 255])+255]; + if(error <= best_error_sofar) + { + error = error + square_table[(block[4*2 + 1] - clamp_table[ ((( ((colorH-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*3 + 1] - clamp_table[ ((( 3*(colorH-colorO) + 4*colorO)+2)>>2) + 255])+255]; + } + return error; +} + +// Calculating the minimum error for the block (in planar mode) if we know the green component for O and V. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcBBBgreen(uint8 *block, int colorO, int colorV) +{ + colorO = (colorO << 1) | (colorO >> 6); + colorV = (colorV << 1) | (colorV >> 6); + + unsigned int error = 0; + + // Now first column: B B B + /* unroll loop for( yy=0; (yy<4) && (error <= best_error_sofar); yy++)*/ + { + error = error + square_table[(block[4*4 + 1] - clamp_table[ ((((colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*4*2 + 1] - clamp_table[ (((((colorV-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*4*3 + 1] - clamp_table[ (((3*(colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + } + + return error; + +} + +// Calculating the minimum error for the block (in planar mode) if we know the green component for H and V. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcCCCgreen(uint8 *block, int colorH, int colorV) +{ + colorH = (colorH << 1) | (colorH >> 6); + colorV = (colorV << 1) | (colorV >> 6); + + unsigned int error=0; + + error = error + square_table[(block[4*4*3 + 4 + 1] - clamp_table[ (((colorH + 3*colorV)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*4*2 + 4*2 + 1] - clamp_table[ (((2*colorH + 2*colorV)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*4 + 4*3 + 1] - clamp_table[ (((3*colorH + colorV)+2)>>2) + 255])+255]; + + return error; +} + +// Calculating the minimum error for the block (in planar mode) if we know the green component for H V and O. +// Uses perceptual error metric. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcErrorPlanarOnlyGreenPerceptual(uint8 *block, int colorO, int colorH, int colorV, unsigned int lowest_possible_error, unsigned int BBBvalue, unsigned int CCCvalue, unsigned int best_error_sofar) +{ + colorO = (colorO << 1) | (colorO >> 6); + colorH = (colorH << 1) | (colorH >> 6); + colorV = (colorV << 1) | (colorV >> 6); + + unsigned int error; + + // The block can be partitioned into: O A A A + // B D1 D3 C3 + // B D2 C2 D5 + // B C1 D4 D6 + + int xpart_times_4; + + // The first part: O A A A. It equals lowest_possible_error previously calculated. + // lowest_possible_error is OAAA, BBBvalue is BBB and CCCvalue is C1C2C3. + error = lowest_possible_error + BBBvalue + CCCvalue; + + // The remaining pixels to cover are D1 through D6. + if(error <= best_error_sofar) + { + // Second column: D1 D2 but not C1 + xpart_times_4 = (colorH-colorO); + error = error + square_table_percep_green[(block[4*4 + 4 + 1] - clamp_table[ (((xpart_times_4 + (colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table_percep_green[(block[4*4*2 + 4 + 1] - clamp_table[ (((xpart_times_4 + ((colorV-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + // Third column: D3 notC2 D4 + xpart_times_4 = (colorH-colorO) << 1; + error = error + square_table_percep_green[(block[4*4 + 4*2 + 1] - clamp_table[ (((xpart_times_4 + (colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + if(error <= best_error_sofar) + { + error = error + square_table_percep_green[(block[4*4*3 + 4*2 + 1] - clamp_table[ (((xpart_times_4 + 3*(colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + // Forth column: notC3 D5 D6 + xpart_times_4 = 3*(colorH-colorO); + error = error + square_table_percep_green[(block[4*4*2 + 4*3 + 1] - clamp_table[ (((xpart_times_4 + ((colorV-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table_percep_green[(block[4*4*3 + 4*3 + 1] - clamp_table[ (((xpart_times_4 + 3*(colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + } + } + return error; +} + +// Calculating the minimum error for the block (in planar mode) if we know the green component for H V and O. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcErrorPlanarOnlyGreen(uint8 *block, int colorO, int colorH, int colorV, unsigned int lowest_possible_error, unsigned int BBBvalue, unsigned int CCCvalue, unsigned int best_error_sofar) +{ + colorO = (colorO << 1) | (colorO >> 6); + colorH = (colorH << 1) | (colorH >> 6); + colorV = (colorV << 1) | (colorV >> 6); + + unsigned int error; + + // The block can be partitioned into: O A A A + // B D1 D3 C3 + // B D2 C2 D5 + // B C1 D4 D6 + int xpart_times_4; + + // The first part: O A A A. It equals lowest_possible_error previously calculated. + // lowest_possible_error is OAAA, BBBvalue is BBB and CCCvalue is C1C2C3. + error = lowest_possible_error + BBBvalue + CCCvalue; + + // The remaining pixels to cover are D1 through D6. + if(error <= best_error_sofar) + { + // Second column: D1 D2 but not C1 + xpart_times_4 = (colorH-colorO); + error = error + square_table[(block[4*4 + 4 + 1] - clamp_table[ (((xpart_times_4 + (colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*4*2 + 4 + 1] - clamp_table[ (((xpart_times_4 + ((colorV-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + // Third column: D3 notC2 D4 + xpart_times_4 = (colorH-colorO) << 1; + error = error + square_table[(block[4*4 + 4*2 + 1] - clamp_table[ (((xpart_times_4 + (colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + if(error <= best_error_sofar) + { + error = error + square_table[(block[4*4*3 + 4*2 + 1] - clamp_table[ (((xpart_times_4 + 3*(colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + // Forth column: notC3 D5 D6 + xpart_times_4 = 3*(colorH-colorO); + error = error + square_table[(block[4*4*2 + 4*3 + 1] - clamp_table[ (((xpart_times_4 + ((colorV-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*4*3 + 4*3 + 1] - clamp_table[ (((xpart_times_4 + 3*(colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + } + } + return error; +} + +// Calculating the minimum error for the block (in planar mode) if we know the blue component for O and V. +// Uses perceptual error metric. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcBBBbluePerceptual(uint8 *block, int colorO, int colorV) +{ + colorO = (colorO << 2) | (colorO >> 4); + colorV = (colorV << 2) | (colorV >> 4); + + unsigned int error = 0; + + // Now first column: B B B + /* unroll loop for( yy=0; (yy<4) && (error <= best_error_sofar); yy++)*/ + { + error = error + square_table_percep_blue[(block[4*4 + 2] - clamp_table[ ((((colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table_percep_blue[(block[4*4*2 + 2] - clamp_table[ (((((colorV-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table_percep_blue[(block[4*4*3 + 2] - clamp_table[ (((3*(colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + } + + return error; +} + +// Calculating the minimum error for the block (in planar mode) if we know the blue component for O and V. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcBBBblue(uint8 *block, int colorO, int colorV) +{ + colorO = (colorO << 2) | (colorO >> 4); + colorV = (colorV << 2) | (colorV >> 4); + + unsigned int error = 0; + + // Now first column: B B B + /* unroll loop for( yy=0; (yy<4) && (error <= best_error_sofar); yy++)*/ + { + error = error + square_table[(block[4*4 + 2] - clamp_table[ ((((colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*4*2 + 2] - clamp_table[ (((((colorV-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*4*3 + 2] - clamp_table[ (((3*(colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + } + + return error; +} + +// Calculating the minimum error for the block (in planar mode) if we know the blue component for H and V. +// Uses perceptual error metric. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcCCCbluePerceptual(uint8 *block, int colorH, int colorV) +{ + colorH = (colorH << 2) | (colorH >> 4); + colorV = (colorV << 2) | (colorV >> 4); + + unsigned int error=0; + + error = error + square_table_percep_blue[(block[4*4*3 + 4 + 2] - clamp_table[ (((colorH + 3*colorV)+2)>>2) + 255])+255]; + error = error + square_table_percep_blue[(block[4*4*2 + 4*2 + 2] - clamp_table[ (((2*colorH + 2*colorV)+2)>>2) + 255])+255]; + error = error + square_table_percep_blue[(block[4*4 + 4*3 + 2] - clamp_table[ (((3*colorH + colorV)+2)>>2) + 255])+255]; + + return error; +} + +// Calculating the minimum error for the block (in planar mode) if we know the blue component for O and V. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcCCCblue(uint8 *block, int colorH, int colorV) +{ + colorH = (colorH << 2) | (colorH >> 4); + colorV = (colorV << 2) | (colorV >> 4); + + unsigned int error=0; + + error = error + square_table[(block[4*4*3 + 4 + 2] - clamp_table[ (((colorH + 3*colorV)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*4*2 + 4*2 + 2] - clamp_table[ (((2*colorH + 2*colorV)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*4 + 4*3 + 2] - clamp_table[ (((3*colorH + colorV)+2)>>2) + 255])+255]; + + return error; +} + +// Calculating the minimum error for the block (in planar mode) if we know the blue component for O and H. +// Uses perceptual error metric. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcLowestPossibleBlueOHperceptual(uint8 *block, int colorO, int colorH, unsigned int best_error_sofar) +{ + colorO = (colorO << 2) | (colorO >> 4); + colorH = (colorH << 2) | (colorH >> 4); + + unsigned int error; + + error = square_table_percep_blue[(block[2] - colorO) + 255]; + error = error + square_table_percep_blue[(block[4+2] - clamp_table[ ((( (colorH-colorO) + 4*colorO)+2)>>2) + 255])+255]; + if(error <= best_error_sofar) + { + error = error + square_table_percep_blue[(block[4*2+2] - clamp_table[ ((( ((colorH-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table_percep_blue[(block[4*3+2] - clamp_table[ ((( 3*(colorH-colorO) + 4*colorO)+2)>>2) + 255])+255]; + } + + return error; +} + +// Calculating the minimum error for the block (in planar mode) if we know the blue component for O and H. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcLowestPossibleBlueOH(uint8 *block, int colorO, int colorH, unsigned int best_error_sofar) +{ + colorO = (colorO << 2) | (colorO >> 4); + colorH = (colorH << 2) | (colorH >> 4); + + unsigned int error; + + error = square_table[(block[2] - colorO) + 255]; + error = error + square_table[(block[4+2] - clamp_table[ ((( (colorH-colorO) + 4*colorO)+2)>>2) + 255])+255]; + if(error <= best_error_sofar) + { + error = error + square_table[(block[4*2+2] - clamp_table[ ((( ((colorH-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*3+2] - clamp_table[ ((( 3*(colorH-colorO) + 4*colorO)+2)>>2) + 255])+255]; + } + + return error; +} + +// Calculating the minimum error for the block (in planar mode) if we know the blue component for O, V and H. +// Uses perceptual error metric. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcErrorPlanarOnlyBluePerceptual(uint8 *block, int colorO, int colorH, int colorV, unsigned int lowest_possible_error, unsigned int BBBvalue, unsigned int CCCvalue, unsigned int best_error_sofar) +{ + colorO = (colorO << 2) | (colorO >> 4); + colorH = (colorH << 2) | (colorH >> 4); + colorV = (colorV << 2) | (colorV >> 4); + + unsigned int error; + + // The block can be partitioned into: O A A A + // B D1 D3 C3 + // B D2 C2 D5 + // B C1 D4 D6 + int xpart_times_4; + + // The first part: O A A A. It equals lowest_possible_error previously calculated. + // lowest_possible_error is OAAA, BBBvalue is BBB and CCCvalue is C1C2C3. + error = lowest_possible_error + BBBvalue + CCCvalue; + + // The remaining pixels to cover are D1 through D6. + if(error <= best_error_sofar) + { + // Second column: D1 D2 but not C1 + xpart_times_4 = (colorH-colorO); + error = error + square_table_percep_blue[(block[4*4 + 4 + 2] - clamp_table[ (((xpart_times_4 + (colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table_percep_blue[(block[4*4*2 + 4 + 2] - clamp_table[ (((xpart_times_4 + ((colorV-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + // Third column: D3 notC2 D4 + xpart_times_4 = (colorH-colorO) << 1; + error = error + square_table_percep_blue[(block[4*4 + 4*2 + 2] - clamp_table[ (((xpart_times_4 + (colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + if(error <= best_error_sofar) + { + error = error + square_table_percep_blue[(block[4*4*3 + 4*2 + 2] - clamp_table[ (((xpart_times_4 + 3*(colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + // Forth column: notC3 D5 D6 + xpart_times_4 = 3*(colorH-colorO); + error = error + square_table_percep_blue[(block[4*4*2 + 4*3 + 2] - clamp_table[ (((xpart_times_4 + ((colorV-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table_percep_blue[(block[4*4*3 + 4*3 + 2] - clamp_table[ (((xpart_times_4 + 3*(colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + } + } + + return error; +} + +// Calculating the minimum error for the block (in planar mode) if we know the blue component for O, V and H. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calcErrorPlanarOnlyBlue(uint8 *block, int colorO, int colorH, int colorV, unsigned int lowest_possible_error, unsigned int BBBvalue, unsigned int CCCvalue, unsigned int best_error_sofar) +{ + colorO = (colorO << 2) | (colorO >> 4); + colorH = (colorH << 2) | (colorH >> 4); + colorV = (colorV << 2) | (colorV >> 4); + + unsigned int error; + + // The block can be partitioned into: O A A A + // B D1 D3 C3 + // B D2 C2 D5 + // B C1 D4 D6 + int xpart_times_4; + + // The first part: O A A A. It equals lowest_possible_error previously calculated. + // lowest_possible_error is OAAA, BBBvalue is BBB and CCCvalue is C1C2C3. + error = lowest_possible_error + BBBvalue + CCCvalue; + + // The remaining pixels to cover are D1 through D6. + if(error <= best_error_sofar) + { + // Second column: D1 D2 but not C1 + xpart_times_4 = (colorH-colorO); + error = error + square_table[(block[4*4 + 4 + 2] - clamp_table[ (((xpart_times_4 + (colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*4*2 + 4 + 2] - clamp_table[ (((xpart_times_4 + ((colorV-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + // Third column: D3 notC2 D4 + xpart_times_4 = (colorH-colorO) << 1; + error = error + square_table[(block[4*4 + 4*2 + 2] - clamp_table[ (((xpart_times_4 + (colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + if(error <= best_error_sofar) + { + error = error + square_table[(block[4*4*3 + 4*2 + 2] - clamp_table[ (((xpart_times_4 + 3*(colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + // Forth column: notC3 D5 D6 + xpart_times_4 = 3*(colorH-colorO); + error = error + square_table[(block[4*4*2 + 4*3 + 2] - clamp_table[ (((xpart_times_4 + ((colorV-colorO)<<1) + 4*colorO)+2)>>2) + 255])+255]; + error = error + square_table[(block[4*4*3 + 4*3 + 2] - clamp_table[ (((xpart_times_4 + 3*(colorV-colorO) + 4*colorO)+2)>>2) + 255])+255]; + } + } + + return error; +} + + + +// This function uses least squares in order to determine the best values of the plane. +// This is close to optimal, but not quite, due to nonlinearities in the expantion from 6 and 7 bits to 8, and +// in the clamping to a number between 0 and the maximum. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void compressBlockPlanar57(uint8 *img, int width,int height,int startx,int starty, unsigned int &compressed57_1, unsigned int &compressed57_2) +{ + // Use least squares to find the solution with the smallest error. + // That is, find the vector x so that |Ax-b|^2 is minimized, where + // x = [Ro Rr Rv]'; + // A = [1 3/4 2/4 1/4 3/4 2/4 1/4 0 2/4 1/4 0 -1/4 1/4 0 -1/4 -2/4 ; + // 0 1/4 2/4 3/4 0 1/4 2/4 3/4 0 1/4 2/4 3/4 0 1/4 2/4 3/4 ; + // 0 0 0 0 1/4 1/4 1/4 1/4 2/4 2/4 2/4 2/4; 3/4 3/4 3/4 3/4]'; + // b = [r11 r12 r13 r14 r21 r22 r23 r24 r31 r32 r33 r34 r41 r42 r43 r44]; + // + // That is, find solution x = inv(A' * A) * A' * b + // = C * A' * b; + // C is always the same, so we have calculated it off-line here. + // = C * D + int xx,yy, cc; + double coeffsA[48]= { 1.00, 0.00, 0.00, + 0.75, 0.25, 0.00, + 0.50, 0.50, 0.00, + 0.25, 0.75, 0.00, + 0.75, 0.00, 0.25, + 0.50, 0.25, 0.25, + 0.25, 0.50, 0.25, + 0.00, 0.75, 0.25, + 0.50, 0.00, 0.50, + 0.25, 0.25, 0.50, + 0.00, 0.50, 0.50, + -0.25, 0.75, 0.50, + 0.25, 0.00, 0.75, + 0.00, 0.25, 0.75, + -0.25, 0.50, 0.75, + -0.50, 0.75, 0.75}; + + double coeffsC[9] = {0.2875, -0.0125, -0.0125, -0.0125, 0.4875, -0.3125, -0.0125, -0.3125, 0.4875}; + double colorO[3], colorH[3], colorV[3]; + uint8 colorO8[3], colorH8[3], colorV8[3]; + + dMatrix *D_matrix; + dMatrix *x_vector; + + dMatrix A_matrix; A_matrix.width = 3; A_matrix.height = 16; + A_matrix.data = coeffsA; + dMatrix C_matrix; C_matrix.width = 3; C_matrix.height = 3; + C_matrix.data = coeffsC; + dMatrix b_vector; b_vector.width = 1; b_vector.height = 16; + b_vector.data = (double*) malloc(sizeof(double)*b_vector.width*b_vector.height); + transposeMatrix(&A_matrix); + + // Red component + + // Load color data into vector b: + for(cc = 0, yy = 0; yy<4; yy++) + for(xx = 0; xx<4; xx++) + b_vector.data[cc++] = img[3*width*(starty+yy) + 3*(startx+xx) + 0]; + + D_matrix = multiplyMatrices(&A_matrix, &b_vector); + x_vector = multiplyMatrices(&C_matrix, D_matrix); + + colorO[0] = CLAMP(0.0, x_vector->data[0], 255.0); + colorH[0] = CLAMP(0.0, x_vector->data[1], 255.0); + colorV[0] = CLAMP(0.0, x_vector->data[2], 255.0); + + free(D_matrix->data); free(D_matrix); + free(x_vector->data); free(x_vector); + + // Green component + + // Load color data into vector b: + for(cc = 0, yy = 0; yy<4; yy++) + for(xx = 0; xx<4; xx++) + b_vector.data[cc++] = img[3*width*(starty+yy) + 3*(startx+xx) + 1]; + + D_matrix = multiplyMatrices(&A_matrix, &b_vector); + x_vector = multiplyMatrices(&C_matrix, D_matrix); + + colorO[1] = CLAMP(0.0, x_vector->data[0], 255.0); + colorH[1] = CLAMP(0.0, x_vector->data[1], 255.0); + colorV[1] = CLAMP(0.0, x_vector->data[2], 255.0); + + free(D_matrix->data); free(D_matrix); + free(x_vector->data); free(x_vector); + + // Blue component + + // Load color data into vector b: + for(cc = 0, yy = 0; yy<4; yy++) + for(xx = 0; xx<4; xx++) + b_vector.data[cc++] = img[3*width*(starty+yy) + 3*(startx+xx) + 2]; + + D_matrix = multiplyMatrices(&A_matrix, &b_vector); + x_vector = multiplyMatrices(&C_matrix, D_matrix); + + colorO[2] = CLAMP(0.0, x_vector->data[0], 255.0); + colorH[2] = CLAMP(0.0, x_vector->data[1], 255.0); + colorV[2] = CLAMP(0.0, x_vector->data[2], 255.0); + + free(D_matrix->data); free(D_matrix); + free(x_vector->data); free(x_vector); + + // Quantize to 6 bits + double D = 255*(1.0/((1<<6)-1.0) ); + colorO8[0] = JAS_ROUND((1.0*colorO[0])/D); + colorO8[2] = JAS_ROUND((1.0*colorO[2])/D); + colorH8[0] = JAS_ROUND((1.0*colorH[0])/D); + colorH8[2] = JAS_ROUND((1.0*colorH[2])/D); + colorV8[0] = JAS_ROUND((1.0*colorV[0])/D); + colorV8[2] = JAS_ROUND((1.0*colorV[2])/D); + + // Quantize to 7 bits + D = 255*(1.0/((1<<7)-1.0) ); + colorO8[1] = JAS_ROUND((1.0*colorO[1])/D); + colorH8[1] = JAS_ROUND((1.0*colorH[1])/D); + colorV8[1] = JAS_ROUND((1.0*colorV[1])/D); + + // Pack bits in 57 bits + + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // ------------------------------------------------------------------------------------------------ + // | R0 | G0 | B0 | RH | GH | + // ------------------------------------------------------------------------------------------------ + // + // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // ------------------------------------------------------------------------------------------------ + // | BH | RV | GV | BV | not used | + // ------------------------------------------------------------------------------------------------ + + compressed57_1 = 0; + compressed57_2 = 0; + PUTBITSHIGH( compressed57_1, colorO8[0], 6, 63); + PUTBITSHIGH( compressed57_1, colorO8[1], 7, 57); + PUTBITSHIGH( compressed57_1, colorO8[2], 6, 50); + PUTBITSHIGH( compressed57_1, colorH8[0], 6, 44); + PUTBITSHIGH( compressed57_1, colorH8[1], 7, 38); + PUTBITS( compressed57_2, colorH8[2], 6, 31); + PUTBITS( compressed57_2, colorV8[0], 6, 25); + PUTBITS( compressed57_2, colorV8[1], 7, 19); + PUTBITS( compressed57_2, colorV8[2], 6, 12); +} + +// During search it is not convenient to store the bits the way they are stored in the +// file format. Hence, after search, it is converted to this format. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void stuff57bits(unsigned int planar57_word1, unsigned int planar57_word2, unsigned int &planar_word1, unsigned int &planar_word2) +{ + // Put bits in twotimer configuration for 57 bits (red and green dont overflow, green does) + // + // Go from this bit layout: + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // ----------------------------------------------------------------------------------------------- + // |R0 |G01G02 |B01B02 ;B03 |RH1 |RH2|GH | + // ----------------------------------------------------------------------------------------------- + // + // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // ----------------------------------------------------------------------------------------------- + // |BH |RV |GV |BV | not used | + // ----------------------------------------------------------------------------------------------- + // + // To this: + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // ------------------------------------------------------------------------------------------------ + // |//|R0 |G01|/|G02 |B01|/ // //|B02 |//|B03 |RH1 |df|RH2| + // ------------------------------------------------------------------------------------------------ + // + // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // ----------------------------------------------------------------------------------------------- + // |GH |BH |RV |GV |BV | + // ----------------------------------------------------------------------------------------------- + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | dcol 2 | base col1 | dcol 2 | base col 1 | dcol 2 | table | table |diff|flip| + // | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + + uint8 RO, GO1, GO2, BO1, BO2, BO3, RH1, RH2, GH, BH, RV, GV, BV; + uint8 bit, a, b, c, d, bits; + + RO = GETBITSHIGH( planar57_word1, 6, 63); + GO1= GETBITSHIGH( planar57_word1, 1, 57); + GO2= GETBITSHIGH( planar57_word1, 6, 56); + BO1= GETBITSHIGH( planar57_word1, 1, 50); + BO2= GETBITSHIGH( planar57_word1, 2, 49); + BO3= GETBITSHIGH( planar57_word1, 3, 47); + RH1= GETBITSHIGH( planar57_word1, 5, 44); + RH2= GETBITSHIGH( planar57_word1, 1, 39); + GH = GETBITSHIGH( planar57_word1, 7, 38); + BH = GETBITS( planar57_word2, 6, 31); + RV = GETBITS( planar57_word2, 6, 25); + GV = GETBITS( planar57_word2, 7, 19); + BV = GETBITS( planar57_word2, 6, 12); + + planar_word1 = 0; planar_word2 = 0; + PUTBITSHIGH( planar_word1, RO, 6, 62); + PUTBITSHIGH( planar_word1, GO1, 1, 56); + PUTBITSHIGH( planar_word1, GO2, 6, 54); + PUTBITSHIGH( planar_word1, BO1, 1, 48); + PUTBITSHIGH( planar_word1, BO2, 2, 44); + PUTBITSHIGH( planar_word1, BO3, 3, 41); + PUTBITSHIGH( planar_word1, RH1, 5, 38); + PUTBITSHIGH( planar_word1, RH2, 1, 32); + PUTBITS( planar_word2, GH, 7, 31); + PUTBITS( planar_word2, BH, 6, 24); + PUTBITS( planar_word2, RV, 6, 18); + PUTBITS( planar_word2, GV, 7, 12); + PUTBITS( planar_word2, BV, 6, 5); + + // Make sure that red does not overflow: + bit = GETBITSHIGH( planar_word1, 1, 62); + PUTBITSHIGH( planar_word1, !bit, 1, 63); + + // Make sure that green does not overflow: + bit = GETBITSHIGH( planar_word1, 1, 54); + PUTBITSHIGH( planar_word1, !bit, 1, 55); + + // Make sure that blue overflows: + a = GETBITSHIGH( planar_word1, 1, 44); + b = GETBITSHIGH( planar_word1, 1, 43); + c = GETBITSHIGH( planar_word1, 1, 41); + d = GETBITSHIGH( planar_word1, 1, 40); + // The following bit abcd bit sequences should be padded with ones: 0111, 1010, 1011, 1101, 1110, 1111 + // The following logical expression checks for the presence of any of those: + bit = (a & c) | (!a & b & c & d) | (a & b & !c & d); + bits = 0xf*bit; + PUTBITSHIGH( planar_word1, bits, 3, 47); + PUTBITSHIGH( planar_word1, !bit, 1, 42); + + // Set diffbit + PUTBITSHIGH( planar_word1, 1, 1, 33); +} + +// During search it is not convenient to store the bits the way they are stored in the +// file format. Hence, after search, it is converted to this format. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void stuff58bits(unsigned int thumbH58_word1, unsigned int thumbH58_word2, unsigned int &thumbH_word1, unsigned int &thumbH_word2) +{ + // Put bits in twotimer configuration for 58 (red doesn't overflow, green does) + // + // Go from this bit layout: + // + // + // |63 62 61 60 59 58|57 56 55 54|53 52 51 50|49 48 47 46|45 44 43 42|41 40 39 38|37 36 35 34|33 32| + // |-------empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|d2 d1| + // + // |31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| + // |---------------------------------------index bits----------------------------------------------| + // + // To this: + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // ----------------------------------------------------------------------------------------------- + // |//|R0 |G0 |// // //|G0|B0|//|B0b |R1 |G1 |B0 |d2|df|d1| + // ----------------------------------------------------------------------------------------------- + // + // |31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| + // |---------------------------------------index bits----------------------------------------------| + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // ----------------------------------------------------------------------------------------------- + // | base col1 | dcol 2 | base col1 | dcol 2 | base col 1 | dcol 2 | table | table |df|fp| + // | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | B1' (5 bits) | dB2 | cw 1 | cw 2 |bt|bt| + // ----------------------------------------------------------------------------------------------- + // + // + // Thus, what we are really doing is going from this bit layout: + // + // + // |63 62 61 60 59 58|57 56 55 54 53 52 51|50 49|48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33|32 | + // |-------empty-----|part0---------------|part1|part2------------------------------------------|part3| + // + // To this: + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------| + // |//|part0 |// // //|part1|//|part2 |df|part3| + // --------------------------------------------------------------------------------------------------| + + unsigned int part0, part1, part2, part3; + uint8 bit, a, b, c, d, bits; + + // move parts + part0 = GETBITSHIGH( thumbH58_word1, 7, 57); + part1 = GETBITSHIGH( thumbH58_word1, 2, 50); + part2 = GETBITSHIGH( thumbH58_word1,16, 48); + part3 = GETBITSHIGH( thumbH58_word1, 1, 32); + thumbH_word1 = 0; + PUTBITSHIGH( thumbH_word1, part0, 7, 62); + PUTBITSHIGH( thumbH_word1, part1, 2, 52); + PUTBITSHIGH( thumbH_word1, part2, 16, 49); + PUTBITSHIGH( thumbH_word1, part3, 1, 32); + + // Make sure that red does not overflow: + bit = GETBITSHIGH( thumbH_word1, 1, 62); + PUTBITSHIGH( thumbH_word1, !bit, 1, 63); + + // Make sure that green overflows: + a = GETBITSHIGH( thumbH_word1, 1, 52); + b = GETBITSHIGH( thumbH_word1, 1, 51); + c = GETBITSHIGH( thumbH_word1, 1, 49); + d = GETBITSHIGH( thumbH_word1, 1, 48); + // The following bit abcd bit sequences should be padded with ones: 0111, 1010, 1011, 1101, 1110, 1111 + // The following logical expression checks for the presence of any of those: + bit = (a & c) | (!a & b & c & d) | (a & b & !c & d); + bits = 0xf*bit; + PUTBITSHIGH( thumbH_word1, bits, 3, 55); + PUTBITSHIGH( thumbH_word1, !bit, 1, 50); + + // Set diffbit + PUTBITSHIGH( thumbH_word1, 1, 1, 33); + thumbH_word2 = thumbH58_word2; + +} + +// copy of above, but diffbit is 0 +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void stuff58bitsDiffFalse(unsigned int thumbH58_word1, unsigned int thumbH58_word2, unsigned int &thumbH_word1, unsigned int &thumbH_word2) +{ + unsigned int part0, part1, part2, part3; + uint8 bit, a, b, c, d, bits; + + // move parts + part0 = GETBITSHIGH( thumbH58_word1, 7, 57); + part1 = GETBITSHIGH( thumbH58_word1, 2, 50); + part2 = GETBITSHIGH( thumbH58_word1,16, 48); + part3 = GETBITSHIGH( thumbH58_word1, 1, 32); + thumbH_word1 = 0; + PUTBITSHIGH( thumbH_word1, part0, 7, 62); + PUTBITSHIGH( thumbH_word1, part1, 2, 52); + PUTBITSHIGH( thumbH_word1, part2, 16, 49); + PUTBITSHIGH( thumbH_word1, part3, 1, 32); + + // Make sure that red does not overflow: + bit = GETBITSHIGH( thumbH_word1, 1, 62); + PUTBITSHIGH( thumbH_word1, !bit, 1, 63); + + // Make sure that green overflows: + a = GETBITSHIGH( thumbH_word1, 1, 52); + b = GETBITSHIGH( thumbH_word1, 1, 51); + c = GETBITSHIGH( thumbH_word1, 1, 49); + d = GETBITSHIGH( thumbH_word1, 1, 48); + // The following bit abcd bit sequences should be padded with ones: 0111, 1010, 1011, 1101, 1110, 1111 + // The following logical expression checks for the presence of any of those: + bit = (a & c) | (!a & b & c & d) | (a & b & !c & d); + bits = 0xf*bit; + PUTBITSHIGH( thumbH_word1, bits, 3, 55); + PUTBITSHIGH( thumbH_word1, !bit, 1, 50); + + // Set diffbit + PUTBITSHIGH( thumbH_word1, 0, 1, 33); + thumbH_word2 = thumbH58_word2; + +} + +// During search it is not convenient to store the bits the way they are stored in the +// file format. Hence, after search, it is converted to this format. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void stuff59bits(unsigned int thumbT59_word1, unsigned int thumbT59_word2, unsigned int &thumbT_word1, unsigned int &thumbT_word2) +{ + // Put bits in twotimer configuration for 59 (red overflows) + // + // Go from this bit layout: + // + // |63 62 61 60 59|58 57 56 55|54 53 52 51|50 49 48 47|46 45 44 43|42 41 40 39|38 37 36 35|34 33 32| + // |----empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|--dist--| + // + // |31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| + // |----------------------------------------index bits---------------------------------------------| + // + // + // To this: + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // ----------------------------------------------------------------------------------------------- + // |// // //|R0a |//|R0b |G0 |B0 |R1 |G1 |B1 |da |df|db| + // ----------------------------------------------------------------------------------------------- + // + // |31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| + // |----------------------------------------index bits---------------------------------------------| + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // ----------------------------------------------------------------------------------------------- + // | base col1 | dcol 2 | base col1 | dcol 2 | base col 1 | dcol 2 | table | table |df|fp| + // | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | B1' (5 bits) | dB2 | cw 1 | cw 2 |bt|bt| + // ------------------------------------------------------------------------------------------------ + + uint8 R0a; + uint8 bit, a, b, c, d, bits; + + R0a = GETBITSHIGH( thumbT59_word1, 2, 58); + + // Fix middle part + thumbT_word1 = thumbT59_word1 << 1; + // Fix R0a (top two bits of R0) + PUTBITSHIGH( thumbT_word1, R0a, 2, 60); + // Fix db (lowest bit of d) + PUTBITSHIGH( thumbT_word1, thumbT59_word1, 1, 32); + // + // Make sure that red overflows: + a = GETBITSHIGH( thumbT_word1, 1, 60); + b = GETBITSHIGH( thumbT_word1, 1, 59); + c = GETBITSHIGH( thumbT_word1, 1, 57); + d = GETBITSHIGH( thumbT_word1, 1, 56); + // The following bit abcd bit sequences should be padded with ones: 0111, 1010, 1011, 1101, 1110, 1111 + // The following logical expression checks for the presence of any of those: + bit = (a & c) | (!a & b & c & d) | (a & b & !c & d); + bits = 0xf*bit; + PUTBITSHIGH( thumbT_word1, bits, 3, 63); + PUTBITSHIGH( thumbT_word1, !bit, 1, 58); + + // Set diffbit + PUTBITSHIGH( thumbT_word1, 1, 1, 33); + thumbT_word2 = thumbT59_word2; +} + + +// Decompress the planar mode and calculate the error per component compared to original image. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void decompressBlockPlanar57errorPerComponent(unsigned int compressed57_1, unsigned int compressed57_2, uint8 *img,int width,int height,int startx,int starty, uint8 *srcimg, unsigned int &error_red, unsigned int &error_green, unsigned int &error_blue) +{ + uint8 colorO[3], colorH[3], colorV[3]; + + colorO[0] = GETBITSHIGH( compressed57_1, 6, 63); + colorO[1] = GETBITSHIGH( compressed57_1, 7, 57); + colorO[2] = GETBITSHIGH( compressed57_1, 6, 50); + colorH[0] = GETBITSHIGH( compressed57_1, 6, 44); + colorH[1] = GETBITSHIGH( compressed57_1, 7, 38); + colorH[2] = GETBITS( compressed57_2, 6, 31); + colorV[0] = GETBITS( compressed57_2, 6, 25); + colorV[1] = GETBITS( compressed57_2, 7, 19); + colorV[2] = GETBITS( compressed57_2, 6, 12); + + colorO[0] = (colorO[0] << 2) | (colorO[0] >> 4); + colorO[1] = (colorO[1] << 1) | (colorO[1] >> 6); + colorO[2] = (colorO[2] << 2) | (colorO[2] >> 4); + + colorH[0] = (colorH[0] << 2) | (colorH[0] >> 4); + colorH[1] = (colorH[1] << 1) | (colorH[1] >> 6); + colorH[2] = (colorH[2] << 2) | (colorH[2] >> 4); + + colorV[0] = (colorV[0] << 2) | (colorV[0] >> 4); + colorV[1] = (colorV[1] << 1) | (colorV[1] >> 6); + colorV[2] = (colorV[2] << 2) | (colorV[2] >> 4); + + int xx, yy; + + for( xx=0; xx<4; xx++) + { + for( yy=0; yy<4; yy++) + { + img[3*width*(starty+yy) + 3*(startx+xx) + 0] = (int)CLAMP(0, JAS_ROUND((xx*(colorH[0]-colorO[0])/4.0 + yy*(colorV[0]-colorO[0])/4.0 + colorO[0])), 255); + img[3*width*(starty+yy) + 3*(startx+xx) + 1] = (int)CLAMP(0, JAS_ROUND((xx*(colorH[1]-colorO[1])/4.0 + yy*(colorV[1]-colorO[1])/4.0 + colorO[1])), 255); + img[3*width*(starty+yy) + 3*(startx+xx) + 2] = (int)CLAMP(0, JAS_ROUND((xx*(colorH[2]-colorO[2])/4.0 + yy*(colorV[2]-colorO[2])/4.0 + colorO[2])), 255); + } + } + + error_red = 0; + error_green= 0; + error_blue = 0; + for( xx=0; xx<4; xx++) + { + for( yy=0; yy<4; yy++) + { + error_red = error_red + SQUARE(srcimg[3*width*(starty+yy) + 3*(startx+xx) + 0] - img[3*width*(starty+yy) + 3*(startx+xx) + 0]); + error_green = error_green + SQUARE(srcimg[3*width*(starty+yy) + 3*(startx+xx) + 1] - img[3*width*(starty+yy) + 3*(startx+xx) + 1]); + error_blue = error_blue + SQUARE(srcimg[3*width*(starty+yy) + 3*(startx+xx) + 2] - img[3*width*(starty+yy) + 3*(startx+xx) + 2]); + + } + } +} + +// Compress using both individual and differential mode in ETC1/ETC2 using combined color +// quantization. Both flip modes are tried. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void compressBlockDiffFlipCombined(uint8 *img,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2) +{ + unsigned int compressed1_norm, compressed2_norm; + unsigned int compressed1_flip, compressed2_flip; + uint8 avg_color_quant1[3], avg_color_quant2[3]; + + float avg_color_float1[3],avg_color_float2[3]; + int enc_color1[3], enc_color2[3], diff[3]; + int min_error=255*255*8*3; + unsigned int best_table_indices1=0, best_table_indices2=0; + unsigned int best_table1=0, best_table2=0; + int diffbit; + + int norm_err=0; + int flip_err=0; + + // First try normal blocks 2x4: + + computeAverageColor2x4noQuantFloat(img,width,height,startx,starty,avg_color_float1); + computeAverageColor2x4noQuantFloat(img,width,height,startx+2,starty,avg_color_float2); + + // First test if avg_color1 is similar enough to avg_color2 so that + // we can use differential coding of colors. + + float eps; + + uint8 dummy[3]; + + quantize555ColorCombined(avg_color_float1, enc_color1, dummy); + quantize555ColorCombined(avg_color_float2, enc_color2, dummy); + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + if( (diff[0] >= -4) && (diff[0] <= 3) && (diff[1] >= -4) && (diff[1] <= 3) && (diff[2] >= -4) && (diff[2] <= 3) ) + { + diffbit = 1; + + // The difference to be coded: + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + avg_color_quant1[0] = enc_color1[0] << 3 | (enc_color1[0] >> 2); + avg_color_quant1[1] = enc_color1[1] << 3 | (enc_color1[1] >> 2); + avg_color_quant1[2] = enc_color1[2] << 3 | (enc_color1[2] >> 2); + avg_color_quant2[0] = enc_color2[0] << 3 | (enc_color2[0] >> 2); + avg_color_quant2[1] = enc_color2[1] << 3 | (enc_color2[1] >> 2); + avg_color_quant2[2] = enc_color2[2] << 3 | (enc_color2[2] >> 2); + + // Pack bits into the first word. + + // ETC1_RGB8_OES: + // + // a) bit layout in bits 63 through 32 if diffbit = 0 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | base col2 | base col1 | base col2 | base col1 | base col2 | table | table |diff|flip| + // | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // b) bit layout in bits 63 through 32 if diffbit = 1 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | dcol 2 | base col1 | dcol 2 | base col 1 | dcol 2 | table | table |diff|flip| + // | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // c) bit layout in bits 31 through 0 (in both cases) + // + // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // -------------------------------------------------------------------------------------------------- + // | most significant pixel index bits | least significant pixel index bits | + // | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a | + // -------------------------------------------------------------------------------------------------- + + compressed1_norm = 0; + PUTBITSHIGH( compressed1_norm, diffbit, 1, 33); + PUTBITSHIGH( compressed1_norm, enc_color1[0], 5, 63); + PUTBITSHIGH( compressed1_norm, enc_color1[1], 5, 55); + PUTBITSHIGH( compressed1_norm, enc_color1[2], 5, 47); + PUTBITSHIGH( compressed1_norm, diff[0], 3, 58); + PUTBITSHIGH( compressed1_norm, diff[1], 3, 50); + PUTBITSHIGH( compressed1_norm, diff[2], 3, 42); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + norm_err = 0; + + // left part of block + norm_err = tryalltables_3bittable2x4(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + + // right part of block + norm_err += tryalltables_3bittable2x4(img,width,height,startx+2,starty,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_norm, best_table1, 3, 39); + PUTBITSHIGH( compressed1_norm, best_table2, 3, 36); + PUTBITSHIGH( compressed1_norm, 0, 1, 32); + + compressed2_norm = 0; + PUTBITS( compressed2_norm, (best_pixel_indices1_MSB ), 8, 23); + PUTBITS( compressed2_norm, (best_pixel_indices2_MSB ), 8, 31); + PUTBITS( compressed2_norm, (best_pixel_indices1_LSB ), 8, 7); + PUTBITS( compressed2_norm, (best_pixel_indices2_LSB ), 8, 15); + + } + else + { + diffbit = 0; + // The difference is bigger than what fits in 555 plus delta-333, so we will have + // to deal with 444 444. + + eps = (float) 0.0001; + + uint8 dummy[3]; + quantize444ColorCombined(avg_color_float1, enc_color1, dummy); + quantize444ColorCombined(avg_color_float2, enc_color2, dummy); + + avg_color_quant1[0] = enc_color1[0] << 4 | enc_color1[0]; + avg_color_quant1[1] = enc_color1[1] << 4 | enc_color1[1]; + avg_color_quant1[2] = enc_color1[2] << 4 | enc_color1[2]; + avg_color_quant2[0] = enc_color2[0] << 4 | enc_color2[0]; + avg_color_quant2[1] = enc_color2[1] << 4 | enc_color2[1]; + avg_color_quant2[2] = enc_color2[2] << 4 | enc_color2[2]; + + + // Pack bits into the first word. + + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | base col2 | base col1 | base col2 | base col1 | base col2 | table | table |diff|flip| + // | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + + compressed1_norm = 0; + PUTBITSHIGH( compressed1_norm, diffbit, 1, 33); + PUTBITSHIGH( compressed1_norm, enc_color1[0], 4, 63); + PUTBITSHIGH( compressed1_norm, enc_color1[1], 4, 55); + PUTBITSHIGH( compressed1_norm, enc_color1[2], 4, 47); + PUTBITSHIGH( compressed1_norm, enc_color2[0], 4, 59); + PUTBITSHIGH( compressed1_norm, enc_color2[1], 4, 51); + PUTBITSHIGH( compressed1_norm, enc_color2[2], 4, 43); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + // left part of block + norm_err = tryalltables_3bittable2x4(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + + // right part of block + norm_err += tryalltables_3bittable2x4(img,width,height,startx+2,starty,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_norm, best_table1, 3, 39); + PUTBITSHIGH( compressed1_norm, best_table2, 3, 36); + PUTBITSHIGH( compressed1_norm, 0, 1, 32); + + compressed2_norm = 0; + PUTBITS( compressed2_norm, (best_pixel_indices1_MSB ), 8, 23); + PUTBITS( compressed2_norm, (best_pixel_indices2_MSB ), 8, 31); + PUTBITS( compressed2_norm, (best_pixel_indices1_LSB ), 8, 7); + PUTBITS( compressed2_norm, (best_pixel_indices2_LSB ), 8, 15); + } + + // Now try flipped blocks 4x2: + + computeAverageColor4x2noQuantFloat(img,width,height,startx,starty,avg_color_float1); + computeAverageColor4x2noQuantFloat(img,width,height,startx,starty+2,avg_color_float2); + + // First test if avg_color1 is similar enough to avg_color2 so that + // we can use differential coding of colors. + + quantize555ColorCombined(avg_color_float1, enc_color1, dummy); + quantize555ColorCombined(avg_color_float2, enc_color2, dummy); + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + if( (diff[0] >= -4) && (diff[0] <= 3) && (diff[1] >= -4) && (diff[1] <= 3) && (diff[2] >= -4) && (diff[2] <= 3) ) + { + diffbit = 1; + + // The difference to be coded: + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + avg_color_quant1[0] = enc_color1[0] << 3 | (enc_color1[0] >> 2); + avg_color_quant1[1] = enc_color1[1] << 3 | (enc_color1[1] >> 2); + avg_color_quant1[2] = enc_color1[2] << 3 | (enc_color1[2] >> 2); + avg_color_quant2[0] = enc_color2[0] << 3 | (enc_color2[0] >> 2); + avg_color_quant2[1] = enc_color2[1] << 3 | (enc_color2[1] >> 2); + avg_color_quant2[2] = enc_color2[2] << 3 | (enc_color2[2] >> 2); + + // Pack bits into the first word. + + compressed1_flip = 0; + PUTBITSHIGH( compressed1_flip, diffbit, 1, 33); + PUTBITSHIGH( compressed1_flip, enc_color1[0], 5, 63); + PUTBITSHIGH( compressed1_flip, enc_color1[1], 5, 55); + PUTBITSHIGH( compressed1_flip, enc_color1[2], 5, 47); + PUTBITSHIGH( compressed1_flip, diff[0], 3, 58); + PUTBITSHIGH( compressed1_flip, diff[1], 3, 50); + PUTBITSHIGH( compressed1_flip, diff[2], 3, 42); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + // upper part of block + flip_err = tryalltables_3bittable4x2(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + // lower part of block + flip_err += tryalltables_3bittable4x2(img,width,height,startx,starty+2,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_flip, best_table1, 3, 39); + PUTBITSHIGH( compressed1_flip, best_table2, 3, 36); + PUTBITSHIGH( compressed1_flip, 1, 1, 32); + + best_pixel_indices1_MSB |= (best_pixel_indices2_MSB << 2); + best_pixel_indices1_LSB |= (best_pixel_indices2_LSB << 2); + + compressed2_flip = ((best_pixel_indices1_MSB & 0xffff) << 16) | (best_pixel_indices1_LSB & 0xffff); + } + else + { + diffbit = 0; + // The difference is bigger than what fits in 555 plus delta-333, so we will have + // to deal with 444 444. + eps = (float) 0.0001; + + uint8 dummy[3]; + quantize444ColorCombined(avg_color_float1, enc_color1, dummy); + quantize444ColorCombined(avg_color_float2, enc_color2, dummy); + + avg_color_quant1[0] = enc_color1[0] << 4 | enc_color1[0]; + avg_color_quant1[1] = enc_color1[1] << 4 | enc_color1[1]; + avg_color_quant1[2] = enc_color1[2] << 4 | enc_color1[2]; + avg_color_quant2[0] = enc_color2[0] << 4 | enc_color2[0]; + avg_color_quant2[1] = enc_color2[1] << 4 | enc_color2[1]; + avg_color_quant2[2] = enc_color2[2] << 4 | enc_color2[2]; + + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | base col2 | base col1 | base col2 | base col1 | base col2 | table | table |diff|flip| + // | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + + + // Pack bits into the first word. + compressed1_flip = 0; + PUTBITSHIGH( compressed1_flip, diffbit, 1, 33); + PUTBITSHIGH( compressed1_flip, enc_color1[0], 4, 63); + PUTBITSHIGH( compressed1_flip, enc_color1[1], 4, 55); + PUTBITSHIGH( compressed1_flip, enc_color1[2], 4, 47); + PUTBITSHIGH( compressed1_flip, enc_color2[0], 4, 59); + PUTBITSHIGH( compressed1_flip, enc_color2[1], 4, 51); + PUTBITSHIGH( compressed1_flip, enc_color2[2], 4, 43); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + // upper part of block + flip_err = tryalltables_3bittable4x2(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + // lower part of block + flip_err += tryalltables_3bittable4x2(img,width,height,startx,starty+2,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_flip, best_table1, 3, 39); + PUTBITSHIGH( compressed1_flip, best_table2, 3, 36); + PUTBITSHIGH( compressed1_flip, 1, 1, 32); + + best_pixel_indices1_MSB |= (best_pixel_indices2_MSB << 2); + best_pixel_indices1_LSB |= (best_pixel_indices2_LSB << 2); + + compressed2_flip = ((best_pixel_indices1_MSB & 0xffff) << 16) | (best_pixel_indices1_LSB & 0xffff); + } + + // Now lets see which is the best table to use. Only 8 tables are possible. + + if(norm_err <= flip_err) + { + compressed1 = compressed1_norm | 0; + compressed2 = compressed2_norm; + } + else + { + compressed1 = compressed1_flip | 1; + compressed2 = compressed2_flip; + } +} + +// Calculation of the two block colors using the LBG-algorithm +// The following method scales down the intensity, since this can be compensated for anyway by both the H and T mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void computeColorLBGHalfIntensityFast(uint8 *img,int width,int startx,int starty, uint8 (LBG_colors)[2][3]) +{ + uint8 block_mask[4][4]; + + // reset rand so that we get predictable output per block + srand(10000); + //LBG-algorithm + double D = 0, oldD, bestD = MAXIMUM_ERROR, eps = 0.0000000001; + double error_a, error_b; + int number_of_iterations = 10; + double t_color[2][3]; + double original_colors[4][4][3]; + double current_colors[2][3]; + double best_colors[2][3]; + double max_v[3]; + double min_v[3]; + int x,y,i; + double red, green, blue; + bool continue_seeding; + int maximum_number_of_seedings = 10; + int seeding; + bool continue_iterate; + + max_v[R] = -512.0; max_v[G] = -512.0; max_v[B] = -512.0; + min_v[R] = 512.0; min_v[G] = 512.0; min_v[B] = 512.0; + + // resolve trainingdata + for (y = 0; y < BLOCKHEIGHT; ++y) + { + for (x = 0; x < BLOCKWIDTH; ++x) + { + red = img[3*((starty+y)*width+startx+x)+R]; + green = img[3*((starty+y)*width+startx+x)+G]; + blue = img[3*((starty+y)*width+startx+x)+B]; + + // Use qrs representation instead of rgb + // qrs = Q * rgb where Q = [a a a ; b -b 0 ; c c -2c]; a = 1/sqrt(3), b= 1/sqrt(2), c = 1/sqrt(6); + // rgb = inv(Q)*qrs = Q' * qrs where ' denotes transpose. + // The q variable holds intensity. r and s hold chrominance. + // q = [0, sqrt(3)*255], r = [-255/sqrt(2), 255/sqrt(2)], s = [-2*255/sqrt(6), 2*255/sqrt(6)]; + // + // The LGB algorithm will only act on the r and s variables and not on q. + // + original_colors[x][y][R] = (1.0/sqrt(1.0*3))*red + (1.0/sqrt(1.0*3))*green + (1.0/sqrt(1.0*3))*blue; + original_colors[x][y][G] = (1.0/sqrt(1.0*2))*red - (1.0/sqrt(1.0*2))*green; + original_colors[x][y][B] = (1.0/sqrt(1.0*6))*red + (1.0/sqrt(1.0*6))*green - (2.0/sqrt(1.0*6))*blue; + + // find max + if (original_colors[x][y][R] > max_v[R]) max_v[R] = original_colors[x][y][R]; + if (original_colors[x][y][G] > max_v[G]) max_v[G] = original_colors[x][y][G]; + if (original_colors[x][y][B] > max_v[B]) max_v[B] = original_colors[x][y][B]; + // find min + if (original_colors[x][y][R] < min_v[R]) min_v[R] = original_colors[x][y][R]; + if (original_colors[x][y][G] < min_v[G]) min_v[G] = original_colors[x][y][G]; + if (original_colors[x][y][B] < min_v[B]) min_v[B] = original_colors[x][y][B]; + } + } + + D = 512*512*3*16.0; + bestD = 512*512*3*16.0; + + continue_seeding = true; + + // loop seeds + for (seeding = 0; (seeding < maximum_number_of_seedings) && continue_seeding; seeding++) + { + // hopefully we will not need more seedings: + continue_seeding = false; + + // calculate seeds + for (uint8 s = 0; s < 2; ++s) + { + for (uint8 c = 0; c < 3; ++c) + { + current_colors[s][c] = double((double(rand())/RAND_MAX)*(max_v[c]-min_v[c])) + min_v[c]; + } + } + + // divide into two quantization sets and calculate distortion + + continue_iterate = true; + for(i = 0; (i < number_of_iterations) && continue_iterate; i++) + { + oldD = D; + D = 0; + int n = 0; + for (y = 0; y < BLOCKHEIGHT; ++y) + { + for (int x = 0; x < BLOCKWIDTH; ++x) + { + error_a = 0.5*SQUARE(original_colors[x][y][R] - current_colors[0][R]) + + SQUARE(original_colors[x][y][G] - current_colors[0][G]) + + SQUARE(original_colors[x][y][B] - current_colors[0][B]); + error_b = 0.5*SQUARE(original_colors[x][y][R] - current_colors[1][R]) + + SQUARE(original_colors[x][y][G] - current_colors[1][G]) + + SQUARE(original_colors[x][y][B] - current_colors[1][B]); + if (error_a < error_b) + { + block_mask[x][y] = 0; + D += error_a; + ++n; + } + else + { + block_mask[x][y] = 1; + D += error_b; + } + } + } + + // compare with old distortion + if (D == 0) + { + // Perfect score -- we dont need to go further iterations. + continue_iterate = false; + continue_seeding = false; + } + if (D == oldD) + { + // Same score as last round -- no need to go for further iterations. + continue_iterate = false; + continue_seeding = false; + } + if (D < bestD) + { + bestD = D; + for(uint8 s = 0; s < 2; ++s) + { + for(uint8 c = 0; c < 3; ++c) + { + best_colors[s][c] = current_colors[s][c]; + } + } + } + if (n == 0 || n == BLOCKWIDTH*BLOCKHEIGHT) + { + // All colors end up in the same voroni region. We need to reseed. + continue_iterate = false; + continue_seeding = true; + } + else + { + // Calculate new reconstruction points using the centroids + + // Find new construction values from average + t_color[0][R] = 0; + t_color[0][G] = 0; + t_color[0][B] = 0; + t_color[1][R] = 0; + t_color[1][G] = 0; + t_color[1][B] = 0; + + for (y = 0; y < BLOCKHEIGHT; ++y) + { + for (int x = 0; x < BLOCKWIDTH; ++x) + { + // use dummy value for q-parameter + t_color[block_mask[x][y]][R] += original_colors[x][y][R]; + t_color[block_mask[x][y]][G] += original_colors[x][y][G]; + t_color[block_mask[x][y]][B] += original_colors[x][y][B]; + } + } + current_colors[0][R] = t_color[0][R] / n; + current_colors[1][R] = t_color[1][R] / (BLOCKWIDTH*BLOCKHEIGHT - n); + current_colors[0][G] = t_color[0][G] / n; + current_colors[1][G] = t_color[1][G] / (BLOCKWIDTH*BLOCKHEIGHT - n); + current_colors[0][B] = t_color[0][B] / n; + current_colors[1][B] = t_color[1][B] / (BLOCKWIDTH*BLOCKHEIGHT - n); + } + } + } + + for(x=0;x<2;x++) + { + double qq, rr, ss; + + qq = best_colors[x][0]; + rr = best_colors[x][1]; + ss = best_colors[x][2]; + + current_colors[x][0] = CLAMP(0, (1.0/sqrt(1.0*3))*qq + (1.0/sqrt(1.0*2))*rr + (1.0/sqrt(1.0*6))*ss, 255); + current_colors[x][1] = CLAMP(0, (1.0/sqrt(1.0*3))*qq - (1.0/sqrt(1.0*2))*rr + (1.0/sqrt(1.0*6))*ss, 255); + current_colors[x][2] = CLAMP(0, (1.0/sqrt(1.0*3))*qq + (0.0 )*rr - (2.0/sqrt(1.0*6))*ss, 255); + } + + for(x=0;x<2;x++) + for(y=0;y<3;y++) + LBG_colors[x][y] = JAS_ROUND(current_colors[x][y]); +} + +// Calculation of the two block colors using the LBG-algorithm +// The following method scales down the intensity, since this can be compensated for anyway by both the H and T mode. +// Faster version +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void computeColorLBGNotIntensityFast(uint8 *img,int width,int startx,int starty, uint8 (LBG_colors)[2][3]) +{ + uint8 block_mask[4][4]; + + // reset rand so that we get predictable output per block + srand(10000); + //LBG-algorithm + double D = 0, oldD, bestD = MAXIMUM_ERROR, eps = 0.0000000001; + double error_a, error_b; + int number_of_iterations = 10; + double t_color[2][3]; + double original_colors[4][4][3]; + double current_colors[2][3]; + double best_colors[2][3]; + double max_v[3]; + double min_v[3]; + int x,y,i; + double red, green, blue; + bool continue_seeding; + int maximum_number_of_seedings = 10; + int seeding; + bool continue_iterate; + + max_v[R] = -512.0; max_v[G] = -512.0; max_v[B] = -512.0; + min_v[R] = 512.0; min_v[G] = 512.0; min_v[B] = 512.0; + + // resolve trainingdata + for (y = 0; y < BLOCKHEIGHT; ++y) + { + for (x = 0; x < BLOCKWIDTH; ++x) + { + red = img[3*((starty+y)*width+startx+x)+R]; + green = img[3*((starty+y)*width+startx+x)+G]; + blue = img[3*((starty+y)*width+startx+x)+B]; + + // Use qrs representation instead of rgb + // qrs = Q * rgb where Q = [a a a ; b -b 0 ; c c -2c]; a = 1/sqrt(1.0*3), b= 1/sqrt(1.0*2), c = 1/sqrt(1.0*6); + // rgb = inv(Q)*qrs = Q' * qrs where ' denotes transpose. + // The q variable holds intensity. r and s hold chrominance. + // q = [0, sqrt(1.0*3)*255], r = [-255/sqrt(1.0*2), 255/sqrt(1.0*2)], s = [-2*255/sqrt(1.0*6), 2*255/sqrt(1.0*6)]; + // + // The LGB algorithm will only act on the r and s variables and not on q. + // + original_colors[x][y][R] = (1.0/sqrt(1.0*3))*red + (1.0/sqrt(1.0*3))*green + (1.0/sqrt(1.0*3))*blue; + original_colors[x][y][G] = (1.0/sqrt(1.0*2))*red - (1.0/sqrt(1.0*2))*green; + original_colors[x][y][B] = (1.0/sqrt(1.0*6))*red + (1.0/sqrt(1.0*6))*green - (2.0/sqrt(1.0*6))*blue; + + // find max + if (original_colors[x][y][R] > max_v[R]) max_v[R] = original_colors[x][y][R]; + if (original_colors[x][y][G] > max_v[G]) max_v[G] = original_colors[x][y][G]; + if (original_colors[x][y][B] > max_v[B]) max_v[B] = original_colors[x][y][B]; + // find min + if (original_colors[x][y][R] < min_v[R]) min_v[R] = original_colors[x][y][R]; + if (original_colors[x][y][G] < min_v[G]) min_v[G] = original_colors[x][y][G]; + if (original_colors[x][y][B] < min_v[B]) min_v[B] = original_colors[x][y][B]; + } + } + + D = 512*512*3*16.0; + bestD = 512*512*3*16.0; + + continue_seeding = true; + + // loop seeds + for (seeding = 0; (seeding < maximum_number_of_seedings) && continue_seeding; seeding++) + { + // hopefully we will not need more seedings: + continue_seeding = false; + + // calculate seeds + for (uint8 s = 0; s < 2; ++s) + { + for (uint8 c = 0; c < 3; ++c) + { + current_colors[s][c] = double((double(rand())/RAND_MAX)*(max_v[c]-min_v[c])) + min_v[c]; + } + } + // divide into two quantization sets and calculate distortion + + continue_iterate = true; + for(i = 0; (i < number_of_iterations) && continue_iterate; i++) + { + oldD = D; + D = 0; + int n = 0; + for (y = 0; y < BLOCKHEIGHT; ++y) + { + for (int x = 0; x < BLOCKWIDTH; ++x) + { + error_a = 0.0*SQUARE(original_colors[x][y][R] - current_colors[0][R]) + + SQUARE(original_colors[x][y][G] - current_colors[0][G]) + + SQUARE(original_colors[x][y][B] - current_colors[0][B]); + error_b = 0.0*SQUARE(original_colors[x][y][R] - current_colors[1][R]) + + SQUARE(original_colors[x][y][G] - current_colors[1][G]) + + SQUARE(original_colors[x][y][B] - current_colors[1][B]); + if (error_a < error_b) + { + block_mask[x][y] = 0; + D += error_a; + ++n; + } + else + { + block_mask[x][y] = 1; + D += error_b; + } + } + } + + // compare with old distortion + if (D == 0) + { + // Perfect score -- we dont need to go further iterations. + continue_iterate = false; + continue_seeding = false; + } + if (D == oldD) + { + // Same score as last round -- no need to go for further iterations. + continue_iterate = false; + continue_seeding = false; + } + if (D < bestD) + { + bestD = D; + for(uint8 s = 0; s < 2; ++s) + { + for(uint8 c = 0; c < 3; ++c) + { + best_colors[s][c] = current_colors[s][c]; + } + } + } + if (n == 0 || n == BLOCKWIDTH*BLOCKHEIGHT) + { + // All colors end up in the same voroni region. We need to reseed. + continue_iterate = false; + continue_seeding = true; + } + else + { + // Calculate new reconstruction points using the centroids + + // Find new construction values from average + t_color[0][R] = 0; + t_color[0][G] = 0; + t_color[0][B] = 0; + t_color[1][R] = 0; + t_color[1][G] = 0; + t_color[1][B] = 0; + + for (y = 0; y < BLOCKHEIGHT; ++y) + { + for (int x = 0; x < BLOCKWIDTH; ++x) + { + // use dummy value for q-parameter + t_color[block_mask[x][y]][R] += original_colors[x][y][R]; + t_color[block_mask[x][y]][G] += original_colors[x][y][G]; + t_color[block_mask[x][y]][B] += original_colors[x][y][B]; + } + } + current_colors[0][R] = t_color[0][R] / n; + current_colors[1][R] = t_color[1][R] / (BLOCKWIDTH*BLOCKHEIGHT - n); + current_colors[0][G] = t_color[0][G] / n; + current_colors[1][G] = t_color[1][G] / (BLOCKWIDTH*BLOCKHEIGHT - n); + current_colors[0][B] = t_color[0][B] / n; + current_colors[1][B] = t_color[1][B] / (BLOCKWIDTH*BLOCKHEIGHT - n); + } + } + } + + for(x=0;x<2;x++) + { + double qq, rr, ss; + + qq = best_colors[x][0]; + rr = best_colors[x][1]; + ss = best_colors[x][2]; + + current_colors[x][0] = CLAMP(0, (1.0/sqrt(1.0*3))*qq + (1.0/sqrt(1.0*2))*rr + (1.0/sqrt(1.0*6))*ss, 255); + current_colors[x][1] = CLAMP(0, (1.0/sqrt(1.0*3))*qq - (1.0/sqrt(1.0*2))*rr + (1.0/sqrt(1.0*6))*ss, 255); + current_colors[x][2] = CLAMP(0, (1.0/sqrt(1.0*3))*qq + (0.0 )*rr - (2.0/sqrt(1.0*6))*ss, 255); + } + + for(x=0;x<2;x++) + for(y=0;y<3;y++) + LBG_colors[x][y] = JAS_ROUND(current_colors[x][y]); +} + +// Calculation of the two block colors using the LBG-algorithm +// The following method completely ignores the intensity, since this can be compensated for anyway by both the H and T mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void computeColorLBGNotIntensity(uint8 *img,int width,int startx,int starty, uint8 (LBG_colors)[2][3]) +{ + uint8 block_mask[4][4]; + + // reset rand so that we get predictable output per block + srand(10000); + //LBG-algorithm + double D = 0, oldD, bestD = MAXIMUM_ERROR, eps = 0.0000000001; + double error_a, error_b; + int number_of_iterations = 10; + double t_color[2][3]; + double original_colors[4][4][3]; + double current_colors[2][3]; + double best_colors[2][3]; + double max_v[3]; + double min_v[3]; + int x,y,i; + double red, green, blue; + bool continue_seeding; + int maximum_number_of_seedings = 10; + int seeding; + bool continue_iterate; + + max_v[R] = -512.0; max_v[G] = -512.0; max_v[B] = -512.0; + min_v[R] = 512.0; min_v[G] = 512.0; min_v[B] = 512.0; + + // resolve trainingdata + for (y = 0; y < BLOCKHEIGHT; ++y) + { + for (x = 0; x < BLOCKWIDTH; ++x) + { + red = img[3*((starty+y)*width+startx+x)+R]; + green = img[3*((starty+y)*width+startx+x)+G]; + blue = img[3*((starty+y)*width+startx+x)+B]; + + // Use qrs representation instead of rgb + // qrs = Q * rgb where Q = [a a a ; b -b 0 ; c c -2c]; a = 1/sqrt(1.0*3), b= 1/sqrt(1.0*2), c = 1/sqrt(1.0*6); + // rgb = inv(Q)*qrs = Q' * qrs where ' denotes transpose. + // The q variable holds intensity. r and s hold chrominance. + // q = [0, sqrt(1.0*3)*255], r = [-255/sqrt(1.0*2), 255/sqrt(1.0*2)], s = [-2*255/sqrt(1.0*6), 2*255/sqrt(1.0*6)]; + // + // The LGB algorithm will only act on the r and s variables and not on q. + // + original_colors[x][y][R] = (1.0/sqrt(1.0*3))*red + (1.0/sqrt(1.0*3))*green + (1.0/sqrt(1.0*3))*blue; + original_colors[x][y][G] = (1.0/sqrt(1.0*2))*red - (1.0/sqrt(1.0*2))*green; + original_colors[x][y][B] = (1.0/sqrt(1.0*6))*red + (1.0/sqrt(1.0*6))*green - (2.0/sqrt(1.0*6))*blue; + + // find max + if (original_colors[x][y][R] > max_v[R]) max_v[R] = original_colors[x][y][R]; + if (original_colors[x][y][G] > max_v[G]) max_v[G] = original_colors[x][y][G]; + if (original_colors[x][y][B] > max_v[B]) max_v[B] = original_colors[x][y][B]; + // find min + if (original_colors[x][y][R] < min_v[R]) min_v[R] = original_colors[x][y][R]; + if (original_colors[x][y][G] < min_v[G]) min_v[G] = original_colors[x][y][G]; + if (original_colors[x][y][B] < min_v[B]) min_v[B] = original_colors[x][y][B]; + } + } + + D = 512*512*3*16.0; + bestD = 512*512*3*16.0; + + continue_seeding = true; + + // loop seeds + for (seeding = 0; (seeding < maximum_number_of_seedings) && continue_seeding; seeding++) + { + // hopefully we will not need more seedings: + continue_seeding = false; + + // calculate seeds + for (uint8 s = 0; s < 2; ++s) + { + for (uint8 c = 0; c < 3; ++c) + { + current_colors[s][c] = double((double(rand())/RAND_MAX)*(max_v[c]-min_v[c])) + min_v[c]; + } + } + + // divide into two quantization sets and calculate distortion + + continue_iterate = true; + for(i = 0; (i < number_of_iterations) && continue_iterate; i++) + { + oldD = D; + D = 0; + int n = 0; + for (y = 0; y < BLOCKHEIGHT; ++y) + { + for (int x = 0; x < BLOCKWIDTH; ++x) + { + error_a = 0.0*SQUARE(original_colors[x][y][R] - current_colors[0][R]) + + SQUARE(original_colors[x][y][G] - current_colors[0][G]) + + SQUARE(original_colors[x][y][B] - current_colors[0][B]); + error_b = 0.0*SQUARE(original_colors[x][y][R] - current_colors[1][R]) + + SQUARE(original_colors[x][y][G] - current_colors[1][G]) + + SQUARE(original_colors[x][y][B] - current_colors[1][B]); + if (error_a < error_b) + { + block_mask[x][y] = 0; + D += error_a; + ++n; + } + else + { + block_mask[x][y] = 1; + D += error_b; + } + } + } + + // compare with old distortion + if (D == 0) + { + // Perfect score -- we dont need to go further iterations. + continue_iterate = false; + continue_seeding = false; + } + if (D == oldD) + { + // Same score as last round -- no need to go for further iterations. + continue_iterate = false; + continue_seeding = true; + } + if (D < bestD) + { + bestD = D; + for(uint8 s = 0; s < 2; ++s) + { + for(uint8 c = 0; c < 3; ++c) + { + best_colors[s][c] = current_colors[s][c]; + } + } + } + if (n == 0 || n == BLOCKWIDTH*BLOCKHEIGHT) + { + // All colors end up in the same voroni region. We need to reseed. + continue_iterate = false; + continue_seeding = true; + } + else + { + // Calculate new reconstruction points using the centroids + + // Find new construction values from average + t_color[0][R] = 0; + t_color[0][G] = 0; + t_color[0][B] = 0; + t_color[1][R] = 0; + t_color[1][G] = 0; + t_color[1][B] = 0; + + for (y = 0; y < BLOCKHEIGHT; ++y) + { + for (int x = 0; x < BLOCKWIDTH; ++x) + { + // use dummy value for q-parameter + t_color[block_mask[x][y]][R] += original_colors[x][y][R]; + t_color[block_mask[x][y]][G] += original_colors[x][y][G]; + t_color[block_mask[x][y]][B] += original_colors[x][y][B]; + } + } + current_colors[0][R] = t_color[0][R] / n; + current_colors[1][R] = t_color[1][R] / (BLOCKWIDTH*BLOCKHEIGHT - n); + current_colors[0][G] = t_color[0][G] / n; + current_colors[1][G] = t_color[1][G] / (BLOCKWIDTH*BLOCKHEIGHT - n); + current_colors[0][B] = t_color[0][B] / n; + current_colors[1][B] = t_color[1][B] / (BLOCKWIDTH*BLOCKHEIGHT - n); + } + } + } + + for(x=0;x<2;x++) + { + double qq, rr, ss; + + qq = best_colors[x][0]; + rr = best_colors[x][1]; + ss = best_colors[x][2]; + + current_colors[x][0] = CLAMP(0, (1.0/sqrt(1.0*3))*qq + (1.0/sqrt(1.0*2))*rr + (1.0/sqrt(1.0*6))*ss, 255); + current_colors[x][1] = CLAMP(0, (1.0/sqrt(1.0*3))*qq - (1.0/sqrt(1.0*2))*rr + (1.0/sqrt(1.0*6))*ss, 255); + current_colors[x][2] = CLAMP(0, (1.0/sqrt(1.0*3))*qq + (0.0 )*rr - (2.0/sqrt(1.0*6))*ss, 255); + } + + for(x=0;x<2;x++) + for(y=0;y<3;y++) + LBG_colors[x][y] = JAS_ROUND(current_colors[x][y]); +} + +// Calculation of the two block colors using the LBG-algorithm +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void computeColorLBG(uint8 *img,int width,int startx,int starty, uint8 (LBG_colors)[2][3]) +{ + uint8 block_mask[4][4]; + + // reset rand so that we get predictable output per block + srand(10000); + //LBG-algorithm + double D = 0, oldD, bestD = MAXIMUM_ERROR, eps = 0.0000000001; + double error_a, error_b; + int number_of_iterations = 10; + double t_color[2][3]; + double original_colors[4][4][3]; + double current_colors[2][3]; + double best_colors[2][3]; + double max_v[3]; + double min_v[3]; + int x,y,i; + double red, green, blue; + bool continue_seeding; + int maximum_number_of_seedings = 10; + int seeding; + bool continue_iterate; + + max_v[R] = -512.0; max_v[G] = -512.0; max_v[B] = -512.0; + min_v[R] = 512.0; min_v[G] = 512.0; min_v[B] = 512.0; + + // resolve trainingdata + for (y = 0; y < BLOCKHEIGHT; ++y) + { + for (x = 0; x < BLOCKWIDTH; ++x) + { + red = img[3*((starty+y)*width+startx+x)+R]; + green = img[3*((starty+y)*width+startx+x)+G]; + blue = img[3*((starty+y)*width+startx+x)+B]; + + original_colors[x][y][R] = red; + original_colors[x][y][G] = green; + original_colors[x][y][B] = blue; + + // find max + if (original_colors[x][y][R] > max_v[R]) max_v[R] = original_colors[x][y][R]; + if (original_colors[x][y][G] > max_v[G]) max_v[G] = original_colors[x][y][G]; + if (original_colors[x][y][B] > max_v[B]) max_v[B] = original_colors[x][y][B]; + // find min + if (original_colors[x][y][R] < min_v[R]) min_v[R] = original_colors[x][y][R]; + if (original_colors[x][y][G] < min_v[G]) min_v[G] = original_colors[x][y][G]; + if (original_colors[x][y][B] < min_v[B]) min_v[B] = original_colors[x][y][B]; + } + } + + D = 512*512*3*16.0; + bestD = 512*512*3*16.0; + + continue_seeding = true; + + // loop seeds + for (seeding = 0; (seeding < maximum_number_of_seedings) && continue_seeding; seeding++) + { + // hopefully we will not need more seedings: + continue_seeding = false; + + // calculate seeds + for (uint8 s = 0; s < 2; ++s) + { + for (uint8 c = 0; c < 3; ++c) + { + current_colors[s][c] = double((double(rand())/RAND_MAX)*(max_v[c]-min_v[c])) + min_v[c]; + } + } + + // divide into two quantization sets and calculate distortion + + continue_iterate = true; + for(i = 0; (i < number_of_iterations) && continue_iterate; i++) + { + oldD = D; + D = 0; + int n = 0; + for (y = 0; y < BLOCKHEIGHT; ++y) + { + for (int x = 0; x < BLOCKWIDTH; ++x) + { + error_a = SQUARE(original_colors[x][y][R] - JAS_ROUND(current_colors[0][R])) + + SQUARE(original_colors[x][y][G] - JAS_ROUND(current_colors[0][G])) + + SQUARE(original_colors[x][y][B] - JAS_ROUND(current_colors[0][B])); + error_b = SQUARE(original_colors[x][y][R] - JAS_ROUND(current_colors[1][R])) + + SQUARE(original_colors[x][y][G] - JAS_ROUND(current_colors[1][G])) + + SQUARE(original_colors[x][y][B] - JAS_ROUND(current_colors[1][B])); + if (error_a < error_b) + { + block_mask[x][y] = 0; + D += error_a; + ++n; + } + else + { + block_mask[x][y] = 1; + D += error_b; + } + } + } + + // compare with old distortion + if (D == 0) + { + // Perfect score -- we dont need to go further iterations. + continue_iterate = false; + continue_seeding = false; + } + if (D == oldD) + { + // Same score as last round -- no need to go for further iterations. + continue_iterate = false; + continue_seeding = true; + } + if (D < bestD) + { + bestD = D; + for(uint8 s = 0; s < 2; ++s) + { + for(uint8 c = 0; c < 3; ++c) + { + best_colors[s][c] = current_colors[s][c]; + } + } + } + if (n == 0 || n == BLOCKWIDTH*BLOCKHEIGHT) + { + // All colors end up in the same voroni region. We need to reseed. + continue_iterate = false; + continue_seeding = true; + } + else + { + // Calculate new reconstruction points using the centroids + + // Find new construction values from average + t_color[0][R] = 0; + t_color[0][G] = 0; + t_color[0][B] = 0; + t_color[1][R] = 0; + t_color[1][G] = 0; + t_color[1][B] = 0; + + for (y = 0; y < BLOCKHEIGHT; ++y) + { + for (int x = 0; x < BLOCKWIDTH; ++x) + { + // use dummy value for q-parameter + t_color[block_mask[x][y]][R] += original_colors[x][y][R]; + t_color[block_mask[x][y]][G] += original_colors[x][y][G]; + t_color[block_mask[x][y]][B] += original_colors[x][y][B]; + } + } + current_colors[0][R] = t_color[0][R] / n; + current_colors[1][R] = t_color[1][R] / (BLOCKWIDTH*BLOCKHEIGHT - n); + current_colors[0][G] = t_color[0][G] / n; + current_colors[1][G] = t_color[1][G] / (BLOCKWIDTH*BLOCKHEIGHT - n); + current_colors[0][B] = t_color[0][B] / n; + current_colors[1][B] = t_color[1][B] / (BLOCKWIDTH*BLOCKHEIGHT - n); + } + } + } + + // Set the best colors as the final block colors + for(int s = 0; s < 2; ++s) + { + for(uint8 c = 0; c < 3; ++c) + { + current_colors[s][c] = best_colors[s][c]; + } + } + + for(x=0;x<2;x++) + for(y=0;y<3;y++) + LBG_colors[x][y] = JAS_ROUND(current_colors[x][y]); +} + +// Calculation of the two block colors using the LBG-algorithm +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void computeColorLBGfast(uint8 *img,int width,int startx,int starty, uint8 (LBG_colors)[2][3]) +{ + uint8 block_mask[4][4]; + + // reset rand so that we get predictable output per block + srand(10000); + //LBG-algorithm + double D = 0, oldD, bestD = MAXIMUM_ERROR, eps = 0.0000000001; + double error_a, error_b; + int number_of_iterations = 10; + double t_color[2][3]; + uint8 original_colors[4][4][3]; + double current_colors[2][3]; + double best_colors[2][3]; + double max_v[3]; + double min_v[3]; + int x,y,i; + bool continue_seeding; + int maximum_number_of_seedings = 10; + int seeding; + bool continue_iterate; + + max_v[R] = -512.0; max_v[G] = -512.0; max_v[B] = -512.0; + min_v[R] = 512.0; min_v[G] = 512.0; min_v[B] = 512.0; + + // resolve trainingdata + for (y = 0; y < BLOCKHEIGHT; ++y) + { + for (x = 0; x < BLOCKWIDTH; ++x) + { + original_colors[x][y][R] = img[3*((starty+y)*width+startx+x)+R]; + original_colors[x][y][G] = img[3*((starty+y)*width+startx+x)+G]; + original_colors[x][y][B] = img[3*((starty+y)*width+startx+x)+B]; + + // find max + if (original_colors[x][y][R] > max_v[R]) max_v[R] = original_colors[x][y][R]; + if (original_colors[x][y][G] > max_v[G]) max_v[G] = original_colors[x][y][G]; + if (original_colors[x][y][B] > max_v[B]) max_v[B] = original_colors[x][y][B]; + // find min + if (original_colors[x][y][R] < min_v[R]) min_v[R] = original_colors[x][y][R]; + if (original_colors[x][y][G] < min_v[G]) min_v[G] = original_colors[x][y][G]; + if (original_colors[x][y][B] < min_v[B]) min_v[B] = original_colors[x][y][B]; + } + } + + D = 512*512*3*16.0; + bestD = 512*512*3*16.0; + + continue_seeding = true; + + // loop seeds + for (seeding = 0; (seeding < maximum_number_of_seedings) && continue_seeding; seeding++) + { + // hopefully we will not need more seedings: + continue_seeding = false; + + // calculate seeds + for (uint8 s = 0; s < 2; ++s) + { + for (uint8 c = 0; c < 3; ++c) + { + current_colors[s][c] = double((double(rand())/RAND_MAX)*(max_v[c]-min_v[c])) + min_v[c]; + } + } + + // divide into two quantization sets and calculate distortion + continue_iterate = true; + for(i = 0; (i < number_of_iterations) && continue_iterate; i++) + { + oldD = D; + D = 0; + int n = 0; + for (y = 0; y < BLOCKHEIGHT; ++y) + { + for (int x = 0; x < BLOCKWIDTH; ++x) + { + error_a = SQUARE(original_colors[x][y][R] - JAS_ROUND(current_colors[0][R])) + + SQUARE(original_colors[x][y][G] - JAS_ROUND(current_colors[0][G])) + + SQUARE(original_colors[x][y][B] - JAS_ROUND(current_colors[0][B])); + error_b = SQUARE(original_colors[x][y][R] - JAS_ROUND(current_colors[1][R])) + + SQUARE(original_colors[x][y][G] - JAS_ROUND(current_colors[1][G])) + + SQUARE(original_colors[x][y][B] - JAS_ROUND(current_colors[1][B])); + if (error_a < error_b) + { + block_mask[x][y] = 0; + D += error_a; + ++n; + } + else + { + block_mask[x][y] = 1; + D += error_b; + } + } + } + + // compare with old distortion + if (D == 0) + { + // Perfect score -- we dont need to go further iterations. + continue_iterate = false; + continue_seeding = false; + } + if (D == oldD) + { + // Same score as last round -- no need to go for further iterations. + continue_iterate = false; + continue_seeding = false; + } + if (D < bestD) + { + bestD = D; + for(uint8 s = 0; s < 2; ++s) + { + for(uint8 c = 0; c < 3; ++c) + { + best_colors[s][c] = current_colors[s][c]; + } + } + } + if (n == 0 || n == BLOCKWIDTH*BLOCKHEIGHT) + { + // All colors end up in the same voroni region. We need to reseed. + continue_iterate = false; + continue_seeding = true; + } + else + { + // Calculate new reconstruction points using the centroids + + // Find new construction values from average + t_color[0][R] = 0; + t_color[0][G] = 0; + t_color[0][B] = 0; + t_color[1][R] = 0; + t_color[1][G] = 0; + t_color[1][B] = 0; + + for (y = 0; y < BLOCKHEIGHT; ++y) + { + for (int x = 0; x < BLOCKWIDTH; ++x) + { + // use dummy value for q-parameter + t_color[block_mask[x][y]][R] += original_colors[x][y][R]; + t_color[block_mask[x][y]][G] += original_colors[x][y][G]; + t_color[block_mask[x][y]][B] += original_colors[x][y][B]; + } + } + current_colors[0][R] = t_color[0][R] / n; + current_colors[1][R] = t_color[1][R] / (BLOCKWIDTH*BLOCKHEIGHT - n); + current_colors[0][G] = t_color[0][G] / n; + current_colors[1][G] = t_color[1][G] / (BLOCKWIDTH*BLOCKHEIGHT - n); + current_colors[0][B] = t_color[0][B] / n; + current_colors[1][B] = t_color[1][B] / (BLOCKWIDTH*BLOCKHEIGHT - n); + } + } + } + + // Set the best colors as the final block colors + for(int s = 0; s < 2; ++s) + { + for(uint8 c = 0; c < 3; ++c) + { + current_colors[s][c] = best_colors[s][c]; + } + } + + for(x=0;x<2;x++) + for(y=0;y<3;y++) + LBG_colors[x][y] = JAS_ROUND(current_colors[x][y]); +} + +// Each color component is compressed to fit in its specified number of bits +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void compressColor(int R_B, int G_B, int B_B, uint8 (current_color)[2][3], uint8 (quantized_color)[2][3]) +{ + // + // The color is calculated as: + // + // c = (c + (2^(8-b))/2) / (255 / (2^b - 1)) where b is the number of bits + // to code color c with + // For instance, if b = 3: + // + // c = (c + 16) / (255 / 7) = 7 * (c + 16) / 255 + // + + quantized_color[0][R] = CLAMP(0,(BINPOW(R_B)-1) * (current_color[0][R] + BINPOW(8-R_B-1)) / 255,255); + quantized_color[0][G] = CLAMP(0,(BINPOW(G_B)-1) * (current_color[0][G] + BINPOW(8-G_B-1)) / 255,255); + quantized_color[0][B] = CLAMP(0,(BINPOW(B_B)-1) * (current_color[0][B] + BINPOW(8-B_B-1)) / 255,255); + + quantized_color[1][R] = CLAMP(0,(BINPOW(R_B)-1) * (current_color[1][R] + BINPOW(8-R_B-1)) / 255,255); + quantized_color[1][G] = CLAMP(0,(BINPOW(G_B)-1) * (current_color[1][G] + BINPOW(8-G_B-1)) / 255,255); + quantized_color[1][B] = CLAMP(0,(BINPOW(B_B)-1) * (current_color[1][B] + BINPOW(8-B_B-1)) / 255,255); +} + +// Swapping two RGB-colors +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void swapColors(uint8 (colors)[2][3]) +{ + uint8 temp = colors[0][R]; + colors[0][R] = colors[1][R]; + colors[1][R] = temp; + + temp = colors[0][G]; + colors[0][G] = colors[1][G]; + colors[1][G] = temp; + + temp = colors[0][B]; + colors[0][B] = colors[1][B]; + colors[1][B] = temp; +} + + +// Calculate the paint colors from the block colors +// using a distance d and one of the H- or T-patterns. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. + +// Calculate the error for the block at position (startx,starty) +// The parameters needed for reconstruction are calculated as well +// +// Please note that the function can change the order between the two colors in colorsRGB444 +// +// In the 59T bit mode, we only have pattern T. +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calculateError59Tperceptual1000(uint8* srcimg, int width, int startx, int starty, uint8 (colorsRGB444)[2][3], uint8 &distance, unsigned int &pixel_indices) +{ + + unsigned int block_error = 0, + best_block_error = MAXERR1000, + pixel_error, + best_pixel_error; + int diff[3]; + uint8 best_sw; + unsigned int pixel_colors; + uint8 colors[2][3]; + uint8 possible_colors[4][3]; + + // First use the colors as they are, then swap them + for (uint8 sw = 0; sw <2; ++sw) + { + if (sw == 1) + { + swapColors(colorsRGB444); + } + decompressColor(R_BITS59T, G_BITS59T, B_BITS59T, colorsRGB444, colors); + + // Test all distances + for (uint8 d = 0; d < BINPOW(TABLE_BITS_59T); ++d) + { + calculatePaintColors59T(d,PATTERN_T, colors, possible_colors); + + block_error = 0; + pixel_colors = 0; + + // Loop block + for (size_t y = 0; y < BLOCKHEIGHT; ++y) + { + for (size_t x = 0; x < BLOCKWIDTH; ++x) + { + best_pixel_error = MAXERR1000; + pixel_colors <<=2; // Make room for next value + + // Loop possible block colors + for (uint8 c = 0; c < 4; ++c) + { + + diff[R] = srcimg[3*((starty+y)*width+startx+x)+R] - CLAMP(0,possible_colors[c][R],255); + diff[G] = srcimg[3*((starty+y)*width+startx+x)+G] - CLAMP(0,possible_colors[c][G],255); + diff[B] = srcimg[3*((starty+y)*width+startx+x)+B] - CLAMP(0,possible_colors[c][B],255); + + pixel_error = PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*SQUARE(diff[R]) + + PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000*SQUARE(diff[G]) + + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*SQUARE(diff[B]); + + // Choose best error + if (pixel_error < best_pixel_error) + { + best_pixel_error = pixel_error; + pixel_colors ^= (pixel_colors & 3); // Reset the two first bits + pixel_colors |= c; + } + } + block_error += best_pixel_error; + } + } + if (block_error < best_block_error) + { + best_block_error = block_error; + distance = d; + pixel_indices = pixel_colors; + best_sw = sw; + } + } + + if (sw == 1 && best_sw == 0) + { + swapColors(colorsRGB444); + } + decompressColor(R_BITS59T, G_BITS59T, B_BITS59T, colorsRGB444, colors); + } + return best_block_error; +} + +// Calculate the error for the block at position (startx,starty) +// The parameters needed for reconstruction is calculated as well +// +// Please note that the function can change the order between the two colors in colorsRGB444 +// +// In the 59T bit mode, we only have pattern T. +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double calculateError59T(uint8* srcimg, int width, int startx, int starty, uint8 (colorsRGB444)[2][3], uint8 &distance, unsigned int &pixel_indices) +{ + double block_error = 0, + best_block_error = MAXIMUM_ERROR, + pixel_error, + best_pixel_error; + int diff[3]; + uint8 best_sw; + unsigned int pixel_colors; + uint8 colors[2][3]; + uint8 possible_colors[4][3]; + + // First use the colors as they are, then swap them + for (uint8 sw = 0; sw <2; ++sw) + { + if (sw == 1) + { + swapColors(colorsRGB444); + } + decompressColor(R_BITS59T, G_BITS59T, B_BITS59T, colorsRGB444, colors); + + // Test all distances + for (uint8 d = 0; d < BINPOW(TABLE_BITS_59T); ++d) + { + calculatePaintColors59T(d,PATTERN_T, colors, possible_colors); + + block_error = 0; + pixel_colors = 0; + + // Loop block + for (size_t y = 0; y < BLOCKHEIGHT; ++y) + { + for (size_t x = 0; x < BLOCKWIDTH; ++x) + { + best_pixel_error = MAXIMUM_ERROR; + pixel_colors <<=2; // Make room for next value + + // Loop possible block colors + for (uint8 c = 0; c < 4; ++c) + { + + diff[R] = srcimg[3*((starty+y)*width+startx+x)+R] - CLAMP(0,possible_colors[c][R],255); + diff[G] = srcimg[3*((starty+y)*width+startx+x)+G] - CLAMP(0,possible_colors[c][G],255); + diff[B] = srcimg[3*((starty+y)*width+startx+x)+B] - CLAMP(0,possible_colors[c][B],255); + + pixel_error = weight[R]*SQUARE(diff[R]) + + weight[G]*SQUARE(diff[G]) + + weight[B]*SQUARE(diff[B]); + + // Choose best error + if (pixel_error < best_pixel_error) + { + best_pixel_error = pixel_error; + pixel_colors ^= (pixel_colors & 3); // Reset the two first bits + pixel_colors |= c; + } + } + block_error += best_pixel_error; + } + } + if (block_error < best_block_error) + { + best_block_error = block_error; + distance = d; + pixel_indices = pixel_colors; + best_sw = sw; + } + } + + if (sw == 1 && best_sw == 0) + { + swapColors(colorsRGB444); + } + decompressColor(R_BITS59T, G_BITS59T, B_BITS59T, colorsRGB444, colors); + } + return best_block_error; +} + +// Calculate the error for the block at position (startx,starty) +// The parameters needed for reconstruction is calculated as well +// +// In the 59T bit mode, we only have pattern T. +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calculateError59TnoSwapPerceptual1000(uint8* srcimg, int width, int startx, int starty, uint8 (colorsRGB444)[2][3], uint8 &distance, unsigned int &pixel_indices) +{ + + unsigned int block_error = 0, + best_block_error = MAXERR1000, + pixel_error, + best_pixel_error; + int diff[3]; + unsigned int pixel_colors; + uint8 colors[2][3]; + uint8 possible_colors[4][3]; + int thebestintheworld; + + // First use the colors as they are, then swap them + decompressColor(R_BITS59T, G_BITS59T, B_BITS59T, colorsRGB444, colors); + + // Test all distances + for (uint8 d = 0; d < BINPOW(TABLE_BITS_59T); ++d) + { + calculatePaintColors59T(d,PATTERN_T, colors, possible_colors); + + block_error = 0; + pixel_colors = 0; + + // Loop block + for (size_t y = 0; y < BLOCKHEIGHT; ++y) + { + for (size_t x = 0; x < BLOCKWIDTH; ++x) + { + best_pixel_error = MAXERR1000; + pixel_colors <<=2; // Make room for next value + + // Loop possible block colors + for (uint8 c = 0; c < 4; ++c) + { + + diff[R] = srcimg[3*((starty+y)*width+startx+x)+R] - CLAMP(0,possible_colors[c][R],255); + diff[G] = srcimg[3*((starty+y)*width+startx+x)+G] - CLAMP(0,possible_colors[c][G],255); + diff[B] = srcimg[3*((starty+y)*width+startx+x)+B] - CLAMP(0,possible_colors[c][B],255); + + pixel_error = PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*SQUARE(diff[R]) + + PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000*SQUARE(diff[G]) + + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*SQUARE(diff[B]); + + // Choose best error + if (pixel_error < best_pixel_error) + { + best_pixel_error = pixel_error; + pixel_colors ^= (pixel_colors & 3); // Reset the two first bits + pixel_colors |= c; + thebestintheworld = c; + } + } + block_error += best_pixel_error; + } + } + if (block_error < best_block_error) + { + best_block_error = block_error; + distance = d; + pixel_indices = pixel_colors; + } + } + + decompressColor(R_BITS59T, G_BITS59T, B_BITS59T, colorsRGB444, colors); + return best_block_error; +} + +// Calculate the error for the block at position (startx,starty) +// The parameters needed for reconstruction is calculated as well +// +// In the 59T bit mode, we only have pattern T. +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double calculateError59TnoSwap(uint8* srcimg, int width, int startx, int starty, uint8 (colorsRGB444)[2][3], uint8 &distance, unsigned int &pixel_indices) +{ + double block_error = 0, + best_block_error = MAXIMUM_ERROR, + pixel_error, + best_pixel_error; + int diff[3]; + unsigned int pixel_colors; + uint8 colors[2][3]; + uint8 possible_colors[4][3]; + int thebestintheworld; + + // First use the colors as they are, then swap them + decompressColor(R_BITS59T, G_BITS59T, B_BITS59T, colorsRGB444, colors); + + // Test all distances + for (uint8 d = 0; d < BINPOW(TABLE_BITS_59T); ++d) + { + calculatePaintColors59T(d,PATTERN_T, colors, possible_colors); + + block_error = 0; + pixel_colors = 0; + + // Loop block + for (size_t y = 0; y < BLOCKHEIGHT; ++y) + { + for (size_t x = 0; x < BLOCKWIDTH; ++x) + { + best_pixel_error = MAXIMUM_ERROR; + pixel_colors <<=2; // Make room for next value + + // Loop possible block colors + for (uint8 c = 0; c < 4; ++c) + { + diff[R] = srcimg[3*((starty+y)*width+startx+x)+R] - CLAMP(0,possible_colors[c][R],255); + diff[G] = srcimg[3*((starty+y)*width+startx+x)+G] - CLAMP(0,possible_colors[c][G],255); + diff[B] = srcimg[3*((starty+y)*width+startx+x)+B] - CLAMP(0,possible_colors[c][B],255); + + pixel_error = weight[R]*SQUARE(diff[R]) + + weight[G]*SQUARE(diff[G]) + + weight[B]*SQUARE(diff[B]); + + // Choose best error + if (pixel_error < best_pixel_error) + { + best_pixel_error = pixel_error; + pixel_colors ^= (pixel_colors & 3); // Reset the two first bits + pixel_colors |= c; + thebestintheworld = c; + } + } + block_error += best_pixel_error; + } + } + if (block_error < best_block_error) + { + best_block_error = block_error; + distance = d; + pixel_indices = pixel_colors; + } + } + + decompressColor(R_BITS59T, G_BITS59T, B_BITS59T, colorsRGB444, colors); + return best_block_error; +} + +// Put the compress params into the compression block +// +// +//|63 62 61 60 59|58 57 56 55|54 53 52 51|50 49 48 47|46 45 44 43|42 41 40 39|38 37 36 35|34 33 32| +//|----empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|--dist--| +// +//|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| +//|----------------------------------------index bits---------------------------------------------| +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void packBlock59T(uint8 (colors)[2][3], uint8 d, unsigned int pixel_indices, unsigned int &compressed1, unsigned int &compressed2) +{ + + compressed1 = 0; + + PUTBITSHIGH( compressed1, colors[0][R], 4, 58); + PUTBITSHIGH( compressed1, colors[0][G], 4, 54); + PUTBITSHIGH( compressed1, colors[0][B], 4, 50); + PUTBITSHIGH( compressed1, colors[1][R], 4, 46); + PUTBITSHIGH( compressed1, colors[1][G], 4, 42); + PUTBITSHIGH( compressed1, colors[1][B], 4, 38); + PUTBITSHIGH( compressed1, d, TABLE_BITS_59T, 34); + pixel_indices=indexConversion(pixel_indices); + compressed2 = 0; + PUTBITS( compressed2, pixel_indices, 32, 31); +} + +// Copy colors from source to dest +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void copyColors(uint8 (source)[2][3], uint8 (dest)[2][3]) +{ + int x,y; + + for (x=0; x<2; x++) + for (y=0; y<3; y++) + dest[x][y] = source[x][y]; +} + +// The below code should compress the block to 59 bits. +// +//|63 62 61 60 59|58 57 56 55|54 53 52 51|50 49 48 47|46 45 44 43|42 41 40 39|38 37 36 35|34 33 32| +//|----empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|--dist--| +// +//|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| +//|----------------------------------------index bits---------------------------------------------| +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int compressBlockTHUMB59TFastestOnlyColorPerceptual1000(uint8 *img,int width,int height,int startx,int starty, int (best_colorsRGB444_packed)[2]) +{ + unsigned int best_error = MAXERR1000; + unsigned int best_pixel_indices; + uint8 best_distance; + + unsigned int error_no_i; + uint8 colorsRGB444_no_i[2][3]; + unsigned int pixel_indices_no_i; + uint8 distance_no_i; + + uint8 colors[2][3]; + + // Calculate average color using the LBG-algorithm + computeColorLBGHalfIntensityFast(img,width,startx,starty, colors); + compressColor(R_BITS59T, G_BITS59T, B_BITS59T, colors, colorsRGB444_no_i); + + // Determine the parameters for the lowest error + error_no_i = calculateError59Tperceptual1000(img, width, startx, starty, colorsRGB444_no_i, distance_no_i, pixel_indices_no_i); + + best_error = error_no_i; + best_distance = distance_no_i; + best_pixel_indices = pixel_indices_no_i; + + best_colorsRGB444_packed[0] = (colorsRGB444_no_i[0][0] << 8) + (colorsRGB444_no_i[0][1] << 4) + (colorsRGB444_no_i[0][2] << 0); + best_colorsRGB444_packed[1] = (colorsRGB444_no_i[1][0] << 8) + (colorsRGB444_no_i[1][1] << 4) + (colorsRGB444_no_i[1][2] << 0); + + return best_error; +} + + +// The below code should compress the block to 59 bits. +// This is supposed to match the first of the three modes in TWOTIMER. +// +//|63 62 61 60 59|58 57 56 55|54 53 52 51|50 49 48 47|46 45 44 43|42 41 40 39|38 37 36 35|34 33 32| +//|----empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|--dist--| +// +//|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| +//|----------------------------------------index bits---------------------------------------------| +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double compressBlockTHUMB59TFastestOnlyColor(uint8 *img,int width,int height,int startx,int starty, int (best_colorsRGB444_packed)[2]) +{ + double best_error = MAXIMUM_ERROR; + unsigned int best_pixel_indices; + uint8 best_distance; + + double error_no_i; + uint8 colorsRGB444_no_i[2][3]; + unsigned int pixel_indices_no_i; + uint8 distance_no_i; + + uint8 colors[2][3]; + + // Calculate average color using the LBG-algorithm + computeColorLBGHalfIntensityFast(img,width,startx,starty, colors); + compressColor(R_BITS59T, G_BITS59T, B_BITS59T, colors, colorsRGB444_no_i); + + // Determine the parameters for the lowest error + error_no_i = calculateError59T(img, width, startx, starty, colorsRGB444_no_i, distance_no_i, pixel_indices_no_i); + + best_error = error_no_i; + best_distance = distance_no_i; + best_pixel_indices = pixel_indices_no_i; + + best_colorsRGB444_packed[0] = (colorsRGB444_no_i[0][0] << 8) + (colorsRGB444_no_i[0][1] << 4) + (colorsRGB444_no_i[0][2] << 0); + best_colorsRGB444_packed[1] = (colorsRGB444_no_i[1][0] << 8) + (colorsRGB444_no_i[1][1] << 4) + (colorsRGB444_no_i[1][2] << 0); + + return best_error; +} + +// The below code should compress the block to 59 bits. +// This is supposed to match the first of the three modes in TWOTIMER. +// +//|63 62 61 60 59|58 57 56 55|54 53 52 51|50 49 48 47|46 45 44 43|42 41 40 39|38 37 36 35|34 33 32| +//|----empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|--dist--| +// +//|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| +//|----------------------------------------index bits---------------------------------------------| +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double compressBlockTHUMB59TFastestPerceptual1000(uint8 *img,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2) +{ + double best_error = MAXIMUM_ERROR; + uint8 best_colorsRGB444[2][3]; + unsigned int best_pixel_indices; + uint8 best_distance; + + double error_no_i; + uint8 colorsRGB444_no_i[2][3]; + unsigned int pixel_indices_no_i; + uint8 distance_no_i; + + uint8 colors[2][3]; + + // Calculate average color using the LBG-algorithm + computeColorLBGHalfIntensityFast(img,width,startx,starty, colors); + compressColor(R_BITS59T, G_BITS59T, B_BITS59T, colors, colorsRGB444_no_i); + + // Determine the parameters for the lowest error + error_no_i = calculateError59Tperceptual1000(img, width, startx, starty, colorsRGB444_no_i, distance_no_i, pixel_indices_no_i); + + best_error = error_no_i; + best_distance = distance_no_i; + best_pixel_indices = pixel_indices_no_i; + copyColors(colorsRGB444_no_i, best_colorsRGB444); + + // Put the compress params into the compression block + packBlock59T(best_colorsRGB444, best_distance, best_pixel_indices, compressed1, compressed2); + + return best_error; +} + +// The below code should compress the block to 59 bits. +// This is supposed to match the first of the three modes in TWOTIMER. +// +//|63 62 61 60 59|58 57 56 55|54 53 52 51|50 49 48 47|46 45 44 43|42 41 40 39|38 37 36 35|34 33 32| +//|----empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|--dist--| +// +//|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| +//|----------------------------------------index bits---------------------------------------------| +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double compressBlockTHUMB59TFastest(uint8 *img,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2) +{ + double best_error = MAXIMUM_ERROR; + uint8 best_colorsRGB444[2][3]; + unsigned int best_pixel_indices; + uint8 best_distance; + + double error_no_i; + uint8 colorsRGB444_no_i[2][3]; + unsigned int pixel_indices_no_i; + uint8 distance_no_i; + + uint8 colors[2][3]; + + // Calculate average color using the LBG-algorithm + computeColorLBGHalfIntensityFast(img,width,startx,starty, colors); + compressColor(R_BITS59T, G_BITS59T, B_BITS59T, colors, colorsRGB444_no_i); + + // Determine the parameters for the lowest error + error_no_i = calculateError59T(img, width, startx, starty, colorsRGB444_no_i, distance_no_i, pixel_indices_no_i); + + best_error = error_no_i; + best_distance = distance_no_i; + best_pixel_indices = pixel_indices_no_i; + copyColors(colorsRGB444_no_i, best_colorsRGB444); + + // Put the compress params into the compression block + packBlock59T(best_colorsRGB444, best_distance, best_pixel_indices, compressed1, compressed2); + + return best_error; +} + +// The below code should compress the block to 59 bits. +// This is supposed to match the first of the three modes in TWOTIMER. +// +//|63 62 61 60 59|58 57 56 55|54 53 52 51|50 49 48 47|46 45 44 43|42 41 40 39|38 37 36 35|34 33 32| +//|----empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|--dist--| +// +//|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| +//|----------------------------------------index bits---------------------------------------------| +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double compressBlockTHUMB59TFast(uint8 *img,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2) +{ + double best_error = MAXIMUM_ERROR; + uint8 best_colorsRGB444[2][3]; + unsigned int best_pixel_indices; + uint8 best_distance; + + double error_no_i; + uint8 colorsRGB444_no_i[2][3]; + unsigned int pixel_indices_no_i; + uint8 distance_no_i; + + double error_half_i; + uint8 colorsRGB444_half_i[2][3]; + unsigned int pixel_indices_half_i; + uint8 distance_half_i; + + double error; + uint8 colorsRGB444[2][3]; + unsigned int pixel_indices; + uint8 distance; + + uint8 colors[2][3]; + + // Calculate average color using the LBG-algorithm + computeColorLBGNotIntensityFast(img,width,startx,starty, colors); + compressColor(R_BITS59T, G_BITS59T, B_BITS59T, colors, colorsRGB444_no_i); + // Determine the parameters for the lowest error + error_no_i = calculateError59T(img, width, startx, starty, colorsRGB444_no_i, distance_no_i, pixel_indices_no_i); + + // Calculate average color using the LBG-algorithm + computeColorLBGHalfIntensityFast(img,width,startx,starty, colors); + compressColor(R_BITS59T, G_BITS59T, B_BITS59T, colors, colorsRGB444_half_i); + // Determine the parameters for the lowest error + error_half_i = calculateError59T(img, width, startx, starty, colorsRGB444_half_i, distance_half_i, pixel_indices_half_i); + + // Calculate average color using the LBG-algorithm + computeColorLBGfast(img,width,startx,starty, colors); + compressColor(R_BITS59T, G_BITS59T, B_BITS59T, colors, colorsRGB444); + // Determine the parameters for the lowest error + error = calculateError59T(img, width, startx, starty, colorsRGB444, distance, pixel_indices); + + best_error = error_no_i; + best_distance = distance_no_i; + best_pixel_indices = pixel_indices_no_i; + copyColors(colorsRGB444_no_i, best_colorsRGB444); + + if(error_half_i < best_error) + { + best_error = error_half_i; + best_distance = distance_half_i; + best_pixel_indices = pixel_indices_half_i; + copyColors (colorsRGB444_half_i, best_colorsRGB444); + } + if(error < best_error) + { + best_error = error; + best_distance = distance; + best_pixel_indices = pixel_indices; + copyColors (colorsRGB444, best_colorsRGB444); + } + + // Put the compress params into the compression block + packBlock59T(best_colorsRGB444, best_distance, best_pixel_indices, compressed1, compressed2); + + return best_error; +} + +// Calculate the error for the block at position (startx,starty) +// The parameters needed for reconstruction is calculated as well +// +// In the 58H bit mode, we only have pattern H. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calculateErrorAndCompress58Hperceptual1000(uint8* srcimg, int width, int startx, int starty, uint8 (colorsRGB444)[2][3], uint8 &distance, unsigned int &pixel_indices) +{ + unsigned int block_error = 0, + best_block_error = MAXERR1000, + pixel_error, + best_pixel_error; + int diff[3]; + unsigned int pixel_colors; + uint8 possible_colors[4][3]; + uint8 colors[2][3]; + + decompressColor(R_BITS58H, G_BITS58H, B_BITS58H, colorsRGB444, colors); + + // Test all distances + for (uint8 d = 0; d < BINPOW(TABLE_BITS_58H); ++d) + { + calculatePaintColors58H(d, PATTERN_H, colors, possible_colors); + + block_error = 0; + pixel_colors = 0; + + // Loop block + for (size_t y = 0; y < BLOCKHEIGHT; ++y) + { + for (size_t x = 0; x < BLOCKWIDTH; ++x) + { + best_pixel_error = MAXERR1000; + pixel_colors <<=2; // Make room for next value + + // Loop possible block colors + for (uint8 c = 0; c < 4; ++c) + { + diff[R] = srcimg[3*((starty+y)*width+startx+x)+R] - CLAMP(0,possible_colors[c][R],255); + diff[G] = srcimg[3*((starty+y)*width+startx+x)+G] - CLAMP(0,possible_colors[c][G],255); + diff[B] = srcimg[3*((starty+y)*width+startx+x)+B] - CLAMP(0,possible_colors[c][B],255); + + pixel_error = PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*SQUARE(diff[R]) + + PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000*SQUARE(diff[G]) + + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*SQUARE(diff[B]); + + // Choose best error + if (pixel_error < best_pixel_error) + { + best_pixel_error = pixel_error; + pixel_colors ^= (pixel_colors & 3); // Reset the two first bits + pixel_colors |= c; + } + } + block_error += best_pixel_error; + } + } + + if (block_error < best_block_error) + { + best_block_error = block_error; + distance = d; + pixel_indices = pixel_colors; + } + } + return best_block_error; +} + +// The H-mode but with punchthrough alpha +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double calculateErrorAndCompress58HAlpha(uint8* srcimg, uint8* alphaimg,int width, int startx, int starty, uint8 (colorsRGB444)[2][3], uint8 &distance, unsigned int &pixel_indices) +{ + double block_error = 0, + best_block_error = MAXIMUM_ERROR, + pixel_error, + best_pixel_error; + int diff[3]; + unsigned int pixel_colors; + uint8 possible_colors[4][3]; + uint8 colors[2][3]; + int alphaindex; + int colorsRGB444_packed[2]; + colorsRGB444_packed[0] = (colorsRGB444[0][R] << 8) + (colorsRGB444[0][G] << 4) + colorsRGB444[0][B]; + colorsRGB444_packed[1] = (colorsRGB444[1][R] << 8) + (colorsRGB444[1][G] << 4) + colorsRGB444[1][B]; + + decompressColor(R_BITS58H, G_BITS58H, B_BITS58H, colorsRGB444, colors); + + // Test all distances + for (uint8 d = 0; d < BINPOW(TABLE_BITS_58H); ++d) + { + alphaindex=2; + if( (colorsRGB444_packed[0] >= colorsRGB444_packed[1]) ^ ((d & 1)==1) ) + { + //we're going to have to swap the colors to be able to choose this distance.. that means + //that the indices will be swapped as well, so C1 will be the one with alpha instead of C3.. + alphaindex=0; + } + + calculatePaintColors58H(d, PATTERN_H, colors, possible_colors); + + block_error = 0; + pixel_colors = 0; + + // Loop block + for (size_t y = 0; y < BLOCKHEIGHT; ++y) + { + for (size_t x = 0; x < BLOCKWIDTH; ++x) + { + int alpha=0; + if(alphaimg[((starty+y)*width+startx+x)]>0) + alpha=1; + if(alphaimg[((starty+y)*width+startx+x)]>0&&alphaimg[((starty+y)*width+startx+x)]<255) + printf("INVALID ALPHA DATA!!\n"); + best_pixel_error = MAXIMUM_ERROR; + pixel_colors <<=2; // Make room for next value + + // Loop possible block colors + for (uint8 c = 0; c < 4; ++c) + { + if(c==alphaindex&&alpha) + { + pixel_error=0; + } + else if(c==alphaindex||alpha) + { + pixel_error=MAXIMUM_ERROR; + } + else + { + diff[R] = srcimg[3*((starty+y)*width+startx+x)+R] - CLAMP(0,possible_colors[c][R],255); + diff[G] = srcimg[3*((starty+y)*width+startx+x)+G] - CLAMP(0,possible_colors[c][G],255); + diff[B] = srcimg[3*((starty+y)*width+startx+x)+B] - CLAMP(0,possible_colors[c][B],255); + + pixel_error = weight[R]*SQUARE(diff[R]) + + weight[G]*SQUARE(diff[G]) + + weight[B]*SQUARE(diff[B]); + } + + // Choose best error + if (pixel_error < best_pixel_error) + { + best_pixel_error = pixel_error; + pixel_colors ^= (pixel_colors & 3); // Reset the two first bits + pixel_colors |= c; + } + } + block_error += best_pixel_error; + } + } + if (block_error < best_block_error) + { + best_block_error = block_error; + distance = d; + pixel_indices = pixel_colors; + } + } + return best_block_error; +} + +// Calculate the error for the block at position (startx,starty) +// The parameters needed for reconstruction is calculated as well +// +// In the 58H bit mode, we only have pattern H. +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double calculateErrorAndCompress58H(uint8* srcimg, int width, int startx, int starty, uint8 (colorsRGB444)[2][3], uint8 &distance, unsigned int &pixel_indices) +{ + double block_error = 0, + best_block_error = MAXIMUM_ERROR, + pixel_error, + best_pixel_error; + int diff[3]; + unsigned int pixel_colors; + uint8 possible_colors[4][3]; + uint8 colors[2][3]; + + + decompressColor(R_BITS58H, G_BITS58H, B_BITS58H, colorsRGB444, colors); + + // Test all distances + for (uint8 d = 0; d < BINPOW(TABLE_BITS_58H); ++d) + { + calculatePaintColors58H(d, PATTERN_H, colors, possible_colors); + + block_error = 0; + pixel_colors = 0; + + // Loop block + for (size_t y = 0; y < BLOCKHEIGHT; ++y) + { + for (size_t x = 0; x < BLOCKWIDTH; ++x) + { + best_pixel_error = MAXIMUM_ERROR; + pixel_colors <<=2; // Make room for next value + + // Loop possible block colors + for (uint8 c = 0; c < 4; ++c) + { + diff[R] = srcimg[3*((starty+y)*width+startx+x)+R] - CLAMP(0,possible_colors[c][R],255); + diff[G] = srcimg[3*((starty+y)*width+startx+x)+G] - CLAMP(0,possible_colors[c][G],255); + diff[B] = srcimg[3*((starty+y)*width+startx+x)+B] - CLAMP(0,possible_colors[c][B],255); + + pixel_error = weight[R]*SQUARE(diff[R]) + + weight[G]*SQUARE(diff[G]) + + weight[B]*SQUARE(diff[B]); + + // Choose best error + if (pixel_error < best_pixel_error) + { + best_pixel_error = pixel_error; + pixel_colors ^= (pixel_colors & 3); // Reset the two first bits + pixel_colors |= c; + } + } + block_error += best_pixel_error; + } + } + + if (block_error < best_block_error) + { + best_block_error = block_error; + distance = d; + pixel_indices = pixel_colors; + } + } + + return best_block_error; +} + +// Makes sure that col0 < col1; +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void sortColorsRGB444(uint8 (colorsRGB444)[2][3]) +{ + unsigned int col0, col1, tcol; + + // sort colors + col0 = 16*16*colorsRGB444[0][R] + 16*colorsRGB444[0][G] + colorsRGB444[0][B]; + col1 = 16*16*colorsRGB444[1][R] + 16*colorsRGB444[1][G] + colorsRGB444[1][B]; + + // After this, col0 should be smaller than col1 (col0 < col1) + if( col0 > col1) + { + tcol = col0; + col0 = col1; + col1 = tcol; + } + else + { + if(col0 == col1) + { + // Both colors are the same. That is useless. If they are both black, + // col1 can just as well be (0,0,1). Else, col0 can be col1 - 1. + if(col0 == 0) + col1 = col0+1; + else + col0 = col1-1; + } + } + + colorsRGB444[0][R] = GETBITS(col0, 4, 11); + colorsRGB444[0][G] = GETBITS(col0, 4, 7); + colorsRGB444[0][B] = GETBITS(col0, 4, 3); + colorsRGB444[1][R] = GETBITS(col1, 4, 11); + colorsRGB444[1][G] = GETBITS(col1, 4, 7); + colorsRGB444[1][B] = GETBITS(col1, 4, 3); +} + +// The below code should compress the block to 58 bits. +// The bit layout is thought to be: +// +//|63 62 61 60 59 58|57 56 55 54|53 52 51 50|49 48 47 46|45 44 43 42|41 40 39 38|37 36 35 34|33 32| +//|-------empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|d2 d1| +// +//|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| +//|----------------------------------------index bits---------------------------------------------| +// +// The distance d is three bits, d2 (MSB), d1 and d0 (LSB). d0 is not stored explicitly. +// Instead if the 12-bit word red0,green0,blue0 < red1,green1,blue1, d0 is assumed to be 0. +// Else, it is assumed to be 1. +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int compressBlockTHUMB58HFastestPerceptual1000(uint8 *img,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2) +{ + unsigned int best_error = MAXERR1000; + uint8 best_colorsRGB444[2][3]; + unsigned int best_pixel_indices; + uint8 best_distance; + + unsigned int error_no_i; + uint8 colorsRGB444_no_i[2][3]; + unsigned int pixel_indices_no_i; + uint8 distance_no_i; + uint8 colors[2][3]; + + // Calculate average color using the LBG-algorithm but discarding the intensity in the error function + computeColorLBGHalfIntensityFast(img, width, startx, starty, colors); + compressColor(R_BITS58H, G_BITS58H, B_BITS58H, colors, colorsRGB444_no_i); + sortColorsRGB444(colorsRGB444_no_i); + + error_no_i = calculateErrorAndCompress58Hperceptual1000(img, width, startx, starty, colorsRGB444_no_i, distance_no_i, pixel_indices_no_i); + + best_error = error_no_i; + best_distance = distance_no_i; + best_pixel_indices = pixel_indices_no_i; + copyColors(colorsRGB444_no_i, best_colorsRGB444); + + // | col0 >= col1 col0 < col1 + //------------------------------------------------------ + // (dist & 1) = 1 | no need to swap | need to swap + // |-----------------+---------------- + // (dist & 1) = 0 | need to swap | no need to swap + // + // This can be done with an xor test. + + int best_colorsRGB444_packed[2]; + best_colorsRGB444_packed[0] = (best_colorsRGB444[0][R] << 8) + (best_colorsRGB444[0][G] << 4) + best_colorsRGB444[0][B]; + best_colorsRGB444_packed[1] = (best_colorsRGB444[1][R] << 8) + (best_colorsRGB444[1][G] << 4) + best_colorsRGB444[1][B]; + if( (best_colorsRGB444_packed[0] >= best_colorsRGB444_packed[1]) ^ ((best_distance & 1)==1) ) + { + swapColors(best_colorsRGB444); + + // Reshuffle pixel indices to to exchange C1 with C3, and C2 with C4 + best_pixel_indices = (0x55555555 & best_pixel_indices) | (0xaaaaaaaa & (~best_pixel_indices)); + } + + // Put the compress params into the compression block + + compressed1 = 0; + + PUTBITSHIGH( compressed1, best_colorsRGB444[0][R], 4, 57); + PUTBITSHIGH( compressed1, best_colorsRGB444[0][G], 4, 53); + PUTBITSHIGH( compressed1, best_colorsRGB444[0][B], 4, 49); + PUTBITSHIGH( compressed1, best_colorsRGB444[1][R], 4, 45); + PUTBITSHIGH( compressed1, best_colorsRGB444[1][G], 4, 41); + PUTBITSHIGH( compressed1, best_colorsRGB444[1][B], 4, 37); + PUTBITSHIGH( compressed1, (best_distance >> 1), 2, 33); + + compressed2 = 0; + best_pixel_indices=indexConversion(best_pixel_indices); + PUTBITS( compressed2, best_pixel_indices, 32, 31); + + return best_error; +} + +// The below code should compress the block to 58 bits. +// This is supposed to match the first of the three modes in TWOTIMER. +// The bit layout is thought to be: +// +//|63 62 61 60 59 58|57 56 55 54|53 52 51 50|49 48 47 46|45 44 43 42|41 40 39 38|37 36 35 34|33 32| +//|-------empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|d2 d1| +// +//|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| +//|----------------------------------------index bits---------------------------------------------| +// +// The distance d is three bits, d2 (MSB), d1 and d0 (LSB). d0 is not stored explicitly. +// Instead if the 12-bit word red0,green0,blue0 < red1,green1,blue1, d0 is assumed to be 0. +// Else, it is assumed to be 1. +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double compressBlockTHUMB58HFastest(uint8 *img,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2) +{ + double best_error = MAXIMUM_ERROR; + uint8 best_colorsRGB444[2][3]; + unsigned int best_pixel_indices; + uint8 best_distance; + + double error_no_i; + uint8 colorsRGB444_no_i[2][3]; + unsigned int pixel_indices_no_i; + uint8 distance_no_i; + uint8 colors[2][3]; + + // Calculate average color using the LBG-algorithm but discarding the intensity in the error function + computeColorLBGHalfIntensityFast(img, width, startx, starty, colors); + compressColor(R_BITS58H, G_BITS58H, B_BITS58H, colors, colorsRGB444_no_i); + sortColorsRGB444(colorsRGB444_no_i); + + error_no_i = calculateErrorAndCompress58H(img, width, startx, starty, colorsRGB444_no_i, distance_no_i, pixel_indices_no_i); + + best_error = error_no_i; + best_distance = distance_no_i; + best_pixel_indices = pixel_indices_no_i; + copyColors(colorsRGB444_no_i, best_colorsRGB444); + + // | col0 >= col1 col0 < col1 + //------------------------------------------------------ + // (dist & 1) = 1 | no need to swap | need to swap + // |-----------------+---------------- + // (dist & 1) = 0 | need to swap | no need to swap + // + // This can be done with an xor test. + + int best_colorsRGB444_packed[2]; + best_colorsRGB444_packed[0] = (best_colorsRGB444[0][R] << 8) + (best_colorsRGB444[0][G] << 4) + best_colorsRGB444[0][B]; + best_colorsRGB444_packed[1] = (best_colorsRGB444[1][R] << 8) + (best_colorsRGB444[1][G] << 4) + best_colorsRGB444[1][B]; + if( (best_colorsRGB444_packed[0] >= best_colorsRGB444_packed[1]) ^ ((best_distance & 1)==1) ) + { + swapColors(best_colorsRGB444); + + // Reshuffle pixel indices to to exchange C1 with C3, and C2 with C4 + best_pixel_indices = (0x55555555 & best_pixel_indices) | (0xaaaaaaaa & (~best_pixel_indices)); + } + + // Put the compress params into the compression block + + compressed1 = 0; + + PUTBITSHIGH( compressed1, best_colorsRGB444[0][R], 4, 57); + PUTBITSHIGH( compressed1, best_colorsRGB444[0][G], 4, 53); + PUTBITSHIGH( compressed1, best_colorsRGB444[0][B], 4, 49); + PUTBITSHIGH( compressed1, best_colorsRGB444[1][R], 4, 45); + PUTBITSHIGH( compressed1, best_colorsRGB444[1][G], 4, 41); + PUTBITSHIGH( compressed1, best_colorsRGB444[1][B], 4, 37); + PUTBITSHIGH( compressed1, (best_distance >> 1), 2, 33); + best_pixel_indices=indexConversion(best_pixel_indices); + compressed2 = 0; + PUTBITS( compressed2, best_pixel_indices, 32, 31); + + return best_error; +} + +//same as above, but with 1-bit alpha +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double compressBlockTHUMB58HAlpha(uint8 *img, uint8* alphaimg, int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2) +{ + double best_error = MAXIMUM_ERROR; + uint8 best_colorsRGB444[2][3]; + unsigned int best_pixel_indices; + uint8 best_distance; + + double error_no_i; + uint8 colorsRGB444_no_i[2][3]; + unsigned int pixel_indices_no_i; + uint8 distance_no_i; + uint8 colors[2][3]; + + // Calculate average color using the LBG-algorithm but discarding the intensity in the error function + computeColorLBGHalfIntensityFast(img, width, startx, starty, colors); + compressColor(R_BITS58H, G_BITS58H, B_BITS58H, colors, colorsRGB444_no_i); + sortColorsRGB444(colorsRGB444_no_i); + + error_no_i = calculateErrorAndCompress58HAlpha(img, alphaimg,width, startx, starty, colorsRGB444_no_i, distance_no_i, pixel_indices_no_i); + + best_error = error_no_i; + best_distance = distance_no_i; + best_pixel_indices = pixel_indices_no_i; + copyColors(colorsRGB444_no_i, best_colorsRGB444); + + // | col0 >= col1 col0 < col1 + //------------------------------------------------------ + // (dist & 1) = 1 | no need to swap | need to swap + // |-----------------+---------------- + // (dist & 1) = 0 | need to swap | no need to swap + // + // This can be done with an xor test. + + int best_colorsRGB444_packed[2]; + best_colorsRGB444_packed[0] = (best_colorsRGB444[0][R] << 8) + (best_colorsRGB444[0][G] << 4) + best_colorsRGB444[0][B]; + best_colorsRGB444_packed[1] = (best_colorsRGB444[1][R] << 8) + (best_colorsRGB444[1][G] << 4) + best_colorsRGB444[1][B]; + if( (best_colorsRGB444_packed[0] >= best_colorsRGB444_packed[1]) ^ ((best_distance & 1)==1) ) + { + swapColors(best_colorsRGB444); + + // Reshuffle pixel indices to to exchange C1 with C3, and C2 with C4 + best_pixel_indices = (0x55555555 & best_pixel_indices) | (0xaaaaaaaa & (~best_pixel_indices)); + } + + // Put the compress params into the compression block + + compressed1 = 0; + + PUTBITSHIGH( compressed1, best_colorsRGB444[0][R], 4, 57); + PUTBITSHIGH( compressed1, best_colorsRGB444[0][G], 4, 53); + PUTBITSHIGH( compressed1, best_colorsRGB444[0][B], 4, 49); + PUTBITSHIGH( compressed1, best_colorsRGB444[1][R], 4, 45); + PUTBITSHIGH( compressed1, best_colorsRGB444[1][G], 4, 41); + PUTBITSHIGH( compressed1, best_colorsRGB444[1][B], 4, 37); + PUTBITSHIGH( compressed1, (best_distance >> 1), 2, 33); + best_pixel_indices=indexConversion(best_pixel_indices); + compressed2 = 0; + PUTBITS( compressed2, best_pixel_indices, 32, 31); + + return best_error; +} + +// The below code should compress the block to 58 bits. +// This is supposed to match the first of the three modes in TWOTIMER. +// The bit layout is thought to be: +// +//|63 62 61 60 59 58|57 56 55 54|53 52 51 50|49 48 47 46|45 44 43 42|41 40 39 38|37 36 35 34|33 32| +//|-------empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|d2 d1| +// +//|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| +//|----------------------------------------index bits---------------------------------------------| +// +// The distance d is three bits, d2 (MSB), d1 and d0 (LSB). d0 is not stored explicitly. +// Instead if the 12-bit word red0,green0,blue0 < red1,green1,blue1, d0 is assumed to be 0. +// Else, it is assumed to be 1. +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double compressBlockTHUMB58HFast(uint8 *img,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2) +{ + double best_error = MAXIMUM_ERROR; + uint8 best_colorsRGB444[2][3]; + unsigned int best_pixel_indices; + uint8 best_distance; + + double error_no_i; + uint8 colorsRGB444_no_i[2][3]; + unsigned int pixel_indices_no_i; + uint8 distance_no_i; + + double error_half_i; + uint8 colorsRGB444_half_i[2][3]; + unsigned int pixel_indices_half_i; + uint8 distance_half_i; + + double error; + uint8 colorsRGB444[2][3]; + unsigned int pixel_indices; + uint8 distance; + + uint8 colors[2][3]; + + // Calculate average color using the LBG-algorithm but discarding the intensity in the error function + computeColorLBGNotIntensity(img, width, startx, starty, colors); + compressColor(R_BITS58H, G_BITS58H, B_BITS58H, colors, colorsRGB444_no_i); + sortColorsRGB444(colorsRGB444_no_i); + error_no_i = calculateErrorAndCompress58H(img, width, startx, starty, colorsRGB444_no_i, distance_no_i, pixel_indices_no_i); + + // Calculate average color using the LBG-algorithm but halfing the influence of the intensity in the error function + computeColorLBGNotIntensity(img, width, startx, starty, colors); + compressColor(R_BITS58H, G_BITS58H, B_BITS58H, colors, colorsRGB444_half_i); + sortColorsRGB444(colorsRGB444_half_i); + error_half_i = calculateErrorAndCompress58H(img, width, startx, starty, colorsRGB444_half_i, distance_half_i, pixel_indices_half_i); + + // Calculate average color using the LBG-algorithm + computeColorLBG(img, width, startx, starty, colors); + compressColor(R_BITS58H, G_BITS58H, B_BITS58H, colors, colorsRGB444); + sortColorsRGB444(colorsRGB444); + error = calculateErrorAndCompress58H(img, width, startx, starty, colorsRGB444, distance, pixel_indices); + + best_error = error_no_i; + best_distance = distance_no_i; + best_pixel_indices = pixel_indices_no_i; + copyColors(colorsRGB444_no_i, best_colorsRGB444); + + if(error_half_i < best_error) + { + best_error = error_half_i; + best_distance = distance_half_i; + best_pixel_indices = pixel_indices_half_i; + copyColors(colorsRGB444_half_i, best_colorsRGB444); + } + + if(error < best_error) + { + best_error = error; + best_distance = distance; + best_pixel_indices = pixel_indices; + copyColors(colorsRGB444, best_colorsRGB444); + } + + // | col0 >= col1 col0 < col1 + //------------------------------------------------------ + // (dist & 1) = 1 | no need to swap | need to swap + // |-----------------+---------------- + // (dist & 1) = 0 | need to swap | no need to swap + // + // This can be done with an xor test. + + int best_colorsRGB444_packed[2]; + best_colorsRGB444_packed[0] = (best_colorsRGB444[0][R] << 8) + (best_colorsRGB444[0][G] << 4) + best_colorsRGB444[0][B]; + best_colorsRGB444_packed[1] = (best_colorsRGB444[1][R] << 8) + (best_colorsRGB444[1][G] << 4) + best_colorsRGB444[1][B]; + if( (best_colorsRGB444_packed[0] >= best_colorsRGB444_packed[1]) ^ ((best_distance & 1)==1) ) + { + swapColors(best_colorsRGB444); + + // Reshuffle pixel indices to to exchange C1 with C3, and C2 with C4 + best_pixel_indices = (0x55555555 & best_pixel_indices) | (0xaaaaaaaa & (~best_pixel_indices)); + } + + // Put the compress params into the compression block + compressed1 = 0; + + PUTBITSHIGH( compressed1, best_colorsRGB444[0][R], 4, 57); + PUTBITSHIGH( compressed1, best_colorsRGB444[0][G], 4, 53); + PUTBITSHIGH( compressed1, best_colorsRGB444[0][B], 4, 49); + PUTBITSHIGH( compressed1, best_colorsRGB444[1][R], 4, 45); + PUTBITSHIGH( compressed1, best_colorsRGB444[1][G], 4, 41); + PUTBITSHIGH( compressed1, best_colorsRGB444[1][B], 4, 37); + PUTBITSHIGH( compressed1, (best_distance >> 1), 2, 33); + best_pixel_indices=indexConversion(best_pixel_indices); + compressed2 = 0; + PUTBITS( compressed2, best_pixel_indices, 32, 31); + + return best_error; +} + +// Compress block testing both individual and differential mode. +// Perceptual error metric. +// Combined quantization for colors. +// Both flipped and unflipped tested. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void compressBlockDiffFlipCombinedPerceptual(uint8 *img,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2) +{ + + unsigned int compressed1_norm, compressed2_norm; + unsigned int compressed1_flip, compressed2_flip; + uint8 avg_color_quant1[3], avg_color_quant2[3]; + + float avg_color_float1[3],avg_color_float2[3]; + int enc_color1[3], enc_color2[3], diff[3]; + int min_error=255*255*8*3; + unsigned int best_table_indices1=0, best_table_indices2=0; + unsigned int best_table1=0, best_table2=0; + int diffbit; + + int norm_err=0; + int flip_err=0; + + // First try normal blocks 2x4: + + computeAverageColor2x4noQuantFloat(img,width,height,startx,starty,avg_color_float1); + computeAverageColor2x4noQuantFloat(img,width,height,startx+2,starty,avg_color_float2); + + // First test if avg_color1 is similar enough to avg_color2 so that + // we can use differential coding of colors. + + float eps; + + uint8 dummy[3]; + + quantize555ColorCombinedPerceptual(avg_color_float1, enc_color1, dummy); + quantize555ColorCombinedPerceptual(avg_color_float2, enc_color2, dummy); + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + if( (diff[0] >= -4) && (diff[0] <= 3) && (diff[1] >= -4) && (diff[1] <= 3) && (diff[2] >= -4) && (diff[2] <= 3) ) + { + diffbit = 1; + + // The difference to be coded: + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + avg_color_quant1[0] = enc_color1[0] << 3 | (enc_color1[0] >> 2); + avg_color_quant1[1] = enc_color1[1] << 3 | (enc_color1[1] >> 2); + avg_color_quant1[2] = enc_color1[2] << 3 | (enc_color1[2] >> 2); + avg_color_quant2[0] = enc_color2[0] << 3 | (enc_color2[0] >> 2); + avg_color_quant2[1] = enc_color2[1] << 3 | (enc_color2[1] >> 2); + avg_color_quant2[2] = enc_color2[2] << 3 | (enc_color2[2] >> 2); + + // Pack bits into the first word. + + // ETC1_RGB8_OES: + // + // a) bit layout in bits 63 through 32 if diffbit = 0 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | base col2 | base col1 | base col2 | base col1 | base col2 | table | table |diff|flip| + // | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // b) bit layout in bits 63 through 32 if diffbit = 1 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | dcol 2 | base col1 | dcol 2 | base col 1 | dcol 2 | table | table |diff|flip| + // | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // c) bit layout in bits 31 through 0 (in both cases) + // + // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // -------------------------------------------------------------------------------------------------- + // | most significant pixel index bits | least significant pixel index bits | + // | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a | + // -------------------------------------------------------------------------------------------------- + + compressed1_norm = 0; + PUTBITSHIGH( compressed1_norm, diffbit, 1, 33); + PUTBITSHIGH( compressed1_norm, enc_color1[0], 5, 63); + PUTBITSHIGH( compressed1_norm, enc_color1[1], 5, 55); + PUTBITSHIGH( compressed1_norm, enc_color1[2], 5, 47); + PUTBITSHIGH( compressed1_norm, diff[0], 3, 58); + PUTBITSHIGH( compressed1_norm, diff[1], 3, 50); + PUTBITSHIGH( compressed1_norm, diff[2], 3, 42); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + norm_err = 0; + + // left part of block + norm_err = tryalltables_3bittable2x4percep(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + + // right part of block + norm_err += tryalltables_3bittable2x4percep(img,width,height,startx+2,starty,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_norm, best_table1, 3, 39); + PUTBITSHIGH( compressed1_norm, best_table2, 3, 36); + PUTBITSHIGH( compressed1_norm, 0, 1, 32); + + compressed2_norm = 0; + PUTBITS( compressed2_norm, (best_pixel_indices1_MSB ), 8, 23); + PUTBITS( compressed2_norm, (best_pixel_indices2_MSB ), 8, 31); + PUTBITS( compressed2_norm, (best_pixel_indices1_LSB ), 8, 7); + PUTBITS( compressed2_norm, (best_pixel_indices2_LSB ), 8, 15); + } + else + { + diffbit = 0; + // The difference is bigger than what fits in 555 plus delta-333, so we will have + // to deal with 444 444. + + eps = (float) 0.0001; + + quantize444ColorCombinedPerceptual(avg_color_float1, enc_color1, dummy); + quantize444ColorCombinedPerceptual(avg_color_float2, enc_color2, dummy); + + avg_color_quant1[0] = enc_color1[0] << 4 | enc_color1[0]; + avg_color_quant1[1] = enc_color1[1] << 4 | enc_color1[1]; + avg_color_quant1[2] = enc_color1[2] << 4 | enc_color1[2]; + avg_color_quant2[0] = enc_color2[0] << 4 | enc_color2[0]; + avg_color_quant2[1] = enc_color2[1] << 4 | enc_color2[1]; + avg_color_quant2[2] = enc_color2[2] << 4 | enc_color2[2]; + + // Pack bits into the first word. + + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | base col2 | base col1 | base col2 | base col1 | base col2 | table | table |diff|flip| + // | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + + compressed1_norm = 0; + PUTBITSHIGH( compressed1_norm, diffbit, 1, 33); + PUTBITSHIGH( compressed1_norm, enc_color1[0], 4, 63); + PUTBITSHIGH( compressed1_norm, enc_color1[1], 4, 55); + PUTBITSHIGH( compressed1_norm, enc_color1[2], 4, 47); + PUTBITSHIGH( compressed1_norm, enc_color2[0], 4, 59); + PUTBITSHIGH( compressed1_norm, enc_color2[1], 4, 51); + PUTBITSHIGH( compressed1_norm, enc_color2[2], 4, 43); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + // left part of block + norm_err = tryalltables_3bittable2x4percep(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + + // right part of block + norm_err += tryalltables_3bittable2x4percep(img,width,height,startx+2,starty,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_norm, best_table1, 3, 39); + PUTBITSHIGH( compressed1_norm, best_table2, 3, 36); + PUTBITSHIGH( compressed1_norm, 0, 1, 32); + + compressed2_norm = 0; + PUTBITS( compressed2_norm, (best_pixel_indices1_MSB ), 8, 23); + PUTBITS( compressed2_norm, (best_pixel_indices2_MSB ), 8, 31); + PUTBITS( compressed2_norm, (best_pixel_indices1_LSB ), 8, 7); + PUTBITS( compressed2_norm, (best_pixel_indices2_LSB ), 8, 15); + } + + // Now try flipped blocks 4x2: + computeAverageColor4x2noQuantFloat(img,width,height,startx,starty,avg_color_float1); + computeAverageColor4x2noQuantFloat(img,width,height,startx,starty+2,avg_color_float2); + + // First test if avg_color1 is similar enough to avg_color2 so that + // we can use differential coding of colors. + quantize555ColorCombinedPerceptual(avg_color_float1, enc_color1, dummy); + quantize555ColorCombinedPerceptual(avg_color_float2, enc_color2, dummy); + + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + if( (diff[0] >= -4) && (diff[0] <= 3) && (diff[1] >= -4) && (diff[1] <= 3) && (diff[2] >= -4) && (diff[2] <= 3) ) + { + diffbit = 1; + + // The difference to be coded: + diff[0] = enc_color2[0]-enc_color1[0]; + diff[1] = enc_color2[1]-enc_color1[1]; + diff[2] = enc_color2[2]-enc_color1[2]; + + avg_color_quant1[0] = enc_color1[0] << 3 | (enc_color1[0] >> 2); + avg_color_quant1[1] = enc_color1[1] << 3 | (enc_color1[1] >> 2); + avg_color_quant1[2] = enc_color1[2] << 3 | (enc_color1[2] >> 2); + avg_color_quant2[0] = enc_color2[0] << 3 | (enc_color2[0] >> 2); + avg_color_quant2[1] = enc_color2[1] << 3 | (enc_color2[1] >> 2); + avg_color_quant2[2] = enc_color2[2] << 3 | (enc_color2[2] >> 2); + + // Pack bits into the first word. + compressed1_flip = 0; + PUTBITSHIGH( compressed1_flip, diffbit, 1, 33); + PUTBITSHIGH( compressed1_flip, enc_color1[0], 5, 63); + PUTBITSHIGH( compressed1_flip, enc_color1[1], 5, 55); + PUTBITSHIGH( compressed1_flip, enc_color1[2], 5, 47); + PUTBITSHIGH( compressed1_flip, diff[0], 3, 58); + PUTBITSHIGH( compressed1_flip, diff[1], 3, 50); + PUTBITSHIGH( compressed1_flip, diff[2], 3, 42); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + // upper part of block + flip_err = tryalltables_3bittable4x2percep(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + // lower part of block + flip_err += tryalltables_3bittable4x2percep(img,width,height,startx,starty+2,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_flip, best_table1, 3, 39); + PUTBITSHIGH( compressed1_flip, best_table2, 3, 36); + PUTBITSHIGH( compressed1_flip, 1, 1, 32); + + best_pixel_indices1_MSB |= (best_pixel_indices2_MSB << 2); + best_pixel_indices1_LSB |= (best_pixel_indices2_LSB << 2); + + compressed2_flip = ((best_pixel_indices1_MSB & 0xffff) << 16) | (best_pixel_indices1_LSB & 0xffff); + } + else + { + diffbit = 0; + // The difference is bigger than what fits in 555 plus delta-333, so we will have + // to deal with 444 444. + eps = (float) 0.0001; + + quantize444ColorCombinedPerceptual(avg_color_float1, enc_color1, dummy); + quantize444ColorCombinedPerceptual(avg_color_float2, enc_color2, dummy); + + avg_color_quant1[0] = enc_color1[0] << 4 | enc_color1[0]; + avg_color_quant1[1] = enc_color1[1] << 4 | enc_color1[1]; + avg_color_quant1[2] = enc_color1[2] << 4 | enc_color1[2]; + avg_color_quant2[0] = enc_color2[0] << 4 | enc_color2[0]; + avg_color_quant2[1] = enc_color2[1] << 4 | enc_color2[1]; + avg_color_quant2[2] = enc_color2[2] << 4 | enc_color2[2]; + + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | base col2 | base col1 | base col2 | base col1 | base col2 | table | table |diff|flip| + // | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + + // Pack bits into the first word. + compressed1_flip = 0; + PUTBITSHIGH( compressed1_flip, diffbit, 1, 33); + PUTBITSHIGH( compressed1_flip, enc_color1[0], 4, 63); + PUTBITSHIGH( compressed1_flip, enc_color1[1], 4, 55); + PUTBITSHIGH( compressed1_flip, enc_color1[2], 4, 47); + PUTBITSHIGH( compressed1_flip, enc_color2[0], 4, 59); + PUTBITSHIGH( compressed1_flip, enc_color2[1], 4, 51); + PUTBITSHIGH( compressed1_flip, enc_color2[2], 4, 43); + + unsigned int best_pixel_indices1_MSB; + unsigned int best_pixel_indices1_LSB; + unsigned int best_pixel_indices2_MSB; + unsigned int best_pixel_indices2_LSB; + + // upper part of block + flip_err = tryalltables_3bittable4x2percep(img,width,height,startx,starty,avg_color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + // lower part of block + flip_err += tryalltables_3bittable4x2percep(img,width,height,startx,starty+2,avg_color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + PUTBITSHIGH( compressed1_flip, best_table1, 3, 39); + PUTBITSHIGH( compressed1_flip, best_table2, 3, 36); + PUTBITSHIGH( compressed1_flip, 1, 1, 32); + + best_pixel_indices1_MSB |= (best_pixel_indices2_MSB << 2); + best_pixel_indices1_LSB |= (best_pixel_indices2_LSB << 2); + + compressed2_flip = ((best_pixel_indices1_MSB & 0xffff) << 16) | (best_pixel_indices1_LSB & 0xffff); + } + + // Now lets see which is the best table to use. Only 8 tables are possible. + if(norm_err <= flip_err) + { + compressed1 = compressed1_norm | 0; + compressed2 = compressed2_norm; + } + else + { + compressed1 = compressed1_flip | 1; + compressed2 = compressed2_flip; + } +} + +// Calculate the error of a block +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double calcBlockErrorRGB(uint8 *img, uint8 *imgdec, int width, int height, int startx, int starty) +{ + int xx,yy; + double err; + + err = 0; + + for(xx = startx; xx< startx+4; xx++) + { + for(yy = starty; yy3) + diff[c]=3; + enc_color2[c]=enc_color1[c]+diff[c]; + } + + avg_color_quant1[0] = enc_color1[0] << 3 | (enc_color1[0] >> 2); + avg_color_quant1[1] = enc_color1[1] << 3 | (enc_color1[1] >> 2); + avg_color_quant1[2] = enc_color1[2] << 3 | (enc_color1[2] >> 2); + avg_color_quant2[0] = enc_color2[0] << 3 | (enc_color2[0] >> 2); + avg_color_quant2[1] = enc_color2[1] << 3 | (enc_color2[1] >> 2); + avg_color_quant2[2] = enc_color2[2] << 3 | (enc_color2[2] >> 2); + + // Pack bits into the first word. + // see regular compressblockdiffflipfast for details + + compressed1_temp = 0; + PUTBITSHIGH( compressed1_temp, !isTransparent, 1, 33); + PUTBITSHIGH( compressed1_temp, enc_color1[0], 5, 63); + PUTBITSHIGH( compressed1_temp, enc_color1[1], 5, 55); + PUTBITSHIGH( compressed1_temp, enc_color1[2], 5, 47); + PUTBITSHIGH( compressed1_temp, diff[0], 3, 58); + PUTBITSHIGH( compressed1_temp, diff[1], 3, 50); + PUTBITSHIGH( compressed1_temp, diff[2], 3, 42); + + temp_err = 0; + + int besterror[2]; + besterror[0]=255*255*3*16; + besterror[1]=255*255*3*16; + int besttable[2]; + int best_indices_LSB[16]; + int best_indices_MSB[16]; + //for each table, we're going to compute the indices required to get minimum error in each half. + //then we'll check if this was the best table for either half, and set besterror/besttable accordingly. + for(int table=0; table<8; table++) + { + int taberror[2];//count will be sort of an index of each pixel within a half, determining where the index will be placed in the bitstream. + + int pixel_indices_LSB[16],pixel_indices_MSB[16]; + + for(int i=0; i<2; i++) + { + taberror[i]=0; + } + for(int x=0; x<4; x++) + { + for(int y=0; y<4; y++) + { + int index = x+startx+(y+starty)*width; + uint8 basecol[3]; + bool transparentPixel=alphaimg[index]<128; + //determine which half of the block this pixel is in, based on the flipbit. + int half=0; + if( (flipbit==0&&x<2) || (flipbit&&y<2) ) + { + basecol[0]=avg_color_quant1[0]; + basecol[1]=avg_color_quant1[1]; + basecol[2]=avg_color_quant1[2]; + } + else + { + half=1; + basecol[0]=avg_color_quant2[0]; + basecol[1]=avg_color_quant2[1]; + basecol[2]=avg_color_quant2[2]; + } + int besterri=255*255*3*2; + int besti=0; + int erri; + for(int i=0; i<4; i++) + { + if(i==1&&isTransparent) + continue; + erri=0; + for(int c=0; c<3; c++) + { + int col=CLAMP(0,((int)basecol[c])+compressParams[table*2][i],255); + if(i==2&&isTransparent) + { + col=(int)basecol[c]; + } + int errcol=col-((int)(img[index*3+c])); + erri=erri+(errcol*errcol); + } + if(erri> 1); + pixel_indices_LSB[x*4+y]=(pixel_index & 1); + } + } + for(int half=0; half<2; half++) + { + if(taberror[half] 128) +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double calcBlockErrorRGBA(uint8 *img, uint8 *imgdec, uint8* alpha, int width, int height, int startx, int starty) +{ + int xx,yy; + double err; + + err = 0; + + for(xx = startx; xx< startx+4; xx++) + { + for(yy = starty; yy128) + { + err += SQUARE(1.0*RED(img,width,xx,yy) - 1.0*RED(imgdec, width, xx,yy)); + err += SQUARE(1.0*GREEN(img,width,xx,yy)- 1.0*GREEN(imgdec, width, xx,yy)); + err += SQUARE(1.0*BLUE(img,width,xx,yy) - 1.0*BLUE(imgdec, width, xx,yy)); + } + } + } + return err; +} + +//calculates the error for a block using the given colors, and the paremeters required to obtain the error. This version uses 1-bit punch-through alpha. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double calculateError59TAlpha(uint8* srcimg, uint8* alpha,int width, int startx, int starty, uint8 (colorsRGB444)[2][3], uint8 &distance, unsigned int &pixel_indices) +{ + + double block_error = 0, + best_block_error = MAXIMUM_ERROR, + pixel_error, + best_pixel_error; + int diff[3]; + uint8 best_sw; + unsigned int pixel_colors; + uint8 colors[2][3]; + uint8 possible_colors[4][3]; + + // First use the colors as they are, then swap them + for (uint8 sw = 0; sw <2; ++sw) + { + if (sw == 1) + { + swapColors(colorsRGB444); + } + decompressColor(R_BITS59T, G_BITS59T, B_BITS59T, colorsRGB444, colors); + + // Test all distances + for (uint8 d = 0; d < BINPOW(TABLE_BITS_59T); ++d) + { + calculatePaintColors59T(d,PATTERN_T, colors, possible_colors); + + block_error = 0; + pixel_colors = 0; + + // Loop block + for (size_t y = 0; y < BLOCKHEIGHT; ++y) + { + for (size_t x = 0; x < BLOCKWIDTH; ++x) + { + best_pixel_error = MAXIMUM_ERROR; + pixel_colors <<=2; // Make room for next value + + // Loop possible block colors + if(alpha[x+startx+(y+starty)*width]==0) + { + best_pixel_error=0; + pixel_colors ^= (pixel_colors & 3); // Reset the two first bits + pixel_colors |= 2; //insert the index for this pixel, two meaning transparent. + } + else + { + for (uint8 c = 0; c < 4; ++c) + { + + if(c==2) + continue; //don't use this, because we don't have alpha here and index 2 means transparent. + diff[R] = srcimg[3*((starty+y)*width+startx+x)+R] - CLAMP(0,possible_colors[c][R],255); + diff[G] = srcimg[3*((starty+y)*width+startx+x)+G] - CLAMP(0,possible_colors[c][G],255); + diff[B] = srcimg[3*((starty+y)*width+startx+x)+B] - CLAMP(0,possible_colors[c][B],255); + + pixel_error = weight[R]*SQUARE(diff[R]) + + weight[G]*SQUARE(diff[G]) + + weight[B]*SQUARE(diff[B]); + + // Choose best error + if (pixel_error < best_pixel_error) + { + best_pixel_error = pixel_error; + pixel_colors ^= (pixel_colors & 3); // Reset the two first bits + pixel_colors |= c; //insert the index for this pixel + } + } + } + block_error += best_pixel_error; + } + } + if (block_error < best_block_error) + { + best_block_error = block_error; + distance = d; + pixel_indices = pixel_colors; + best_sw = sw; + } + } + + if (sw == 1 && best_sw == 0) + { + swapColors(colorsRGB444); + } + decompressColor(R_BITS59T, G_BITS59T, B_BITS59T, colorsRGB444, colors); + } + return best_block_error; +} + +// same as fastest t-mode compressor above, but here one of the colors (the central one in the T) is used to also signal that the pixel is transparent. +// the only difference is that calculateError has been swapped out to one that considers alpha. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double compressBlockTHUMB59TAlpha(uint8 *img, uint8* alpha, int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2) +{ + double best_error = MAXIMUM_ERROR; + uint8 best_colorsRGB444[2][3]; + unsigned int best_pixel_indices; + uint8 best_distance; + + double error_no_i; + uint8 colorsRGB444_no_i[2][3]; + unsigned int pixel_indices_no_i; + uint8 distance_no_i; + + uint8 colors[2][3]; + + // Calculate average color using the LBG-algorithm + computeColorLBGHalfIntensityFast(img,width,startx,starty, colors); + compressColor(R_BITS59T, G_BITS59T, B_BITS59T, colors, colorsRGB444_no_i); + + // Determine the parameters for the lowest error + error_no_i = calculateError59TAlpha(img, alpha, width, startx, starty, colorsRGB444_no_i, distance_no_i, pixel_indices_no_i); + + best_error = error_no_i; + best_distance = distance_no_i; + best_pixel_indices = pixel_indices_no_i; + copyColors(colorsRGB444_no_i, best_colorsRGB444); + + // Put the compress params into the compression block + packBlock59T(best_colorsRGB444, best_distance, best_pixel_indices, compressed1, compressed2); + + return best_error; +} + +// Put bits in order for the format. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void stuff59bitsDiffFalse(unsigned int thumbT59_word1, unsigned int thumbT59_word2, unsigned int &thumbT_word1, unsigned int &thumbT_word2) +{ + // Put bits in twotimer configuration for 59 (red overflows) + // + // Go from this bit layout: + // + // |63 62 61 60 59|58 57 56 55|54 53 52 51|50 49 48 47|46 45 44 43|42 41 40 39|38 37 36 35|34 33 32| + // |----empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|--dist--| + // + // |31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| + // |----------------------------------------index bits---------------------------------------------| + // + // + // To this: + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // ----------------------------------------------------------------------------------------------- + // |// // //|R0a |//|R0b |G0 |B0 |R1 |G1 |B1 |da |df|db| + // ----------------------------------------------------------------------------------------------- + // + // |31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| + // |----------------------------------------index bits---------------------------------------------| + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // ----------------------------------------------------------------------------------------------- + // | base col1 | dcol 2 | base col1 | dcol 2 | base col 1 | dcol 2 | table | table |df|fp| + // | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | B1' (5 bits) | dB2 | cw 1 | cw 2 |bt|bt| + // ------------------------------------------------------------------------------------------------ + + uint8 R0a; + uint8 bit, a, b, c, d, bits; + + R0a = GETBITSHIGH( thumbT59_word1, 2, 58); + + // Fix middle part + thumbT_word1 = thumbT59_word1 << 1; + // Fix R0a (top two bits of R0) + PUTBITSHIGH( thumbT_word1, R0a, 2, 60); + // Fix db (lowest bit of d) + PUTBITSHIGH( thumbT_word1, thumbT59_word1, 1, 32); + // + // Make sure that red overflows: + a = GETBITSHIGH( thumbT_word1, 1, 60); + b = GETBITSHIGH( thumbT_word1, 1, 59); + c = GETBITSHIGH( thumbT_word1, 1, 57); + d = GETBITSHIGH( thumbT_word1, 1, 56); + // The following bit abcd bit sequences should be padded with ones: 0111, 1010, 1011, 1101, 1110, 1111 + // The following logical expression checks for the presence of any of those: + bit = (a & c) | (!a & b & c & d) | (a & b & !c & d); + bits = 0xf*bit; + PUTBITSHIGH( thumbT_word1, bits, 3, 63); + PUTBITSHIGH( thumbT_word1, !bit, 1, 58); + + // Set diffbit + PUTBITSHIGH( thumbT_word1, 0, 1, 33); + thumbT_word2 = thumbT59_word2; +} + +// Tests if there is at least one pixel in the image which would get alpha = 0 in punchtrough mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +bool hasAlpha(uint8* alphaimg, int ix, int iy, int width) +{ + for(int x=ix; x> 8) & 0xff; + bytes[1] = (block >> 0) & 0xff; + + fwrite(&bytes[0],1,1,f); + fwrite(&bytes[1],1,1,f); +} + + +// Write a word in big endian style +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void write_big_endian_4byte_word(unsigned int *blockadr, FILE *f) +{ + uint8 bytes[4]; + unsigned int block; + + block = blockadr[0]; + + bytes[0] = (block >> 24) & 0xff; + bytes[1] = (block >> 16) & 0xff; + bytes[2] = (block >> 8) & 0xff; + bytes[3] = (block >> 0) & 0xff; + + fwrite(&bytes[0],1,1,f); + fwrite(&bytes[1],1,1,f); + fwrite(&bytes[2],1,1,f); + fwrite(&bytes[3],1,1,f); +} + +extern int alphaTable[256][8]; +extern int alphaBase[16][4]; + +// valtab holds precalculated data used for compressing using EAC2. +// Note that valtab is constructed using get16bits11bits, which means +// that it already is expanded to 16 bits. +// Note also that it its contents will depend on the value of formatSigned. +int *valtab; + +void setupAlphaTableAndValtab() +{ + setupAlphaTable(); + + //fix precomputation table..! + valtab = new int[1024*512]; + int16 val16; + int count=0; + for(int base=0; base<256; base++) + { + for(int tab=0; tab<16; tab++) + { + for(int mul=0; mul<16; mul++) + { + for(int index=0; index<8; index++) + { + if(formatSigned) + { + val16=get16bits11signed(base,tab,mul,index); + valtab[count] = val16 + 256*128; + } + else + valtab[count]=get16bits11bits(base,tab,mul,index); + count++; + } + } + } + } +} + +// Reads alpha data +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void readAlpha(uint8* &data, int &width, int &height, int &extendedwidth, int &extendedheight) +{ + //width and height are already known..? + uint8* tempdata; + int wantedBitDepth; + if(format==ETC2PACKAGE_RGBA_NO_MIPMAPS||format==ETC2PACKAGE_RGBA1_NO_MIPMAPS||format==ETC2PACKAGE_sRGBA_NO_MIPMAPS||format==ETC2PACKAGE_sRGBA1_NO_MIPMAPS) + { + wantedBitDepth=8; + } + else if(format==ETC2PACKAGE_R_NO_MIPMAPS) + { + wantedBitDepth=16; + } + else + { + printf("invalid format for alpha reading!\n"); + exit(1); + } + fReadPGM("alpha.pgm",width,height,tempdata,wantedBitDepth); + extendedwidth=4*((width+3)/4); + extendedheight=4*((height+3)/4); + + if(width==extendedwidth&&height==extendedheight) + { + data=tempdata; + } + else + { + data = (uint8*)malloc(extendedwidth*extendedheight*wantedBitDepth/8); + uint8 last=0; + uint8 lastlast=0; + for(int x=0; xmaxdist) + maxdist=abs(alpha-data[ix+x+(iy+y)*width]); //maximum distance from average + } + } + int approxPos = (maxdist*255)/160-4; //experimentally derived formula for calculating approximate table position given a max distance from average + if(approxPos>255) + approxPos=255; + int startTable=approxPos-15; //first table to be tested + if(startTable<0) + startTable=0; + int endTable=clamp(approxPos+15); //last table to be tested + + int bestsum=1000000000; + int besttable=-3; + int bestalpha=128; + int prevalpha=alpha; + + //main loop: determine best base alpha value and offset table to use for compression + //try some different alpha tables. + for(int table = startTable; table0; table++) + { + int tablealpha=prevalpha; + int tablebestsum=1000000000; + //test some different alpha values, trying to find the best one for the given table. + for(int alphascale=16; alphascale>0; alphascale/=4) + { + int startalpha; + int endalpha; + if(alphascale==16) + { + startalpha = clamp(tablealpha-alphascale*4); + endalpha = clamp(tablealpha+alphascale*4); + } + else + { + startalpha = clamp(tablealpha-alphascale*2); + endalpha = clamp(tablealpha+alphascale*2); + } + for(alpha=startalpha; alpha<=endalpha; alpha+=alphascale) + { + int sum=0; + int val,diff,bestdiff=10000000,index; + for(int x=0; x<4; x++) + { + for(int y=0; y<4; y++) + { + //compute best offset here, add square difference to sum.. + val=data[ix+x+(iy+y)*width]; + bestdiff=1000000000; + //the values are always ordered from small to large, with the first 4 being negative and the last 4 positive + //search is therefore made in the order 0-1-2-3 or 7-6-5-4, stopping when error increases compared to the previous entry tested. + if(val>alpha) + { + for(index=7; index>3; index--) + { + diff=clamp_table[alpha+(int)(alphaTable[table][index])+255]-val; + diff*=diff; + if(diff<=bestdiff) + { + bestdiff=diff; + } + else + break; + } + } + else + { + for(index=0; index<4; index++) + { + diff=clamp_table[alpha+(int)(alphaTable[table][index])+255]-val; + diff*=diff; + if(diffbestsum) + { + x=9999; //just to make it large and get out of the x<4 loop + break; + } + } + } + if(sum7) + { + bit=0; + byte++; + } + } + } + } +} + +// Helper function for the below function +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +int getPremulIndex(int base, int tab, int mul, int index) +{ + return (base<<11)+(tab<<7)+(mul<<3)+index; +} + +// Calculates the error used in compressBlockAlpha16() +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double calcError(uint8* data, int ix, int iy, int width, int height, int base, int tab, int mul, double prevbest) +{ + int offset = getPremulIndex(base,tab,mul,0); + double error=0; + for (int y=0; y<4; y++) + { + for(int x=0; x<4; x++) + { + double besthere = (1<<20); + besthere*=besthere; + uint8 byte1 = data[2*(x+ix+(y+iy)*width)]; + uint8 byte2 = data[2*(x+ix+(y+iy)*width)+1]; + int alpha = (byte1<<8)+byte2; + for(int index=0; index<8; index++) + { + double indexError; + indexError = alpha-valtab[offset+index]; + indexError*=indexError; + if(indexError=prevbest) + return prevbest+(1<<30); + } + } + return error; +} + +// compressBlockAlpha16 +// +// Compresses a block using the 11-bit EAC formats. +// Depends on the global variable formatSigned. +// +// COMPRESSED_R11_EAC (if formatSigned = 0) +// This is an 11-bit unsigned format. Since we do not have a good 11-bit file format, we use 16-bit pgm instead. +// Here we assume that, in the input 16-bit pgm file, 0 represents 0.0 and 65535 represents 1.0. The function compressBlockAlpha16 +// will find the compressed block which best matches the data. In detail, it will find the compressed block, which +// if decompressed, will generate an 11-bit block that after bit replication to 16-bits will generate the closest +// block to the original 16-bit pgm block. +// +// COMPRESSED_SIGNED_R11_EAC (if formatSigned = 1) +// This is an 11-bit signed format. Since we do not have any signed file formats, we use unsigned 16-bit pgm instead. +// Hence we assume that, in the input 16-bit pgm file, 1 represents -1.0, 32768 represents 0.0 and 65535 represents 1.0. +// The function compresseBlockAlpha16 will find the compressed block, which if decompressed, will generate a signed +// 11-bit block that after bit replication to 16-bits and conversion to unsigned (1 equals -1.0, 32768 equals 0.0 and +// 65535 equals 1.0) will generate the closest block to the original 16-bit pgm block. +// +// COMPRESSED_RG11_EAC is compressed by calling the function twice, dito for COMPRESSED_SIGNED_RG11_EAC. +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void compressBlockAlpha16(uint8* data, int ix, int iy, int width, int height, uint8* returnData) +{ + unsigned int bestbase, besttable, bestmul; + double besterror; + besterror=1<<20; + besterror*=besterror; + for(int base=0; base<256; base++) + { + for(int table=0; table<16; table++) + { + for(int mul=0; mul<16; mul++) + { + double e = calcError(data, ix, iy, width, height,base,table,mul,besterror); + if(e7) + { + bit=0; + byte++; + } + } + } + } +} + +// Exhaustive compression of alpha compression in a GL_COMPRESSED_RGB8_ETC2 block +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void compressBlockAlphaSlow(uint8* data, int ix, int iy, int width, int height, uint8* returnData) +{ + //determine the best table and base alpha value for this block using MSE + int alphasum=0; + int maxdist=-2; + for(int x=0; x<4; x++) + { + for(int y=0; y<4; y++) + { + alphasum+=data[ix+x+(iy+y)*width]; + } + } + int alpha = (int)( ((float)alphasum)/16.0f+0.5f); //average pixel value, used as guess for base value. + + int bestsum=1000000000; + int besttable=-3; + int bestalpha=128; + int prevalpha=alpha; + + //main loop: determine best base alpha value and offset table to use for compression + //try some different alpha tables. + for(int table = 0; table<256&&bestsum>0; table++) + { + int tablealpha=prevalpha; + int tablebestsum=1000000000; + //test some different alpha values, trying to find the best one for the given table. + for(int alphascale=32; alphascale>0; alphascale/=8) + { + + int startalpha = clamp(tablealpha-alphascale*4); + int endalpha = clamp(tablealpha+alphascale*4); + + for(alpha=startalpha; alpha<=endalpha; alpha+=alphascale) { + int sum=0; + int val,diff,bestdiff=10000000,index; + for(int x=0; x<4; x++) + { + for(int y=0; y<4; y++) + { + //compute best offset here, add square difference to sum.. + val=data[ix+x+(iy+y)*width]; + bestdiff=1000000000; + //the values are always ordered from small to large, with the first 4 being negative and the last 4 positive + //search is therefore made in the order 0-1-2-3 or 7-6-5-4, stopping when error increases compared to the previous entry tested. + if(val>alpha) + { + for(index=7; index>3; index--) + { + diff=clamp_table[alpha+(alphaTable[table][index])+255]-val; + diff*=diff; + if(diff<=bestdiff) + { + bestdiff=diff; + } + else + break; + } + } + else + { + for(index=0; index<5; index++) + { + diff=clamp_table[alpha+(alphaTable[table][index])+255]-val; + diff*=diff; + if(difftablebestsum) + { + x=9999; //just to make it large and get out of the x<4 loop + break; + } + } + } + if(sum7) + { + bit=0; + byte++; + } + } + } + } +} + +// Calculate weighted PSNR +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double calculateWeightedPSNR(uint8 *lossyimg, uint8 *origimg, int width, int height, double w1, double w2, double w3) +{ + // Note: This calculation of PSNR uses the formula + // + // PSNR = 10 * log_10 ( 255^2 / wMSE ) + // + // where the wMSE is calculated as + // + // 1/(N*M) * sum ( ( w1*(R' - R)^2 + w2*(G' - G)^2 + w3*(B' - B)^2) ) + // + // typical weights are 0.299, 0.587, 0.114 for perceptually weighted PSNR and + // 1.0/3.0, 1.0/3.0, 1.0/3.0 for nonweighted PSNR + + int x,y; + double wMSE; + double PSNR; + double err; + wMSE = 0; + + for(y=0;y.\n",srcfile); + exit(1); + } + height=active_height; + width=active_width; + fclose(f); +} + +// Writes output file +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void writeOutputFile(char *dstfile, uint8* img, uint8* alphaimg, int width, int height) +{ + char str[300]; + + if(format!=ETC2PACKAGE_R_NO_MIPMAPS&&format!=ETC2PACKAGE_RG_NO_MIPMAPS) + { + fWritePPM("tmp.ppm",width,height,img,8,false); + printf("Saved file tmp.ppm \n\n"); + } + else if(format==ETC2PACKAGE_RG_NO_MIPMAPS) + { + fWritePPM("tmp.ppm",width,height,img,16,false); + } + if(format==ETC2PACKAGE_RGBA_NO_MIPMAPS||format==ETC2PACKAGE_RGBA1_NO_MIPMAPS||format==ETC2PACKAGE_sRGBA_NO_MIPMAPS||format==ETC2PACKAGE_sRGBA1_NO_MIPMAPS) + fWritePGM("alphaout.pgm",width,height,alphaimg,false,8); + if(format==ETC2PACKAGE_R_NO_MIPMAPS) + fWritePGM("alphaout.pgm",width,height,alphaimg,false,16); + + // Delete destination file if it exists + if(fileExist(dstfile)) + { + sprintf(str, "del %s\n",dstfile); + system(str); + } + + int q = find_pos_of_extension(dstfile); + if(!strcmp(&dstfile[q],".ppm")&&format!=ETC2PACKAGE_R_NO_MIPMAPS) + { + // Already a .ppm file. Just rename. + sprintf(str,"move tmp.ppm %s\n",dstfile); + printf("Renaming destination file to %s\n",dstfile); + } + else + { + // Converting from .ppm to other file format + // + // Use your favorite command line image converter program, + // for instance Image Magick. Just make sure the syntax can + // be written as below: + // + // C:\imconv source.ppm dest.jpg + // + if(format==ETC2PACKAGE_RGBA_NO_MIPMAPS||format==ETC2PACKAGE_RGBA1_NO_MIPMAPS||format==ETC2PACKAGE_sRGBA_NO_MIPMAPS||format==ETC2PACKAGE_sRGBA1_NO_MIPMAPS) + { + // Somewhere after version 6.7.1-2 of ImageMagick the following command gives the wrong result due to a bug. + // sprintf(str,"composite -compose CopyOpacity alphaout.pgm tmp.ppm %s\n",dstfile); + // Instead we read the file and write a tga. + + printf("Converting destination file from .ppm/.pgm to %s with alpha\n",dstfile); + int rw, rh; + unsigned char *pixelsRGB; + unsigned char *pixelsA; + fReadPPM("tmp.ppm", rw, rh, pixelsRGB, 8); + fReadPGM("alphaout.pgm", rw, rh, pixelsA, 8); + fWriteTGAfromRGBandA(dstfile, rw, rh, pixelsRGB, pixelsA, true); + free(pixelsRGB); + free(pixelsA); + sprintf(str,""); // Nothing to execute. + } + else if(format==ETC2PACKAGE_R_NO_MIPMAPS) + { + sprintf(str,"imconv alphaout.pgm %s\n",dstfile); + printf("Converting destination file from .pgm to %s\n",dstfile); + } + else + { + sprintf(str,"imconv tmp.ppm %s\n",dstfile); + printf("Converting destination file from .ppm to %s\n",dstfile); + } + } + // Execute system call + system(str); + + free(img); + if(alphaimg!=NULL) + free(alphaimg); +} + +// Calculates the PSNR between two files +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double calculatePSNRfile(char *srcfile, uint8 *origimg, uint8* origalpha) +{ + uint8 *alphaimg, *img; + int active_width, active_height; + uncompressFile(srcfile,img,alphaimg,active_width,active_height); + + // calculate Mean Square Error (MSE) + double MSER=0,MSEG=0,MSEB=0,MSEA, PSNRR,PSNRG,PSNRA; + double MSE; + double wMSE; + double PSNR=0; + double wPSNR; + double err; + MSE = 0; + MSEA=0; + wMSE = 0; + int width=((active_width+3)/4)*4; + int height=((active_height+3)/4)*4; + int numpixels = 0; + for(int y=0;y 0) + { + err = img[y*active_width*3+x*3+0] - origimg[y*width*3+x*3+0]; + MSE += ((err * err)/3.0); + wMSE += PERCEPTUAL_WEIGHT_R_SQUARED * (err*err); + err = img[y*active_width*3+x*3+1] - origimg[y*width*3+x*3+1]; + MSE += ((err * err)/3.0); + wMSE += PERCEPTUAL_WEIGHT_G_SQUARED * (err*err); + err = img[y*active_width*3+x*3+2] - origimg[y*width*3+x*3+2]; + MSE += ((err * err)/3.0); + wMSE += PERCEPTUAL_WEIGHT_B_SQUARED * (err*err); + numpixels++; + } + } + else if(format==ETC2PACKAGE_RG_NO_MIPMAPS) + { + int rorig = (origimg[6*(y*width+x)+0]<<8)+origimg[6*(y*width+x)+1]; + int rnew = ( img[6*(y*active_width+x)+0]<<8)+ img[6*(y*active_width+x)+1]; + int gorig = (origimg[6*(y*width+x)+2]<<8)+origimg[6*(y*width+x)+3]; + int gnew = ( img[6*(y*active_width+x)+2]<<8)+ img[6*(y*active_width+x)+3]; + err=rorig-rnew; + MSER+=(err*err); + err=gorig-gnew; + MSEG+=(err*err); + } + else if(format==ETC2PACKAGE_R_NO_MIPMAPS) + { + int aorig = (((int)origalpha[2*(y*width+x)+0])<<8)+origalpha[2*(y*width+x)+1]; + int anew = (((int)alphaimg[2*(y*active_width+x)+0])<<8)+alphaimg[2*(y*active_width+x)+1]; + err=aorig-anew; + MSEA+=(err*err); + } + } + } + if(format == ETC2PACKAGE_RGBA1_NO_MIPMAPS || format == ETC2PACKAGE_sRGBA1_NO_MIPMAPS) + { + MSE = MSE / (1.0 * numpixels); + wMSE = wMSE / (1.0 * numpixels); + PSNR = 10*log((1.0*255*255)/MSE)/log(10.0); + wPSNR = 10*log((1.0*255*255)/wMSE)/log(10.0); + printf("PSNR only calculated on pixels where compressed alpha > 0\n"); + printf("color PSNR: %lf\nweighted PSNR: %lf\n",PSNR,wPSNR); + } + else if(format!=ETC2PACKAGE_R_NO_MIPMAPS&&format!=ETC2PACKAGE_RG_NO_MIPMAPS) + { + MSE = MSE / (active_width * active_height); + wMSE = wMSE / (active_width * active_height); + PSNR = 10*log((1.0*255*255)/MSE)/log(10.0); + wPSNR = 10*log((1.0*255*255)/wMSE)/log(10.0); + if(format == ETC2PACKAGE_RGBA_NO_MIPMAPS || format == ETC2PACKAGE_sRGBA_NO_MIPMAPS) + printf("PSNR only calculated on RGB, not on alpha\n"); + printf("color PSNR: %lf\nweighted PSNR: %lf\n",PSNR,wPSNR); + } + else if(format==ETC2PACKAGE_RG_NO_MIPMAPS) + { + MSER = MSER / (active_width * active_height); + MSEG = MSEG / (active_width * active_height); + PSNRR = 10*log((1.0*65535*65535)/MSER)/log(10.0); + PSNRG = 10*log((1.0*65535*65535)/MSEG)/log(10.0); + printf("red PSNR: %lf\ngreen PSNR: %lf\n",PSNRR,PSNRG); + } + else if(format==ETC2PACKAGE_R_NO_MIPMAPS) + { + MSEA = MSEA / (active_width * active_height); + PSNRA = 10*log((1.0*65535.0*65535.0)/MSEA)/log(10.0); + printf("PSNR: %lf\n",PSNRA); + } + free(img); + return PSNR; +} + +//// Exhaustive code starts here. + +#if EXHAUSTIVE_CODE_ACTIVE +// Precomutes a table that is used when compressing a block exhaustively +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +inline unsigned int precompute_3bittable_all_subblocksRG_withtest_perceptual1000(uint8 *block,uint8 *avg_color, unsigned int *precalc_err_UL_R, unsigned int *precalc_err_UR_R, unsigned int *precalc_err_LL_R, unsigned int *precalc_err_LR_R,unsigned int *precalc_err_UL_RG, unsigned int *precalc_err_UR_RG, unsigned int *precalc_err_LL_RG, unsigned int *precalc_err_LR_RG, unsigned int best_err) +{ + int table; + int index; + int orig[3],approx[3][4]; + int x; + int intensity_modifier; + const int *table_indices; + + int good_enough_to_test; + unsigned int err[4]; + unsigned int err_this_table_upper; + unsigned int err_this_table_lower; + unsigned int err_this_table_left; + unsigned int err_this_table_right; + + // If the error in the red and green component is already larger than best_err for all 8 tables in + // all of upper, lower, left and right, this combination of red and green will never be used in + // the optimal color configuration. Therefore we can avoid testing all the blue colors for this + // combination. + good_enough_to_test = false; + + for(table=0;table<8;table++) // try all the 8 tables. + { + table_indices = &compressParamsFast[table*4]; + + intensity_modifier = table_indices[0]; + approx[1][0]=CLAMP(0, avg_color[1]+intensity_modifier,255); + intensity_modifier = table_indices[1]; + approx[1][1]=CLAMP(0, avg_color[1]+intensity_modifier,255); + intensity_modifier = table_indices[2]; + approx[1][2]=CLAMP(0, avg_color[1]+intensity_modifier,255); + intensity_modifier = table_indices[3]; + approx[1][3]=CLAMP(0, avg_color[1]+intensity_modifier,255); + + err_this_table_upper = 0; + err_this_table_lower = 0; + err_this_table_left = 0; + err_this_table_right = 0; + for(x=0; x<4; x++) + { + orig[0]=block[x*4]; + orig[1]=block[x*4+1]; + orig[2]=block[x*4+2]; + for(index=0;index<4;index++) + { + err[index] = precalc_err_UL_R[table*4*4+x*4+index] + + PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000 * SQUARE(approx[1][index]-orig[1]); + precalc_err_UL_RG[table*4*4+x*4+index] = err[index]; + } + if(err[0] > err[1]) + err[0] = err[1]; + if(err[2] > err[3]) + err[2] = err[3]; + if(err[0] > err[2]) + err[0] = err[2]; + err_this_table_upper+=err[0]; + err_this_table_left+=err[0]; + } + for(x=4; x<8; x++) + { + orig[0]=block[x*4]; + orig[1]=block[x*4+1]; + orig[2]=block[x*4+2]; + for(index=0;index<4;index++) + { + err[index] = precalc_err_UR_R[table*4*4+(x-4)*4+index] + + PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000 * SQUARE(approx[1][index]-orig[1]); + precalc_err_UR_RG[table*4*4+(x-4)*4+index] = err[index]; + } + if(err[0] > err[1]) + err[0] = err[1]; + if(err[2] > err[3]) + err[2] = err[3]; + if(err[0] > err[2]) + err[0] = err[2]; + err_this_table_upper+=err[0]; + err_this_table_right+=err[0]; + } + for(x=8; x<12; x++) + { + orig[0]=block[x*4]; + orig[1]=block[x*4+1]; + orig[2]=block[x*4+2]; + + for(index=0;index<4;index++) + { + err[index] = precalc_err_LL_R[table*4*4+(x-8)*4+index] + + PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000 * SQUARE(approx[1][index]-orig[1]); + precalc_err_LL_RG[table*4*4+(x-8)*4+index] = err[index]; + } + if(err[0] > err[1]) + err[0] = err[1]; + if(err[2] > err[3]) + err[2] = err[3]; + if(err[0] > err[2]) + err[0] = err[2]; + err_this_table_lower+=err[0]; + err_this_table_left+=err[0]; + } + for(x=12; x<16; x++) + { + orig[0]=block[x*4]; + orig[1]=block[x*4+1]; + orig[2]=block[x*4+2]; + + for(index=0;index<4;index++) + { + err[index] = precalc_err_LR_R[table*4*4+(x-12)*4+index] + + PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000 * SQUARE(approx[1][index]-orig[1]); + precalc_err_LR_RG[table*4*4+(x-12)*4+index] = err[index]; + } + if(err[0] > err[1]) + err[0] = err[1]; + if(err[2] > err[3]) + err[2] = err[3]; + if(err[0] > err[2]) + err[0] = err[2]; + err_this_table_lower+=err[0]; + err_this_table_right+=err[0]; + } + if(err_this_table_upper < best_err) + good_enough_to_test = true; + if(err_this_table_lower < best_err) + good_enough_to_test = true; + if(err_this_table_left < best_err) + good_enough_to_test = true; + if(err_this_table_right < best_err) + good_enough_to_test = true; + } + return good_enough_to_test; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precomutes a table that is used when compressing a block exhaustively +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +inline int precompute_3bittable_all_subblocksRG_withtest(uint8 *block,uint8 *avg_color, unsigned int *precalc_err_UL_R, unsigned int *precalc_err_UR_R, unsigned int *precalc_err_LL_R, unsigned int *precalc_err_LR_R,unsigned int *precalc_err_UL_RG, unsigned int *precalc_err_UR_RG, unsigned int *precalc_err_LL_RG, unsigned int *precalc_err_LR_RG, unsigned int best_err) +{ + int table; + int index; + int orig[3],approx[3][4]; + int x; + int intensity_modifier; + const int *table_indices; + + int good_enough_to_test; + unsigned int err[4]; + unsigned int err_this_table_upper; + unsigned int err_this_table_lower; + unsigned int err_this_table_left; + unsigned int err_this_table_right; + + // If the error in the red and green component is already larger than best_err for all 8 tables in + // all of upper, lower, left and right, this combination of red and green will never be used in + // the optimal color configuration. Therefore we can avoid testing all the blue colors for this + // combination. + good_enough_to_test = false; + + for(table=0;table<8;table++) // try all the 8 tables. + { + table_indices = &compressParamsFast[table*4]; + + intensity_modifier = table_indices[0]; + approx[1][0]=CLAMP(0, avg_color[1]+intensity_modifier,255); + intensity_modifier = table_indices[1]; + approx[1][1]=CLAMP(0, avg_color[1]+intensity_modifier,255); + intensity_modifier = table_indices[2]; + approx[1][2]=CLAMP(0, avg_color[1]+intensity_modifier,255); + intensity_modifier = table_indices[3]; + approx[1][3]=CLAMP(0, avg_color[1]+intensity_modifier,255); + + err_this_table_upper = 0; + err_this_table_lower = 0; + err_this_table_left = 0; + err_this_table_right = 0; + for(x=0; x<4; x++) + { + orig[0]=block[x*4]; + orig[1]=block[x*4+1]; + orig[2]=block[x*4+2]; + for(index=0;index<4;index++) + { + err[index] = precalc_err_UL_R[table*4*4+x*4+index]+SQUARE(approx[1][index]-orig[1]); + precalc_err_UL_RG[table*4*4+x*4+index] = err[index]; + } + if(err[0] > err[1]) + err[0] = err[1]; + if(err[2] > err[3]) + err[2] = err[3]; + if(err[0] > err[2]) + err[0] = err[2]; + err_this_table_upper+=err[0]; + err_this_table_left+=err[0]; + } + for(x=4; x<8; x++) + { + orig[0]=block[x*4]; + orig[1]=block[x*4+1]; + orig[2]=block[x*4+2]; + for(index=0;index<4;index++) + { + err[index] = precalc_err_UR_R[table*4*4+(x-4)*4+index]+SQUARE(approx[1][index]-orig[1]); + precalc_err_UR_RG[table*4*4+(x-4)*4+index] = err[index]; + } + if(err[0] > err[1]) + err[0] = err[1]; + if(err[2] > err[3]) + err[2] = err[3]; + if(err[0] > err[2]) + err[0] = err[2]; + err_this_table_upper+=err[0]; + err_this_table_right+=err[0]; + } + for(x=8; x<12; x++) + { + orig[0]=block[x*4]; + orig[1]=block[x*4+1]; + orig[2]=block[x*4+2]; + + for(index=0;index<4;index++) + { + err[index] = precalc_err_LL_R[table*4*4+(x-8)*4+index]+SQUARE(approx[1][index]-orig[1]); + precalc_err_LL_RG[table*4*4+(x-8)*4+index] = err[index]; + } + if(err[0] > err[1]) + err[0] = err[1]; + if(err[2] > err[3]) + err[2] = err[3]; + if(err[0] > err[2]) + err[0] = err[2]; + err_this_table_lower+=err[0]; + err_this_table_left+=err[0]; + } + for(x=12; x<16; x++) + { + orig[0]=block[x*4]; + orig[1]=block[x*4+1]; + orig[2]=block[x*4+2]; + + for(index=0;index<4;index++) + { + err[index] = precalc_err_LR_R[table*4*4+(x-12)*4+index]+SQUARE(approx[1][index]-orig[1]); + precalc_err_LR_RG[table*4*4+(x-12)*4+index] = err[index]; + } + if(err[0] > err[1]) + err[0] = err[1]; + if(err[2] > err[3]) + err[2] = err[3]; + if(err[0] > err[2]) + err[0] = err[2]; + err_this_table_lower+=err[0]; + err_this_table_right+=err[0]; + } + if(err_this_table_upper < best_err) + good_enough_to_test = true; + if(err_this_table_lower < best_err) + good_enough_to_test = true; + if(err_this_table_left < best_err) + good_enough_to_test = true; + if(err_this_table_right < best_err) + good_enough_to_test = true; + } + return good_enough_to_test; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precomutes a table that is used when compressing a block exhaustively +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +inline unsigned int precompute_3bittable_all_subblocksR_with_test_perceptual1000(uint8 *block,uint8 *avg_color, unsigned int *precalc_err_UL_R, unsigned int *precalc_err_UR_R, unsigned int *precalc_err_LL_R, unsigned int *precalc_err_LR_R, unsigned int best_err) +{ + int table; + int index; + int orig[3],approx[3][4]; + int x; + int intensity_modifier; + const int *table_indices; + + unsigned int err[4]; + unsigned int err_this_table_upper; + unsigned int err_this_table_lower; + unsigned int err_this_table_left; + unsigned int err_this_table_right; + + int good_enough_to_test; + + good_enough_to_test = false; + + for(table=0;table<8;table++) // try all the 8 tables. + { + err_this_table_upper = 0; + err_this_table_lower = 0; + err_this_table_left = 0; + err_this_table_right = 0; + + table_indices = &compressParamsFast[table*4]; + + intensity_modifier = table_indices[0]; + approx[0][0]=CLAMP(0, avg_color[0]+intensity_modifier,255); + intensity_modifier = table_indices[1]; + approx[0][1]=CLAMP(0, avg_color[0]+intensity_modifier,255); + intensity_modifier = table_indices[2]; + approx[0][2]=CLAMP(0, avg_color[0]+intensity_modifier,255); + intensity_modifier = table_indices[3]; + approx[0][3]=CLAMP(0, avg_color[0]+intensity_modifier,255); + + for(x=0; x<4; x++) + { + orig[0]=block[x*4]; + orig[1]=block[x*4+1]; + orig[2]=block[x*4+2]; + for(index=0;index<4;index++) + { + err[index]=PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*SQUARE(approx[0][index]-orig[0]); + precalc_err_UL_R[table*4*4+x*4+index]=err[index]; + } + if(err[0] > err[1]) + err[0] = err[1]; + if(err[2] > err[3]) + err[2] = err[3]; + if(err[0] > err[2]) + err[0] = err[2]; + err_this_table_upper+=err[0]; + err_this_table_left+=err[0]; + } + for(x=4; x<8; x++) + { + orig[0]=block[x*4]; + orig[1]=block[x*4+1]; + orig[2]=block[x*4+2]; + for(index=0;index<4;index++) + { + err[index]=PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*SQUARE(approx[0][index]-orig[0]); + precalc_err_UR_R[table*4*4+(x-4)*4+index]=err[index]; + } + if(err[0] > err[1]) + err[0] = err[1]; + if(err[2] > err[3]) + err[2] = err[3]; + if(err[0] > err[2]) + err[0] = err[2]; + err_this_table_upper+=err[0]; + err_this_table_right+=err[0]; + } + for(x=8; x<12; x++) + { + orig[0]=block[x*4]; + orig[1]=block[x*4+1]; + orig[2]=block[x*4+2]; + + for(index=0;index<4;index++) + { + err[index]=PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*SQUARE(approx[0][index]-orig[0]); + precalc_err_LL_R[table*4*4+(x-8)*4+index]=err[index]; + } + if(err[0] > err[1]) + err[0] = err[1]; + if(err[2] > err[3]) + err[2] = err[3]; + if(err[0] > err[2]) + err[0] = err[2]; + err_this_table_lower+=err[0]; + err_this_table_left+=err[0]; + + } + for(x=12; x<16; x++) + { + orig[0]=block[x*4]; + orig[1]=block[x*4+1]; + orig[2]=block[x*4+2]; + + for(index=0;index<4;index++) + { + err[index]=PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*SQUARE(approx[0][index]-orig[0]); + precalc_err_LR_R[table*4*4+(x-12)*4+index]=err[index]; + } + if(err[0] > err[1]) + err[0] = err[1]; + if(err[2] > err[3]) + err[2] = err[3]; + if(err[0] > err[2]) + err[0] = err[2]; + err_this_table_lower+=err[0]; + err_this_table_right+=err[0]; + } + if(err_this_table_upper < best_err) + good_enough_to_test = true; + if(err_this_table_lower < best_err) + good_enough_to_test = true; + if(err_this_table_left < best_err) + good_enough_to_test = true; + if(err_this_table_right < best_err) + good_enough_to_test = true; + } + return good_enough_to_test; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precomutes a table that is used when compressing a block exhaustively +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +inline int precompute_3bittable_all_subblocksR_with_test(uint8 *block,uint8 *avg_color, unsigned int *precalc_err_UL_R, unsigned int *precalc_err_UR_R, unsigned int *precalc_err_LL_R, unsigned int *precalc_err_LR_R, unsigned int best_err) +{ + int table; + int index; + int orig[3],approx[3][4]; + int x; + int intensity_modifier; + const int *table_indices; + + unsigned int err[4]; + unsigned int err_this_table_upper; + unsigned int err_this_table_lower; + unsigned int err_this_table_left; + unsigned int err_this_table_right; + + int good_enough_to_test; + + good_enough_to_test = false; + + for(table=0;table<8;table++) // try all the 8 tables. + { + err_this_table_upper = 0; + err_this_table_lower = 0; + err_this_table_left = 0; + err_this_table_right = 0; + + table_indices = &compressParamsFast[table*4]; + + intensity_modifier = table_indices[0]; + approx[0][0]=CLAMP(0, avg_color[0]+intensity_modifier,255); + intensity_modifier = table_indices[1]; + approx[0][1]=CLAMP(0, avg_color[0]+intensity_modifier,255); + intensity_modifier = table_indices[2]; + approx[0][2]=CLAMP(0, avg_color[0]+intensity_modifier,255); + intensity_modifier = table_indices[3]; + approx[0][3]=CLAMP(0, avg_color[0]+intensity_modifier,255); + + for(x=0; x<4; x++) + { + orig[0]=block[x*4]; + orig[1]=block[x*4+1]; + orig[2]=block[x*4+2]; + for(index=0;index<4;index++) + { + err[index]=SQUARE(approx[0][index]-orig[0]); + precalc_err_UL_R[table*4*4+x*4+index]=err[index]; + } + if(err[0] > err[1]) + err[0] = err[1]; + if(err[2] > err[3]) + err[2] = err[3]; + if(err[0] > err[2]) + err[0] = err[2]; + err_this_table_upper+=err[0]; + err_this_table_left+=err[0]; + } + for(x=4; x<8; x++) + { + orig[0]=block[x*4]; + orig[1]=block[x*4+1]; + orig[2]=block[x*4+2]; + for(index=0;index<4;index++) + { + err[index]=SQUARE(approx[0][index]-orig[0]); + precalc_err_UR_R[table*4*4+(x-4)*4+index]=err[index]; + } + if(err[0] > err[1]) + err[0] = err[1]; + if(err[2] > err[3]) + err[2] = err[3]; + if(err[0] > err[2]) + err[0] = err[2]; + err_this_table_upper+=err[0]; + err_this_table_right+=err[0]; + } + for(x=8; x<12; x++) + { + orig[0]=block[x*4]; + orig[1]=block[x*4+1]; + orig[2]=block[x*4+2]; + + for(index=0;index<4;index++) + { + err[index]=SQUARE(approx[0][index]-orig[0]); + precalc_err_LL_R[table*4*4+(x-8)*4+index]=err[index]; + } + if(err[0] > err[1]) + err[0] = err[1]; + if(err[2] > err[3]) + err[2] = err[3]; + if(err[0] > err[2]) + err[0] = err[2]; + err_this_table_lower+=err[0]; + err_this_table_left+=err[0]; + + } + for(x=12; x<16; x++) + { + orig[0]=block[x*4]; + orig[1]=block[x*4+1]; + orig[2]=block[x*4+2]; + + for(index=0;index<4;index++) + { + err[index]=SQUARE(approx[0][index]-orig[0]); + precalc_err_LR_R[table*4*4+(x-12)*4+index]=err[index]; + } + if(err[0] > err[1]) + err[0] = err[1]; + if(err[2] > err[3]) + err[2] = err[3]; + if(err[0] > err[2]) + err[0] = err[2]; + err_this_table_lower+=err[0]; + err_this_table_right+=err[0]; + } + if(err_this_table_upper < best_err) + good_enough_to_test = true; + if(err_this_table_lower < best_err) + good_enough_to_test = true; + if(err_this_table_left < best_err) + good_enough_to_test = true; + if(err_this_table_right < best_err) + good_enough_to_test = true; + } + return good_enough_to_test; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Tries all index-tables, used when compressing a block exhaustively +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +inline void tryalltables_3bittable_all_subblocks_using_precalc(uint8 *block_2x2,uint8 *color_quant1, unsigned int *precalc_err_UL_RG, unsigned int *precalc_err_UR_RG, unsigned int *precalc_err_LL_RG, unsigned int *precalc_err_LR_RG, unsigned int &err_upper, unsigned int &err_lower, unsigned int &err_left, unsigned int &err_right, unsigned int best_err) +{ + unsigned int err_this_table_upper; + unsigned int err_this_table_lower; + unsigned int err_this_table_left; + unsigned int err_this_table_right; + int orig[3],approx[4]; + int err[4]; + err_upper = 3*255*255*16; + err_lower = 3*255*255*16; + err_left = 3*255*255*16; + err_right = 3*255*255*16; + +#define ONE_PIXEL_UL(table_nbr,xx)\ + orig[0]=block_2x2[xx*4];\ + orig[1]=block_2x2[xx*4+1];\ + orig[2]=block_2x2[xx*4+2];\ + /* unrolled loop for(index=0;index<4;index++)*/\ + err[0]=precalc_err_UL_RG[table_nbr*4*4+xx*4+0] + square_table[approx[0]-orig[2]];\ + err[1]=precalc_err_UL_RG[table_nbr*4*4+xx*4+1] + square_table[approx[1]-orig[2]];\ + err[2]=precalc_err_UL_RG[table_nbr*4*4+xx*4+2] + square_table[approx[2]-orig[2]];\ + err[3]=precalc_err_UL_RG[table_nbr*4*4+xx*4+3] + square_table[approx[3]-orig[2]];\ + /* end unrolled loop*/\ + if(err[0] > err[1])\ + err[0] = err[1];\ + if(err[2] > err[3])\ + err[2] = err[3];\ + if(err[0] > err[2])\ + err[0] = err[2];\ + err_this_table_upper+=err[0];\ + err_this_table_left+=err[0];\ + +#define ONE_PIXEL_UR(table_nbr,xx)\ + orig[0]=block_2x2[xx*4];\ + orig[1]=block_2x2[xx*4+1];\ + orig[2]=block_2x2[xx*4+2];\ + /* unrolled loop for(index=0;index<4;index++)*/\ + err[0]=precalc_err_UR_RG[table_nbr*4*4+(xx-4)*4+0] + square_table[approx[0]-orig[2]];\ + err[1]=precalc_err_UR_RG[table_nbr*4*4+(xx-4)*4+1] + square_table[approx[1]-orig[2]];\ + err[2]=precalc_err_UR_RG[table_nbr*4*4+(xx-4)*4+2] + square_table[approx[2]-orig[2]];\ + err[3]=precalc_err_UR_RG[table_nbr*4*4+(xx-4)*4+3] + square_table[approx[3]-orig[2]];\ + /* end unrolled loop */\ + if(err[0] > err[1])\ + err[0] = err[1];\ + if(err[2] > err[3])\ + err[2] = err[3];\ + if(err[0] > err[2])\ + err[0] = err[2];\ + err_this_table_upper+=err[0];\ + err_this_table_right+=err[0]; + +#define ONE_PIXEL_LL(table_nbr,xx)\ + orig[0]=block_2x2[xx*4];\ + orig[1]=block_2x2[xx*4+1];\ + orig[2]=block_2x2[xx*4+2];\ + /* unrolled loop for(index=0;index<4;index++)*/\ + err[0]=precalc_err_LL_RG[table_nbr*4*4+(xx-8)*4+0] + square_table[approx[0]-orig[2]];\ + err[1]=precalc_err_LL_RG[table_nbr*4*4+(xx-8)*4+1] + square_table[approx[1]-orig[2]];\ + err[2]=precalc_err_LL_RG[table_nbr*4*4+(xx-8)*4+2] + square_table[approx[2]-orig[2]];\ + err[3]=precalc_err_LL_RG[table_nbr*4*4+(xx-8)*4+3] + square_table[approx[3]-orig[2]];\ + /* end unrolled loop*/\ + if(err[0] > err[1])\ + err[0] = err[1];\ + if(err[2] > err[3])\ + err[2] = err[3];\ + if(err[0] > err[2])\ + err[0] = err[2];\ + err_this_table_lower+=err[0];\ + err_this_table_left+=err[0];\ + +#define ONE_PIXEL_LR(table_nbr,xx)\ + orig[0]=block_2x2[xx*4];\ + orig[1]=block_2x2[xx*4+1];\ + orig[2]=block_2x2[xx*4+2];\ + /* unrolled loop for(index=0;index<4;index++)*/\ + err[0]=precalc_err_LR_RG[table_nbr*4*4+(xx-12)*4+0] + square_table[approx[0]-orig[2]];\ + err[1]=precalc_err_LR_RG[table_nbr*4*4+(xx-12)*4+1] + square_table[approx[1]-orig[2]];\ + err[2]=precalc_err_LR_RG[table_nbr*4*4+(xx-12)*4+2] + square_table[approx[2]-orig[2]];\ + err[3]=precalc_err_LR_RG[table_nbr*4*4+(xx-12)*4+3] + square_table[approx[3]-orig[2]];\ + /* end unrolled loop*/\ + if(err[0] > err[1])\ + err[0] = err[1];\ + if(err[2] > err[3])\ + err[2] = err[3];\ + if(err[0] > err[2])\ + err[0] = err[2];\ + err_this_table_lower+=err[0];\ + err_this_table_right+=err[0];\ + +#define ONE_TABLE_3(table_nbr)\ + err_this_table_upper = 0;\ + err_this_table_lower = 0;\ + err_this_table_left = 0;\ + err_this_table_right = 0;\ + approx[0]=clamp_table_plus_255[color_quant1[2]+compressParamsFast[table_nbr*4+0]+255];\ + approx[1]=clamp_table_plus_255[color_quant1[2]+compressParamsFast[table_nbr*4+1]+255];\ + approx[2]=clamp_table_plus_255[color_quant1[2]+compressParamsFast[table_nbr*4+2]+255];\ + approx[3]=clamp_table_plus_255[color_quant1[2]+compressParamsFast[table_nbr*4+3]+255];\ + /* unroll loop for(xx=0; xx<4; xx++) */\ + ONE_PIXEL_UL(table_nbr,0)\ + ONE_PIXEL_UL(table_nbr,1)\ + ONE_PIXEL_UL(table_nbr,2)\ + ONE_PIXEL_UL(table_nbr,3)\ + /* end unroll loop */\ + /* unroll loop for(xx=4; xx<8; xx++) */\ + ONE_PIXEL_LR(table_nbr,12)\ + ONE_PIXEL_LR(table_nbr,13)\ + ONE_PIXEL_LR(table_nbr,14)\ + ONE_PIXEL_LR(table_nbr,15)\ + /* end unroll loop */\ + /* If error in the top left 2x2 pixel area is already larger than the best error, and */\ + /* The same is true for the bottom right 2x2 pixel area, this combination of table and color */\ + /* can never be part of an optimal solution and therefore we do not need to test the other */\ + /* two 2x2 pixel areas */\ + if((err_this_table_upper err[1])\ + err[0] = err[1];\ + if(err[2] > err[3])\ + err[2] = err[3];\ + if(err[0] > err[2])\ + err[0] = err[2];\ + err_this_table_upper+=err[0];\ + err_this_table_left+=err[0];\ + +#define ONE_PIXEL_UR_PERCEP(table_nbr,xx)\ + orig[0]=block_2x2[xx*4];\ + orig[1]=block_2x2[xx*4+1];\ + orig[2]=block_2x2[xx*4+2];\ + /* unrolled loop for(index=0;index<4;index++)*/\ + err[0]=precalc_err_UR_RG[table_nbr*4*4+(xx-4)*4+0] + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*square_table[approx[0]-orig[2]];\ + err[1]=precalc_err_UR_RG[table_nbr*4*4+(xx-4)*4+1] + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*square_table[approx[1]-orig[2]];\ + err[2]=precalc_err_UR_RG[table_nbr*4*4+(xx-4)*4+2] + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*square_table[approx[2]-orig[2]];\ + err[3]=precalc_err_UR_RG[table_nbr*4*4+(xx-4)*4+3] + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*square_table[approx[3]-orig[2]];\ + /* end unrolled loop */\ + if(err[0] > err[1])\ + err[0] = err[1];\ + if(err[2] > err[3])\ + err[2] = err[3];\ + if(err[0] > err[2])\ + err[0] = err[2];\ + err_this_table_upper+=err[0];\ + err_this_table_right+=err[0]; + +#define ONE_PIXEL_LL_PERCEP(table_nbr,xx)\ + orig[0]=block_2x2[xx*4];\ + orig[1]=block_2x2[xx*4+1];\ + orig[2]=block_2x2[xx*4+2];\ + /* unrolled loop for(index=0;index<4;index++)*/\ + err[0]=precalc_err_LL_RG[table_nbr*4*4+(xx-8)*4+0] + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*square_table[approx[0]-orig[2]];\ + err[1]=precalc_err_LL_RG[table_nbr*4*4+(xx-8)*4+1] + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*square_table[approx[1]-orig[2]];\ + err[2]=precalc_err_LL_RG[table_nbr*4*4+(xx-8)*4+2] + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*square_table[approx[2]-orig[2]];\ + err[3]=precalc_err_LL_RG[table_nbr*4*4+(xx-8)*4+3] + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*square_table[approx[3]-orig[2]];\ + /* end unrolled loop*/\ + if(err[0] > err[1])\ + err[0] = err[1];\ + if(err[2] > err[3])\ + err[2] = err[3];\ + if(err[0] > err[2])\ + err[0] = err[2];\ + err_this_table_lower+=err[0];\ + err_this_table_left+=err[0];\ + +#define ONE_PIXEL_LR_PERCEP(table_nbr,xx)\ + orig[0]=block_2x2[xx*4];\ + orig[1]=block_2x2[xx*4+1];\ + orig[2]=block_2x2[xx*4+2];\ + /* unrolled loop for(index=0;index<4;index++)*/\ + err[0]=precalc_err_LR_RG[table_nbr*4*4+(xx-12)*4+0] + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*square_table[approx[0]-orig[2]];\ + err[1]=precalc_err_LR_RG[table_nbr*4*4+(xx-12)*4+1] + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*square_table[approx[1]-orig[2]];\ + err[2]=precalc_err_LR_RG[table_nbr*4*4+(xx-12)*4+2] + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*square_table[approx[2]-orig[2]];\ + err[3]=precalc_err_LR_RG[table_nbr*4*4+(xx-12)*4+3] + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*square_table[approx[3]-orig[2]];\ + /* end unrolled loop*/\ + if(err[0] > err[1])\ + err[0] = err[1];\ + if(err[2] > err[3])\ + err[2] = err[3];\ + if(err[0] > err[2])\ + err[0] = err[2];\ + err_this_table_lower+=err[0];\ + err_this_table_right+=err[0];\ + +#define ONE_TABLE_3_PERCEP(table_nbr)\ + err_this_table_upper = 0;\ + err_this_table_lower = 0;\ + err_this_table_left = 0;\ + err_this_table_right = 0;\ + approx[0]=clamp_table_plus_255[color_quant1[2]+compressParamsFast[table_nbr*4+0]+255];\ + approx[1]=clamp_table_plus_255[color_quant1[2]+compressParamsFast[table_nbr*4+1]+255];\ + approx[2]=clamp_table_plus_255[color_quant1[2]+compressParamsFast[table_nbr*4+2]+255];\ + approx[3]=clamp_table_plus_255[color_quant1[2]+compressParamsFast[table_nbr*4+3]+255];\ + /* unroll loop for(xx=0; xx<4; xx++) */\ + ONE_PIXEL_UL_PERCEP(table_nbr,0)\ + ONE_PIXEL_UL_PERCEP(table_nbr,1)\ + ONE_PIXEL_UL_PERCEP(table_nbr,2)\ + ONE_PIXEL_UL_PERCEP(table_nbr,3)\ + /* end unroll loop */\ + /* unroll loop for(xx=4; xx<8; xx++) */\ + ONE_PIXEL_LR_PERCEP(table_nbr,12)\ + ONE_PIXEL_LR_PERCEP(table_nbr,13)\ + ONE_PIXEL_LR_PERCEP(table_nbr,14)\ + ONE_PIXEL_LR_PERCEP(table_nbr,15)\ + /* end unroll loop */\ + /* If error in the top left 2x2 pixel area is already larger than the best error, and */\ + /* The same is true for the bottom right 2x2 pixel area, this combination of table and color */\ + /* can never be part of an optimal solution and therefore we do not need to test the other */\ + /* two 2x2 pixel areas */\ + if((err_this_table_upper> 5; + bytediff[1] = bytediff[1] >> 5; + bytediff[2] = bytediff[2] >> 5; + best_enc_color2[0]= best_enc_color1[0] + bytediff[0]; + best_enc_color2[1]= best_enc_color1[1] + bytediff[1]; + best_enc_color2[2]= best_enc_color1[2] + bytediff[2]; + + // allocate memory for errors: + err_upper = (unsigned int*) malloc(32*32*32*sizeof(unsigned int)); + if(!err_upper){printf("Out of memory allocating \n");exit(1);} + err_lower = (unsigned int*) malloc(32*32*32*sizeof(unsigned int)); + if(!err_lower){printf("Out of memory allocating \n");exit(1);} + err_left = (unsigned int*) malloc(32*32*32*sizeof(unsigned int)); + if(!err_left){printf("Out of memory allocating \n");exit(1);} + err_right = (unsigned int*) malloc(32*32*32*sizeof(unsigned int)); + if(!err_right){printf("Out of memory allocating \n");exit(1);} + + int q; + // Calculate all errors + for(enc_color1[0]=0; enc_color1[0]<32; enc_color1[0]++) + { + color_quant1[0] = enc_color1[0] << 3 | (enc_color1[0] >> 2); + if(precompute_3bittable_all_subblocksR_with_test_perceptual1000(block_2x2, color_quant1, precalc_err_UL_R, precalc_err_UR_R, precalc_err_LL_R, precalc_err_LR_R, best_error_so_far)) + { + for(enc_color1[1]=0; enc_color1[1]<32; enc_color1[1]++) + { + color_quant1[1] = enc_color1[1] << 3 | (enc_color1[1] >> 2); + if(precompute_3bittable_all_subblocksRG_withtest_perceptual1000(block_2x2, color_quant1, precalc_err_UL_R, precalc_err_UR_R, precalc_err_LL_R, precalc_err_LR_R, precalc_err_UL_RG, precalc_err_UR_RG, precalc_err_LL_RG, precalc_err_LR_RG, best_error_so_far)) + { + for(enc_color1[2]=0; enc_color1[2]<32; enc_color1[2]++) + { + color_quant1[2] = enc_color1[2] << 3 | (enc_color1[2] >> 2); + tryalltables_3bittable_all_subblocks_using_precalc_perceptual1000(block_2x2, color_quant1, precalc_err_UL_RG, precalc_err_UR_RG, precalc_err_LL_RG, precalc_err_LR_RG, err_upper[32*32*enc_color1[0]+32*enc_color1[1]+enc_color1[2]], err_lower[32*32*enc_color1[0]+32*enc_color1[1]+enc_color1[2]], err_left[32*32*enc_color1[0]+32*enc_color1[1]+enc_color1[2]], err_right[32*32*enc_color1[0]+32*enc_color1[1]+enc_color1[2]], best_error_so_far); + } + } + else + { + for(q=0;q<32;q++) + { + err_upper[32*32*enc_color1[0]+32*enc_color1[1]+q] = MAXERR1000; + err_lower[32*32*enc_color1[0]+32*enc_color1[1]+q] = MAXERR1000; + err_left[32*32*enc_color1[0]+32*enc_color1[1]+q] = MAXERR1000; + err_right[32*32*enc_color1[0]+32*enc_color1[1]+q] = MAXERR1000; + } + } + } + } + else + { + for(q=0;q<32*32;q++) + { + err_upper[32*32*enc_color1[0]+q] = MAXERR1000; + err_lower[32*32*enc_color1[0]+q] = MAXERR1000; + err_left[32*32*enc_color1[0]+q] = MAXERR1000; + err_right[32*32*enc_color1[0]+q] = MAXERR1000; + } + } + } + for(enc_color1[0]=0; enc_color1[0]<32; enc_color1[0]++) + { + for(enc_color1[1]=0; enc_color1[1]<32; enc_color1[1]++) + { + for(enc_color1[2]=0; enc_color1[2]<4; enc_color1[2]++) + { + error_lying = err_upper[32*32*enc_color1[0]+32*enc_color1[1]+enc_color1[2]]; + error_standing = err_left[32*32*enc_color1[0]+32*enc_color1[1]+enc_color1[2]]; + if(error_lying < best_error_so_far || error_standing < best_error_so_far) + { + for(enc_color2[0]=JAS_MAX(0,enc_color1[0]-4); enc_color2[0]> 2); + color_quant1[1] = best_enc_color1[1] << 3 | (best_enc_color1[1] >> 2); + color_quant1[2] = best_enc_color1[2] << 3 | (best_enc_color1[2] >> 2); + if(best_flip == 0) + tryalltables_3bittable2x4percep1000(img,width,height,startx,starty,color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + else + tryalltables_3bittable4x2percep1000(img,width,height,startx,starty,color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + + color_quant2[0] = best_enc_color2[0] << 3 | (best_enc_color2[0] >> 2); + color_quant2[1] = best_enc_color2[1] << 3 | (best_enc_color2[1] >> 2); + color_quant2[2] = best_enc_color2[2] << 3 | (best_enc_color2[2] >> 2); + if(best_flip == 0) + tryalltables_3bittable2x4percep1000(img,width,height,startx+2,starty,color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + else + tryalltables_3bittable4x2percep1000(img,width,height,startx,starty+2,color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + diff[0] = best_enc_color2[0]-best_enc_color1[0]; + diff[1] = best_enc_color2[1]-best_enc_color1[1]; + diff[2] = best_enc_color2[2]-best_enc_color1[2]; + + // ETC1_RGB8_OES: + // + // a) bit layout in bits 63 through 32 if diffbit = 0 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | base col2 | base col1 | base col2 | base col1 | base col2 | table | table |diff|flip| + // | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // b) bit layout in bits 63 through 32 if diffbit = 1 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | dcol 2 | base col1 | dcol 2 | base col 1 | dcol 2 | table | table |diff|flip| + // | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // c) bit layout in bits 31 through 0 (in both cases) + // + // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // -------------------------------------------------------------------------------------------------- + // | most significant pixel index bits | least significant pixel index bits | + // | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a | + // -------------------------------------------------------------------------------------------------- + + diffbit = 1; + compressed1 = 0; + PUTBITSHIGH( compressed1, diffbit, 1, 33); + PUTBITSHIGH( compressed1, best_enc_color1[0], 5, 63); + PUTBITSHIGH( compressed1, best_enc_color1[1], 5, 55); + PUTBITSHIGH( compressed1, best_enc_color1[2], 5, 47); + PUTBITSHIGH( compressed1, diff[0], 3, 58); + PUTBITSHIGH( compressed1, diff[1], 3, 50); + PUTBITSHIGH( compressed1, diff[2], 3, 42); + PUTBITSHIGH( compressed1, best_table1, 3, 39); + PUTBITSHIGH( compressed1, best_table2, 3, 36); + PUTBITSHIGH( compressed1, best_flip, 1, 32); + + if(best_flip == 0) + { + compressed2 = 0; + PUTBITS( compressed2, (best_pixel_indices1_MSB ), 8, 23); + PUTBITS( compressed2, (best_pixel_indices2_MSB ), 8, 31); + PUTBITS( compressed2, (best_pixel_indices1_LSB ), 8, 7); + PUTBITS( compressed2, (best_pixel_indices2_LSB ), 8, 15); + } + else + { + best_pixel_indices1_MSB |= (best_pixel_indices2_MSB << 2); + best_pixel_indices1_LSB |= (best_pixel_indices2_LSB << 2); + compressed2 = ((best_pixel_indices1_MSB & 0xffff) << 16) | (best_pixel_indices1_LSB & 0xffff); + } + return best_error_using_diff_mode; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Compresses the differential mode exhaustively. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int compressBlockDifferentialExhaustive(uint8 *img,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2, unsigned int previous_best_err) +{ + unsigned int best_err_norm_diff = 255*255*16*3; + unsigned int best_err_norm_444 = 255*255*16*3; + unsigned int best_err_flip_diff = 255*255*16*3; + unsigned int best_err_flip_444 = 255*255*16*3; + uint8 color_quant1[3], color_quant2[3]; + + int enc_color1[3], enc_color2[3], diff[3]; + int best_enc_color1[3], best_enc_color2[3]; + + int min_error=255*255*8*3; + unsigned int best_pixel_indices1_MSB=0; + unsigned int best_pixel_indices1_LSB=0; + unsigned int best_pixel_indices2_MSB=0; + unsigned int best_pixel_indices2_LSB=0; + unsigned int pixel_indices1_MSB=0; + unsigned int pixel_indices1_LSB=0; + unsigned int pixel_indices2_MSB=0; + + unsigned int *err_upper, *err_lower; + unsigned int *err_left, *err_right; + + unsigned int pixel_indices2_LSB=0; + + unsigned int table1=0, table2=0; + unsigned int best_table1=0, best_table2=0; + + unsigned int precalc_err_UL_R[8*4*4]; + unsigned int precalc_err_UR_R[8*4*4]; + unsigned int precalc_err_LL_R[8*4*4]; + unsigned int precalc_err_LR_R[8*4*4]; + + unsigned int precalc_err_UL_RG[8*4*4]; + unsigned int precalc_err_UR_RG[8*4*4]; + unsigned int precalc_err_LL_RG[8*4*4]; + unsigned int precalc_err_LR_RG[8*4*4]; + + int diffbit; + uint8 block_2x2[4*4*4]; + + unsigned int error, error_lying, error_standing, best_err, total_best_err; + unsigned int *err_lower_adr; + int best_flip; + unsigned int *err_right_adr; + + int xx,yy,count = 0; + + // Reshuffle pixels so that the top left 2x2 pixels arrive first, then the top right 2x2 pixels etc. Also put use 4 bytes per pixel to make it 32-word aligned. + for(xx = 0; xx<2; xx++) + { + for(yy=0; yy<2; yy++) + { + block_2x2[(count)*4] = img[((starty+yy)*width+(startx+xx))*3]; + block_2x2[(count)*4+1] = img[((starty+yy)*width+(startx+xx))*3+1]; + block_2x2[(count)*4+2] = img[((starty+yy)*width+(startx+xx))*3+2]; + block_2x2[(count)*4+3] = 0; + count++; + } + } + for(xx = 2; xx<4; xx++) + { + for(yy=0; yy<2; yy++) + { + block_2x2[(count)*4] = img[((starty+yy)*width+(startx+xx))*3]; + block_2x2[(count)*4+1] = img[((starty+yy)*width+(startx+xx))*3+1]; + block_2x2[(count)*4+2] = img[((starty+yy)*width+(startx+xx))*3+2]; + block_2x2[(count)*4+3] = 0; + count++; + } + } + for(xx = 0; xx<2; xx++) + { + for(yy=2; yy<4; yy++) + { + block_2x2[(count)*4] = img[((starty+yy)*width+(startx+xx))*3]; + block_2x2[(count)*4+1] = img[((starty+yy)*width+(startx+xx))*3+1]; + block_2x2[(count)*4+2] = img[((starty+yy)*width+(startx+xx))*3+2]; + block_2x2[(count)*4+3] = 0; + count++; + } + } + for(xx = 2; xx<4; xx++) + { + for(yy=2; yy<4; yy++) + { + block_2x2[(count)*4] = img[((starty+yy)*width+(startx+xx))*3]; + block_2x2[(count)*4+1] = img[((starty+yy)*width+(startx+xx))*3+1]; + block_2x2[(count)*4+2] = img[((starty+yy)*width+(startx+xx))*3+2]; + block_2x2[(count)*4+3] = 0; + count++; + } + } + + + unsigned int test1, test2; + best_err = (unsigned int)compressBlockOnlyDiffFlipAverage(img, width, height, startx, starty, test1, test2, best_enc_color1, best_enc_color2, best_flip); + if(previous_best_err < best_err) + total_best_err = previous_best_err; + else + total_best_err = best_err; + + // allocate memory for errors: + err_upper = (unsigned int*) malloc(32*32*32*sizeof(unsigned int)); + if(!err_upper){printf("Out of memory allocating \n");exit(1);} + err_lower = (unsigned int*) malloc(32*32*32*sizeof(unsigned int)); + if(!err_lower){printf("Out of memory allocating \n");exit(1);} + err_left = (unsigned int*) malloc(32*32*32*sizeof(unsigned int)); + if(!err_left){printf("Out of memory allocating \n");exit(1);} + err_right = (unsigned int*) malloc(32*32*32*sizeof(unsigned int)); + if(!err_right){printf("Out of memory allocating \n");exit(1);} + + int q; + // Calculate all errors + for(enc_color1[0]=0; enc_color1[0]<32; enc_color1[0]++) + { + color_quant1[0] = enc_color1[0] << 3 | (enc_color1[0] >> 2); + if(precompute_3bittable_all_subblocksR_with_test(block_2x2, color_quant1, precalc_err_UL_R, precalc_err_UR_R, precalc_err_LL_R, precalc_err_LR_R, total_best_err)) + { + for(enc_color1[1]=0; enc_color1[1]<32; enc_color1[1]++) + { + color_quant1[1] = enc_color1[1] << 3 | (enc_color1[1] >> 2); + if(precompute_3bittable_all_subblocksRG_withtest(block_2x2, color_quant1, precalc_err_UL_R, precalc_err_UR_R, precalc_err_LL_R, precalc_err_LR_R, precalc_err_UL_RG, precalc_err_UR_RG, precalc_err_LL_RG, precalc_err_LR_RG, total_best_err)) + { + for(enc_color1[2]=0; enc_color1[2]<32; enc_color1[2]++) + { + color_quant1[2] = enc_color1[2] << 3 | (enc_color1[2] >> 2); + tryalltables_3bittable_all_subblocks_using_precalc(block_2x2, color_quant1, precalc_err_UL_RG, precalc_err_UR_RG, precalc_err_LL_RG, precalc_err_LR_RG, err_upper[32*32*enc_color1[0]+32*enc_color1[1]+enc_color1[2]], err_lower[32*32*enc_color1[0]+32*enc_color1[1]+enc_color1[2]], err_left[32*32*enc_color1[0]+32*enc_color1[1]+enc_color1[2]], err_right[32*32*enc_color1[0]+32*enc_color1[1]+enc_color1[2]], total_best_err); + } + } + else + { + for(q=0;q<32;q++) + { + err_upper[32*32*enc_color1[0]+32*enc_color1[1]+q] = 255*255*16*3; + err_lower[32*32*enc_color1[0]+32*enc_color1[1]+q] = 255*255*16*3; + err_left[32*32*enc_color1[0]+32*enc_color1[1]+q] = 255*255*16*3; + err_right[32*32*enc_color1[0]+32*enc_color1[1]+q] = 255*255*16*3; + } + } + } + } + else + { + for(q=0;q<32*32;q++) + { + err_upper[32*32*enc_color1[0]+q] = 255*255*16*3; + err_lower[32*32*enc_color1[0]+q] = 255*255*16*3; + err_left[32*32*enc_color1[0]+q] = 255*255*16*3; + err_right[32*32*enc_color1[0]+q] = 255*255*16*3; + } + } + } + + for(enc_color1[0]=0; enc_color1[0]<32; enc_color1[0]++) + { + for(enc_color1[1]=0; enc_color1[1]<32; enc_color1[1]++) + { + for(enc_color1[2]=0; enc_color1[2]<4; enc_color1[2]++) + { + error_lying = err_upper[32*32*enc_color1[0]+32*enc_color1[1]+enc_color1[2]]; + error_standing = err_left[32*32*enc_color1[0]+32*enc_color1[1]+enc_color1[2]]; + if(error_lying < total_best_err || error_standing < total_best_err) + { + for(enc_color2[0]=JAS_MAX(0,enc_color1[0]-4); enc_color2[0]> 2); + color_quant1[1] = best_enc_color1[1] << 3 | (best_enc_color1[1] >> 2); + color_quant1[2] = best_enc_color1[2] << 3 | (best_enc_color1[2] >> 2); + if(best_flip == 0) + tryalltables_3bittable2x4(img,width,height,startx,starty,color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + else + tryalltables_3bittable4x2(img,width,height,startx,starty,color_quant1,best_table1,best_pixel_indices1_MSB, best_pixel_indices1_LSB); + + color_quant2[0] = best_enc_color2[0] << 3 | (best_enc_color2[0] >> 2); + color_quant2[1] = best_enc_color2[1] << 3 | (best_enc_color2[1] >> 2); + color_quant2[2] = best_enc_color2[2] << 3 | (best_enc_color2[2] >> 2); + if(best_flip == 0) + tryalltables_3bittable2x4(img,width,height,startx+2,starty,color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + else + tryalltables_3bittable4x2(img,width,height,startx,starty+2,color_quant2,best_table2,best_pixel_indices2_MSB, best_pixel_indices2_LSB); + + diff[0] = best_enc_color2[0]-best_enc_color1[0]; + diff[1] = best_enc_color2[1]-best_enc_color1[1]; + diff[2] = best_enc_color2[2]-best_enc_color1[2]; + + // ETC1_RGB8_OES: + // + // a) bit layout in bits 63 through 32 if diffbit = 0 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | base col2 | base col1 | base col2 | base col1 | base col2 | table | table |diff|flip| + // | R1 (4bits)| R2 (4bits)| G1 (4bits)| G2 (4bits)| B1 (4bits)| B2 (4bits)| cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // b) bit layout in bits 63 through 32 if diffbit = 1 + // + // 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 + // --------------------------------------------------------------------------------------------------- + // | base col1 | dcol 2 | base col1 | dcol 2 | base col 1 | dcol 2 | table | table |diff|flip| + // | R1' (5 bits) | dR2 | G1' (5 bits) | dG2 | B1' (5 bits) | dB2 | cw 1 | cw 2 |bit |bit | + // --------------------------------------------------------------------------------------------------- + // + // c) bit layout in bits 31 through 0 (in both cases) + // + // 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 + // -------------------------------------------------------------------------------------------------- + // | most significant pixel index bits | least significant pixel index bits | + // | p| o| n| m| l| k| j| i| h| g| f| e| d| c| b| a| p| o| n| m| l| k| j| i| h| g| f| e| d| c | b | a | + // -------------------------------------------------------------------------------------------------- + + diffbit = 1; + compressed1 = 0; + PUTBITSHIGH( compressed1, diffbit, 1, 33); + PUTBITSHIGH( compressed1, best_enc_color1[0], 5, 63); + PUTBITSHIGH( compressed1, best_enc_color1[1], 5, 55); + PUTBITSHIGH( compressed1, best_enc_color1[2], 5, 47); + PUTBITSHIGH( compressed1, diff[0], 3, 58); + PUTBITSHIGH( compressed1, diff[1], 3, 50); + PUTBITSHIGH( compressed1, diff[2], 3, 42); + PUTBITSHIGH( compressed1, best_table1, 3, 39); + PUTBITSHIGH( compressed1, best_table2, 3, 36); + PUTBITSHIGH( compressed1, best_flip, 1, 32); + + if(best_flip == 0) + { + compressed2 = 0; + PUTBITS( compressed2, (best_pixel_indices1_MSB ), 8, 23); + PUTBITS( compressed2, (best_pixel_indices2_MSB ), 8, 31); + PUTBITS( compressed2, (best_pixel_indices1_LSB ), 8, 7); + PUTBITS( compressed2, (best_pixel_indices2_LSB ), 8, 15); + } + else + { + best_pixel_indices1_MSB |= (best_pixel_indices2_MSB << 2); + best_pixel_indices1_LSB |= (best_pixel_indices2_LSB << 2); + compressed2 = ((best_pixel_indices1_MSB & 0xffff) << 16) | (best_pixel_indices1_LSB & 0xffff); + } + return best_err; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// This function uses real exhaustive search for the planar mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void compressBlockPlanar57ExhaustivePerceptual(uint8 *img, int width,int height,int startx,int starty, unsigned int &compressed57_1, unsigned int &compressed57_2, unsigned int best_error_sofar, unsigned int best_error_planar_red, unsigned int best_error_planar_green, unsigned int best_error_planar_blue) +{ + int colorO_enc[3], colorH_enc[3], colorV_enc[3]; + int best_colorO_enc[3], best_colorH_enc[3], best_colorV_enc[3]; + + unsigned int error; + unsigned int best_error; + unsigned int lowest_possible_error; + unsigned int best_error_red_sofar; + unsigned int best_error_green_sofar; + unsigned int best_error_blue_sofar; + unsigned int BBBtable[128*128]; + unsigned int CCCtable[128*128]; + + uint8 block[4*4*4]; + + // Use 4 bytes per pixel to make it 32-word aligned. + int count = 0; + int xx, yy; + for(yy=0; yy<4; yy++) + { + for(xx = 0; xx<4; xx++) + { + block[(count)*4] = img[((starty+yy)*width+(startx+xx))*3]; + block[(count)*4+1] = img[((starty+yy)*width+(startx+xx))*3+1]; + block[(count)*4+2] = img[((starty+yy)*width+(startx+xx))*3+2]; + block[(count)*4+3] = 0; + count++; + } + } + + // The task is to calculate the sum of the error over the entire area of the block. + // + // The block can be partitioned into: O A A A + // B D D C + // B D C D + // B C D D + // where the error in + // O only depends on colorO + // A only depends on colorO and colorH + // B only depends on colorO and colorV + // C only depends on colorH and colorV + // D depends on all three (colorO, colorH and colorV) + // + // Note that B can be precalculated for all combinations of colorO and colorV + // and the precalculated values can be used instead of calculating it in the inner loop. + // The same applies to C. + // + // In the code below, the squared error over O A A A is calculated and stored in lowest_possible_error + + // Precalc BBB errors + for(colorO_enc[0] = 0; colorO_enc[0]<64; colorO_enc[0]++) + { + for(colorV_enc[0] = 0; colorV_enc[0]<64; colorV_enc[0]++) + { + BBBtable[colorO_enc[0]*64+colorV_enc[0]] = PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*calcBBBred(block, colorO_enc[0], colorV_enc[0]); + } + } + // Precalc CCC errors + for(colorH_enc[0] = 0; colorH_enc[0]<64; colorH_enc[0]++) + { + for(colorV_enc[0] = 0; colorV_enc[0]<64; colorV_enc[0]++) + { + CCCtable[colorH_enc[0]*64+colorV_enc[0]] = PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*calcCCCred(block, colorH_enc[0], colorV_enc[0]); + } + } + best_error = MAXERR1000; + + best_error_red_sofar = JAS_MIN(best_error_planar_red, best_error_sofar); + for(colorO_enc[0] = 0; colorO_enc[0]<64; colorO_enc[0]++) + { + for(colorH_enc[0] = 0; colorH_enc[0]<64; colorH_enc[0]++) + { + lowest_possible_error = calcLowestPossibleRedOHperceptual(block, colorO_enc[0], colorH_enc[0], best_error_red_sofar); + if(lowest_possible_error <= best_error_red_sofar) + { + for(colorV_enc[0] = 0; colorV_enc[0]<64; colorV_enc[0]++) + { + error = calcErrorPlanarOnlyRedPerceptual(block, colorO_enc[0], colorH_enc[0], colorV_enc[0], lowest_possible_error, BBBtable[colorO_enc[0]*64+colorV_enc[0]], CCCtable[colorH_enc[0]*64+colorV_enc[0]], best_error_red_sofar); + if(error < best_error) + { + best_error = error; + best_colorO_enc[0] = colorO_enc[0]; + best_colorH_enc[0] = colorH_enc[0]; + best_colorV_enc[0] = colorV_enc[0]; + } + } + } + } + } + + if(best_error < best_error_planar_red) + best_error_planar_red = best_error; + + if(best_error_planar_red > best_error_sofar) + { + // The red component in itself is already bigger than the previously best value ---- we can give up. + // use the dummy color black for all colors and report that the errors for the different color components are infinite + best_error_planar_green = MAXERR1000; + best_error_planar_blue = MAXERR1000; + compressed57_1 = 0; + compressed57_2 = 0; + return; + } + + // The task is to calculate the sum of the error over the entire area of the block. + // + // The block can be partitioned into: O A A A + // B D D C + // B D C D + // B C D D + // where the error in + // O only depends on colorO + // A only depends on colorO and colorH + // B only depends on colorO and colorV + // C only depends on colorH and colorV + // D depends on all three (colorO, colorH and colorV) + // + // Note that B can be precalculated for all combinations of colorO and colorV + // and the precalculated values can be used instead of calculating it in the inner loop. + // The same applies to C. + // + // In the code below, the squared error over O A A A is calculated and store in lowest_possible_error + + // Precalc BBB errors + for(colorO_enc[1] = 0; colorO_enc[1]<128; colorO_enc[1]++) + { + for(colorV_enc[1] = 0; colorV_enc[1]<128; colorV_enc[1]++) + { + BBBtable[colorO_enc[1]*128+colorV_enc[1]] = PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000*calcBBBgreen(block, colorO_enc[1], colorV_enc[1]); + } + } + // Precalc CCC errors + for(colorH_enc[1] = 0; colorH_enc[1]<128; colorH_enc[1]++) + { + for(colorV_enc[1] = 0; colorV_enc[1]<128; colorV_enc[1]++) + { + CCCtable[colorH_enc[1]*128+colorV_enc[1]] = PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000*calcCCCgreen(block, colorH_enc[1], colorV_enc[1]); + } + } + best_error = MAXERR1000; + best_error_green_sofar = JAS_MIN(best_error_planar_green, best_error_sofar); + for(colorO_enc[1] = 0; colorO_enc[1]<128; colorO_enc[1]++) + { + for(colorH_enc[1] = 0; colorH_enc[1]<128; colorH_enc[1]++) + { + lowest_possible_error = calcLowestPossibleGreenOHperceptual(block, colorO_enc[1], colorH_enc[1], best_error_green_sofar); + if(lowest_possible_error <= best_error_green_sofar) + { + for(colorV_enc[1] = 0; colorV_enc[1]<128; colorV_enc[1]++) + { + error = calcErrorPlanarOnlyGreenPerceptual(block, colorO_enc[1], colorH_enc[1], colorV_enc[1], lowest_possible_error, BBBtable[colorO_enc[1]*128+colorV_enc[1]], CCCtable[colorH_enc[1]*128+colorV_enc[1]], best_error_green_sofar); + if(error < best_error) + { + best_error = error; + best_colorO_enc[1] = colorO_enc[1]; + best_colorH_enc[1] = colorH_enc[1]; + best_colorV_enc[1] = colorV_enc[1]; + } + } + } + } + } + + if(best_error < best_error_planar_green) + best_error_planar_green = best_error; + + if(best_error_planar_red + best_error_planar_green > best_error_sofar) + { + // The red component in itself is already bigger than the previously best value ---- we can give up. + // use the dummy color black for all colors and report that the errors for the different color components are infinite + best_error_planar_blue = MAXERR1000; + compressed57_1 = 0; + compressed57_2 = 0; + return; + } + + // The task is to calculate the sum of the error over the entire area of the block. + // + // The block can be partitioned into: O A A A + // B D D C + // B D C D + // B C D D + // where the error in + // O only depends on colorO + // A only depends on colorO and colorH + // B only depends on colorO and colorV + // C only depends on colorH and colorV + // D depends on all three (colorO, colorH and colorV) + // + // Note that B can be precalculated for all combinations of colorO and colorV + // and the precalculated values can be used instead of calculating it in the inner loop. + // The same applies to C. + // + // In the code below, the squared error over O A A A is calculated and store in lowest_possible_error + + // Precalc BBB errors + for(colorO_enc[2] = 0; colorO_enc[2]<64; colorO_enc[2]++) + { + for(colorV_enc[2] = 0; colorV_enc[2]<64; colorV_enc[2]++) + { + BBBtable[colorO_enc[2]*64+colorV_enc[2]] = calcBBBbluePerceptual(block, colorO_enc[2], colorV_enc[2]); + } + } + // Precalc CCC errors + for(colorH_enc[2] = 0; colorH_enc[2]<64; colorH_enc[2]++) + { + for(colorV_enc[2] = 0; colorV_enc[2]<64; colorV_enc[2]++) + { + CCCtable[colorH_enc[2]*64+colorV_enc[2]] = calcCCCbluePerceptual(block, colorH_enc[2], colorV_enc[2]); + } + } + best_error = MAXERR1000; + best_error_blue_sofar = JAS_MIN(best_error_planar_blue, best_error_sofar); + for(colorO_enc[2] = 0; colorO_enc[2]<64; colorO_enc[2]++) + { + for(colorH_enc[2] = 0; colorH_enc[2]<64; colorH_enc[2]++) + { + lowest_possible_error = calcLowestPossibleBlueOHperceptual(block, colorO_enc[2], colorH_enc[2], best_error_blue_sofar); + if(lowest_possible_error <= best_error_blue_sofar) + { + for(colorV_enc[2] = 0; colorV_enc[2]<64; colorV_enc[2]++) + { + error = calcErrorPlanarOnlyBluePerceptual(block, colorO_enc[2], colorH_enc[2], colorV_enc[2], lowest_possible_error, BBBtable[colorO_enc[2]*64+colorV_enc[2]], CCCtable[colorH_enc[2]*64+colorV_enc[2]], best_error_blue_sofar); + if(error < best_error) + { + best_error = error; + best_colorO_enc[2] = colorO_enc[2]; + best_colorH_enc[2] = colorH_enc[2]; + best_colorV_enc[2] = colorV_enc[2]; + } + } + } + } + } + + if(best_error < best_error_planar_blue) + best_error_planar_blue = best_error; + + compressed57_1 = 0; + compressed57_2 = 0; + PUTBITSHIGH( compressed57_1, best_colorO_enc[0], 6, 63); + PUTBITSHIGH( compressed57_1, best_colorO_enc[1], 7, 57); + PUTBITSHIGH( compressed57_1, best_colorO_enc[2], 6, 50); + PUTBITSHIGH( compressed57_1, best_colorH_enc[0], 6, 44); + PUTBITSHIGH( compressed57_1, best_colorH_enc[1], 7, 38); + PUTBITS( compressed57_2, best_colorH_enc[2], 6, 31); + PUTBITS( compressed57_2, best_colorV_enc[0], 6, 25); + PUTBITS( compressed57_2, best_colorV_enc[1], 7, 19); + PUTBITS( compressed57_2, best_colorV_enc[2], 6, 12); + +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// This function uses real exhaustive search for the planar mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void compressBlockPlanar57Exhaustive(uint8 *img, int width,int height,int startx,int starty, unsigned int &compressed57_1, unsigned int &compressed57_2, unsigned int best_error_sofar, unsigned int best_error_red, unsigned int best_error_green, unsigned int best_error_blue) +{ + int colorO_enc[3], colorH_enc[3], colorV_enc[3]; + int best_colorO_enc[3], best_colorH_enc[3], best_colorV_enc[3]; + + unsigned int error; + unsigned int best_error; + unsigned int lowest_possible_error; + unsigned int best_error_red_sofar; + unsigned int best_error_green_sofar; + unsigned int best_error_blue_sofar; + unsigned int BBBtable[128*128]; + unsigned int CCCtable[128*128]; + + uint8 block[4*4*4]; + + // Use 4 bytes per pixel to make it 32-word aligned. + int count = 0; + int xx, yy; + for(yy=0; yy<4; yy++) + { + for(xx = 0; xx<4; xx++) + { + block[(count)*4] = img[((starty+yy)*width+(startx+xx))*3]; + block[(count)*4+1] = img[((starty+yy)*width+(startx+xx))*3+1]; + block[(count)*4+2] = img[((starty+yy)*width+(startx+xx))*3+2]; + block[(count)*4+3] = 0; + count++; + } + } + + // The task is to calculate the sum of the error over the entire area of the block. + // + // The block can be partitioned into: O A A A + // B D D C + // B D C D + // B C D D + // where the error in + // O only depends on colorO + // A only depends on colorO and colorH + // B only depends on colorO and colorV + // C only depends on colorH and colorV + // D depends on all three (colorO, colorH and colorV) + // + // Note that B can be precalculated for all combinations of colorO and colorV + // and the precalculated values can be used instead of calculating it in the inner loop. + // The same applies to C. + // + // In the code below, the squared error over O A A A is calculated and store in lowest_possible_error + + // Precalc BBB errors + for(colorO_enc[0] = 0; colorO_enc[0]<64; colorO_enc[0]++) + { + for(colorV_enc[0] = 0; colorV_enc[0]<64; colorV_enc[0]++) + { + BBBtable[colorO_enc[0]*64+colorV_enc[0]] = calcBBBred(block, colorO_enc[0], colorV_enc[0]); + } + } + // Precalc CCC errors + for(colorH_enc[0] = 0; colorH_enc[0]<64; colorH_enc[0]++) + { + for(colorV_enc[0] = 0; colorV_enc[0]<64; colorV_enc[0]++) + { + CCCtable[colorH_enc[0]*64+colorV_enc[0]] = calcCCCred(block, colorH_enc[0], colorV_enc[0]); + } + } + best_error = MAXERR1000; + best_error_red_sofar = JAS_MIN(best_error_red, best_error_sofar); + for(colorO_enc[0] = 0; colorO_enc[0]<64; colorO_enc[0]++) + { + for(colorH_enc[0] = 0; colorH_enc[0]<64; colorH_enc[0]++) + { + lowest_possible_error = calcLowestPossibleRedOH(block, colorO_enc[0], colorH_enc[0], best_error_red_sofar); + if(lowest_possible_error <= best_error_red_sofar) + { + for(colorV_enc[0] = 0; colorV_enc[0]<64; colorV_enc[0]++) + { + error = calcErrorPlanarOnlyRed(block, colorO_enc[0], colorH_enc[0], colorV_enc[0], lowest_possible_error, BBBtable[colorO_enc[0]*64+colorV_enc[0]], CCCtable[colorH_enc[0]*64+colorV_enc[0]], best_error_red_sofar); + if(error < best_error) + { + best_error = error; + best_colorO_enc[0] = colorO_enc[0]; + best_colorH_enc[0] = colorH_enc[0]; + best_colorV_enc[0] = colorV_enc[0]; + } + } + } + } + } + + // The task is to calculate the sum of the error over the entire area of the block. + // + // The block can be partitioned into: O A A A + // B D D C + // B D C D + // B C D D + // where the error in + // O only depends on colorO + // A only depends on colorO and colorH + // B only depends on colorO and colorV + // C only depends on colorH and colorV + // D depends on all three (colorO, colorH and colorV) + // + // Note that B can be precalculated for all combinations of colorO and colorV + // and the precalculated values can be used instead of calculating it in the inner loop. + // The same applies to C. + // + // In the code below, the squared error over O A A A is calculated and store in lowest_possible_error + + // Precalc BBB errors + for(colorO_enc[1] = 0; colorO_enc[1]<128; colorO_enc[1]++) + { + for(colorV_enc[1] = 0; colorV_enc[1]<128; colorV_enc[1]++) + { + BBBtable[colorO_enc[1]*128+colorV_enc[1]] = calcBBBgreen(block, colorO_enc[1], colorV_enc[1]); + } + } + // Precalc CCC errors + for(colorH_enc[1] = 0; colorH_enc[1]<128; colorH_enc[1]++) + { + for(colorV_enc[1] = 0; colorV_enc[1]<128; colorV_enc[1]++) + { + CCCtable[colorH_enc[1]*128+colorV_enc[1]] = calcCCCgreen(block, colorH_enc[1], colorV_enc[1]); + } + } + best_error = MAXERR1000; + best_error_green_sofar = JAS_MIN(best_error_green, best_error_sofar); + for(colorO_enc[1] = 0; colorO_enc[1]<128; colorO_enc[1]++) + { + for(colorH_enc[1] = 0; colorH_enc[1]<128; colorH_enc[1]++) + { + lowest_possible_error = calcLowestPossibleGreenOH(block, colorO_enc[1], colorH_enc[1], best_error_green_sofar); + if(lowest_possible_error <= best_error_green_sofar) + { + for(colorV_enc[1] = 0; colorV_enc[1]<128; colorV_enc[1]++) + { + error = calcErrorPlanarOnlyGreen(block, colorO_enc[1], colorH_enc[1], colorV_enc[1], lowest_possible_error, BBBtable[colorO_enc[1]*128+colorV_enc[1]], CCCtable[colorH_enc[1]*128+colorV_enc[1]], best_error_green_sofar); + if(error < best_error) + { + best_error = error; + best_colorO_enc[1] = colorO_enc[1]; + best_colorH_enc[1] = colorH_enc[1]; + best_colorV_enc[1] = colorV_enc[1]; + } + } + } + } + } + + // The task is to calculate the sum of the error over the entire area of the block. + // + // The block can be partitioned into: O A A A + // B D D C + // B D C D + // B C D D + // where the error in + // O only depends on colorO + // A only depends on colorO and colorH + // B only depends on colorO and colorV + // C only depends on colorH and colorV + // D depends on all three (colorO, colorH and colorV) + // + // Note that B can be precalculated for all combinations of colorO and colorV + // and the precalculated values can be used instead of calculating it in the inner loop. + // The same applies to C. + // + // In the code below, the squared error over O A A A is calculated and store in lowest_possible_error + + // Precalc BBB errors + for(colorO_enc[2] = 0; colorO_enc[2]<64; colorO_enc[2]++) + { + for(colorV_enc[2] = 0; colorV_enc[2]<64; colorV_enc[2]++) + { + BBBtable[colorO_enc[2]*64+colorV_enc[2]] = calcBBBblue(block, colorO_enc[2], colorV_enc[2]); + } + } + // Precalc CCC errors + for(colorH_enc[2] = 0; colorH_enc[2]<64; colorH_enc[2]++) + { + for(colorV_enc[2] = 0; colorV_enc[2]<64; colorV_enc[2]++) + { + CCCtable[colorH_enc[2]*64+colorV_enc[2]] = calcCCCblue(block, colorH_enc[2], colorV_enc[2]); + } + } + best_error = MAXERR1000; + best_error_blue_sofar = JAS_MIN(best_error_blue, best_error_sofar); + for(colorO_enc[2] = 0; colorO_enc[2]<64; colorO_enc[2]++) + { + for(colorH_enc[2] = 0; colorH_enc[2]<64; colorH_enc[2]++) + { + lowest_possible_error = calcLowestPossibleBlueOH(block, colorO_enc[2], colorH_enc[2], best_error_blue_sofar); + if(lowest_possible_error <= best_error_blue_sofar) + { + for(colorV_enc[2] = 0; colorV_enc[2]<64; colorV_enc[2]++) + { + error = calcErrorPlanarOnlyBlue(block, colorO_enc[2], colorH_enc[2], colorV_enc[2], lowest_possible_error, BBBtable[colorO_enc[2]*64+colorV_enc[2]], CCCtable[colorH_enc[2]*64+colorV_enc[2]], best_error_blue_sofar); + if(error < best_error) + { + best_error = error; + best_colorO_enc[2] = colorO_enc[2]; + best_colorH_enc[2] = colorH_enc[2]; + best_colorV_enc[2] = colorV_enc[2]; + } + } + } + } + } + + compressed57_1 = 0; + compressed57_2 = 0; + PUTBITSHIGH( compressed57_1, best_colorO_enc[0], 6, 63); + PUTBITSHIGH( compressed57_1, best_colorO_enc[1], 7, 57); + PUTBITSHIGH( compressed57_1, best_colorO_enc[2], 6, 50); + PUTBITSHIGH( compressed57_1, best_colorH_enc[0], 6, 44); + PUTBITSHIGH( compressed57_1, best_colorH_enc[1], 7, 38); + PUTBITS( compressed57_2, best_colorH_enc[2], 6, 31); + PUTBITS( compressed57_2, best_colorV_enc[0], 6, 25); + PUTBITS( compressed57_2, best_colorV_enc[1], 7, 19); + PUTBITS( compressed57_2, best_colorV_enc[2], 6, 12); + +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precalculates a table used in exhaustive compression of the T-mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void precalcError59T_col0_Rpercep1000(uint8* block, int colorRGB444_packed, unsigned int *precalc_err_col0_R) +{ + unsigned int block_error = 0, + best_block_error = MAXERR1000, + pixel_error, + best_pixel_error; + int diff; + uint8 color; + uint8 possible_colors[3]; + + color = ((colorRGB444_packed >> 8) & 0xf)*17; + + // Test all distances + for (uint8 d = 0; d < 8; d++) + { + + possible_colors[0] = CLAMP(0,color - table59T[d],255); + possible_colors[1] = CLAMP(0,color,255); + possible_colors[2] = CLAMP(0,color + table59T[d],255); + + // Loop block + for (int x = 0; x < 16; x++) + { + best_pixel_error = MAXERR1000; + + // Loop possible block colors + for (uint8 c = 0; c < 3; c++) + { + + diff = block[4*x + R] - CLAMP(0,possible_colors[c],255); + + pixel_error = PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*SQUARE(diff); + + // Choose best error + if (pixel_error < best_pixel_error) + best_pixel_error = pixel_error; + } + + precalc_err_col0_R[((colorRGB444_packed>>8)*8 + d)*16 + x] = (unsigned int) best_pixel_error; + } + + } + +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precalculates a table used in exhaustive compression of the T-mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void precalcError59T_col0_R(uint8* block, int colorRGB444_packed, unsigned int *precalc_err_col0_R) +{ + unsigned int block_error = 0, + best_block_error = MAXIMUM_ERROR, + pixel_error, + best_pixel_error; + int diff; + uint8 color; + uint8 possible_colors[3]; + + color = ((colorRGB444_packed >> 8) & 0xf)*17; + + // Test all distances + for (uint8 d = 0; d < 8; d++) + { + + possible_colors[0] = CLAMP(0,color - table59T[d],255); + possible_colors[1] = CLAMP(0,color,255); + possible_colors[2] = CLAMP(0,color + table59T[d],255); + + // Loop block + for (int x = 0; x < 16; x++) + { + best_pixel_error = MAXIMUM_ERROR; + + // Loop possible block colors + for (uint8 c = 0; c < 3; c++) + { + + diff = block[4*x + R] - CLAMP(0,possible_colors[c],255); + + pixel_error = SQUARE(diff); + + // Choose best error + if (pixel_error < best_pixel_error) + best_pixel_error = pixel_error; + } + precalc_err_col0_R[((colorRGB444_packed>>8)*8 + d)*16 + x] = (unsigned int) best_pixel_error; + } + } +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precalculates a table used in exhaustive compression of the T-mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void precalcError59T_col0_RGpercep1000(uint8* block, int colorRGB444_packed, unsigned int *precalc_err_col0_RG) +{ + unsigned int block_error = 0, + best_block_error = MAXERR1000, + pixel_error, + best_pixel_error; + int diff[3]; + uint8 color[3]; + uint8 possible_colors[3][2]; + + color[R] = ((colorRGB444_packed >> 8) & 0xf)*17; + color[G] = ((colorRGB444_packed >> 4) & 0xf)*17; + + // Test all distances + for (uint8 d = 0; d < 8; d++) + { + + possible_colors[0][R] = CLAMP(0,color[R] - table59T[d],255); + possible_colors[0][G] = CLAMP(0,color[G] - table59T[d],255); + + possible_colors[1][R] = CLAMP(0,color[R],255); + possible_colors[1][G] = CLAMP(0,color[G],255); + + possible_colors[2][R] = CLAMP(0,color[R] + table59T[d],255); + possible_colors[2][G] = CLAMP(0,color[G] + table59T[d],255); + + + + // Loop block + for (int x = 0; x < 16; x++) + { + best_pixel_error = MAXERR1000; + + // Loop possible block colors + for (uint8 c = 0; c < 3; c++) + { + + diff[R] = block[4*x + R] - CLAMP(0,possible_colors[c][R],255); + diff[G] = block[4*x + G] - CLAMP(0,possible_colors[c][G],255); + + pixel_error = PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*SQUARE(diff[R]) + PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000*SQUARE(diff[G]); + + // Choose best error + if (pixel_error < best_pixel_error) + best_pixel_error = pixel_error; + } + precalc_err_col0_RG[((colorRGB444_packed>>4)*8 + d)*16 + x] = (unsigned int) best_pixel_error; + } + } +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precalculates a table used in exhaustive compression of the T-mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void precalcError59T_col0_RG(uint8* block, int colorRGB444_packed, unsigned int *precalc_err_col0_RG) +{ + unsigned int block_error = 0, + best_block_error = MAXIMUM_ERROR, + pixel_error, + best_pixel_error; + int diff[3]; + uint8 color[3]; + uint8 possible_colors[3][2]; + + color[R] = ((colorRGB444_packed >> 8) & 0xf)*17; + color[G] = ((colorRGB444_packed >> 4) & 0xf)*17; + + // Test all distances + for (uint8 d = 0; d < 8; d++) + { + + possible_colors[0][R] = CLAMP(0,color[R] - table59T[d],255); + possible_colors[0][G] = CLAMP(0,color[G] - table59T[d],255); + + possible_colors[1][R] = CLAMP(0,color[R],255); + possible_colors[1][G] = CLAMP(0,color[G],255); + + possible_colors[2][R] = CLAMP(0,color[R] + table59T[d],255); + possible_colors[2][G] = CLAMP(0,color[G] + table59T[d],255); + + // Loop block + for (int x = 0; x < 16; x++) + { + best_pixel_error = MAXIMUM_ERROR; + + // Loop possible block colors + for (uint8 c = 0; c < 3; c++) + { + diff[R] = block[4*x + R] - CLAMP(0,possible_colors[c][R],255); + diff[G] = block[4*x + G] - CLAMP(0,possible_colors[c][G],255); + + pixel_error = SQUARE(diff[R]) + SQUARE(diff[G]); + + // Choose best error + if (pixel_error < best_pixel_error) + best_pixel_error = pixel_error; + } + precalc_err_col0_RG[((colorRGB444_packed>>4)*8 + d)*16 + x] = (unsigned int) best_pixel_error; + } + } +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precalculates a table used in exhaustive compression of the T-mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void precalcError59T_col1_Rpercep1000(uint8* block, int colorRGB444_packed, unsigned int *precalc_err_col1_R) +{ + unsigned int pixel_error; + int diff; + uint8 color; + + color = ((colorRGB444_packed >> 8) & 0xf)*17; + + // Loop block + for (int x = 0; x < 16; x++) + { + diff = block[4*x + R] - color; + pixel_error = PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*SQUARE(diff); + precalc_err_col1_R[((colorRGB444_packed>>8))*16 + x] = (unsigned int) pixel_error; + } +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +/** + * Calculate the error for the block at position (startx,starty) + * The parameters needed for reconstruction is calculated as well + * + * In the 59T bit mode, we only have pattern T. + */ +void precalcError59T_col1_R(uint8* block, int colorRGB444_packed, unsigned int *precalc_err_col1_R) +{ + unsigned int pixel_error; + int diff; + uint8 color; + + color = ((colorRGB444_packed >> 8) & 0xf)*17; + + // Loop block + for (int x = 0; x < 16; x++) + { + diff = block[4*x + R] - color; + pixel_error = SQUARE(diff); + precalc_err_col1_R[((colorRGB444_packed>>8))*16 + x] = (unsigned int) pixel_error; + } +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precalculates a table used in exhaustive compression of the T-mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void precalcError59T_col1_RGpercep1000(uint8* block, int colorRGB444_packed, unsigned int *precalc_err_col1_RG) +{ + unsigned int pixel_error; + int diff[3]; + uint8 color[2]; + + color[R] = ((colorRGB444_packed >> 8) & 0xf)*17; + color[G] = ((colorRGB444_packed >> 4) & 0xf)*17; + + // Loop block + for (int x = 0; x < 16; x++) + { + diff[R] = block[4*x + R] - color[R]; + diff[G] = block[4*x + G] - color[G]; + pixel_error = PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*SQUARE(diff[R]) + PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000*SQUARE(diff[G]); + precalc_err_col1_RG[((colorRGB444_packed>>4))*16 + x] = (unsigned int) pixel_error; + } +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precalculates a table used in exhaustive compression of the T-mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void precalcError59T_col1_RG(uint8* block, int colorRGB444_packed, unsigned int *precalc_err_col1_RG) +{ + unsigned int pixel_error; + int diff[3]; + uint8 color[2]; + + color[R] = ((colorRGB444_packed >> 8) & 0xf)*17; + color[G] = ((colorRGB444_packed >> 4) & 0xf)*17; + + // Loop block + for (int x = 0; x < 16; x++) + { + diff[R] = block[4*x + R] - color[R]; + diff[G] = block[4*x + G] - color[G]; + pixel_error = SQUARE(diff[R]) + SQUARE(diff[G]); + precalc_err_col1_RG[((colorRGB444_packed>>4))*16 + x] = (unsigned int) pixel_error; + } +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precalculates a table used in exhaustive compression of the T-mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void precalcError59T_col0_RGBpercep1000(uint8* block, int colorRGB444_packed, unsigned int *precalc_err_col0_RGB) +{ + unsigned int block_error = 0, + best_block_error = MAXERR1000, + pixel_error, + best_pixel_error; + uint8 color[3]; + int possible_colors[3][3]; + unsigned int *precalc_err_col0_RGB_adr; + +#define ONEPOINT59RGB_PERCEP(xval) \ + /* Loop possible block colors */\ + /* unroll loop for (uint8 c = 0; c < 3; c++) */\ + {\ + best_pixel_error = PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*square_table[block[4*xval + R] - possible_colors[0][R]]\ + + PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000*square_table[block[4*xval + G] - possible_colors[0][G]] \ + + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*square_table[block[4*xval + B] - possible_colors[0][B]];\ + pixel_error = PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*square_table[block[4*xval + R] - possible_colors[1][R]]\ + + PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000*square_table[block[4*xval + G] - possible_colors[1][G]]\ + + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*square_table[block[4*xval + B] - possible_colors[1][B]];\ + if (pixel_error < best_pixel_error)\ + best_pixel_error = pixel_error;\ + pixel_error = PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*square_table[block[4*xval + R] - possible_colors[2][R]]\ + + PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000*square_table[block[4*xval + G] - possible_colors[2][G]]\ + + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*square_table[block[4*xval + B] - possible_colors[2][B]];\ + if (pixel_error < best_pixel_error)\ + best_pixel_error = pixel_error;\ + }\ + precalc_err_col0_RGB_adr[xval] = (unsigned int) best_pixel_error;\ + +#define ONETABLE59RGB_PERCEP(dval) \ + possible_colors[0][R] = clamp_table[color[R] - table59T[dval]+255]-255;\ + possible_colors[0][G] = clamp_table[color[G] - table59T[dval]+255]-255;\ + possible_colors[0][B] = clamp_table[color[B] - table59T[dval]+255]-255;\ + possible_colors[1][R] = color[R]-255;\ + possible_colors[1][G] = color[G]-255;\ + possible_colors[1][B] = color[B]-255;\ + possible_colors[2][R] = clamp_table[color[R] + table59T[dval]+255]-255;\ + possible_colors[2][G] = clamp_table[color[G] + table59T[dval]+255]-255;\ + possible_colors[2][B] = clamp_table[color[B] + table59T[dval]+255]-255;\ + precalc_err_col0_RGB_adr = &precalc_err_col0_RGB[(colorRGB444_packed*8 + dval)*16];\ + /* Loop block */\ + /* unroll loop for (int x = 0; x < 16; x++) */\ + {\ + ONEPOINT59RGB_PERCEP(0)\ + ONEPOINT59RGB_PERCEP(1)\ + ONEPOINT59RGB_PERCEP(2)\ + ONEPOINT59RGB_PERCEP(3)\ + ONEPOINT59RGB_PERCEP(4)\ + ONEPOINT59RGB_PERCEP(5)\ + ONEPOINT59RGB_PERCEP(6)\ + ONEPOINT59RGB_PERCEP(7)\ + ONEPOINT59RGB_PERCEP(8)\ + ONEPOINT59RGB_PERCEP(9)\ + ONEPOINT59RGB_PERCEP(10)\ + ONEPOINT59RGB_PERCEP(11)\ + ONEPOINT59RGB_PERCEP(12)\ + ONEPOINT59RGB_PERCEP(13)\ + ONEPOINT59RGB_PERCEP(14)\ + ONEPOINT59RGB_PERCEP(15)\ + }\ + + color[R] = (((colorRGB444_packed >> 8) ) << 4) | ((colorRGB444_packed >> 8) ) ; + color[G] = (((colorRGB444_packed >> 4) & 0xf) << 4) | ((colorRGB444_packed >> 4) & 0xf) ; + color[B] = (((colorRGB444_packed) & 0xf) << 4) | ((colorRGB444_packed) & 0xf) ; + + /* Test all distances */ + /* unroll loop for (uint8 d = 0; d < 8; ++d) */ + { + ONETABLE59RGB_PERCEP(0) + ONETABLE59RGB_PERCEP(1) + ONETABLE59RGB_PERCEP(2) + ONETABLE59RGB_PERCEP(3) + ONETABLE59RGB_PERCEP(4) + ONETABLE59RGB_PERCEP(5) + ONETABLE59RGB_PERCEP(6) + ONETABLE59RGB_PERCEP(7) + } +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precalculates a table used in exhaustive compression of the T-mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void precalcError59T_col0_RGB(uint8* block, int colorRGB444_packed, unsigned int *precalc_err_col0_RGB) +{ + unsigned int block_error = 0, + best_block_error = MAXIMUM_ERROR, + pixel_error, + best_pixel_error; + uint8 color[3]; + int possible_colors[3][3]; + unsigned int *precalc_err_col0_RGB_adr; + +#define ONEPOINT59RGB(xval) \ + /* Loop possible block colors */\ + /* unroll loop for (uint8 c = 0; c < 3; c++) */\ + {\ + best_pixel_error = square_table[block[4*xval + R] - possible_colors[0][R]]\ + + square_table[block[4*xval + G] - possible_colors[0][G]] \ + + square_table[block[4*xval + B] - possible_colors[0][B]];\ + pixel_error = square_table[block[4*xval + R] - possible_colors[1][R]]\ + + square_table[block[4*xval + G] - possible_colors[1][G]]\ + + square_table[block[4*xval + B] - possible_colors[1][B]];\ + if (pixel_error < best_pixel_error)\ + best_pixel_error = pixel_error;\ + pixel_error = square_table[block[4*xval + R] - possible_colors[2][R]]\ + + square_table[block[4*xval + G] - possible_colors[2][G]]\ + + square_table[block[4*xval + B] - possible_colors[2][B]];\ + if (pixel_error < best_pixel_error)\ + best_pixel_error = pixel_error;\ + }\ + precalc_err_col0_RGB_adr[xval] = (unsigned int) best_pixel_error;\ + +#define ONETABLE59RGB(dval) \ + possible_colors[0][R] = clamp_table[color[R] - table59T[dval]+255]-255;\ + possible_colors[0][G] = clamp_table[color[G] - table59T[dval]+255]-255;\ + possible_colors[0][B] = clamp_table[color[B] - table59T[dval]+255]-255;\ + possible_colors[1][R] = color[R]-255;\ + possible_colors[1][G] = color[G]-255;\ + possible_colors[1][B] = color[B]-255;\ + possible_colors[2][R] = clamp_table[color[R] + table59T[dval]+255]-255;\ + possible_colors[2][G] = clamp_table[color[G] + table59T[dval]+255]-255;\ + possible_colors[2][B] = clamp_table[color[B] + table59T[dval]+255]-255;\ + precalc_err_col0_RGB_adr = &precalc_err_col0_RGB[(colorRGB444_packed*8 + dval)*16];\ + /* Loop block */\ + /* unroll loop for (int x = 0; x < 16; x++) */\ + {\ + ONEPOINT59RGB(0)\ + ONEPOINT59RGB(1)\ + ONEPOINT59RGB(2)\ + ONEPOINT59RGB(3)\ + ONEPOINT59RGB(4)\ + ONEPOINT59RGB(5)\ + ONEPOINT59RGB(6)\ + ONEPOINT59RGB(7)\ + ONEPOINT59RGB(8)\ + ONEPOINT59RGB(9)\ + ONEPOINT59RGB(10)\ + ONEPOINT59RGB(11)\ + ONEPOINT59RGB(12)\ + ONEPOINT59RGB(13)\ + ONEPOINT59RGB(14)\ + ONEPOINT59RGB(15)\ + }\ + + color[R] = (((colorRGB444_packed >> 8) ) << 4) | ((colorRGB444_packed >> 8) ) ; + color[G] = (((colorRGB444_packed >> 4) & 0xf) << 4) | ((colorRGB444_packed >> 4) & 0xf) ; + color[B] = (((colorRGB444_packed) & 0xf) << 4) | ((colorRGB444_packed) & 0xf) ; + + /* Test all distances */ + /* unroll loop for (uint8 d = 0; d < 8; ++d) */ + { + ONETABLE59RGB(0) + ONETABLE59RGB(1) + ONETABLE59RGB(2) + ONETABLE59RGB(3) + ONETABLE59RGB(4) + ONETABLE59RGB(5) + ONETABLE59RGB(6) + ONETABLE59RGB(7) + } +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precalculates a table used in exhaustive compression of the T-mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void precalcError59T_col1_RGBpercep1000(uint8* block, int colorRGB444_packed, unsigned int *precalc_err_col1_RGB) +{ + unsigned int pixel_error; + int diff[3]; + uint8 colorRGB[3]; + + colorRGB[0] = ((colorRGB444_packed >> 8) & 0xf)*17; + colorRGB[1] = ((colorRGB444_packed >> 4) & 0xf)*17; + colorRGB[2] = ((colorRGB444_packed >> 0) & 0xf)*17; + + // Loop block + for (int x = 0; x < 16; x++) + { + diff[R] = block[4*x + R] - colorRGB[R]; + diff[G] = block[4*x + G] - colorRGB[G]; + diff[B] = block[4*x + B] - colorRGB[B]; + + pixel_error = PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*SQUARE(diff[R]) + PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000*SQUARE(diff[G]) + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*SQUARE(diff[B]); + + precalc_err_col1_RGB[(colorRGB444_packed)*16 + x] = (unsigned int) pixel_error; + } +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precalculates a table used in exhaustive compression of the T-mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void precalcError59T_col1_RGB(uint8* block, int colorRGB444_packed, unsigned int *precalc_err_col1_RGB) +{ + unsigned int pixel_error; + int diff[3]; + uint8 colorRGB[3]; + + colorRGB[0] = ((colorRGB444_packed >> 8) & 0xf)*17; + colorRGB[1] = ((colorRGB444_packed >> 4) & 0xf)*17; + colorRGB[2] = ((colorRGB444_packed >> 0) & 0xf)*17; + + // Loop block + for (int x = 0; x < 16; x++) + { + diff[R] = block[4*x + R] - colorRGB[R]; + diff[G] = block[4*x + G] - colorRGB[G]; + diff[B] = block[4*x + B] - colorRGB[B]; + + pixel_error = SQUARE(diff[R]) + SQUARE(diff[G]) + SQUARE(diff[B]); + precalc_err_col1_RGB[(colorRGB444_packed)*16 + x] = (unsigned int) pixel_error; + } +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Calculate a minimal error for the T-mode when compressing exhaustively. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calculateError59TusingPrecalcRperceptual1000(uint8* block, int *colorsRGB444_packed, unsigned int *precalc_err_col0_R, unsigned int *precalc_err_col1_R, unsigned int best_error_so_far) +{ + unsigned int block_error = 0, + best_block_error = MAXERR1000; + + unsigned int *pixel_error_col0_base_adr; + unsigned int *pixel_error_col0_adr, *pixel_error_col1_adr; + +#define FIRSTCHOICE59R_PERCEP\ + if(*pixel_error_col0_adr < *pixel_error_col1_adr)\ + block_error = *pixel_error_col0_adr;\ + else\ + block_error = *pixel_error_col1_adr;\ + +#define CHOICE59R_PERCEP(xval)\ + if(pixel_error_col0_adr[xval] < pixel_error_col1_adr[xval])\ + block_error += pixel_error_col0_adr[xval];\ + else\ + block_error += pixel_error_col1_adr[xval];\ + +#define ONETABLE59R_PERCEP(dval) \ + pixel_error_col0_adr = &pixel_error_col0_base_adr[dval*16];\ + /* unroll loop for(int x = 0; block_error < best_error_so_far && x<16; x++) */\ + {\ + FIRSTCHOICE59R_PERCEP\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59R_PERCEP(1)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59R_PERCEP(2)\ + CHOICE59R_PERCEP(3)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59R_PERCEP(4)\ + CHOICE59R_PERCEP(5)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59R_PERCEP(6)\ + CHOICE59R_PERCEP(7)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59R_PERCEP(8)\ + CHOICE59R_PERCEP(9)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59R_PERCEP(10)\ + CHOICE59R_PERCEP(11)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59R_PERCEP(12)\ + CHOICE59R_PERCEP(13)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59R_PERCEP(14)\ + CHOICE59R_PERCEP(15)\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + if (block_error < best_block_error)\ + best_block_error = block_error;\ + + pixel_error_col0_base_adr = &precalc_err_col0_R[((colorsRGB444_packed[0]>>8)*8)*16]; + pixel_error_col1_adr = &precalc_err_col1_R[((colorsRGB444_packed[1]>>8))*16]; + + // Test all distances + /* unroll loop for (uint8 d = 0; d < 8; d++) */ + { + ONETABLE59R_PERCEP(0) + ONETABLE59R_PERCEP(1) + ONETABLE59R_PERCEP(2) + ONETABLE59R_PERCEP(3) + ONETABLE59R_PERCEP(4) + ONETABLE59R_PERCEP(5) + ONETABLE59R_PERCEP(6) + ONETABLE59R_PERCEP(7) + } + return best_block_error; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Calculate a minimal error for the T-mode when compressing exhaustively. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calculateError59TusingPrecalcR(uint8* block, int *colorsRGB444_packed, unsigned int *precalc_err_col0_R, unsigned int *precalc_err_col1_R, unsigned int best_error_so_far) +{ + unsigned int block_error = 0, + best_block_error = MAXIMUM_ERROR; + + unsigned int *pixel_error_col0_base_adr; + unsigned int *pixel_error_col0_adr, *pixel_error_col1_adr; + +#define FIRSTCHOICE59R\ + if(*pixel_error_col0_adr < *pixel_error_col1_adr)\ + block_error = *pixel_error_col0_adr;\ + else\ + block_error = *pixel_error_col1_adr;\ + +#define CHOICE59R(xval)\ + if(pixel_error_col0_adr[xval] < pixel_error_col1_adr[xval])\ + block_error += pixel_error_col0_adr[xval];\ + else\ + block_error += pixel_error_col1_adr[xval];\ + +#define ONETABLE59R(dval) \ + pixel_error_col0_adr = &pixel_error_col0_base_adr[dval*16];\ + /* unroll loop for(int x = 0; block_error < best_error_so_far && x<16; x++) */\ + {\ + FIRSTCHOICE59R\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59R(1)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59R(2)\ + CHOICE59R(3)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59R(4)\ + CHOICE59R(5)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59R(6)\ + CHOICE59R(7)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59R(8)\ + CHOICE59R(9)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59R(10)\ + CHOICE59R(11)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59R(12)\ + CHOICE59R(13)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59R(14)\ + CHOICE59R(15)\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + if (block_error < best_block_error)\ + best_block_error = block_error;\ + + pixel_error_col0_base_adr = &precalc_err_col0_R[((colorsRGB444_packed[0]>>8)*8)*16]; + pixel_error_col1_adr = &precalc_err_col1_R[((colorsRGB444_packed[1]>>8))*16]; + + + // Test all distances + /* unroll loop for (uint8 d = 0; d < 8; d++) */ + { + ONETABLE59R(0) + ONETABLE59R(1) + ONETABLE59R(2) + ONETABLE59R(3) + ONETABLE59R(4) + ONETABLE59R(5) + ONETABLE59R(6) + ONETABLE59R(7) + } + + return best_block_error; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Calculate a minimal error for the T-mode when compressing exhaustively. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calculateError59TusingPrecalcRGperceptual1000(uint8* block, int *colorsRGB444_packed, unsigned int *precalc_err_col0_RG, unsigned int *precalc_err_col1_RG, unsigned int best_error_so_far) +{ + unsigned int block_error = 0, + best_block_error = MAXERR1000; + + unsigned int *pixel_error_col0_adr, *pixel_error_col1_adr; + unsigned int *pixel_error_col0_base_adr; + +#define FIRSTCHOICE59RG_PERCEP \ + if(*pixel_error_col0_adr < *pixel_error_col1_adr)\ + block_error = *pixel_error_col0_adr;\ + else\ + block_error = *pixel_error_col1_adr;\ + + +#define CHOICE59RG_PERCEP(xval) \ + if(pixel_error_col0_adr[xval] < pixel_error_col1_adr[xval])\ + block_error += pixel_error_col0_adr[xval];\ + else\ + block_error += pixel_error_col1_adr[xval];\ + +#define ONETABLE59RG_PERCEP(dval)\ + pixel_error_col0_adr = &pixel_error_col0_base_adr[dval*16];\ + /* unroll loop for(int x = 0; block_error < best_error_so_far && x<16; x++) */\ + {\ + FIRSTCHOICE59RG_PERCEP\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59RG_PERCEP(1)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59RG_PERCEP(2)\ + CHOICE59RG_PERCEP(3)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59RG_PERCEP(4)\ + CHOICE59RG_PERCEP(5)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59RG_PERCEP(6)\ + CHOICE59RG_PERCEP(7)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59RG_PERCEP(8)\ + CHOICE59RG_PERCEP(9)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59RG_PERCEP(10)\ + CHOICE59RG_PERCEP(11)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59RG_PERCEP(12)\ + CHOICE59RG_PERCEP(13)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59RG_PERCEP(14)\ + CHOICE59RG_PERCEP(15)\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + if (block_error < best_block_error)\ + best_block_error = block_error;\ + + + pixel_error_col0_base_adr = &precalc_err_col0_RG[((colorsRGB444_packed[0]>>4)*8)*16]; + pixel_error_col1_adr = &precalc_err_col1_RG[((colorsRGB444_packed[1]>>4))*16]; + + // Test all distances + /* unroll loop for (uint8 d = 0; d < 8; d++) */ + { + + ONETABLE59RG_PERCEP(0) + ONETABLE59RG_PERCEP(1) + ONETABLE59RG_PERCEP(2) + ONETABLE59RG_PERCEP(3) + ONETABLE59RG_PERCEP(4) + ONETABLE59RG_PERCEP(5) + ONETABLE59RG_PERCEP(6) + ONETABLE59RG_PERCEP(7) + } + return best_block_error; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Calculate a minimal error for the T-mode when compressing exhaustively. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calculateError59TusingPrecalcRG(uint8* block, int *colorsRGB444_packed, unsigned int *precalc_err_col0_RG, unsigned int *precalc_err_col1_RG, unsigned int best_error_so_far) +{ + unsigned int block_error = 0, + best_block_error = MAXIMUM_ERROR; + + unsigned int *pixel_error_col0_adr, *pixel_error_col1_adr; + unsigned int *pixel_error_col0_base_adr; + +#define FIRSTCHOICE59RG \ + if(*pixel_error_col0_adr < *pixel_error_col1_adr)\ + block_error = *pixel_error_col0_adr;\ + else\ + block_error = *pixel_error_col1_adr;\ + +#define CHOICE59RG(xval) \ + if(pixel_error_col0_adr[xval] < pixel_error_col1_adr[xval])\ + block_error += pixel_error_col0_adr[xval];\ + else\ + block_error += pixel_error_col1_adr[xval];\ + +#define ONETABLE59RG(dval)\ + pixel_error_col0_adr = &pixel_error_col0_base_adr[dval*16];\ + /* unroll loop for(int x = 0; block_error < best_error_so_far && x<16; x++) */\ + {\ + FIRSTCHOICE59RG\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59RG(1)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59RG(2)\ + CHOICE59RG(3)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59RG(4)\ + CHOICE59RG(5)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59RG(6)\ + CHOICE59RG(7)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59RG(8)\ + CHOICE59RG(9)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59RG(10)\ + CHOICE59RG(11)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59RG(12)\ + CHOICE59RG(13)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59RG(14)\ + CHOICE59RG(15)\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + if (block_error < best_block_error)\ + best_block_error = block_error;\ + + pixel_error_col0_base_adr = &precalc_err_col0_RG[((colorsRGB444_packed[0]>>4)*8)*16]; + pixel_error_col1_adr = &precalc_err_col1_RG[((colorsRGB444_packed[1]>>4))*16]; + + // Test all distances + /* unroll loop for (uint8 d = 0; d < 8; d++) */ + { + ONETABLE59RG(0) + ONETABLE59RG(1) + ONETABLE59RG(2) + ONETABLE59RG(3) + ONETABLE59RG(4) + ONETABLE59RG(5) + ONETABLE59RG(6) + ONETABLE59RG(7) + } + return best_block_error; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Calculate a minimal error for the T-mode when compressing exhaustively. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calculateError59TusingPrecalcRGBperceptual1000(uint8* block, int *colorsRGB444_packed, unsigned int *precalc_err_col0_RGB, unsigned int *precalc_err_col1_RGB, unsigned int best_error_so_far) +{ + unsigned int block_error = 0, + best_block_error = MAXERR1000; + unsigned int *pixel_error_col0_adr, *pixel_error_col1_adr; + unsigned int *pixel_error_col0_base_adr; + +#define FIRSTCHOICE59_PERCEP \ + if(*pixel_error_col0_adr < *pixel_error_col1_adr)\ + block_error = *pixel_error_col0_adr;\ + else\ + block_error = *pixel_error_col1_adr;\ + +#define CHOICE59_PERCEP(xval) \ + if(pixel_error_col0_adr[xval] < pixel_error_col1_adr[xval])\ + block_error += pixel_error_col0_adr[xval];\ + else\ + block_error += pixel_error_col1_adr[xval];\ + +#define ONETABLE59T_PERCEP(dval)\ + pixel_error_col0_adr = &pixel_error_col0_base_adr[dval*16];\ + /* unroll for(int x = 0; block_error < best_error_so_far && x<16; x++) */\ + {\ + FIRSTCHOICE59_PERCEP\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59_PERCEP(1)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59_PERCEP(2)\ + CHOICE59_PERCEP(3)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59_PERCEP(4)\ + CHOICE59_PERCEP(5)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59_PERCEP(6)\ + CHOICE59_PERCEP(7)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59_PERCEP(8)\ + CHOICE59_PERCEP(9)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59_PERCEP(10)\ + CHOICE59_PERCEP(11)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59_PERCEP(12)\ + CHOICE59_PERCEP(13)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59_PERCEP(14)\ + CHOICE59_PERCEP(15)\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + if (block_error < best_block_error)\ + best_block_error = block_error;\ + + pixel_error_col1_adr = &precalc_err_col1_RGB[(colorsRGB444_packed[1])*16]; + pixel_error_col0_base_adr = &precalc_err_col0_RGB[(colorsRGB444_packed[0]*8)*16]; + + // Test all distances + /* unroll loop for (uint8 d = 0; d < 8; d++)*/ + { + ONETABLE59T_PERCEP(0) + ONETABLE59T_PERCEP(1) + ONETABLE59T_PERCEP(2) + ONETABLE59T_PERCEP(3) + ONETABLE59T_PERCEP(4) + ONETABLE59T_PERCEP(5) + ONETABLE59T_PERCEP(6) + ONETABLE59T_PERCEP(7) + } + return best_block_error; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Calculate a minimal error for the T-mode when compressing exhaustively. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calculateError59TusingPrecalcRGB(uint8* block, int *colorsRGB444_packed, unsigned int *precalc_err_col0_RGB, unsigned int *precalc_err_col1_RGB, unsigned int best_error_so_far) +{ + unsigned int block_error = 0, + best_block_error = MAXIMUM_ERROR; + unsigned int *pixel_error_col0_adr, *pixel_error_col1_adr; + unsigned int *pixel_error_col0_base_adr; + +#define FIRSTCHOICE59 \ + if(*pixel_error_col0_adr < *pixel_error_col1_adr)\ + block_error = *pixel_error_col0_adr;\ + else\ + block_error = *pixel_error_col1_adr;\ + +#define CHOICE59(xval) \ + if(pixel_error_col0_adr[xval] < pixel_error_col1_adr[xval])\ + block_error += pixel_error_col0_adr[xval];\ + else\ + block_error += pixel_error_col1_adr[xval];\ + +#define ONETABLE59T(dval)\ + pixel_error_col0_adr = &pixel_error_col0_base_adr[dval*16];\ + /* unroll for(int x = 0; block_error < best_error_so_far && x<16; x++) */\ + {\ + FIRSTCHOICE59\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59(1)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59(2)\ + CHOICE59(3)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59(4)\ + CHOICE59(5)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59(6)\ + CHOICE59(7)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59(8)\ + CHOICE59(9)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59(10)\ + CHOICE59(11)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59(12)\ + CHOICE59(13)\ + if( block_error < best_error_so_far)\ + {\ + CHOICE59(14)\ + CHOICE59(15)\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + if (block_error < best_block_error)\ + best_block_error = block_error;\ + + pixel_error_col1_adr = &precalc_err_col1_RGB[(colorsRGB444_packed[1])*16]; + pixel_error_col0_base_adr = &precalc_err_col0_RGB[(colorsRGB444_packed[0]*8)*16]; + + // Test all distances + /* unroll loop for (uint8 d = 0; d < 8; d++)*/ + { + ONETABLE59T(0) + ONETABLE59T(1) + ONETABLE59T(2) + ONETABLE59T(3) + ONETABLE59T(4) + ONETABLE59T(5) + ONETABLE59T(6) + ONETABLE59T(7) + } + return best_block_error; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// The below code should compress the block to 59 bits. +// This is supposed to match the first of the three modes in TWOTIMER. +// +//|63 62 61 60 59|58 57 56 55|54 53 52 51|50 49 48 47|46 45 44 43|42 41 40 39|38 37 36 35|34 33 32| +//|----empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|--dist--| +// +//|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| +//|----------------------------------------index bits---------------------------------------------| +// +// Note that this method might not return the best possible compression for the T-mode. It will only do so if the best possible T-representation +// is less than best_error_so_far. To guarantee that the best possible T-representation is found, the function should be called using +// best_error_so_far = 255*255*3*16, which is the maximum error for a block. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int compressBlockTHUMB59TExhaustivePerceptual(uint8 *img,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2, unsigned int best_error_so_far) +{ + uint8 colorsRGB444[2][3]; + unsigned int pixel_indices; + uint8 distance; + + uint8 block[4*4*4]; + + unsigned int *precalc_err_col0_RGB; + unsigned int *precalc_err_col1_RGB; + unsigned int *precalc_err_col0_RG; + unsigned int *precalc_err_col1_RG; + unsigned int *precalc_err_col0_R; + unsigned int *precalc_err_col1_R; + + int colorRGB444_packed; + + int colorsRGB444_packed[2]; + int best_colorsRGB444_packed[2]; + + unsigned int best_error_using_Tmode; + + // First compress block quickly to a resonable quality so that we can + // rule out all blocks that are of worse quality than that. + best_error_using_Tmode = (unsigned int) compressBlockTHUMB59TFastestOnlyColorPerceptual1000(img, width, height, startx, starty, best_colorsRGB444_packed); + if(best_error_using_Tmode < best_error_so_far) + best_error_so_far = best_error_using_Tmode; + + // Color numbering is reversed between the above function and the precalc functions below; swap colors. + int temp = best_colorsRGB444_packed[0]; + best_colorsRGB444_packed[0] = best_colorsRGB444_packed[1]; + best_colorsRGB444_packed[1] = temp; + + int xx,yy,count = 0; + + // Use 4 bytes per pixel to make it 32-word aligned. + for(xx = 0; xx<4; xx++) + { + for(yy=0; yy<4; yy++) + { + block[(count)*4] = img[((starty+yy)*width+(startx+xx))*3]; + block[(count)*4+1] = img[((starty+yy)*width+(startx+xx))*3+1]; + block[(count)*4+2] = img[((starty+yy)*width+(startx+xx))*3+2]; + block[(count)*4+3] = 0; + count++; + } + } + + // Precalculate error for color 0 (which produces the upper half of the T) + precalc_err_col0_RGB = (unsigned int*) malloc(4096*8*16*sizeof(unsigned int)); + if(!precalc_err_col0_RGB){printf("Out of memory allocating \n");exit(1);} + + for( colorRGB444_packed = 0; colorRGB444_packed<16*16*16; colorRGB444_packed++) + { + precalcError59T_col0_RGBpercep1000(block, colorRGB444_packed, precalc_err_col0_RGB); + } + + // Precalculate error for color 1 (which produces the lower half of the T -- the lone color) + precalc_err_col1_RGB = (unsigned int*) malloc(4096*16*sizeof(unsigned int)); + if(!precalc_err_col1_RGB){printf("Out of memory allocating \n");exit(1);} + + for( colorRGB444_packed = 0; colorRGB444_packed<16*16*16; colorRGB444_packed++) + { + precalcError59T_col1_RGBpercep1000(block, colorRGB444_packed, precalc_err_col1_RGB); + } + + precalc_err_col0_RG = (unsigned int*) malloc(16*16*8*16*sizeof(unsigned int)); + if(!precalc_err_col0_RG){printf("Out of memory allocating \n");exit(1);} + + for( colorRGB444_packed = 0; colorRGB444_packed<16*16*16; colorRGB444_packed+=16) + { + precalcError59T_col0_RGpercep1000(block, colorRGB444_packed, precalc_err_col0_RG); + } + + precalc_err_col1_RG = (unsigned int*) malloc(16*16*16*sizeof(unsigned int)); + if(!precalc_err_col1_RG){printf("Out of memory allocating \n");exit(1);} + + for( colorRGB444_packed = 0; colorRGB444_packed<16*16*16; colorRGB444_packed+=16) + { + precalcError59T_col1_RGpercep1000(block, colorRGB444_packed, precalc_err_col1_RG); + } + + precalc_err_col0_R = (unsigned int*) malloc(16*8*16*sizeof(unsigned int)); + if(!precalc_err_col0_R){printf("Out of memory allocating \n");exit(1);} + + for( colorRGB444_packed = 0; colorRGB444_packed<16*16*16; colorRGB444_packed+=16*16) + { + precalcError59T_col0_Rpercep1000(block, colorRGB444_packed, precalc_err_col0_R); + } + + precalc_err_col1_R = (unsigned int*) malloc(16*16*sizeof(unsigned int)); + if(!precalc_err_col1_R){printf("Out of memory allocating \n");exit(1);} + + for( colorRGB444_packed = 0; colorRGB444_packed<16*16*16; colorRGB444_packed+=16*16) + { + precalcError59T_col1_Rpercep1000(block, colorRGB444_packed, precalc_err_col1_R); + } + + unsigned int error; + unsigned int avoided = 0; + unsigned int notavoided = 0; + + for(colorsRGB444[0][0] = 0; colorsRGB444[0][0] < 16; colorsRGB444[0][0]++) + { + for(colorsRGB444[1][0] = 0; colorsRGB444[1][0] < 16; colorsRGB444[1][0]++) + { + colorsRGB444_packed[0] = (colorsRGB444[0][0] << 8); + colorsRGB444_packed[1] = (colorsRGB444[1][0] << 8); + error = calculateError59TusingPrecalcRperceptual1000(block, colorsRGB444_packed, precalc_err_col0_R, precalc_err_col1_R, best_error_so_far); + if(error < best_error_so_far) + { + notavoided = notavoided + 1; + for(colorsRGB444[0][1] = 0; colorsRGB444[0][1] < 16; colorsRGB444[0][1]++) + { + colorsRGB444_packed[0] = (colorsRGB444[0][0] << 8) + (colorsRGB444[0][1] <<4); + for(colorsRGB444[1][1] = 0; colorsRGB444[1][1] < 16; colorsRGB444[1][1]++) + { + colorsRGB444_packed[1] = (colorsRGB444[1][0] << 8) + (colorsRGB444[1][1] <<4); + error = calculateError59TusingPrecalcRGperceptual1000(block, colorsRGB444_packed, precalc_err_col0_RG, precalc_err_col1_RG, best_error_so_far); + if(error < best_error_so_far) + { + for(colorsRGB444[0][2] = 0; colorsRGB444[0][2] < 16; colorsRGB444[0][2]++) + { + colorsRGB444_packed[0] = (colorsRGB444[0][0] << 8) + (colorsRGB444[0][1] <<4) + colorsRGB444[0][2]; + for(colorsRGB444[1][2] = 0; colorsRGB444[1][2] < 16; colorsRGB444[1][2]++) + { + colorsRGB444_packed[1] = (colorsRGB444[1][0] << 8) + (colorsRGB444[1][1] <<4) + colorsRGB444[1][2]; + error = calculateError59TusingPrecalcRGBperceptual1000(block, colorsRGB444_packed, precalc_err_col0_RGB, precalc_err_col1_RGB, best_error_so_far); + + if(error < best_error_so_far) + { + best_error_so_far = error; + best_error_using_Tmode = error; + best_colorsRGB444_packed[0] = colorsRGB444_packed[0]; + best_colorsRGB444_packed[1] = colorsRGB444_packed[1]; + } + } + } + } + } + } + } + } + } + + free(precalc_err_col0_RGB); + free(precalc_err_col1_RGB); + free(precalc_err_col0_RG); + free(precalc_err_col1_RG); + free(precalc_err_col0_R); + free(precalc_err_col1_R); + + // We have got the two best colors. Now find the best distance and pixel indices. + + // Color numbering are reversed between precalc and noSwap + colorsRGB444[0][0] = (best_colorsRGB444_packed[1] >> 8) & 0xf; + colorsRGB444[0][1] = (best_colorsRGB444_packed[1] >> 4) & 0xf; + colorsRGB444[0][2] = (best_colorsRGB444_packed[1] >> 0) & 0xf; + + colorsRGB444[1][0] = (best_colorsRGB444_packed[0] >> 8) & 0xf; + colorsRGB444[1][1] = (best_colorsRGB444_packed[0] >> 4) & 0xf; + colorsRGB444[1][2] = (best_colorsRGB444_packed[0] >> 0) & 0xf; + + calculateError59TnoSwapPerceptual1000(img, width, startx, starty, colorsRGB444, distance, pixel_indices); + + // Put the compress params into the compression block + packBlock59T(colorsRGB444, distance, pixel_indices, compressed1, compressed2); + + return best_error_using_Tmode; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// The below code should compress the block to 59 bits. +// This is supposed to match the first of the three modes in TWOTIMER. +// +//|63 62 61 60 59|58 57 56 55|54 53 52 51|50 49 48 47|46 45 44 43|42 41 40 39|38 37 36 35|34 33 32| +//|----empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|--dist--| +// +//|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| +//|----------------------------------------index bits---------------------------------------------| +// +// Note that this method might not return the best possible compression for the T-mode. It will only do so if the best possible T-representation +// is less than best_error_so_far. To guarantee that the best possible T-representation is found, the function should be called using +// best_error_so_far = 255*255*3*16, which is the maximum error for a block. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int compressBlockTHUMB59TExhaustive(uint8 *img,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2, unsigned int best_error_so_far) +{ + uint8 colorsRGB444[2][3]; + unsigned int pixel_indices; + uint8 distance; + + uint8 block[4*4*4]; + + unsigned int *precalc_err_col0_RGB; + unsigned int *precalc_err_col1_RGB; + unsigned int *precalc_err_col0_RG; + unsigned int *precalc_err_col1_RG; + unsigned int *precalc_err_col0_R; + unsigned int *precalc_err_col1_R; + + int colorRGB444_packed; + + int colorsRGB444_packed[2]; + int best_colorsRGB444_packed[2]; + + unsigned int best_error_using_Tmode; + + // First compress block quickly to a resonable quality so that we can + // rule out all blocks that are of worse quality than that. + best_error_using_Tmode = (unsigned int) compressBlockTHUMB59TFastestOnlyColor(img, width, height, startx, starty, best_colorsRGB444_packed); + if(best_error_using_Tmode < best_error_so_far) + best_error_so_far = best_error_using_Tmode; + + + // Colors numbering is reversed between the above function and the precalc below: + int temp = best_colorsRGB444_packed[0]; + best_colorsRGB444_packed[0] = best_colorsRGB444_packed[1]; + best_colorsRGB444_packed[1] = temp; + + int xx,yy,count = 0; + + // Use 4 bytes per pixel to make it 32-word aligned. + for(xx = 0; xx<4; xx++) + { + for(yy=0; yy<4; yy++) + { + block[(count)*4] = img[((starty+yy)*width+(startx+xx))*3]; + block[(count)*4+1] = img[((starty+yy)*width+(startx+xx))*3+1]; + block[(count)*4+2] = img[((starty+yy)*width+(startx+xx))*3+2]; + block[(count)*4+3] = 0; + count++; + } + } + + // Precalculate error for color 0 (which produces the upper half of the T) + precalc_err_col0_RGB = (unsigned int*) malloc(4096*8*16*sizeof(unsigned int)); + if(!precalc_err_col0_RGB){printf("Out of memory allocating \n");exit(1);} + + for( colorRGB444_packed = 0; colorRGB444_packed<16*16*16; colorRGB444_packed++) + { + precalcError59T_col0_RGB(block, colorRGB444_packed, precalc_err_col0_RGB); + } + + // Precalculate error for color 1 (which produces the lower half of the T -- the lone color) + precalc_err_col1_RGB = (unsigned int*) malloc(4096*16*sizeof(unsigned int)); + if(!precalc_err_col1_RGB){printf("Out of memory allocating \n");exit(1);} + + for( colorRGB444_packed = 0; colorRGB444_packed<16*16*16; colorRGB444_packed++) + { + precalcError59T_col1_RGB(block, colorRGB444_packed, precalc_err_col1_RGB); + } + + precalc_err_col0_RG = (unsigned int*) malloc(16*16*8*16*sizeof(unsigned int)); + if(!precalc_err_col0_RG){printf("Out of memory allocating \n");exit(1);} + + for( colorRGB444_packed = 0; colorRGB444_packed<16*16*16; colorRGB444_packed+=16) + { + precalcError59T_col0_RG(block, colorRGB444_packed, precalc_err_col0_RG); + } + + precalc_err_col1_RG = (unsigned int*) malloc(16*16*16*sizeof(unsigned int)); + if(!precalc_err_col1_RG){printf("Out of memory allocating \n");exit(1);} + + for( colorRGB444_packed = 0; colorRGB444_packed<16*16*16; colorRGB444_packed+=16) + { + precalcError59T_col1_RG(block, colorRGB444_packed, precalc_err_col1_RG); + } + + precalc_err_col0_R = (unsigned int*) malloc(16*8*16*sizeof(unsigned int)); + if(!precalc_err_col0_R){printf("Out of memory allocating \n");exit(1);} + + for( colorRGB444_packed = 0; colorRGB444_packed<16*16*16; colorRGB444_packed+=16*16) + { + precalcError59T_col0_R(block, colorRGB444_packed, precalc_err_col0_R); + } + + precalc_err_col1_R = (unsigned int*) malloc(16*16*sizeof(unsigned int)); + if(!precalc_err_col1_R){printf("Out of memory allocating \n");exit(1);} + + for( colorRGB444_packed = 0; colorRGB444_packed<16*16*16; colorRGB444_packed+=16*16) + { + precalcError59T_col1_R(block, colorRGB444_packed, precalc_err_col1_R); + } + + unsigned int error; + unsigned int avoided = 0; + unsigned int notavoided = 0; + + for(colorsRGB444[0][0] = 0; colorsRGB444[0][0] < 16; colorsRGB444[0][0]++) + { + for(colorsRGB444[1][0] = 0; colorsRGB444[1][0] < 16; colorsRGB444[1][0]++) + { + colorsRGB444_packed[0] = (colorsRGB444[0][0] << 8); + colorsRGB444_packed[1] = (colorsRGB444[1][0] << 8); + error = calculateError59TusingPrecalcR(block, colorsRGB444_packed, precalc_err_col0_R, precalc_err_col1_R, best_error_so_far); + if(error < best_error_so_far) + { + notavoided = notavoided + 1; + for(colorsRGB444[0][1] = 0; colorsRGB444[0][1] < 16; colorsRGB444[0][1]++) + { + colorsRGB444_packed[0] = (colorsRGB444[0][0] << 8) + (colorsRGB444[0][1] <<4); + for(colorsRGB444[1][1] = 0; colorsRGB444[1][1] < 16; colorsRGB444[1][1]++) + { + colorsRGB444_packed[1] = (colorsRGB444[1][0] << 8) + (colorsRGB444[1][1] <<4); + error = calculateError59TusingPrecalcRG(block, colorsRGB444_packed, precalc_err_col0_RG, precalc_err_col1_RG, best_error_so_far); + if(error < best_error_so_far) + { + for(colorsRGB444[0][2] = 0; colorsRGB444[0][2] < 16; colorsRGB444[0][2]++) + { + colorsRGB444_packed[0] = (colorsRGB444[0][0] << 8) + (colorsRGB444[0][1] <<4) + colorsRGB444[0][2]; + for(colorsRGB444[1][2] = 0; colorsRGB444[1][2] < 16; colorsRGB444[1][2]++) + { + colorsRGB444_packed[1] = (colorsRGB444[1][0] << 8) + (colorsRGB444[1][1] <<4) + colorsRGB444[1][2]; + error = calculateError59TusingPrecalcRGB(block, colorsRGB444_packed, precalc_err_col0_RGB, precalc_err_col1_RGB, best_error_so_far); + + if(error < best_error_so_far) + { + best_error_so_far = error; + best_error_using_Tmode = error; + best_colorsRGB444_packed[0] = colorsRGB444_packed[0]; + best_colorsRGB444_packed[1] = colorsRGB444_packed[1]; + } + } + } + } + } + } + } + } + } + + free(precalc_err_col0_RGB); + free(precalc_err_col1_RGB); + free(precalc_err_col0_RG); + free(precalc_err_col1_RG); + free(precalc_err_col0_R); + free(precalc_err_col1_R); + + // We have got the two best colors. Now find the best distance and pixel indices. + + // Color numbering are reversed between precalc and noSwap + colorsRGB444[0][0] = (best_colorsRGB444_packed[1] >> 8) & 0xf; + colorsRGB444[0][1] = (best_colorsRGB444_packed[1] >> 4) & 0xf; + colorsRGB444[0][2] = (best_colorsRGB444_packed[1] >> 0) & 0xf; + + colorsRGB444[1][0] = (best_colorsRGB444_packed[0] >> 8) & 0xf; + colorsRGB444[1][1] = (best_colorsRGB444_packed[0] >> 4) & 0xf; + colorsRGB444[1][2] = (best_colorsRGB444_packed[0] >> 0) & 0xf; + + calculateError59TnoSwap(img, width, startx, starty, colorsRGB444, distance, pixel_indices); + + // Put the compress params into the compression block + packBlock59T(colorsRGB444, distance, pixel_indices, compressed1, compressed2); + + return best_error_using_Tmode; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precalculates tables used in the exhaustive compression of the H-mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void precalcErrorR_58Hperceptual1000(uint8* srcimg, int width, int startx, int starty, uint8 (colorsRGB444)[2][3],int colorRGB444_packed, unsigned int *precalc_errR) +{ + unsigned int block_error = 0, + best_block_error = MAXERR1000, + pixel_error, + best_pixel_error; + int diff[3]; + unsigned int pixel_colors; + uint8 possible_colors[2][3]; + uint8 colors[2][3]; + + decompressColor(R_BITS58H, G_BITS58H, B_BITS58H, colorsRGB444, colors); + + // Test all distances + for (uint8 d = 0; d < BINPOW(TABLE_BITS_58H); ++d) + { + possible_colors[0][R] = CLAMP(0,colors[0][R] - table58H[d],255); + possible_colors[1][R] = CLAMP(0,colors[0][R] + table58H[d],255); + + block_error = 0; + pixel_colors = 0; + + // Loop block + for (size_t y = 0; y < BLOCKHEIGHT; ++y) + { + for (size_t x = 0; x < BLOCKWIDTH; ++x) + { + best_pixel_error = MAXERR1000; + + // Loop possible block colors + for (uint8 c = 0; c < 2; ++c) + { + diff[R] = srcimg[3*((starty+y)*width+startx+x)+R] - CLAMP(0,possible_colors[c][R],255); + + pixel_error = PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*SQUARE(diff[R]); + + // Choose best error + if (pixel_error < best_pixel_error) + { + best_pixel_error = pixel_error; + } + } + precalc_errR[((colorRGB444_packed>>8)*8 + d)*16 + (y*4)+x] = (unsigned int) best_pixel_error; + } + } + } +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precalculates tables used in the exhaustive compression of the H-mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void precalcErrorR_58H(uint8* srcimg, int width, int startx, int starty, uint8 (colorsRGB444)[2][3],int colorRGB444_packed, unsigned int *precalc_errR) +{ + double block_error = 0, + best_block_error = MAXIMUM_ERROR, + pixel_error, + best_pixel_error; + int diff[3]; + unsigned int pixel_colors; + uint8 possible_colors[2][3]; + uint8 colors[2][3]; + + decompressColor(R_BITS58H, G_BITS58H, B_BITS58H, colorsRGB444, colors); + + // Test all distances + for (uint8 d = 0; d < BINPOW(TABLE_BITS_58H); ++d) + { + possible_colors[0][R] = CLAMP(0,colors[0][R] - table58H[d],255); + possible_colors[1][R] = CLAMP(0,colors[0][R] + table58H[d],255); + + block_error = 0; + pixel_colors = 0; + + // Loop block + for (size_t y = 0; y < BLOCKHEIGHT; ++y) + { + for (size_t x = 0; x < BLOCKWIDTH; ++x) + { + best_pixel_error = MAXIMUM_ERROR; + + // Loop possible block colors + for (uint8 c = 0; c < 2; ++c) + { + diff[R] = srcimg[3*((starty+y)*width+startx+x)+R] - CLAMP(0,possible_colors[c][R],255); + + pixel_error = weight[R]*SQUARE(diff[R]); + + // Choose best error + if (pixel_error < best_pixel_error) + { + best_pixel_error = pixel_error; + } + } + precalc_errR[((colorRGB444_packed>>8)*8 + d)*16 + (y*4)+x] = (unsigned int) best_pixel_error; + } + } + } +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precalculates tables used in the exhaustive compression of the H-mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void precalcErrorRG_58Hperceptual1000(uint8* srcimg, int width, int startx, int starty, uint8 (colorsRGB444)[2][3],int colorRGB444_packed, unsigned int *precalc_errRG) +{ + unsigned int block_error = 0, + best_block_error = MAXERR1000, + pixel_error, + best_pixel_error; + int diff[3]; + unsigned int pixel_colors; + uint8 possible_colors[2][3]; + uint8 colors[2][3]; + + decompressColor(R_BITS58H, G_BITS58H, B_BITS58H, colorsRGB444, colors); + + // Test all distances + for (uint8 d = 0; d < BINPOW(TABLE_BITS_58H); ++d) + { + possible_colors[0][R] = CLAMP(0,colors[0][R] - table58H[d],255); + possible_colors[0][G] = CLAMP(0,colors[0][G] - table58H[d],255); + possible_colors[1][R] = CLAMP(0,colors[0][R] + table58H[d],255); + possible_colors[1][G] = CLAMP(0,colors[0][G] + table58H[d],255); + + block_error = 0; + pixel_colors = 0; + + // Loop block + for (size_t y = 0; y < BLOCKHEIGHT; ++y) + { + for (size_t x = 0; x < BLOCKWIDTH; ++x) + { + best_pixel_error = MAXERR1000; + + // Loop possible block colors + for (uint8 c = 0; c < 2; ++c) + { + diff[R] = srcimg[3*((starty+y)*width+startx+x)+R] - CLAMP(0,possible_colors[c][R],255); + diff[G] = srcimg[3*((starty+y)*width+startx+x)+G] - CLAMP(0,possible_colors[c][G],255); + + pixel_error = PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*SQUARE(diff[R]) + + PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000*SQUARE(diff[G]); + + // Choose best error + if (pixel_error < best_pixel_error) + { + best_pixel_error = pixel_error; + } + } + precalc_errRG[((colorRGB444_packed>>4)*8 + d)*16 + (y*4)+x] = (unsigned int) best_pixel_error; + } + } + } +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precalculates tables used in the exhaustive compression of the H-mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void precalcErrorRG_58H(uint8* srcimg, int width, int startx, int starty, uint8 (colorsRGB444)[2][3],int colorRGB444_packed, unsigned int *precalc_errRG) +{ + double block_error = 0, + best_block_error = MAXIMUM_ERROR, + pixel_error, + best_pixel_error; + int diff[3]; + unsigned int pixel_colors; + uint8 possible_colors[2][3]; + uint8 colors[2][3]; + + decompressColor(R_BITS58H, G_BITS58H, B_BITS58H, colorsRGB444, colors); + + // Test all distances + for (uint8 d = 0; d < BINPOW(TABLE_BITS_58H); ++d) + { + possible_colors[0][R] = CLAMP(0,colors[0][R] - table58H[d],255); + possible_colors[0][G] = CLAMP(0,colors[0][G] - table58H[d],255); + possible_colors[1][R] = CLAMP(0,colors[0][R] + table58H[d],255); + possible_colors[1][G] = CLAMP(0,colors[0][G] + table58H[d],255); + + block_error = 0; + pixel_colors = 0; + + // Loop block + for (size_t y = 0; y < BLOCKHEIGHT; ++y) + { + for (size_t x = 0; x < BLOCKWIDTH; ++x) + { + best_pixel_error = MAXIMUM_ERROR; + + // Loop possible block colors + for (uint8 c = 0; c < 2; ++c) + { + diff[R] = srcimg[3*((starty+y)*width+startx+x)+R] - CLAMP(0,possible_colors[c][R],255); + diff[G] = srcimg[3*((starty+y)*width+startx+x)+G] - CLAMP(0,possible_colors[c][G],255); + + pixel_error = weight[R]*SQUARE(diff[R]) + + weight[G]*SQUARE(diff[G]); + + // Choose best error + if (pixel_error < best_pixel_error) + { + best_pixel_error = pixel_error; + } + } + precalc_errRG[((colorRGB444_packed>>4)*8 + d)*16 + (y*4)+x] = (unsigned int) best_pixel_error; + } + } + } +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precalculates a table used in the exhaustive compression of the H-mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void precalcError58Hperceptual1000(uint8* block, uint8 (colorsRGB444)[2][3],int colorRGB444_packed, unsigned int *precalc_err) +{ + unsigned int pixel_error, + best_pixel_error; + int possible_colors[2][3]; + uint8 colors[2][3]; + unsigned int *precalc_err_tab; + int red_original; + int green_original; + int blue_original; + +#define PRECALC_ONE_58H_PERCEP(qvalue)\ + red_original = block[qvalue*4];\ + green_original = block[qvalue*4+1];\ + blue_original = block[qvalue*4+2];\ + /* unroll loop for (color = 0; color< 2; color++) */\ + best_pixel_error = PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*square_table[(possible_colors[0][R] - red_original)] \ + + PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000*square_table[(possible_colors[0][G] - green_original)]\ + + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*square_table[(possible_colors[0][B] - blue_original)];\ + pixel_error = PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000*square_table[(possible_colors[1][R] - red_original)]\ + + PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000*square_table[(possible_colors[1][G] - green_original)]\ + + PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000*square_table[(possible_colors[1][B] - blue_original)];\ + if (pixel_error < best_pixel_error)\ + best_pixel_error = pixel_error;\ + /* end unroll loop */\ + precalc_err_tab[qvalue] = best_pixel_error;\ + +#define PRECALC_ONE_TABLE_58H_PERCEP(dvalue)\ + precalc_err_tab = &precalc_err[((colorRGB444_packed*8)+dvalue)*16];\ + possible_colors[0][R] = CLAMP_LEFT_ZERO(colors[0][R] - table58H[dvalue])+255;\ + possible_colors[0][G] = CLAMP_LEFT_ZERO(colors[0][G] - table58H[dvalue])+255;\ + possible_colors[0][B] = CLAMP_LEFT_ZERO(colors[0][B] - table58H[dvalue])+255;\ + possible_colors[1][R] = CLAMP_RIGHT_255(colors[0][R] + table58H[dvalue])+255;\ + possible_colors[1][G] = CLAMP_RIGHT_255(colors[0][G] + table58H[dvalue])+255;\ + possible_colors[1][B] = CLAMP_RIGHT_255(colors[0][B] + table58H[dvalue])+255;\ + /* unrolled loop for(q = 0; q<16; q++)*/\ + PRECALC_ONE_58H_PERCEP(0)\ + PRECALC_ONE_58H_PERCEP(1)\ + PRECALC_ONE_58H_PERCEP(2)\ + PRECALC_ONE_58H_PERCEP(3)\ + PRECALC_ONE_58H_PERCEP(4)\ + PRECALC_ONE_58H_PERCEP(5)\ + PRECALC_ONE_58H_PERCEP(6)\ + PRECALC_ONE_58H_PERCEP(7)\ + PRECALC_ONE_58H_PERCEP(8)\ + PRECALC_ONE_58H_PERCEP(9)\ + PRECALC_ONE_58H_PERCEP(10)\ + PRECALC_ONE_58H_PERCEP(11)\ + PRECALC_ONE_58H_PERCEP(12)\ + PRECALC_ONE_58H_PERCEP(13)\ + PRECALC_ONE_58H_PERCEP(14)\ + PRECALC_ONE_58H_PERCEP(15)\ + /* end unroll loop */\ + + colors[0][R] = (colorsRGB444[0][R] << 4) | colorsRGB444[0][R]; + colors[0][G] = (colorsRGB444[0][G] << 4) | colorsRGB444[0][G]; + colors[0][B] = (colorsRGB444[0][B] << 4) | colorsRGB444[0][B]; + + // Test all distances + /* unroll loop for (uint8 d = 0; d < 8; ++d) */ + + PRECALC_ONE_TABLE_58H_PERCEP(0) + PRECALC_ONE_TABLE_58H_PERCEP(1) + PRECALC_ONE_TABLE_58H_PERCEP(2) + PRECALC_ONE_TABLE_58H_PERCEP(3) + PRECALC_ONE_TABLE_58H_PERCEP(4) + PRECALC_ONE_TABLE_58H_PERCEP(5) + PRECALC_ONE_TABLE_58H_PERCEP(6) + PRECALC_ONE_TABLE_58H_PERCEP(7) + + /* end unroll loop */ +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Precalculates a table used in the exhaustive compression of the H-mode. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void precalcError58H(uint8* block, uint8 (colorsRGB444)[2][3],int colorRGB444_packed, unsigned int *precalc_err) +{ + unsigned int pixel_error, + best_pixel_error; + int possible_colors[2][3]; + uint8 colors[2][3]; + unsigned int *precalc_err_tab; + int red_original; + int green_original; + int blue_original; + +#define PRECALC_ONE_58H(qvalue)\ + red_original = block[qvalue*4];\ + green_original = block[qvalue*4+1];\ + blue_original = block[qvalue*4+2];\ + /* unroll loop for (color = 0; color< 2; color++) */\ + best_pixel_error = square_table[(possible_colors[0][R] - red_original)] + square_table[(possible_colors[0][G] - green_original)] + square_table[(possible_colors[0][B] - blue_original)];\ + pixel_error = square_table[(possible_colors[1][R] - red_original)] + square_table[(possible_colors[1][G] - green_original)] + square_table[(possible_colors[1][B] - blue_original)];\ + if (pixel_error < best_pixel_error)\ + best_pixel_error = pixel_error;\ + /* end unroll loop */\ + precalc_err_tab[qvalue] = best_pixel_error;\ + +#define PRECALC_ONE_TABLE_58H(dvalue)\ + precalc_err_tab = &precalc_err[((colorRGB444_packed*8)+dvalue)*16];\ + possible_colors[0][R] = CLAMP_LEFT_ZERO(colors[0][R] - table58H[dvalue])+255;\ + possible_colors[0][G] = CLAMP_LEFT_ZERO(colors[0][G] - table58H[dvalue])+255;\ + possible_colors[0][B] = CLAMP_LEFT_ZERO(colors[0][B] - table58H[dvalue])+255;\ + possible_colors[1][R] = CLAMP_RIGHT_255(colors[0][R] + table58H[dvalue])+255;\ + possible_colors[1][G] = CLAMP_RIGHT_255(colors[0][G] + table58H[dvalue])+255;\ + possible_colors[1][B] = CLAMP_RIGHT_255(colors[0][B] + table58H[dvalue])+255;\ + /* unrolled loop for(q = 0; q<16; q++)*/\ + PRECALC_ONE_58H(0)\ + PRECALC_ONE_58H(1)\ + PRECALC_ONE_58H(2)\ + PRECALC_ONE_58H(3)\ + PRECALC_ONE_58H(4)\ + PRECALC_ONE_58H(5)\ + PRECALC_ONE_58H(6)\ + PRECALC_ONE_58H(7)\ + PRECALC_ONE_58H(8)\ + PRECALC_ONE_58H(9)\ + PRECALC_ONE_58H(10)\ + PRECALC_ONE_58H(11)\ + PRECALC_ONE_58H(12)\ + PRECALC_ONE_58H(13)\ + PRECALC_ONE_58H(14)\ + PRECALC_ONE_58H(15)\ + /* end unroll loop */\ + + colors[0][R] = (colorsRGB444[0][R] << 4) | colorsRGB444[0][R]; + colors[0][G] = (colorsRGB444[0][G] << 4) | colorsRGB444[0][G]; + colors[0][B] = (colorsRGB444[0][B] << 4) | colorsRGB444[0][B]; + + // Test all distances + /* unroll loop for (uint8 d = 0; d < 8; ++d) */ + + PRECALC_ONE_TABLE_58H(0) + PRECALC_ONE_TABLE_58H(1) + PRECALC_ONE_TABLE_58H(2) + PRECALC_ONE_TABLE_58H(3) + PRECALC_ONE_TABLE_58H(4) + PRECALC_ONE_TABLE_58H(5) + PRECALC_ONE_TABLE_58H(6) + PRECALC_ONE_TABLE_58H(7) + + /* end unroll loop */ +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Calculate a minimum error for the H-mode when doing exhaustive compression. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calculateErrorFromPrecalcR58Hperceptual1000(int *colorsRGB444_packed, unsigned int *precalc_errR, unsigned int best_err_so_far) +{ + unsigned int block_error = 0; + unsigned int best_block_error = MAXERR1000; + unsigned int *precalc_col1, *precalc_col2; + unsigned int *precalc_col1tab, *precalc_col2tab; + + precalc_col1 = &precalc_errR[(colorsRGB444_packed[0]>>8)*8*16]; + precalc_col2 = &precalc_errR[(colorsRGB444_packed[1]>>8)*8*16]; + +#define CHOICE_R58H_PERCEP(value)\ + if(precalc_col1tab[value] < precalc_col2tab[value])\ + block_error += precalc_col1tab[value];\ + else\ + block_error += precalc_col2tab[value];\ + + // Test all distances + for (uint8 d = 0; d < 8; ++d) + { + block_error = 0; + precalc_col1tab = &precalc_col1[d*16]; + precalc_col2tab = &precalc_col2[d*16]; + // Loop block + + /* unroll loop for(q = 0; q<16 && block_error < best_err_so_far; q++) */ + CHOICE_R58H_PERCEP(0) + if( block_error < best_err_so_far ) + { + CHOICE_R58H_PERCEP(1) + if( block_error < best_err_so_far ) + { + CHOICE_R58H_PERCEP(2) + if( block_error < best_err_so_far ) + { + CHOICE_R58H_PERCEP(3) + if( block_error < best_err_so_far ) + { + CHOICE_R58H_PERCEP(4) + if( block_error < best_err_so_far ) + { + CHOICE_R58H_PERCEP(5) + if( block_error < best_err_so_far ) + { + CHOICE_R58H_PERCEP(6) + if( block_error < best_err_so_far ) + { + CHOICE_R58H_PERCEP(7) + if( block_error < best_err_so_far ) + { + CHOICE_R58H_PERCEP(8) + if( block_error < best_err_so_far ) + { + CHOICE_R58H_PERCEP(9) + if( block_error < best_err_so_far ) + { + CHOICE_R58H_PERCEP(10) + if( block_error < best_err_so_far ) + { + CHOICE_R58H_PERCEP(11) + if( block_error < best_err_so_far ) + { + CHOICE_R58H_PERCEP(12) + if( block_error < best_err_so_far ) + { + CHOICE_R58H_PERCEP(13) + if( block_error < best_err_so_far ) + { + CHOICE_R58H_PERCEP(14) + if( block_error < best_err_so_far ) + { + CHOICE_R58H_PERCEP(15) + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + /* end unroll loop */ + + if (block_error < best_block_error) + best_block_error = block_error; + } + return best_block_error; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Calculate a minimum error for the H-mode when doing exhaustive compression. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calculateErrorFromPrecalcR58H(int *colorsRGB444_packed, unsigned int *precalc_errR, unsigned int best_err_so_far) +{ + unsigned int block_error = 0; + unsigned int best_block_error = MAXIMUM_ERROR; + unsigned int *precalc_col1, *precalc_col2; + unsigned int *precalc_col1tab, *precalc_col2tab; + + precalc_col1 = &precalc_errR[(colorsRGB444_packed[0]>>8)*8*16]; + precalc_col2 = &precalc_errR[(colorsRGB444_packed[1]>>8)*8*16]; + +#define CHOICE_R58H(value)\ + if(precalc_col1tab[value] < precalc_col2tab[value])\ + block_error += precalc_col1tab[value];\ + else\ + block_error += precalc_col2tab[value];\ + + // Test all distances + for (uint8 d = 0; d < 8; ++d) + { + block_error = 0; + precalc_col1tab = &precalc_col1[d*16]; + precalc_col2tab = &precalc_col2[d*16]; + // Loop block + + /* unroll loop for(q = 0; q<16 && block_error < best_err_so_far; q++) */ + CHOICE_R58H(0) + if( block_error < best_err_so_far ) + { + CHOICE_R58H(1) + if( block_error < best_err_so_far ) + { + CHOICE_R58H(2) + if( block_error < best_err_so_far ) + { + CHOICE_R58H(3) + if( block_error < best_err_so_far ) + { + CHOICE_R58H(4) + if( block_error < best_err_so_far ) + { + CHOICE_R58H(5) + if( block_error < best_err_so_far ) + { + CHOICE_R58H(6) + if( block_error < best_err_so_far ) + { + CHOICE_R58H(7) + if( block_error < best_err_so_far ) + { + CHOICE_R58H(8) + if( block_error < best_err_so_far ) + { + CHOICE_R58H(9) + if( block_error < best_err_so_far ) + { + CHOICE_R58H(10) + if( block_error < best_err_so_far ) + { + CHOICE_R58H(11) + if( block_error < best_err_so_far ) + { + CHOICE_R58H(12) + if( block_error < best_err_so_far ) + { + CHOICE_R58H(13) + if( block_error < best_err_so_far ) + { + CHOICE_R58H(14) + if( block_error < best_err_so_far ) + { + CHOICE_R58H(15) + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + /* end unroll loop */ + + if (block_error < best_block_error) + best_block_error = block_error; + + } + return best_block_error; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Calculate a minimum error for the H-mode when doing exhaustive compression. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calculateErrorFromPrecalcRG58Hperceptual1000(int *colorsRGB444_packed, unsigned int *precalc_errRG, unsigned int best_err_so_far) +{ + unsigned int block_error = 0; + unsigned int best_block_error = MAXIMUM_ERROR; + unsigned int *precalc_col1, *precalc_col2; + unsigned int *precalc_col1tab, *precalc_col2tab; + + precalc_col1 = &precalc_errRG[(colorsRGB444_packed[0]>>4)*8*16]; + precalc_col2 = &precalc_errRG[(colorsRGB444_packed[1]>>4)*8*16]; + +#define CHOICE_RG58H_PERCEP(value)\ + if(precalc_col1tab[value] < precalc_col2tab[value])\ + block_error += precalc_col1tab[value];\ + else\ + block_error += precalc_col2tab[value];\ + + // Test all distances + for (uint8 d = 0; d < 8; ++d) + { + block_error = 0; + precalc_col1tab = &precalc_col1[d*16]; + precalc_col2tab = &precalc_col2[d*16]; + // Loop block + + /* unroll loop for(q = 0; q<16 && block_error < best_err_so_far; q++) */ + CHOICE_RG58H_PERCEP(0) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H_PERCEP(1) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H_PERCEP(2) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H_PERCEP(3) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H_PERCEP(4) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H_PERCEP(5) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H_PERCEP(6) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H_PERCEP(7) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H_PERCEP(8) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H_PERCEP(9) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H_PERCEP(10) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H_PERCEP(11) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H_PERCEP(12) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H_PERCEP(13) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H_PERCEP(14) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H_PERCEP(15) + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + /* end unroll loop */ + + if (block_error < best_block_error) + best_block_error = block_error; + } + return best_block_error; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Calculate a minimum error for the H-mode when doing exhaustive compression. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calculateErrorFromPrecalcRG58H(int *colorsRGB444_packed, unsigned int *precalc_errRG, unsigned int best_err_so_far) +{ + unsigned int block_error = 0; + unsigned int best_block_error = MAXIMUM_ERROR; + unsigned int *precalc_col1, *precalc_col2; + unsigned int *precalc_col1tab, *precalc_col2tab; + + precalc_col1 = &precalc_errRG[(colorsRGB444_packed[0]>>4)*8*16]; + precalc_col2 = &precalc_errRG[(colorsRGB444_packed[1]>>4)*8*16]; + +#define CHOICE_RG58H(value)\ + if(precalc_col1tab[value] < precalc_col2tab[value])\ + block_error += precalc_col1tab[value];\ + else\ + block_error += precalc_col2tab[value];\ + + // Test all distances + for (uint8 d = 0; d < 8; ++d) + { + block_error = 0; + precalc_col1tab = &precalc_col1[d*16]; + precalc_col2tab = &precalc_col2[d*16]; + // Loop block + + /* unroll loop for(q = 0; q<16 && block_error < best_err_so_far; q++) */ + CHOICE_RG58H(0) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H(1) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H(2) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H(3) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H(4) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H(5) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H(6) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H(7) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H(8) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H(9) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H(10) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H(11) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H(12) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H(13) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H(14) + if( block_error < best_err_so_far ) + { + CHOICE_RG58H(15) + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + /* end unroll loop */ + + if (block_error < best_block_error) + best_block_error = block_error; + } + return best_block_error; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Calculate a minimum error for the H-mode when doing exhaustive compression. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calculateErrorFromPrecalc58Hperceptual1000(int *colorsRGB444_packed, unsigned int *precalc_err, unsigned int total_best_err) +{ + unsigned int block_error;\ + unsigned int *precalc_col1, *precalc_col2;\ + unsigned int *precalc_col1tab, *precalc_col2tab;\ + + unsigned int error; + +#define FIRSTCHOICE_RGB58H_PERCEP(value)\ + if(precalc_col1tab[value] < precalc_col2tab[value])\ + block_error = precalc_col1tab[value];\ + else\ + block_error = precalc_col2tab[value];\ + +#define CHOICE_RGB58H_PERCEP(value)\ + if(precalc_col1tab[value] < precalc_col2tab[value])\ + block_error += precalc_col1tab[value];\ + else\ + block_error += precalc_col2tab[value];\ + +#define ONETABLE_RGB58H_PERCEP(distance)\ + precalc_col1tab = &precalc_col1[distance*16];\ + precalc_col2tab = &precalc_col2[distance*16];\ + /* unroll loop for(q = 0; q<16 && block_error < total_best_err; q++) */\ + FIRSTCHOICE_RGB58H_PERCEP(0)\ + if( block_error < total_best_err)\ + {\ + CHOICE_RGB58H_PERCEP(1)\ + if( block_error < total_best_err)\ + {\ + CHOICE_RGB58H_PERCEP(2)\ + CHOICE_RGB58H_PERCEP(3)\ + if( block_error < total_best_err)\ + {\ + CHOICE_RGB58H_PERCEP(4)\ + CHOICE_RGB58H_PERCEP(5)\ + if( block_error < total_best_err)\ + {\ + CHOICE_RGB58H_PERCEP(6)\ + CHOICE_RGB58H_PERCEP(7)\ + if( block_error < total_best_err)\ + {\ + CHOICE_RGB58H_PERCEP(8)\ + CHOICE_RGB58H_PERCEP(9)\ + if( block_error < total_best_err)\ + {\ + CHOICE_RGB58H_PERCEP(10)\ + CHOICE_RGB58H_PERCEP(11)\ + if( block_error < total_best_err)\ + {\ + CHOICE_RGB58H_PERCEP(12)\ + CHOICE_RGB58H_PERCEP(13)\ + if( block_error < total_best_err)\ + {\ + CHOICE_RGB58H_PERCEP(14)\ + CHOICE_RGB58H_PERCEP(15)\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + /* end unroll loop */\ + if (block_error < error)\ + error = block_error;\ + +#define CALCULATE_ERROR_FROM_PRECALC_RGB58H_PERCEP\ + error = MAXERR1000;\ + precalc_col1 = &precalc_err[colorsRGB444_packed[0]*8*16];\ + precalc_col2 = &precalc_err[colorsRGB444_packed[1]*8*16];\ + /* Test all distances*/\ + /* unroll loop for (uint8 d = 0; d < 8; ++d) */\ + ONETABLE_RGB58H_PERCEP(0)\ + ONETABLE_RGB58H_PERCEP(1)\ + ONETABLE_RGB58H_PERCEP(2)\ + ONETABLE_RGB58H_PERCEP(3)\ + ONETABLE_RGB58H_PERCEP(4)\ + ONETABLE_RGB58H_PERCEP(5)\ + ONETABLE_RGB58H_PERCEP(6)\ + ONETABLE_RGB58H_PERCEP(7)\ + /* end unroll loop */\ + + CALCULATE_ERROR_FROM_PRECALC_RGB58H_PERCEP + return error;\ +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Calculate a minimum error for the H-mode when doing exhaustive compression. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int calculateErrorFromPrecalc58H(int *colorsRGB444_packed, unsigned int *precalc_err, unsigned int total_best_err) +{ + unsigned int block_error;\ + unsigned int *precalc_col1, *precalc_col2;\ + unsigned int *precalc_col1tab, *precalc_col2tab;\ + + unsigned int error; + +#define FIRSTCHOICE_RGB58H(value)\ + if(precalc_col1tab[value] < precalc_col2tab[value])\ + block_error = precalc_col1tab[value];\ + else\ + block_error = precalc_col2tab[value];\ + +#define CHOICE_RGB58H(value)\ + if(precalc_col1tab[value] < precalc_col2tab[value])\ + block_error += precalc_col1tab[value];\ + else\ + block_error += precalc_col2tab[value];\ + +#define ONETABLE_RGB58H(distance)\ + precalc_col1tab = &precalc_col1[distance*16];\ + precalc_col2tab = &precalc_col2[distance*16];\ + /* unroll loop for(q = 0; q<16 && block_error < total_best_err; q++) */\ + FIRSTCHOICE_RGB58H(0)\ + if( block_error < total_best_err)\ + {\ + CHOICE_RGB58H(1)\ + if( block_error < total_best_err)\ + {\ + CHOICE_RGB58H(2)\ + CHOICE_RGB58H(3)\ + if( block_error < total_best_err)\ + {\ + CHOICE_RGB58H(4)\ + CHOICE_RGB58H(5)\ + if( block_error < total_best_err)\ + {\ + CHOICE_RGB58H(6)\ + CHOICE_RGB58H(7)\ + if( block_error < total_best_err)\ + {\ + CHOICE_RGB58H(8)\ + CHOICE_RGB58H(9)\ + if( block_error < total_best_err)\ + {\ + CHOICE_RGB58H(10)\ + CHOICE_RGB58H(11)\ + if( block_error < total_best_err)\ + {\ + CHOICE_RGB58H(12)\ + CHOICE_RGB58H(13)\ + if( block_error < total_best_err)\ + {\ + CHOICE_RGB58H(14)\ + CHOICE_RGB58H(15)\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + }\ + /* end unroll loop */\ + if (block_error < error)\ + error = block_error;\ + +#define CALCULATE_ERROR_FROM_PRECALC_RGB58H\ + error = MAXIMUM_ERROR;\ + precalc_col1 = &precalc_err[colorsRGB444_packed[0]*8*16];\ + precalc_col2 = &precalc_err[colorsRGB444_packed[1]*8*16];\ + /* Test all distances*/\ + /* unroll loop for (uint8 d = 0; d < 8; ++d) */\ + ONETABLE_RGB58H(0)\ + ONETABLE_RGB58H(1)\ + ONETABLE_RGB58H(2)\ + ONETABLE_RGB58H(3)\ + ONETABLE_RGB58H(4)\ + ONETABLE_RGB58H(5)\ + ONETABLE_RGB58H(6)\ + ONETABLE_RGB58H(7)\ + /* end unroll loop */\ + + CALCULATE_ERROR_FROM_PRECALC_RGB58H + return error;\ +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// The below code should compress the block to 58 bits. +// This is supposed to match the first of the three modes in TWOTIMER. +// The bit layout is thought to be: +// +//|63 62 61 60 59 58|57 56 55 54|53 52 51 50|49 48 47 46|45 44 43 42|41 40 39 38|37 36 35 34|33 32| +//|-------empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|d2 d1| +// +//|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| +//|----------------------------------------index bits---------------------------------------------| +// +// The distance d is three bits, d2 (MSB), d1 and d0 (LSB). d0 is not stored explicitly. +// Instead if the 12-bit word red0,green0,blue0 < red1,green1,blue1, d0 is assumed to be 0. +// Else, it is assumed to be 1. + +// The below code should compress the block to 58 bits. +// This is supposed to match the first of the three modes in TWOTIMER. +// The bit layout is thought to be: +// +//|63 62 61 60 59 58|57 56 55 54|53 52 51 50|49 48 47 46|45 44 43 42|41 40 39 38|37 36 35 34|33 32| +//|-------empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|d2 d1| +// +//|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| +//|----------------------------------------index bits---------------------------------------------| +// +// The distance d is three bits, d2 (MSB), d1 and d0 (LSB). d0 is not stored explicitly. +// Instead if the 12-bit word red0,green0,blue0 < red1,green1,blue1, d0 is assumed to be 0. +// Else, it is assumed to be 1. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int compressBlockTHUMB58HExhaustivePerceptual(uint8 *img,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2, unsigned int best_error_so_far) +{ + unsigned int best_error_using_Hmode; + uint8 best_colorsRGB444[2][3]; + unsigned int best_pixel_indices; + uint8 best_distance; + + unsigned int error; + uint8 colorsRGB444[2][3]; + int colorsRGB444_packed[2]; + int best_colorsRGB444_packed[2]; + int colorRGB444_packed; + unsigned int pixel_indices; + uint8 distance; + unsigned int *precalc_err; // smallest error per color, table and pixel + unsigned int *precalc_err_RG; // smallest pixel error for an entire table + unsigned int *precalc_err_R; // smallest pixel error for an entire table + uint8 block[4*4*4]; + + best_error_using_Hmode = MAXERR1000; + + precalc_err = (unsigned int*) malloc(4096*8*16*sizeof(unsigned int)); + if(!precalc_err){printf("Out of memory allocating \n");exit(1);} + + precalc_err_RG = (unsigned int*) malloc(16*16*8*16*sizeof(unsigned int)); + if(!precalc_err_RG){printf("Out of memory allocating \n");exit(1);} + + precalc_err_R = (unsigned int*) malloc(16*8*16*sizeof(unsigned int)); + if(!precalc_err_R){printf("Out of memory allocating \n");exit(1);} + + unsigned int test1, test2; + best_error_using_Hmode = (unsigned int)compressBlockTHUMB58HFastestPerceptual1000(img,width, height, startx, starty, test1, test2); + best_colorsRGB444_packed[0] = 0; + best_colorsRGB444_packed[0] = GETBITSHIGH(test1, 12, 57); + best_colorsRGB444_packed[1] = 0; + best_colorsRGB444_packed[1] = GETBITSHIGH(test1, 12, 45); + + if(best_error_using_Hmode < best_error_so_far) + best_error_so_far = best_error_using_Hmode; + + int xx,yy,count = 0; + + // Use 4 bytes per pixel to make it 32-word aligned. + for(xx = 0; xx<4; xx++) + { + for(yy=0; yy<4; yy++) + { + block[(count)*4] = img[((starty+yy)*width+(startx+xx))*3]; + block[(count)*4+1] = img[((starty+yy)*width+(startx+xx))*3+1]; + block[(count)*4+2] = img[((starty+yy)*width+(startx+xx))*3+2]; + block[(count)*4+3] = 0; + count++; + } + } + + for( colorRGB444_packed = 0; colorRGB444_packed<16*16*16; colorRGB444_packed++) + { + colorsRGB444[0][0] = (colorRGB444_packed >> 8) & 0xf; + colorsRGB444[0][1] = (colorRGB444_packed >> 4) & 0xf; + colorsRGB444[0][2] = (colorRGB444_packed) & 0xf; + + precalcError58Hperceptual1000(block, colorsRGB444, colorRGB444_packed, precalc_err); + } + + for( colorRGB444_packed = 0; colorRGB444_packed<16*16*16; colorRGB444_packed+=16) + { + colorsRGB444[0][0] = (colorRGB444_packed >> 8) & 0xf; + colorsRGB444[0][1] = (colorRGB444_packed >> 4) & 0xf; + colorsRGB444[0][2] = (colorRGB444_packed) & 0xf; + precalcErrorRG_58Hperceptual1000(img, width, startx, starty, colorsRGB444, colorRGB444_packed, precalc_err_RG); + } + + for( colorRGB444_packed = 0; colorRGB444_packed<16*16*16; colorRGB444_packed+=16*16) + { + colorsRGB444[0][0] = (colorRGB444_packed >> 8) & 0xf; + colorsRGB444[0][1] = (colorRGB444_packed >> 4) & 0xf; + colorsRGB444[0][2] = (colorRGB444_packed) & 0xf; + precalcErrorR_58Hperceptual1000(img, width, startx, starty, colorsRGB444, colorRGB444_packed, precalc_err_R); + } + + int trycols = 0; + int allcols = 0; + + for( colorsRGB444[0][0] = 0; colorsRGB444[0][0] <16; colorsRGB444[0][0]++) + { + colorsRGB444_packed[0] = colorsRGB444[0][0]*256; + for( colorsRGB444[1][0] = 0; colorsRGB444[1][0] <16; colorsRGB444[1][0]++) + { + colorsRGB444_packed[1] = colorsRGB444[1][0]*256; + if(colorsRGB444_packed[0] <= colorsRGB444_packed[1]) + { + error = calculateErrorFromPrecalcR58Hperceptual1000(colorsRGB444_packed, precalc_err_R, best_error_so_far); + if(error < best_error_so_far) + { + for( colorsRGB444[0][1] = 0; colorsRGB444[0][1] <16; colorsRGB444[0][1]++) + { + colorsRGB444_packed[0] = colorsRGB444[0][0]*256 + colorsRGB444[0][1]*16; + for( colorsRGB444[1][1] = 0; colorsRGB444[1][1] <16; colorsRGB444[1][1]++) + { + colorsRGB444_packed[1] = colorsRGB444[1][0]*256 + colorsRGB444[1][1]*16; + if(colorsRGB444_packed[0] <= colorsRGB444_packed[1]) + { + error = calculateErrorFromPrecalcRG58Hperceptual1000(colorsRGB444_packed, precalc_err_RG, best_error_so_far); + if(error < best_error_so_far) + { + for( colorsRGB444[0][2] = 0; colorsRGB444[0][2] <16; colorsRGB444[0][2]++) + { + colorsRGB444_packed[0] = colorsRGB444[0][0]*256 + colorsRGB444[0][1]*16 + colorsRGB444[0][2]; + for( colorsRGB444[1][2] = 0; colorsRGB444[1][2] <16; colorsRGB444[1][2]++) + { + colorsRGB444_packed[1] = colorsRGB444[1][0]*256 + colorsRGB444[1][1]*16 + colorsRGB444[1][2]; + if(colorsRGB444_packed[0] < colorsRGB444_packed[1]) + { + error = calculateErrorFromPrecalc58Hperceptual1000(colorsRGB444_packed, precalc_err, best_error_so_far); + if(error < best_error_so_far) + { + best_error_so_far = error; + best_error_using_Hmode = error; + best_colorsRGB444_packed[0] = colorsRGB444_packed[0]; + best_colorsRGB444_packed[1] = colorsRGB444_packed[1]; + } + } + } + } + } + } + } + } + } + } + } + } + best_colorsRGB444[0][0] = (best_colorsRGB444_packed[0] >> 8) & 0xf; + best_colorsRGB444[0][1] = (best_colorsRGB444_packed[0] >> 4) & 0xf; + best_colorsRGB444[0][2] = (best_colorsRGB444_packed[0]) & 0xf; + best_colorsRGB444[1][0] = (best_colorsRGB444_packed[1] >> 8) & 0xf; + best_colorsRGB444[1][1] = (best_colorsRGB444_packed[1] >> 4) & 0xf; + best_colorsRGB444[1][2] = (best_colorsRGB444_packed[1]) & 0xf; + + free(precalc_err); + free(precalc_err_RG); + free(precalc_err_R); + + error = (unsigned int) calculateErrorAndCompress58Hperceptual1000(img, width, startx, starty, best_colorsRGB444, distance, pixel_indices); + best_distance = distance; + best_pixel_indices = pixel_indices; + + // | col0 >= col1 col0 < col1 + //------------------------------------------------------ + // (dist & 1) = 1 | no need to swap | need to swap + // |-----------------+---------------- + // (dist & 1) = 0 | need to swap | no need to swap + // + // This can be done with an xor test. + + best_colorsRGB444_packed[0] = (best_colorsRGB444[0][R] << 8) + (best_colorsRGB444[0][G] << 4) + best_colorsRGB444[0][B]; + best_colorsRGB444_packed[1] = (best_colorsRGB444[1][R] << 8) + (best_colorsRGB444[1][G] << 4) + best_colorsRGB444[1][B]; + if( (best_colorsRGB444_packed[0] >= best_colorsRGB444_packed[1]) ^ ((best_distance & 1)==1) ) + { + swapColors(best_colorsRGB444); + + // Reshuffle pixel indices to to exchange C1 with C3, and C2 with C4 + best_pixel_indices = (0x55555555 & best_pixel_indices) | (0xaaaaaaaa & (~best_pixel_indices)); + } + + // Put the compress params into the compression block + compressed1 = 0; + + PUTBITSHIGH( compressed1, best_colorsRGB444[0][R], 4, 57); + PUTBITSHIGH( compressed1, best_colorsRGB444[0][G], 4, 53); + PUTBITSHIGH( compressed1, best_colorsRGB444[0][B], 4, 49); + PUTBITSHIGH( compressed1, best_colorsRGB444[1][R], 4, 45); + PUTBITSHIGH( compressed1, best_colorsRGB444[1][G], 4, 41); + PUTBITSHIGH( compressed1, best_colorsRGB444[1][B], 4, 37); + PUTBITSHIGH( compressed1, (best_distance >> 1), 2, 33); + best_pixel_indices=indexConversion(best_pixel_indices); + compressed2 = 0; + PUTBITS( compressed2, best_pixel_indices, 32, 31); + + return best_error_using_Hmode; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// The below code should compress the block to 58 bits. +// This is supposed to match the first of the three modes in TWOTIMER. +// The bit layout is thought to be: +// +//|63 62 61 60 59 58|57 56 55 54|53 52 51 50|49 48 47 46|45 44 43 42|41 40 39 38|37 36 35 34|33 32| +//|-------empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|d2 d1| +// +//|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| +//|----------------------------------------index bits---------------------------------------------| +// +// The distance d is three bits, d2 (MSB), d1 and d0 (LSB). d0 is not stored explicitly. +// Instead if the 12-bit word red0,green0,blue0 < red1,green1,blue1, d0 is assumed to be 0. +// Else, it is assumed to be 1. + +// The below code should compress the block to 58 bits. +// This is supposed to match the first of the three modes in TWOTIMER. +// The bit layout is thought to be: +// +//|63 62 61 60 59 58|57 56 55 54|53 52 51 50|49 48 47 46|45 44 43 42|41 40 39 38|37 36 35 34|33 32| +//|-------empty-----|---red 0---|--green 0--|--blue 0---|---red 1---|--green 1--|--blue 1---|d2 d1| +// +//|31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 09 08 07 06 05 04 03 02 01 00| +//|----------------------------------------index bits---------------------------------------------| +// +// The distance d is three bits, d2 (MSB), d1 and d0 (LSB). d0 is not stored explicitly. +// Instead if the 12-bit word red0,green0,blue0 < red1,green1,blue1, d0 is assumed to be 0. +// Else, it is assumed to be 1. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +unsigned int compressBlockTHUMB58HExhaustive(uint8 *img,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2, unsigned int best_error_so_far) +{ + unsigned int best_error_using_Hmode; + uint8 best_colorsRGB444[2][3]; + unsigned int best_pixel_indices; + uint8 best_distance; + + unsigned int error; + uint8 colorsRGB444[2][3]; + int colorsRGB444_packed[2]; + int best_colorsRGB444_packed[2]; + int colorRGB444_packed; + unsigned int pixel_indices; + uint8 distance; + unsigned int *precalc_err; // smallest error per color, table and pixel + unsigned int *precalc_err_RG; // smallest pixel error for an entire table + unsigned int *precalc_err_R; // smallest pixel error for an entire table + uint8 block[4*4*4]; + + best_error_using_Hmode = MAXIMUM_ERROR; + + precalc_err = (unsigned int*) malloc(4096*8*16*sizeof(unsigned int)); + if(!precalc_err){printf("Out of memory allocating \n");exit(1);} + + precalc_err_RG = (unsigned int*) malloc(16*16*8*16*sizeof(unsigned int)); + if(!precalc_err_RG){printf("Out of memory allocating \n");exit(1);} + + precalc_err_R = (unsigned int*) malloc(16*8*16*sizeof(unsigned int)); + if(!precalc_err_R){printf("Out of memory allocating \n");exit(1);} + + unsigned int test1, test2; + best_error_using_Hmode = (unsigned int)compressBlockTHUMB58HFastest(img,width, height, startx, starty, test1, test2); + best_colorsRGB444_packed[0] = 0; + best_colorsRGB444_packed[0] = GETBITSHIGH(test1, 12, 57); + best_colorsRGB444_packed[1] = 0; + best_colorsRGB444_packed[1] = GETBITSHIGH(test1, 12, 45); + + if(best_error_using_Hmode < best_error_so_far) + best_error_so_far = best_error_using_Hmode; + + int xx,yy,count = 0; + + // Reshuffle pixels so that the top left 2x2 pixels arrive first, then the top right 2x2 pixels etc. Also put use 4 bytes per pixel to make it 32-word aligned. + for(xx = 0; xx<4; xx++) + { + for(yy=0; yy<4; yy++) + { + block[(count)*4] = img[((starty+yy)*width+(startx+xx))*3]; + block[(count)*4+1] = img[((starty+yy)*width+(startx+xx))*3+1]; + block[(count)*4+2] = img[((starty+yy)*width+(startx+xx))*3+2]; + block[(count)*4+3] = 0; + count++; + } + } + + for( colorRGB444_packed = 0; colorRGB444_packed<16*16*16; colorRGB444_packed++) + { + colorsRGB444[0][0] = (colorRGB444_packed >> 8) & 0xf; + colorsRGB444[0][1] = (colorRGB444_packed >> 4) & 0xf; + colorsRGB444[0][2] = (colorRGB444_packed) & 0xf; + precalcError58H(block, colorsRGB444, colorRGB444_packed, precalc_err); + } + + for( colorRGB444_packed = 0; colorRGB444_packed<16*16*16; colorRGB444_packed+=16) + { + colorsRGB444[0][0] = (colorRGB444_packed >> 8) & 0xf; + colorsRGB444[0][1] = (colorRGB444_packed >> 4) & 0xf; + colorsRGB444[0][2] = (colorRGB444_packed) & 0xf; + precalcErrorRG_58H(img, width, startx, starty, colorsRGB444, colorRGB444_packed, precalc_err_RG); + } + + for( colorRGB444_packed = 0; colorRGB444_packed<16*16*16; colorRGB444_packed+=16*16) + { + colorsRGB444[0][0] = (colorRGB444_packed >> 8) & 0xf; + colorsRGB444[0][1] = (colorRGB444_packed >> 4) & 0xf; + colorsRGB444[0][2] = (colorRGB444_packed) & 0xf; + precalcErrorR_58H(img, width, startx, starty, colorsRGB444, colorRGB444_packed, precalc_err_R); + } + + int trycols = 0; + int allcols = 0; + + for( colorsRGB444[0][0] = 0; colorsRGB444[0][0] <16; colorsRGB444[0][0]++) + { + colorsRGB444_packed[0] = colorsRGB444[0][0]*256; + for( colorsRGB444[1][0] = 0; colorsRGB444[1][0] <16; colorsRGB444[1][0]++) + { + colorsRGB444_packed[1] = colorsRGB444[1][0]*256; + if(colorsRGB444_packed[0] <= colorsRGB444_packed[1]) + { + error = calculateErrorFromPrecalcR58H(colorsRGB444_packed, precalc_err_R, best_error_so_far); + if(error < best_error_so_far) + { + for( colorsRGB444[0][1] = 0; colorsRGB444[0][1] <16; colorsRGB444[0][1]++) + { + colorsRGB444_packed[0] = colorsRGB444[0][0]*256 + colorsRGB444[0][1]*16; + for( colorsRGB444[1][1] = 0; colorsRGB444[1][1] <16; colorsRGB444[1][1]++) + { + colorsRGB444_packed[1] = colorsRGB444[1][0]*256 + colorsRGB444[1][1]*16; + if(colorsRGB444_packed[0] <= colorsRGB444_packed[1]) + { + error = calculateErrorFromPrecalcRG58H(colorsRGB444_packed, precalc_err_RG, best_error_so_far); + if(error < best_error_so_far) + { + for( colorsRGB444[0][2] = 0; colorsRGB444[0][2] <16; colorsRGB444[0][2]++) + { + colorsRGB444_packed[0] = colorsRGB444[0][0]*256 + colorsRGB444[0][1]*16 + colorsRGB444[0][2]; + for( colorsRGB444[1][2] = 0; colorsRGB444[1][2] <16; colorsRGB444[1][2]++) + { + colorsRGB444_packed[1] = colorsRGB444[1][0]*256 + colorsRGB444[1][1]*16 + colorsRGB444[1][2]; + if(colorsRGB444_packed[0] < colorsRGB444_packed[1]) + { + error = calculateErrorFromPrecalc58H(colorsRGB444_packed, precalc_err, best_error_so_far); + if(error < best_error_so_far) + { + best_error_so_far = error; + best_error_using_Hmode = error; + best_colorsRGB444_packed[0] = colorsRGB444_packed[0]; + best_colorsRGB444_packed[1] = colorsRGB444_packed[1]; + } + } + } + } + } + } + } + } + } + } + } + } + best_colorsRGB444[0][0] = (best_colorsRGB444_packed[0] >> 8) & 0xf; + best_colorsRGB444[0][1] = (best_colorsRGB444_packed[0] >> 4) & 0xf; + best_colorsRGB444[0][2] = (best_colorsRGB444_packed[0]) & 0xf; + best_colorsRGB444[1][0] = (best_colorsRGB444_packed[1] >> 8) & 0xf; + best_colorsRGB444[1][1] = (best_colorsRGB444_packed[1] >> 4) & 0xf; + best_colorsRGB444[1][2] = (best_colorsRGB444_packed[1]) & 0xf; + + free(precalc_err); + free(precalc_err_RG); + free(precalc_err_R); + + error = (unsigned int) calculateErrorAndCompress58H(img, width, startx, starty, best_colorsRGB444, distance, pixel_indices); + best_distance = distance; + best_pixel_indices = pixel_indices; + + // | col0 >= col1 col0 < col1 + //------------------------------------------------------ + // (dist & 1) = 1 | no need to swap | need to swap + // |-----------------+---------------- + // (dist & 1) = 0 | need to swap | no need to swap + // + // This can be done with an xor test. + + best_colorsRGB444_packed[0] = (best_colorsRGB444[0][R] << 8) + (best_colorsRGB444[0][G] << 4) + best_colorsRGB444[0][B]; + best_colorsRGB444_packed[1] = (best_colorsRGB444[1][R] << 8) + (best_colorsRGB444[1][G] << 4) + best_colorsRGB444[1][B]; + if( (best_colorsRGB444_packed[0] >= best_colorsRGB444_packed[1]) ^ ((best_distance & 1)==1) ) + { + swapColors(best_colorsRGB444); + + // Reshuffle pixel indices to to exchange C1 with C3, and C2 with C4 + best_pixel_indices = (0x55555555 & best_pixel_indices) | (0xaaaaaaaa & (~best_pixel_indices)); + } + + // Put the compress params into the compression block + compressed1 = 0; + + PUTBITSHIGH( compressed1, best_colorsRGB444[0][R], 4, 57); + PUTBITSHIGH( compressed1, best_colorsRGB444[0][G], 4, 53); + PUTBITSHIGH( compressed1, best_colorsRGB444[0][B], 4, 49); + PUTBITSHIGH( compressed1, best_colorsRGB444[1][R], 4, 45); + PUTBITSHIGH( compressed1, best_colorsRGB444[1][G], 4, 41); + PUTBITSHIGH( compressed1, best_colorsRGB444[1][B], 4, 37); + PUTBITSHIGH( compressed1, (best_distance >> 1), 2, 33); + best_pixel_indices=indexConversion(best_pixel_indices); + compressed2 = 0; + PUTBITS( compressed2, best_pixel_indices, 32, 31); + + return best_error_using_Hmode; +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Compress a block exhaustively for the ETC1 codec. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void compressBlockETC1Exhaustive(uint8 *img, uint8 *imgdec,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2) +{ + unsigned int error_currently_best; + + unsigned int etc1_differential_word1; + unsigned int etc1_differential_word2; + unsigned int error_etc1_differential; + + unsigned int etc1_individual_word1; + unsigned int etc1_individual_word2; + unsigned int error_etc1_individual; + + unsigned int error_best; + signed char best_char; + int best_mode; + + error_currently_best = 255*255*16*3; + + // First pass -- quickly find a low error so that we can later cull away a lot of + // calculations later that are guaranteed to be higher than that error. + unsigned int error_etc1; + unsigned int etc1_word1; + unsigned int etc1_word2; + + error_etc1 = (unsigned int) compressBlockDiffFlipFast(img, imgdec, width, height, startx, starty, etc1_word1, etc1_word2); + if(error_etc1 < error_currently_best) + error_currently_best = error_etc1; + + error_etc1_individual = compressBlockIndividualExhaustive(img, width, height, startx, starty, etc1_individual_word1, etc1_individual_word2, error_currently_best); + if(error_etc1_individual < error_currently_best) + error_currently_best = error_etc1_individual; + + error_etc1_differential = compressBlockDifferentialExhaustive(img, width, height, startx, starty, etc1_differential_word1, etc1_differential_word2, error_currently_best); + if(error_etc1_differential < error_currently_best) + error_currently_best = error_etc1_differential; + + error_best = error_etc1_differential; + compressed1 = etc1_differential_word1; + compressed2 = etc1_differential_word2; + best_char = '.'; + best_mode = MODE_ETC1; + + if(error_etc1_individual < error_best) + { + compressed1 = etc1_individual_word1; + compressed2 = etc1_individual_word2; + best_char = ','; + error_best = error_etc1_individual; + best_mode = MODE_ETC1; + } + + if(error_etc1 < error_best) + { + compressed1 = etc1_word1; + compressed2 = etc1_word2; + best_char = '.'; + error_best = error_etc1; + best_mode = MODE_ETC1; + } +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Compress a block exhaustively for the ETC1 codec using perceptual error measure. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void compressBlockETC1ExhaustivePerceptual(uint8 *img, uint8 *imgdec,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2) +{ + unsigned int error_currently_best; + + unsigned int etc1_differential_word1; + unsigned int etc1_differential_word2; + unsigned int error_etc1_differential; + + unsigned int etc1_individual_word1; + unsigned int etc1_individual_word2; + unsigned int error_etc1_individual; + + unsigned int error_best; + signed char best_char; + int best_mode; + + + error_currently_best = 255*255*16*1000; + + // First pass -- quickly find a low error so that we can later cull away a lot of + // calculations later that are guaranteed to be higher than that error. + unsigned int error_etc1; + unsigned int etc1_word1; + unsigned int etc1_word2; + + compressBlockDiffFlipFastPerceptual(img, imgdec, width, height, startx, starty, etc1_word1, etc1_word2); + decompressBlockDiffFlip(etc1_word1, etc1_word2, imgdec, width, height, startx, starty); + error_etc1 = 1000*calcBlockPerceptualErrorRGB(img, imgdec, width, height, startx, starty); + if(error_etc1 < error_currently_best) + error_currently_best = error_etc1; + + // Second pass --- now find the lowest error, but only if it is lower than error_currently_best + + error_etc1_differential = compressBlockDifferentialExhaustivePerceptual(img, width, height, startx, starty, etc1_differential_word1, etc1_differential_word2, error_currently_best); + if(error_etc1_differential < error_currently_best) + error_currently_best = error_etc1_differential; + + error_etc1_individual = compressBlockIndividualExhaustivePerceptual(img, width, height, startx, starty, etc1_individual_word1, etc1_individual_word2, error_currently_best); + if(error_etc1_individual < error_currently_best) + error_currently_best = error_etc1_individual; + + // Now find the best error. + error_best = error_etc1; + compressed1 = etc1_word1; + compressed2 = etc1_word2; + best_char = '.'; + best_mode = MODE_ETC1; + + if(error_etc1_differential < error_best) + { + error_best = error_etc1_differential; + compressed1 = etc1_differential_word1; + compressed2 = etc1_differential_word2; + best_char = '.'; + best_mode = MODE_ETC1; + } + + if(error_etc1_individual < error_best) + { + compressed1 = etc1_individual_word1; + compressed2 = etc1_individual_word2; + best_char = ','; + error_best = error_etc1_individual; + best_mode = MODE_ETC1; + } +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Compress a block exhaustively for the ETC2 RGB codec using perceptual error measure. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void compressBlockETC2ExhaustivePerceptual(uint8 *img, uint8 *imgdec,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2) +{ + unsigned int error_currently_best; + + unsigned int etc1_differential_word1; + unsigned int etc1_differential_word2; + unsigned int error_etc1_differential; + + unsigned int etc1_individual_word1; + unsigned int etc1_individual_word2; + unsigned int error_etc1_individual; + + unsigned int planar57_word1; + unsigned int planar57_word2; + unsigned int planar_word1; + unsigned int planar_word2; + double error_planar; + unsigned int error_planar_red, error_planar_green, error_planar_blue; + + unsigned int thumbH58_word1; + unsigned int thumbH58_word2; + unsigned int thumbH_word1; + unsigned int thumbH_word2; + unsigned int error_thumbH; + + unsigned int thumbT59_word1; + unsigned int thumbT59_word2; + unsigned int thumbT_word1; + unsigned int thumbT_word2; + unsigned int error_thumbT; + + unsigned int error_best; + signed char best_char; + int best_mode; + + error_currently_best = 255*255*16*1000; + + // First pass -- quickly find a low error so that we can later cull away a lot of + // calculations later that are guaranteed to be higher than that error. + unsigned int error_etc1; + unsigned int etc1_word1; + unsigned int etc1_word2; + + compressBlockDiffFlipFastPerceptual(img, imgdec, width, height, startx, starty, etc1_word1, etc1_word2); + decompressBlockDiffFlip(etc1_word1, etc1_word2, imgdec, width, height, startx, starty); + error_etc1 = 1000*calcBlockPerceptualErrorRGB(img, imgdec, width, height, startx, starty); + if(error_etc1 < error_currently_best) + error_currently_best = error_etc1; + + // The planar mode treats every channel independently and should not be affected by the weights in the error measure. + // We can hence use the nonperceptual version of the encoder also to find the best perceptual description of the block. + compressBlockPlanar57(img, width, height, startx, starty, planar57_word1, planar57_word2); + decompressBlockPlanar57errorPerComponent(planar57_word1, planar57_word2, imgdec, width, height, startx, starty, img, error_planar_red, error_planar_green, error_planar_blue); + error_planar = 1000*calcBlockPerceptualErrorRGB(img, imgdec, width, height, startx, starty); + stuff57bits(planar57_word1, planar57_word2, planar_word1, planar_word2); + if(error_planar < error_currently_best) + error_currently_best = (unsigned int) error_planar; + + error_thumbT = (unsigned int) compressBlockTHUMB59TFastestPerceptual1000(img,width, height, startx, starty, thumbT59_word1, thumbT59_word2); + stuff59bits(thumbT59_word1, thumbT59_word2, thumbT_word1, thumbT_word2); + if(error_thumbT < error_currently_best) + error_currently_best = error_thumbT; + + error_thumbH = (unsigned int) compressBlockTHUMB58HFastestPerceptual1000(img,width,height,startx, starty, thumbH58_word1, thumbH58_word2); + stuff58bits(thumbH58_word1, thumbH58_word2, thumbH_word1, thumbH_word2); + if(error_thumbH < error_currently_best) + error_currently_best = error_thumbH; + + // Second pass --- now find the lowest error, but only if it is lower than error_currently_best + + // Correct the individual errors for the different planes so that they sum to 1000 instead of 1. + error_planar_red *=PERCEPTUAL_WEIGHT_R_SQUARED_TIMES1000; + error_planar_green *=PERCEPTUAL_WEIGHT_G_SQUARED_TIMES1000; + error_planar_blue *=PERCEPTUAL_WEIGHT_B_SQUARED_TIMES1000; + compressBlockPlanar57ExhaustivePerceptual(img, width, height, startx, starty, planar57_word1, planar57_word2, error_currently_best, error_planar_red, error_planar_green, error_planar_blue); + decompressBlockPlanar57(planar57_word1, planar57_word2, imgdec, width, height, startx, starty); + error_planar = 1000*calcBlockPerceptualErrorRGB(img, imgdec, width, height, startx, starty); + stuff57bits(planar57_word1, planar57_word2, planar_word1, planar_word2); + if(error_planar < error_currently_best) + error_currently_best = (unsigned int) error_planar; + + error_etc1_differential = compressBlockDifferentialExhaustivePerceptual(img, width, height, startx, starty, etc1_differential_word1, etc1_differential_word2, error_currently_best); + if(error_etc1_differential < error_currently_best) + error_currently_best = error_etc1_differential; + + error_etc1_individual = compressBlockIndividualExhaustivePerceptual(img, width, height, startx, starty, etc1_individual_word1, etc1_individual_word2, error_currently_best); + if(error_etc1_individual < error_currently_best) + error_currently_best = error_etc1_individual; + + error_thumbH = compressBlockTHUMB58HExhaustivePerceptual(img,width,height,startx, starty, thumbH58_word1, thumbH58_word2, error_currently_best); + stuff58bits(thumbH58_word1, thumbH58_word2, thumbH_word1, thumbH_word2); + if( error_thumbH < error_currently_best) + error_currently_best = error_thumbH; + + error_thumbT = compressBlockTHUMB59TExhaustivePerceptual(img,width, height, startx, starty, thumbT59_word1, thumbT59_word2, error_currently_best); + stuff59bits(thumbT59_word1, thumbT59_word2, thumbT_word1, thumbT_word2); + if(error_thumbT < error_currently_best) + error_currently_best = error_thumbT; + + // Now find the best error. + error_best = error_etc1; + compressed1 = etc1_word1; + compressed2 = etc1_word2; + best_char = '.'; + best_mode = MODE_ETC1; + + if(error_etc1_differential < error_best) + { + error_best = error_etc1_differential; + compressed1 = etc1_differential_word1; + compressed2 = etc1_differential_word2; + best_char = '.'; + best_mode = MODE_ETC1; + } + + if(error_etc1_individual < error_best) + { + compressed1 = etc1_individual_word1; + compressed2 = etc1_individual_word2; + best_char = ','; + error_best = error_etc1_individual; + best_mode = MODE_ETC1; + } + if(error_planar < error_best) + { + compressed1 = planar_word1; + compressed2 = planar_word2; + best_char = 'p'; + error_best = (unsigned int) error_planar; + best_mode = MODE_PLANAR; + } + if(error_thumbH < error_best) + { + compressed1 = thumbH_word1; + compressed2 = thumbH_word2; + best_char = 'H'; + error_best = error_thumbH; + best_mode = MODE_THUMB_H; + } + if(error_thumbT < error_best) + { + compressed1 = thumbT_word1; + compressed2 = thumbT_word2; + best_char = 'T'; + error_best = error_thumbT; + best_mode = MODE_THUMB_T; + } +} +#endif + +#if EXHAUSTIVE_CODE_ACTIVE +// Compress a block exhaustively for the ETC2 RGB codec. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void compressBlockETC2Exhaustive(uint8 *img, uint8 *imgdec,int width,int height,int startx,int starty, unsigned int &compressed1, unsigned int &compressed2) +{ + unsigned int error_currently_best; + + unsigned int etc1_differential_word1; + unsigned int etc1_differential_word2; + unsigned int error_etc1_differential; + + unsigned int etc1_individual_word1; + unsigned int etc1_individual_word2; + unsigned int error_etc1_individual; + + unsigned int planar57_word1; + unsigned int planar57_word2; + unsigned int planar_word1; + unsigned int planar_word2; + double error_planar; + unsigned int error_planar_red; + unsigned int error_planar_green; + unsigned int error_planar_blue; + + unsigned int thumbH58_word1; + unsigned int thumbH58_word2; + unsigned int thumbH_word1; + unsigned int thumbH_word2; + unsigned int error_thumbH; + + unsigned int thumbT59_word1; + unsigned int thumbT59_word2; + unsigned int thumbT_word1; + unsigned int thumbT_word2; + unsigned int error_thumbT; + + unsigned int error_best; + signed char best_char; + int best_mode; + + error_currently_best = 255*255*16*3; + + // First pass -- quickly find a low error so that we can later cull away a lot of + // calculations later that are guaranteed to be higher than that error. + unsigned int error_etc1; + unsigned int etc1_word1; + unsigned int etc1_word2; + + error_etc1 = (unsigned int) compressBlockDiffFlipFast(img, imgdec, width, height, startx, starty, etc1_word1, etc1_word2); + if(error_etc1 < error_currently_best) + error_currently_best = error_etc1; + + compressBlockPlanar57(img, width, height, startx, starty, planar57_word1, planar57_word2); + decompressBlockPlanar57errorPerComponent(planar57_word1, planar57_word2, imgdec, width, height, startx, starty, img, error_planar_red, error_planar_green, error_planar_blue); + error_planar = calcBlockErrorRGB(img, imgdec, width, height, startx, starty); + stuff57bits(planar57_word1, planar57_word2, planar_word1, planar_word2); + if(error_planar < error_currently_best) + error_currently_best = (unsigned int) error_planar; + + error_thumbT = (unsigned int) compressBlockTHUMB59TFastest(img,width, height, startx, starty, thumbT59_word1, thumbT59_word2); + stuff59bits(thumbT59_word1, thumbT59_word2, thumbT_word1, thumbT_word2); + if(error_thumbT < error_currently_best) + error_currently_best = error_thumbT; + + error_thumbH = (unsigned int) compressBlockTHUMB58HFastest(img,width,height,startx, starty, thumbH58_word1, thumbH58_word2); + stuff58bits(thumbH58_word1, thumbH58_word2, thumbH_word1, thumbH_word2); + if(error_thumbH < error_currently_best) + error_currently_best = error_thumbH; + + // Second pass --- now find the lowest error, but only if it is lower than error_currently_best + error_etc1_differential = compressBlockDifferentialExhaustive(img, width, height, startx, starty, etc1_differential_word1, etc1_differential_word2, error_currently_best); + if(error_etc1_differential < error_currently_best) + error_currently_best = error_etc1_differential; + + compressBlockPlanar57Exhaustive(img, width, height, startx, starty, planar57_word1, planar57_word2, error_currently_best, error_planar_red, error_planar_green, error_planar_blue); + decompressBlockPlanar57(planar57_word1, planar57_word2, imgdec, width, height, startx, starty); + error_planar = calcBlockErrorRGB(img, imgdec, width, height, startx, starty); + stuff57bits(planar57_word1, planar57_word2, planar_word1, planar_word2); + if(error_planar < error_currently_best) + error_currently_best = (unsigned int) error_planar; + + error_etc1_individual = compressBlockIndividualExhaustive(img, width, height, startx, starty, etc1_individual_word1, etc1_individual_word2, error_currently_best); + if(error_etc1_individual < error_currently_best) + error_currently_best = error_etc1_individual; + + error_thumbH = compressBlockTHUMB58HExhaustive(img,width,height,startx, starty, thumbH58_word1, thumbH58_word2, error_currently_best); + if( error_thumbH < error_currently_best) + error_currently_best = error_thumbH; + stuff58bits(thumbH58_word1, thumbH58_word2, thumbH_word1, thumbH_word2); + + error_thumbT = compressBlockTHUMB59TExhaustive(img,width, height, startx, starty, thumbT59_word1, thumbT59_word2, error_currently_best); + if(error_thumbT < error_currently_best) + error_currently_best = error_thumbT; + stuff59bits(thumbT59_word1, thumbT59_word2, thumbT_word1, thumbT_word2); + + error_best = 255*255*3*16; + // Now find the best error. + error_best = error_etc1; + compressed1 = etc1_word1; + compressed2 = etc1_word2; + best_char = '.'; + best_mode = MODE_ETC1; + + if(error_etc1_differential < error_best) + { + error_best = error_etc1_differential; + compressed1 = etc1_differential_word1; + compressed2 = etc1_differential_word2; + best_char = '.'; + best_mode = MODE_ETC1; + } + if(error_etc1_individual < error_best) + { + compressed1 = etc1_individual_word1; + compressed2 = etc1_individual_word2; + best_char = ','; + error_best = error_etc1_individual; + best_mode = MODE_ETC1; + } + if(error_planar < error_best) + { + compressed1 = planar_word1; + compressed2 = planar_word2; + best_char = 'p'; + error_best = (unsigned int) error_planar; + best_mode = MODE_PLANAR; + } + if(error_thumbH < error_best) + { + compressed1 = thumbH_word1; + compressed2 = thumbH_word2; + best_char = 'H'; + error_best = error_thumbH; + best_mode = MODE_THUMB_H; + } + if(error_thumbT < error_best) + { + compressed1 = thumbT_word1; + compressed2 = thumbT_word2; + best_char = 'T'; + error_best = error_thumbT; + best_mode = MODE_THUMB_T; + } +} +#endif + +//// Exhaustive code ends here. + + +// Compress an image file. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void compressImageFile(uint8 *img, uint8 *alphaimg,int width,int height,char *dstfile, int expandedwidth, int expandedheight) +{ + FILE *f; + int x,y,w,h; + unsigned int block1, block2; + unsigned short wi, hi; + unsigned char magic[4]; + unsigned char version[2]; + unsigned short texture_type=format; + uint8 *imgdec; + uint8* alphaimg2; + imgdec = (unsigned char*) malloc(expandedwidth*expandedheight*3); + if(!imgdec) + { + printf("Could not allocate decompression buffer --- exiting\n"); + } + + magic[0] = 'P'; magic[1] = 'K'; magic[2] = 'M'; magic[3] = ' '; + + if(codec==CODEC_ETC2) + { + version[0] = '2'; version[1] = '0'; + } + else + { + version[0] = '1'; version[1] = '0'; + } + + if(f=fopen(dstfile,"wb")) + { + w=expandedwidth/4; w*=4; + h=expandedheight/4; h*=4; + wi = w; + hi = h; + if(ktxFile) + { + //.ktx file: KTX header followed by compressed binary data. + KTX_header header; + //identifier + for(int i=0; i<12; i++) + { + header.identifier[i]=ktx_identifier[i]; + } + //endianess int.. if this comes out reversed, all of the other ints will too. + header.endianness=KTX_ENDIAN_REF; + + //these values are always 0/1 for compressed textures. + header.glType=0; + header.glTypeSize=1; + header.glFormat=0; + + header.pixelWidth=width; + header.pixelHeight=height; + header.pixelDepth=0; + + //we only support single non-mipmapped non-cubemap textures.. + header.numberOfArrayElements=0; + header.numberOfFaces=1; + header.numberOfMipmapLevels=1; + + //and no metadata.. + header.bytesOfKeyValueData=0; + + int halfbytes=1; + //header.glInternalFormat=? + //header.glBaseInternalFormat=? + if(format==ETC2PACKAGE_R_NO_MIPMAPS) + { + header.glBaseInternalFormat=GL_R; + if(formatSigned) + header.glInternalFormat=GL_COMPRESSED_SIGNED_R11_EAC; + else + header.glInternalFormat=GL_COMPRESSED_R11_EAC; + } + else if(format==ETC2PACKAGE_RG_NO_MIPMAPS) + { + halfbytes=2; + header.glBaseInternalFormat=GL_RG; + if(formatSigned) + header.glInternalFormat=GL_COMPRESSED_SIGNED_RG11_EAC; + else + header.glInternalFormat=GL_COMPRESSED_RG11_EAC; + } + else if(format==ETC2PACKAGE_RGB_NO_MIPMAPS) + { + header.glBaseInternalFormat=GL_RGB; + header.glInternalFormat=GL_COMPRESSED_RGB8_ETC2; + } + else if(format==ETC2PACKAGE_sRGB_NO_MIPMAPS) + { + header.glBaseInternalFormat=GL_SRGB; + header.glInternalFormat=GL_COMPRESSED_SRGB8_ETC2; + } + else if(format==ETC2PACKAGE_RGBA_NO_MIPMAPS) + { + halfbytes=2; + header.glBaseInternalFormat=GL_RGBA; + header.glInternalFormat=GL_COMPRESSED_RGBA8_ETC2_EAC; + } + else if(format==ETC2PACKAGE_sRGBA_NO_MIPMAPS) + { + halfbytes=2; + header.glBaseInternalFormat=GL_SRGB8_ALPHA8; + header.glInternalFormat=GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC; + } + else if(format==ETC2PACKAGE_RGBA1_NO_MIPMAPS) + { + header.glBaseInternalFormat=GL_RGBA; + header.glInternalFormat=GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2; + } + else if(format==ETC2PACKAGE_sRGBA1_NO_MIPMAPS) + { + header.glBaseInternalFormat=GL_SRGB8_ALPHA8; + header.glInternalFormat=GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2; + } + else if(format==ETC1_RGB_NO_MIPMAPS) + { + header.glBaseInternalFormat=GL_RGB; + header.glInternalFormat=GL_ETC1_RGB8_OES; + } + else + { + printf("internal error: bad format!\n"); + exit(1); + } + //write header + fwrite(&header,sizeof(KTX_header),1,f); + + //write size of compressed data.. which depend on the expanded size.. + unsigned int imagesize=(w*h*halfbytes)/2; + fwrite(&imagesize,sizeof(int),1,f); + } + else + { + //.pkm file, contains small header.. + + // Write magic number + fwrite(&magic[0], sizeof(unsigned char), 1, f); + fwrite(&magic[1], sizeof(unsigned char), 1, f); + fwrite(&magic[2], sizeof(unsigned char), 1, f); + fwrite(&magic[3], sizeof(unsigned char), 1, f); + + // Write version + fwrite(&version[0], sizeof(unsigned char), 1, f); + fwrite(&version[1], sizeof(unsigned char), 1, f); + + // Write texture type + if(texture_type==ETC2PACKAGE_RG_NO_MIPMAPS&&formatSigned) + { + unsigned short temp = ETC2PACKAGE_RG_SIGNED_NO_MIPMAPS; + write_big_endian_2byte_word(&temp,f); + } + else if(texture_type==ETC2PACKAGE_R_NO_MIPMAPS&&formatSigned) + { + unsigned short temp = ETC2PACKAGE_R_SIGNED_NO_MIPMAPS; + write_big_endian_2byte_word(&temp,f); + } + else + write_big_endian_2byte_word(&texture_type, f); + + // Write binary header: the width and height as unsigned 16-bit words + write_big_endian_2byte_word(&wi, f); + write_big_endian_2byte_word(&hi, f); + + // Also write the active pixels. For instance, if we want to compress + // a 128 x 129 image, we have to extend it to 128 x 132 pixels. + // Then the wi and hi written above will be 128 and 132, but the + // additional information that we write below will be 128 and 129, + // to indicate that it is only the top 129 lines of data in the + // decompressed image that will be valid data, and the rest will + // be just garbage. + + unsigned short activew, activeh; + activew = width; + activeh = height; + + write_big_endian_2byte_word(&activew, f); + write_big_endian_2byte_word(&activeh, f); + } + int totblocks = expandedheight/4 * expandedwidth/4; + int countblocks = 0; + double percentageblocks=-1.0; + double oldpercentageblocks; + + if(format==ETC2PACKAGE_RG_NO_MIPMAPS) + { + //extract data from red and green channel into two alpha channels. + //note that the image will be 16-bit per channel in this case. + alphaimg= (unsigned char*)malloc(expandedwidth*expandedheight*2); + alphaimg2=(unsigned char*)malloc(expandedwidth*expandedheight*2); + setupAlphaTableAndValtab(); + if(!alphaimg||!alphaimg2) + { + printf("failed allocating space for alpha buffers!\n"); + exit(1); + } + for(y=0;y.\n",dstfile); + } +} + +#if 0 + +// Compress an file. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void compressFile(char *srcfile,char *dstfile) +{ + uint8 *srcimg; + int width,height; + int extendedwidth, extendedheight; + struct _timeb tstruct; + int tstart; + int tstop; + // 0: compress from .any to .pkm with SPEED_FAST, METRIC_NONPERCEPTUAL, ETC + // 1: compress from .any to .pkm with SPEED_MEDIUM, METRIC_NONPERCEPTUAL, ETC + // 2: compress from .any to .pkm with SPEED_SLOW, METRIC_NONPERCEPTUAL, ETC + // 3: compress from .any to .pkm with SPEED_FAST, METRIC_PERCEPTUAL, ETC + // 4: compress from .any to .pkm with SPEED_MEDIUM, METRIC_PERCEPTUAL, ETC + // 5: compress from .any to .pkm with SPEED_SLOW, METRIC_PERCEPTUAL, ETC + // 6: decompress from .pkm to .any + // 7: calculate PSNR between .any and .any + // 8: compress from .any to .pkm with SPEED_FAST, METRIC_NONPERCEPTUAL, ETC2 + // 9: compress from .any to .pkm with SPEED_MEDIUM, METRIC_NONPERCEPTUAL, ETC2 + //10: compress from .any to .pkm with SPEED_SLOW, METRIC_NONPERCEPTUAL, ETC2 + //11: compress from .any to .pkm with SPEED_FAST, METRIC_PERCEPTUAL, ETC2 + //12: compress from .any to .pkm with SPEED_MEDIUM, METRIC_PERCEPTUAL, ETC2 + //13: compress from .any to .pkm with SPEED_SLOW, METRIC_PERCEPTUAL, ETC2 + + printf("\n"); + if(codec==CODEC_ETC) + printf("ETC codec, "); + else + printf("ETC2 codec, "); + if(speed==SPEED_FAST) + printf("using FAST compression mode and "); + else if(speed==SPEED_MEDIUM) + printf("using MEDIUM compression mode and "); + else + printf("using SLOW compression mode and "); + if(metric==METRIC_PERCEPTUAL) + printf("PERCEPTUAL error metric, "); + else + printf("NONPERCEPTUAL error metric, "); + if(format==ETC2PACKAGE_RGBA_NO_MIPMAPS) + printf("in RGBA format"); + else if(format==ETC2PACKAGE_sRGBA_NO_MIPMAPS) + printf("in sRGBA format"); + else if(format==ETC2PACKAGE_RGBA1_NO_MIPMAPS) + printf("in RGB + punch-through alpha format"); + else if(format==ETC2PACKAGE_sRGBA1_NO_MIPMAPS) + printf("in sRGB + punch-through alpha format"); + else if(format==ETC2PACKAGE_R_NO_MIPMAPS) + printf("in R format"); + else if(format==ETC2PACKAGE_RGB_NO_MIPMAPS||format==ETC1_RGB_NO_MIPMAPS) + printf("in RGB format"); + else if(format==ETC2PACKAGE_RG_NO_MIPMAPS) + printf("in RG format"); + else + printf("in OTHER format"); + printf("\n"); + if(readCompressParams()) + { + if(format==ETC2PACKAGE_R_NO_MIPMAPS||readSrcFile(srcfile,srcimg,width,height,extendedwidth, extendedheight)) + { + //make sure that alphasrcimg contains the alpha channel or is null here, and pass it to compressimagefile + uint8* alphaimg=NULL; + if(format==ETC2PACKAGE_RGBA_NO_MIPMAPS||format==ETC2PACKAGE_RGBA1_NO_MIPMAPS||format==ETC2PACKAGE_sRGBA_NO_MIPMAPS||format==ETC2PACKAGE_sRGBA1_NO_MIPMAPS) + { + char str[300]; + //printf("reading alpha channel...."); + sprintf(str,"imconv %s -alpha extract alpha.pgm\n",srcfile); + system(str); + readAlpha(alphaimg,width,height,extendedwidth,extendedheight); + printf("ok!\n"); + setupAlphaTableAndValtab(); + } + else if(format==ETC2PACKAGE_R_NO_MIPMAPS) + { + char str[300]; + sprintf(str,"imconv %s alpha.pgm\n",srcfile); + system(str); + readAlpha(alphaimg,width,height,extendedwidth,extendedheight); + printf("read alpha ok, size is %d,%d (%d,%d)",width,height,extendedwidth,extendedheight); + setupAlphaTableAndValtab(); + } + printf("Compressing...\n"); + + tstart=time(NULL); + _ftime( &tstruct ); + tstart=tstart*1000+tstruct.millitm; + compressImageFile(srcimg,alphaimg,width,height,dstfile,extendedwidth, extendedheight); + tstop = time(NULL); + _ftime( &tstruct ); + tstop = tstop*1000+tstruct.millitm; + printf( "It took %u milliseconds to compress:\n", tstop - tstart); + calculatePSNRfile(dstfile,srcimg,alphaimg); + } + } +} + + +// Calculates the PSNR between two files. +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +double calculatePSNRTwoFiles(char *srcfile1,char *srcfile2) +{ + uint8 *srcimg1; + uint8 *srcimg2; + int width1, height1; + int width2, height2; + double PSNR; + double perceptually_weighted_PSNR; + + if(readSrcFileNoExpand(srcfile1,srcimg1,width1,height1)) + { + if(readSrcFileNoExpand(srcfile2,srcimg2,width2,height2)) + { + if((width1 == width2) && (height1 == height2)) + { + PSNR = calculatePSNR(srcimg1, srcimg2, width1, height1); + printf("%f\n",PSNR); + perceptually_weighted_PSNR = calculateWeightedPSNR(srcimg1, srcimg2, width1, height1, 0.299, 0.587, 0.114); + } + else + { + printf("\n Width and height do no not match for image: width, height = (%d, %d) and (%d, %d)\n",width1,height1, width2, height2); + } + } + else + { + printf("Couldn't open file %s.\n",srcfile2); + } + } + else + { + printf("Couldn't open file %s.\n",srcfile1); + } + + return PSNR; +} + + +// Main function +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +int main(int argc,char *argv[]) +{ + if(argc==3 || argc==4 || argc == 5 || argc == 7 || argc == 9 || argc == 11 || argc == 13) + { + // The source file is always the second last one. + char srcfile[200]; + char dstfile[200]; + readArguments(argc,argv,srcfile,dstfile); + + int q = find_pos_of_extension(srcfile); + int q2 = find_pos_of_extension(dstfile); + + if(!fileExist(srcfile)) + { + printf("Error: file <%s> does not exist.\n",srcfile); + exit(0); + } + + if(mode==MODE_UNCOMPRESS) + { + printf("Decompressing .pkm/.ktx file ...\n"); + uint8* alphaimg=NULL, *img; + int w, h; + uncompressFile(srcfile,img,alphaimg,w,h); + writeOutputFile(dstfile,img,alphaimg,w,h); + } + else if(mode==MODE_PSNR) + { + calculatePSNRTwoFiles(srcfile,dstfile); + } + else + { + compressFile(srcfile, dstfile); + } + } + else + { + printf("ETCPACK v2.74 For ETC and ETC2\n"); + printf("Compresses and decompresses images using the Ericsson Texture Compression (ETC) version 1.0 and 2.0.\n\nUsage: etcpack srcfile dstfile\n\n"); + printf(" -s {fast|slow} Compression speed. Slow = exhaustive \n"); + printf(" search for optimal quality\n"); + printf(" (default: fast)\n"); + printf(" -e {perceptual|nonperceptual} Error metric: Perceptual (nicest) or \n"); + printf(" nonperceptual (highest PSNR)\n"); + printf(" (default: perceptual)\n"); + printf(" -c {etc1|etc2} Codec: etc1 (most compatible) or \n"); + printf(" etc2 (highest quality)\n"); + printf(" (default: etc2)\n"); + printf(" -f {R|R_signed|RG|RG_signed| Format: one, two, three or four \n"); + printf(" RGB|RGBA1|RGBA8| channels, and 1 or 8 bits for alpha\n"); + printf(" sRGB|sRGBA1|sRGBA8|} RGB or sRGB.\n"); + printf(" (1 equals punchthrough)\n"); + printf(" (default: RGB)\n"); + printf(" -v {on|off} Detailed progress info. (default on)\n"); + printf(" \n"); + printf("Examples: \n"); + printf(" etcpack img.ppm img.pkm Compresses img.ppm to img.pkm in\n"); + printf(" ETC2 RGB format\n"); + printf(" etcpack img.ppm img.ktx Compresses img.ppm to img.ktx in\n"); + printf(" ETC2 RGB format\n"); + printf(" etcpack img.pkm img_copy.ppm Decompresses img.pkm to img_copy.ppm\n"); + printf(" etcpack -s slow img.ppm img.pkm Compress using the slow mode.\n"); + printf(" etcpack -p orig.ppm copy.ppm Calculate PSNR between orig and copy\n"); + printf(" etcpack -f RGBA8 img.tga img.pkm Compresses img.tga to img.pkm, using \n"); + printf(" etc2 + alpha.\n"); + printf(" etcpack -f RG img.ppm img.pkm Compresses red and green channels of\n"); + printf(" img.ppm\n"); + } + return 0; +} + +#endif // 0 diff --git a/extern/etcpack/image.cxx b/extern/etcpack/image.cxx new file mode 100755 index 0000000..8ab8c66 --- /dev/null +++ b/extern/etcpack/image.cxx @@ -0,0 +1,461 @@ +//// etcpack v2.74 +//// +//// NO WARRANTY +//// +//// BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE THE PROGRAM IS PROVIDED +//// "AS IS". ERICSSON MAKES NO REPRESENTATIONS OF ANY KIND, EXTENDS NO +//// WARRANTIES OR CONDITIONS OF ANY KIND; EITHER EXPRESS, IMPLIED OR +//// STATUTORY; INCLUDING, BUT NOT LIMITED TO, EXPRESS, IMPLIED OR +//// STATUTORY WARRANTIES OR CONDITIONS OF TITLE, MERCHANTABILITY, +//// SATISFACTORY QUALITY, SUITABILITY AND FITNESS FOR A PARTICULAR +//// PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +//// PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME +//// THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. ERICSSON +//// MAKES NO WARRANTY THAT THE MANUFACTURE, SALE, OFFERING FOR SALE, +//// DISTRIBUTION, LEASE, USE OR IMPORTATION UNDER THE LICENSE WILL BE FREE +//// FROM INFRINGEMENT OF PATENTS, COPYRIGHTS OR OTHER INTELLECTUAL +//// PROPERTY RIGHTS OF OTHERS, AND THE VALIDITY OF THE LICENSE IS SUBJECT +//// TO YOUR SOLE RESPONSIBILITY TO MAKE SUCH DETERMINATION AND ACQUIRE +//// SUCH LICENSES AS MAY BE NECESSARY WITH RESPECT TO PATENTS, COPYRIGHT +//// AND OTHER INTELLECTUAL PROPERTY OF THIRD PARTIES. +//// +//// FOR THE AVOIDANCE OF DOUBT THE PROGRAM (I) IS NOT LICENSED FOR; (II) +//// IS NOT DESIGNED FOR OR INTENDED FOR; AND (III) MAY NOT BE USED FOR; +//// ANY MISSION CRITICAL APPLICATIONS SUCH AS, BUT NOT LIMITED TO +//// OPERATION OF NUCLEAR OR HEALTHCARE COMPUTER SYSTEMS AND/OR NETWORKS, +//// AIRCRAFT OR TRAIN CONTROL AND/OR COMMUNICATION SYSTEMS OR ANY OTHER +//// COMPUTER SYSTEMS AND/OR NETWORKS OR CONTROL AND/OR COMMUNICATION +//// SYSTEMS ALL IN WHICH CASE THE FAILURE OF THE PROGRAM COULD LEAD TO +//// DEATH, PERSONAL INJURY, OR SEVERE PHYSICAL, MATERIAL OR ENVIRONMENTAL +//// DAMAGE. YOUR RIGHTS UNDER THIS LICENSE WILL TERMINATE AUTOMATICALLY +//// AND IMMEDIATELY WITHOUT NOTICE IF YOU FAIL TO COMPLY WITH THIS +//// PARAGRAPH. +//// +//// IN NO EVENT WILL ERICSSON, BE LIABLE FOR ANY DAMAGES WHATSOEVER, +//// INCLUDING BUT NOT LIMITED TO PERSONAL INJURY, ANY GENERAL, SPECIAL, +//// INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF OR IN +//// CONNECTION WITH THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT +//// NOT LIMITED TO LOSS OF PROFITS, BUSINESS INTERUPTIONS, OR ANY OTHER +//// COMMERCIAL DAMAGES OR LOSSES, LOSS OF DATA OR DATA BEING RENDERED +//// INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF +//// THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS) REGARDLESS OF THE +//// THEORY OF LIABILITY (CONTRACT, TORT OR OTHERWISE), EVEN IF SUCH HOLDER +//// OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +//// +//// (C) Ericsson AB 2005-2013. All Rights Reserved. +//// + +#include +#include +#include +#include "image.h" + +// Remove warnings for unsafe functions such as strcpy +#pragma warning(disable : 4996) +// Remove warnings for conversions between different time variables +#pragma warning(disable : 4244) + +// Removes comments in a .ppm file +// (i.e., lines starting with #) +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void removeComments(FILE *f1) +{ + int c; + + while((c = getc(f1)) == '#') + { + char line[1024]; + fgets(line, 1024, f1); + } + ungetc(c, f1); +} + + +// Removes white spaces in a .ppm file +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +void removeSpaces(FILE *f1) +{ + int c; + + c = getc(f1); + while(c == ' ' || c == '\t' || c == '\n' || c == '\f' || c == '\r') + { + c = getc(f1); + } + ungetc(c, f1); +} + +// fReadPPM +// +// reads a ppm file with P6 header (meaning binary, as opposed to P5, which is ascII) +// and returns the image in pixels. +// +// The header must look like this: +// +// P6 +// # Comments (not necessary) +// width height +// 255 +// +// after that follows RGBRGBRGB... +// +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +bool fReadPPM(char *filename, int &width, int &height, unsigned char *&pixels, int targetbitrate) +{ + FILE *f1; + int maximum; + f1 = fopen(filename, "rb"); + + if(f1) + { + char line[255]; + + removeSpaces(f1); + removeComments(f1); + removeSpaces(f1); + + fscanf(f1, "%s", line); + + if(strcmp(line, "P6")!=0) + { + printf("Error: %s is not binary\n"); + printf("(Binary .ppm files start with P6).\n"); + fclose(f1); + return false; + } + removeSpaces(f1); + removeComments(f1); + removeSpaces(f1); + + fscanf(f1, "%d %d", &width, &height); + if( width<=0 || height <=0) + { + printf("Error: width or height negative. File: %s\n",filename); + fclose(f1); + return false; + } + + removeSpaces(f1); + removeComments(f1); + removeSpaces(f1); + + fscanf(f1, "%d", &maximum); + if( maximum!= 255&&maximum!=(1<<16)-1) + { + printf("Error: Color resolution must be 255. File: %s\n",filename); + fclose(f1); + return false; + } + + //printf("maximum is %d\n",maximum); + int bitrate=8; + if(maximum!=255) + bitrate=16; + + // We need to remove the newline. + char c = 0; + while(c != '\n') + fscanf(f1, "%c", &c); + + unsigned char* readbuffer = (unsigned char*) malloc(3*width*height*bitrate/8); + if(!readbuffer) + { + printf("Error: Could not allocate memory for image. File: %s\n", filename); + fclose(f1); + return false; + } + + if(fread(readbuffer, 3*width*height*bitrate/8, 1, f1) != 1) + { + printf("Error: Could not read all pixels. File: %s\n", filename); + free(pixels); + fclose(f1); + return false; + } + + // If we have reached this point, we have successfully loaded the image. + + //now, convert it to the target bitrate + if(targetbitrate==bitrate) + pixels=readbuffer; + else + { + pixels = (unsigned char*) malloc(3*width*height*targetbitrate/8); + if(targetbitrate 8 bits.. + printf("converting 16 bit input to 8 bits\n"); + for(int x=0; x 16 bits... + printf("converting 8 bit input to 16 bits\n"); + for(int x=0; x.\n",filename); + return false; + } +} + +/* reads a ppm file with the P6 header (means raw RGB), puts data into pixel pointer and returns bit depth (8 or 16 bpp) */ +/* the header looks like this: + *--------- + * P5 + * # comments if you want to + * width height + * 255 + *--------- + * then follows RGBRGBRGBRGBRGB... + */ +// NO WARRANTY --- SEE STATEMENT IN TOP OF FILE (C) Ericsson AB 2005-2013. All Rights Reserved. +int fReadPGM(char *filename, int &width, int &height, unsigned char *&pixels, int wantedBitDepth) +{ + FILE *f; + int colres; + int bitdepth=8; + f=fopen(filename,"rb"); + if(f) + { + char str[100]; + removeSpaces(f); + removeComments(f); + removeSpaces(f); + fscanf(f,"%s",str); + if(strcmp(str,"P5")!=0) + { + printf("Error: the alpha image file must be of raw color PGM format,\n"); + printf("i.e., it must have P5 in the header. File: %s\n",filename); + fclose(f); + return 0; + } + removeSpaces(f); + removeComments(f); + removeSpaces(f); + fscanf(f,"%d %d",&width,&height); + if(width<=0 || height<=0) + { + printf("Error: width and height of the image must be greater than zero. File: %s\n",filename); + fclose(f); + return 0; + } + removeSpaces(f); + removeComments(f); + removeSpaces(f); + fscanf(f,"%d",&colres); + if(colres!=255&&colres!=65535) + { + printf("Error: color resolution must be 255 or 65535.File: %s\n",filename); + fclose(f); + return 0; + } + if(colres==65535) + bitdepth=16; + + /* gotta eat the newline too */ + char ch=0; + while(ch!='\n') fscanf(f,"%c",&ch); + + pixels=(unsigned char*)malloc(width*height*bitdepth/8); + if(!pixels) + { + printf("Error: could not allocate memory for the pixels of the texture. File: %s\n",filename); + fclose(f); + return 0; + } + + if(fread(pixels,width*height*bitdepth/8,1,f)!=1) + { + printf("Error: could not read %d bytes of pixel info. File: %s\n",width*height*bitdepth/8,filename); + free(pixels); + fclose(f); + return 0; + } + fclose(f); + printf("read %d-bit alpha channel",bitdepth); + if(bitdepth!=wantedBitDepth) + { + printf(", converting to %d-bit!",wantedBitDepth); + unsigned char* newpixels = (unsigned char*)malloc(width*height*wantedBitDepth/8); + for(int x=0; x=0; yy--) + { + for(xx = 0; xx +class CPVRTArray +{ +public: + /*!*************************************************************************** + @Function CPVRTArray + @Description Blank constructor. Makes a default sized array. + *****************************************************************************/ + CPVRTArray() : m_uiSize(0), m_uiCapacity(GetDefaultSize()) + { + m_pArray = new T[m_uiCapacity]; + } + + /*!*************************************************************************** + @Function CPVRTArray + @Input uiSize intial size of array + @Description Constructor taking initial size of array in elements. + *****************************************************************************/ + CPVRTArray(const unsigned int uiSize) : m_uiSize(0), m_uiCapacity(uiSize) + { + _ASSERT(uiSize != 0); + m_pArray = new T[uiSize]; + } + + /*!*************************************************************************** + @Function CPVRTArray + @Input original the other dynamic array + @Description Copy constructor. + *****************************************************************************/ + CPVRTArray(const CPVRTArray& original) : m_uiSize(original.m_uiSize), + m_uiCapacity(original.m_uiCapacity) + { + m_pArray = new T[m_uiCapacity]; + for(unsigned int i=0;i= m_uiSize) // Are we adding to the end + uiIndex = Append(addT); + else + { + unsigned int uiNewCapacity = 0; + T* pArray = m_pArray; + + if(m_uiSize > m_uiCapacity) + { + uiNewCapacity = m_uiCapacity + 10; // Expand the array by 10. + + pArray = new T[uiNewCapacity]; // New Array + + if(!pArray) + return -1; // Failed to allocate memory! + + // Copy the first half to the new array + for(unsigned int i = 0; i < pos; ++i) + { + pArray[i] = m_pArray[i]; + } + } + + // Copy last half to the new array + for(unsigned int i = m_uiSize; i > pos; --i) + { + pArray[i] = m_pArray[i - 1]; + } + + // Insert our new element + pArray[pos] = addT; + uiIndex = pos; + + // Increase our size + ++m_uiSize; + + // Switch pointers and free memory if needed + if(pArray != m_pArray) + { + m_uiCapacity = uiNewCapacity; + delete[] m_pArray; + m_pArray = pArray; + } + } + + return uiIndex; + } + + /*!*************************************************************************** + @Function Append + @Input addT The element to append + @Return The index of the new item. + @Description Appends an element to the end of the array, expanding it + if necessary. + *****************************************************************************/ + unsigned int Append(const T& addT) + { + unsigned int uiIndex = Append(); + m_pArray[uiIndex] = addT; + return uiIndex; + } + + /*!*************************************************************************** + @Function Append + @Return The index of the new item. + @Description Creates space for a new item, but doesn't add. Instead + returns the index of the new item. + *****************************************************************************/ + unsigned int Append() + { + unsigned int uiIndex = m_uiSize; + SetCapacity(m_uiSize+1); + m_uiSize++; + + return uiIndex; + } + + /*!*************************************************************************** + @Function Clear + @Description Clears the array. + *****************************************************************************/ + void Clear() + { + m_uiSize = 0U; + } + + /*!*************************************************************************** + @Function Resize + @Input uiSize New size of array + @Description Changes the array to the new size + *****************************************************************************/ + EPVRTError Resize(const unsigned int uiSize) + { + EPVRTError err = SetCapacity(uiSize); + + if(err != PVR_SUCCESS) + return err; + + m_uiSize = uiSize; + return PVR_SUCCESS; + } + + /*!*************************************************************************** + @Function SetCapacity + @Input uiSize New capacity of array + @Description Expands array to new capacity + *****************************************************************************/ + EPVRTError SetCapacity(const unsigned int uiSize) + { + if(uiSize <= m_uiCapacity) + return PVR_SUCCESS; // nothing to be done + + unsigned int uiNewCapacity; + if(uiSize < m_uiCapacity*2) + { + uiNewCapacity = m_uiCapacity*2; // Ignore the new size. Expand to twice the previous size. + } + else + { + uiNewCapacity = uiSize; + } + + T* pNewArray = new T[uiNewCapacity]; // New Array + if(!pNewArray) + return PVR_FAIL; // Failed to allocate memory! + + // Copy source data to new array + for(unsigned int i = 0; i < m_uiSize; ++i) + { + pNewArray[i] = m_pArray[i]; + } + + // Switch pointers and free memory + m_uiCapacity = uiNewCapacity; + T* pOldArray = m_pArray; + m_pArray = pNewArray; + delete [] pOldArray; + return PVR_SUCCESS; + } + + /*!*************************************************************************** + @Function Copy + @Input other The CPVRTArray needing copied + @Description A copy function. Will attempt to copy from other CPVRTArrays + if this is possible. + *****************************************************************************/ + template + void Copy(const CPVRTArray& other) + { + T* pNewArray = new T[other.GetCapacity()]; + if(pNewArray) + { + // Copy data + for(unsigned int i = 0; i < other.GetSize(); i++) + { + pNewArray[i] = other[i]; + } + + // Free current array + if(m_pArray) + delete [] m_pArray; + + // Swap pointers + m_pArray = pNewArray; + + m_uiCapacity = other.GetCapacity(); + m_uiSize = other.GetSize(); + } + } + + /*!*************************************************************************** + @Function = + @Input other The CPVRTArray needing copied + @Description assignment operator. + *****************************************************************************/ + CPVRTArray& operator=(const CPVRTArray& other) + { + if(&other != this) + Copy(other); + + return *this; + } + + /*!*************************************************************************** + @Function operator+= + @Input other the array to append. + @Description appends an existing CPVRTArray on to this one. + *****************************************************************************/ + CPVRTArray& operator+=(const CPVRTArray& other) + { + if(&other != this) + { + for(unsigned int uiIndex = 0; uiIndex < other.GetSize(); ++uiIndex) + { + Append(other[uiIndex]); + } + } + + return *this; + } + + /*!*************************************************************************** + @Function [] + @Input uiIndex index of element in array + @Return the element indexed + @Description indexed access into array. Note that this has no error + checking whatsoever + *****************************************************************************/ + T& operator[](const unsigned int uiIndex) + { + _ASSERT(uiIndex < m_uiCapacity); + return m_pArray[uiIndex]; + } + + /*!*************************************************************************** + @Function [] + @Input uiIndex index of element in array + @Return The element indexed + @Description Indexed access into array. Note that this has no error + checking whatsoever + *****************************************************************************/ + const T& operator[](const unsigned int uiIndex) const + { + _ASSERT(uiIndex < m_uiCapacity); + return m_pArray[uiIndex]; + } + + /*!*************************************************************************** + @Function GetSize + @Return Size of array + @Description Gives current size of array/number of elements + *****************************************************************************/ + unsigned int GetSize() const + { + return m_uiSize; + } + + /*!*************************************************************************** + @Function GetDefaultSize + @Return Default size of array + @Description Gives the default size of array/number of elements + *****************************************************************************/ + static unsigned int GetDefaultSize() + { + return 16U; + } + + /*!*************************************************************************** + @Function GetCapacity + @Return Capacity of array + @Description Gives current allocated size of array/number of elements + *****************************************************************************/ + unsigned int GetCapacity() const + { + return m_uiCapacity; + } + + /*!*************************************************************************** + @Function Contains + @Input object The object to check in the array + @Return true if object is contained in this array. + @Description Indicates whether the given object resides inside the + array. + *****************************************************************************/ + bool Contains(const T& object) const + { + for(unsigned int uiIndex = 0; uiIndex < m_uiSize; ++uiIndex) + { + if(m_pArray[uiIndex] == object) + return true; + } + return false; + } + + /*!*************************************************************************** + @Function Find + @Input object The object to check in the array + @Return pointer to the found object or NULL. + @Description Attempts to find the object in the array and returns a + pointer if it is found, or NULL if not found. The time + taken is O(N). + *****************************************************************************/ + T* Find(const T& object) const + { + for(unsigned int uiIndex = 0; uiIndex < m_uiSize; ++uiIndex) + { + if(m_pArray[uiIndex] == object) + return &m_pArray[uiIndex]; + } + return NULL; + } + + /*!*************************************************************************** + @Function Sort + @Input predicate The object which defines "bool operator()" + @Description Simple bubble-sort of the array. Pred should be an object that + defines a bool operator(). + *****************************************************************************/ + template + void Sort(Pred predicate) + { + bool bSwap; + for(unsigned int i=0; i < m_uiSize; ++i) + { + bSwap = false; + for(unsigned int j=0; j < m_uiSize-1; ++j) + { + if(predicate(m_pArray[j], m_pArray[j+1])) + { + PVRTswap(m_pArray[j], m_pArray[j+1]); + bSwap = true; + } + } + + if(!bSwap) + return; + } + } + + /*!*************************************************************************** + @Function Remove + @Input uiIndex The index to remove + @Return success or failure + @Description Removes an element from the array. + *****************************************************************************/ + virtual EPVRTError Remove(unsigned int uiIndex) + { + _ASSERT(uiIndex < m_uiSize); + if(m_uiSize == 0) + return PVR_FAIL; + + if(uiIndex == m_uiSize-1) + { + return RemoveLast(); + } + + m_uiSize--; + // Copy the data. memmove will only work for built-in types. + for(unsigned int uiNewIdx = uiIndex; uiNewIdx < m_uiSize; ++uiNewIdx) + { + m_pArray[uiNewIdx] = m_pArray[uiNewIdx+1]; + } + + return PVR_SUCCESS; + } + + /*!*************************************************************************** + @Function RemoveLast + @Return success or failure + @Description Removes the last element. Simply decrements the size value + *****************************************************************************/ + virtual EPVRTError RemoveLast() + { + if(m_uiSize > 0) + { + m_uiSize--; + return PVR_SUCCESS; + } + else + { + return PVR_FAIL; + } + } + +protected: + unsigned int m_uiSize; /*! current size of contents of array */ + unsigned int m_uiCapacity; /*! currently allocated size of array */ + T *m_pArray; /*! the actual array itself */ +}; + +// note "this" is required for ISO standard C++ and gcc complains otherwise +// http://lists.apple.com/archives/Xcode-users//2005/Dec/msg00644.html +template +class CPVRTArrayManagedPointers : public CPVRTArray +{ +public: + virtual ~CPVRTArrayManagedPointers() + { + if(this->m_pArray) + { + for(unsigned int i=0;im_uiSize;i++) + { + delete(this->m_pArray[i]); + } + } + } + + /*!*************************************************************************** + @Function Remove + @Input uiIndex The index to remove + @Return success or failure + @Description Removes an element from the array. + *****************************************************************************/ + virtual EPVRTError Remove(unsigned int uiIndex) + { + _ASSERT(uiIndex < this->m_uiSize); + if(this->m_uiSize == 0) + return PVR_FAIL; + + if(uiIndex == this->m_uiSize-1) + { + return this->RemoveLast(); + } + + unsigned int uiSize = (this->m_uiSize - (uiIndex+1)) * sizeof(T*); + + delete this->m_pArray[uiIndex]; + memmove(this->m_pArray + uiIndex, this->m_pArray + (uiIndex+1), uiSize); + + this->m_uiSize--; + return PVR_SUCCESS; + } + + /*!*************************************************************************** + @Function RemoveLast + @Return success or failure + @Description Removes the last element. Simply decrements the size value + *****************************************************************************/ + virtual EPVRTError RemoveLast() + { + if(this->m_uiSize > 0 && this->m_pArray) + { + delete this->m_pArray[this->m_uiSize-1]; + this->m_uiSize--; + return PVR_SUCCESS; + } + else + { + return PVR_FAIL; + } + } +}; + +#endif // __PVRTARRAY_H__ + +/***************************************************************************** +End of file (PVRTArray.h) +*****************************************************************************/ + diff --git a/extern/pvrtextool/Include/PVRTDecompress.h b/extern/pvrtextool/Include/PVRTDecompress.h new file mode 100644 index 0000000..d817d7e --- /dev/null +++ b/extern/pvrtextool/Include/PVRTDecompress.h @@ -0,0 +1,58 @@ +/****************************************************************************** + + @File PVRTDecompress.h + + @Title PVRTDecompress + + @Version + + @Copyright Copyright (c) Imagination Technologies Limited. All Rights Reserved. Strictly Confidential. + + @Platform ANSI compatible + + @Description PVRTC and ETC Texture Decompression. + +******************************************************************************/ + +#ifndef _PVRTDECOMPRESS_H_ +#define _PVRTDECOMPRESS_H_ + +/*!*********************************************************************** + @Function PVRTDecompressPVRTC + @Input pCompressedData The PVRTC texture data to decompress + @Input Do2bitMode Signifies whether the data is PVRTC2 or PVRTC4 + @Input XDim X dimension of the texture + @Input YDim Y dimension of the texture + @Return Returns the amount of data that was decompressed. + @Modified pResultImage The decompressed texture data + @Description Decompresses PVRTC to RGBA 8888 +*************************************************************************/ +int PVRTDecompressPVRTC(const void *pCompressedData, + const int Do2bitMode, + const int XDim, + const int YDim, + unsigned char* pResultImage); + +/*!*********************************************************************** +@Function PVRTDecompressETC +@Input pSrcData The ETC texture data to decompress +@Input x X dimension of the texture +@Input y Y dimension of the texture +@Modified pDestData The decompressed texture data +@Input nMode The format of the data +@Returns The number of bytes of ETC data decompressed +@Description Decompresses ETC to RGBA 8888 +*************************************************************************/ +int PVRTDecompressETC(const void * const pSrcData, + const unsigned int &x, + const unsigned int &y, + void *pDestData, + const int &nMode); + + +#endif /* _PVRTDECOMPRESS_H_ */ + +/***************************************************************************** + End of file (PVRTBoneBatch.h) +*****************************************************************************/ + diff --git a/extern/pvrtextool/Include/PVRTError.h b/extern/pvrtextool/Include/PVRTError.h new file mode 100644 index 0000000..8103508 --- /dev/null +++ b/extern/pvrtextool/Include/PVRTError.h @@ -0,0 +1,71 @@ +/****************************************************************************** + + @File PVRTError.h + + @Title PVRTError + + @Version + + @Copyright Copyright (c) Imagination Technologies Limited. All Rights Reserved. Strictly Confidential. + + @Platform ANSI compatible + + @Description + +******************************************************************************/ +#ifndef _PVRTERROR_H_ +#define _PVRTERROR_H_ + +#if defined(ANDROID) + #include +#else + #if defined(_WIN32) + #include + #else + #include + #endif +#endif +/*!*************************************************************************** + Macros +*****************************************************************************/ + +/*! Outputs a string to the standard error if built for debugging. */ +#if !defined(PVRTERROR_OUTPUT_DEBUG) + #if defined(_DEBUG) || defined(DEBUG) + #if defined(ANDROID) + #define PVRTERROR_OUTPUT_DEBUG(A) __android_log_print(ANDROID_LOG_INFO, "PVRTools", A); + #elif defined(_WIN32) + #define PVRTERROR_OUTPUT_DEBUG(A) OutputDebugStringA(A); + #else + #define PVRTERROR_OUTPUT_DEBUG(A) fprintf(stderr,A); + #endif + #else + #define PVRTERROR_OUTPUT_DEBUG(A) + #endif +#endif + + +/*!*************************************************************************** + Enums +*****************************************************************************/ +/*! Enum error codes */ +enum EPVRTError +{ + PVR_SUCCESS = 0, + PVR_FAIL = 1, + PVR_OVERFLOW = 2 +}; + +/*!*************************************************************************** + @Function PVRTErrorOutputDebug + @Input format printf style format followed by arguments it requires + @Description Outputs a string to the standard error. +*****************************************************************************/ +void PVRTErrorOutputDebug(char const * const format, ...); + +#endif // _PVRTERROR_H_ + +/***************************************************************************** +End of file (PVRTError.h) +*****************************************************************************/ + diff --git a/extern/pvrtextool/Include/PVRTGlobal.h b/extern/pvrtextool/Include/PVRTGlobal.h new file mode 100644 index 0000000..a7df228 --- /dev/null +++ b/extern/pvrtextool/Include/PVRTGlobal.h @@ -0,0 +1,278 @@ +/****************************************************************************** + + @File PVRTGlobal.h + + @Title PVRTGlobal + + @Version + + @Copyright Copyright (c) Imagination Technologies Limited. All Rights Reserved. Strictly Confidential. + + @Platform ANSI compatible + + @Description Global defines and typedefs for PVRTools + +******************************************************************************/ +#ifndef _PVRTGLOBAL_H_ +#define _PVRTGLOBAL_H_ + +/*!*************************************************************************** + Macros +*****************************************************************************/ +#define PVRT_MIN(a,b) (((a) < (b)) ? (a) : (b)) +#define PVRT_MAX(a,b) (((a) > (b)) ? (a) : (b)) +#define PVRT_CLAMP(x, l, h) (PVRT_MIN((h), PVRT_MAX((x), (l)))) + +// avoid warning about unused parameter +#define PVRT_UNREFERENCED_PARAMETER(x) ((void) x) + +#if defined(_WIN32) && !defined(__QT__) /* Windows desktop */ +#if !defined(_CRTDBG_MAP_ALLOC) + #define _CRTDBG_MAP_ALLOC +#endif + #include + #include + #include +#endif + +#if defined(_WIN32) && !defined(__QT__) + +#else +#if defined(__linux__) || defined(__APPLE__) + #define _ASSERT(a)((void)0) + #define _ASSERTE(a)((void)0) + #ifdef _DEBUG + #ifndef _RPT0 + #define _RPT0(a,b) printf(b) + #endif + #ifndef _RPT1 + #define _RPT1(a,b,c) printf(b,c) + #endif + #else + #ifndef _RPT0 + #define _RPT0(a,b)((void)0) + #endif + #ifndef _RPT1 + #define _RPT1(a,b,c)((void)0) + #endif + #endif + #define _RPT2(a,b,c,d)((void)0) + #define _RPT3(a,b,c,d,e)((void)0) + #define _RPT4(a,b,c,d,e,f)((void)0) + #include + #include + #define BYTE unsigned char + #define WORD unsigned short + #define DWORD unsigned int + typedef struct tagRGBQUAD { + BYTE rgbBlue; + BYTE rgbGreen; + BYTE rgbRed; + BYTE rgbReserved; + } RGBQUAD; + #define BOOL int +#if !defined(TRUE) + #define TRUE 1 +#endif +#if !defined(FALSE) + #define FALSE 0 +#endif +#else + #define _CRT_WARN 0 + #define _RPT0(a,b) + #define _RPT1(a,b,c) + #define _RPT2(a,b,c,d) + #define _RPT3(a,b,c,d,e) + #define _RPT4(a,b,c,d,e,f) + #define _ASSERT(X) + #define _ASSERTE(X) +#endif +#endif + +#include + +#define FREE(X) { if(X) { free(X); (X) = 0; } } + +#if 1 //ACS: I split this out into PVRTTypes.h + + #include "PVRTTypes.h" + +#else + +// This macro is used to check at compile time that types are of a certain size +// If the size does not equal the expected size, this typedefs an array of size 0 +// which causes a compile error +#define PVRTSIZEASSERT(T, size) typedef int (sizeof_##T)[sizeof(T) == (size)] +#define PVRTCOMPILEASSERT(T, expr) typedef int (assert_##T)[expr] + + +/**************************************************************************** +** Integer types +****************************************************************************/ + +typedef char PVRTchar8; +typedef signed char PVRTint8; +typedef signed short PVRTint16; +typedef signed int PVRTint32; +typedef unsigned char PVRTuint8; +typedef unsigned short PVRTuint16; +typedef unsigned int PVRTuint32; + +typedef float PVRTfloat32; + +#if (defined(__int64) || defined(_WIN32)) +typedef signed __int64 PVRTint64; +typedef unsigned __int64 PVRTuint64; +#elif defined(TInt64) +typedef TInt64 PVRTint64; +typedef TUInt64 PVRTuint64; +#else +typedef signed long long PVRTint64; +typedef unsigned long long PVRTuint64; +#endif + +#if __SIZEOF_WCHAR_T__ == 4 || __WCHAR_MAX__ > 0x10000 + #define PVRTSIZEOFWCHAR 4 +#else + #define PVRTSIZEOFWCHAR 2 +#endif + +PVRTSIZEASSERT(PVRTchar8, 1); +PVRTSIZEASSERT(PVRTint8, 1); +PVRTSIZEASSERT(PVRTuint8, 1); +PVRTSIZEASSERT(PVRTint16, 2); +PVRTSIZEASSERT(PVRTuint16, 2); +PVRTSIZEASSERT(PVRTint32, 4); +PVRTSIZEASSERT(PVRTuint32, 4); +PVRTSIZEASSERT(PVRTint64, 8); +PVRTSIZEASSERT(PVRTuint64, 8); +PVRTSIZEASSERT(PVRTfloat32, 4); + +/*!************************************************************************** +@Enum ETextureFilter +@Brief Enum values for defining texture filtering +****************************************************************************/ +enum ETextureFilter +{ + eFilter_Nearest, + eFilter_Linear, + eFilter_None, + + eFilter_Size, + eFilter_Default = eFilter_Nearest, + eFilter_MipDefault = eFilter_None +}; + +/*!************************************************************************** +@Enum ETextureWrap +@Brief Enum values for defining texture wrapping +****************************************************************************/ +enum ETextureWrap +{ + eWrap_Clamp, + eWrap_Repeat, + + eWrap_Size, + eWrap_Default = eWrap_Repeat +}; +#endif + + +/**************************************************************************** +** swap template function +****************************************************************************/ +/*!*************************************************************************** + @Function PVRTswap + @Input a Type a + @Input b Type b + @Description A swap template function that swaps a and b +*****************************************************************************/ + +template +inline void PVRTswap(T& a, T& b) +{ + T temp = a; + a = b; + b = temp; +} + +/*!*************************************************************************** + @Function PVRTClamp + @Input val Value to clamp + @Input min Minimum legal value + @Input max Maximum legal value + @Description A clamp template function that clamps val between min and max. +*****************************************************************************/ +template +inline T PVRTClamp(const T& val, const T& min, const T& max) +{ + if(val > max) + return max; + if(val < min) + return min; + return val; +} + +/*!*************************************************************************** + @Function PVRTByteSwap + @Input pBytes A number + @Input i32ByteNo Number of bytes in pBytes + @Description Swaps the endianness of pBytes in place +*****************************************************************************/ +inline void PVRTByteSwap(unsigned char* pBytes, int i32ByteNo) +{ + int i = 0, j = i32ByteNo - 1; + + while(i < j) + PVRTswap(pBytes[i++], pBytes[j--]); +} + +/*!*************************************************************************** + @Function PVRTByteSwap32 + @Input ui32Long A number + @Returns ui32Long with its endianness changed + @Description Converts the endianness of an unsigned int +*****************************************************************************/ +inline unsigned int PVRTByteSwap32(unsigned int ui32Long) +{ + return ((ui32Long&0x000000FF)<<24) + ((ui32Long&0x0000FF00)<<8) + ((ui32Long&0x00FF0000)>>8) + ((ui32Long&0xFF000000) >> 24); +} + +/*!*************************************************************************** + @Function PVRTByteSwap16 + @Input ui16Short A number + @Returns ui16Short with its endianness changed + @Description Converts the endianness of a unsigned short +*****************************************************************************/ +inline unsigned short PVRTByteSwap16(unsigned short ui16Short) +{ + return (ui16Short>>8) | (ui16Short<<8); +} + +/*!*************************************************************************** + @Function PVRTIsLittleEndian + @Returns True if the platform the code is ran on is little endian + @Description Returns true if the platform the code is ran on is little endian +*****************************************************************************/ +inline bool PVRTIsLittleEndian() +{ + static bool bLittleEndian; + static bool bIsInit = false; + + if(!bIsInit) + { + short int word = 0x0001; + char *byte = (char*) &word; + bLittleEndian = byte[0] ? true : false; + bIsInit = true; + } + + return bLittleEndian; +} + +#endif // _PVRTGLOBAL_H_ + +/***************************************************************************** + End of file (Tools.h) +*****************************************************************************/ + diff --git a/extern/pvrtextool/Include/PVRTMap.h b/extern/pvrtextool/Include/PVRTMap.h new file mode 100644 index 0000000..8442516 --- /dev/null +++ b/extern/pvrtextool/Include/PVRTMap.h @@ -0,0 +1,222 @@ +/****************************************************************************** + + @File PVRTMap.h + + @Title PVRTArray + + @Version + + @Copyright Copyright (c) Imagination Technologies Limited. All Rights Reserved. Strictly Confidential. + + @Platform ANSI compatible + + @Description A simple and easy to use implementation of a map. + +******************************************************************************/ +#ifndef __PVRTMAP_H__ +#define __PVRTMAP_H__ + +#include "PVRTArray.h" + +/*!**************************************************************************** +Class +******************************************************************************/ + +/*!*************************************************************************** +* @Class CPVRTMap +* @Brief Expanding map template class. +* @Description A simple and easy to use implementation of a map. +*****************************************************************************/ +template +class CPVRTMap +{ +public: + + /*!*********************************************************************** + @Function CPVRTMap + @Return A new CPVRTMap. + @Description Constructor for a CPVRTMap. + *************************************************************************/ + CPVRTMap() : m_Keys(), m_Data(), m_uiSize(0) + {} + + /*!*********************************************************************** + @Function ~CPVRTMap + @Description Destructor for a CPVRTMap. + *************************************************************************/ + ~CPVRTMap() + { + //Clear the map, that's enough - the CPVRTArray members will tidy everything else up. + Clear(); + } + + EPVRTError Reserve(const PVRTuint32 uiSize) + { + //Sets the capacity of each member array to the requested size. The array used will only expand. + //Returns the most serious error from either method. + return PVRT_MAX(m_Keys.SetCapacity(uiSize),m_Data.SetCapacity(uiSize)); + } + + /*!*********************************************************************** + @Function GetSize + @Return Number of meaningful members in the map. + @Description Returns the number of meaningful members in the map. + *************************************************************************/ + PVRTuint32 GetSize() const + { + //Return the size. + return m_uiSize; + } + + /*!*********************************************************************** + @Function GetIndexOf + @Input key + @Return The index value for a mapped item. + @Description Gets the position of a particular key/data within the map. + If the return value is exactly equal to the value of + GetSize() then the item has not been found. + *************************************************************************/ + PVRTuint32 GetIndexOf(const KeyType key) const + { + //Loop through all the valid keys. + for (PVRTuint32 i=0; i m_Keys; + + //Array of pointers to all the allocated data. + CPVRTArray m_Data; + + //The number of meaningful members in the map. + PVRTuint32 m_uiSize; +}; + +#endif // __PVRTMAP_H__ + +/***************************************************************************** +End of file (PVRTMap.h) +*****************************************************************************/ + diff --git a/extern/pvrtextool/Include/PVRTString.h b/extern/pvrtextool/Include/PVRTString.h new file mode 100644 index 0000000..4dede32 --- /dev/null +++ b/extern/pvrtextool/Include/PVRTString.h @@ -0,0 +1,985 @@ +/****************************************************************************** + + @File PVRTString.h + + @Title PVRTString + + @Version @Version + + @Copyright Copyright (c) Imagination Technologies Limited. All Rights Reserved. Strictly Confidential. + + @Platform ANSI compatible + + @Description A string class that can be used as drop-in replacement for + std::string on platforms/compilers that don't provide a full C++ + standard library. + +******************************************************************************/ +#ifndef _PVRTSTRING_H_ +#define _PVRTSTRING_H_ + +#include +#define _USING_PVRTSTRING_ + +/*!*************************************************************************** +@Class CPVRTString +@Brief A string class +*****************************************************************************/ +class CPVRTString +{ + +private: + + // Checking printf and scanf format strings +#if defined(_CC_GNU_) || defined(__GNUG__) || defined(__GNUC__) +#define FX_PRINTF(fmt,arg) __attribute__((format(printf,fmt,arg))) +#define FX_SCANF(fmt,arg) __attribute__((format(scanf,fmt,arg))) +#else +#define FX_PRINTF(fmt,arg) +#define FX_SCANF(fmt,arg) +#endif + +public: + typedef size_t size_type; + typedef char value_type; + typedef char& reference; + typedef const char& const_reference; + + static const size_type npos; + + + + + /*!*********************************************************************** + @Function CPVRTString + @Input _Ptr A string + @Input _Count Length of _Ptr + @Description Constructor + ************************************************************************/ + CPVRTString(const char* _Ptr, size_t _Count = npos); + + /*!*********************************************************************** + @Function CPVRTString + @Input _Right A string + @Input _Roff Offset into _Right + @Input _Count Number of chars from _Right to assign to the new string + @Description Constructor + ************************************************************************/ + CPVRTString(const CPVRTString& _Right, size_t _Roff = 0, size_t _Count = npos); + + /*!*********************************************************************** + @Function CPVRTString + @Input _Count Length of new string + @Input _Ch A char to fill it with + @Description Constructor + *************************************************************************/ + CPVRTString(size_t _Count, const char _Ch); + + /*!*********************************************************************** + @Function CPVRTString + @Input _Ch A char + @Description Constructor + *************************************************************************/ + CPVRTString(const char _Ch); + + /*!*********************************************************************** + @Function CPVRTString + @Description Constructor + ************************************************************************/ + CPVRTString(); + + /*!*********************************************************************** + @Function ~CPVRTString + @Description Destructor + ************************************************************************/ + virtual ~CPVRTString(); + + /*!*********************************************************************** + @Function append + @Input _Ptr A string + @Returns Updated string + @Description Appends a string + *************************************************************************/ + CPVRTString& append(const char* _Ptr); + + /*!*********************************************************************** + @Function append + @Input _Ptr A string + @Input _Count String length + @Returns Updated string + @Description Appends a string of length _Count + *************************************************************************/ + CPVRTString& append(const char* _Ptr, size_t _Count); + + /*!*********************************************************************** + @Function append + @Input _Str A string + @Returns Updated string + @Description Appends a string + *************************************************************************/ + CPVRTString& append(const CPVRTString& _Str); + + /*!*********************************************************************** + @Function append + @Input _Str A string + @Input _Off A position in string + @Input _Count Number of letters to append + @Returns Updated string + @Description Appends _Count letters of _Str from _Off in _Str + *************************************************************************/ + CPVRTString& append(const CPVRTString& _Str, size_t _Off, size_t _Count); + + /*!*********************************************************************** + @Function append + @Input _Ch A char + @Input _Count Number of times to append _Ch + @Returns Updated string + @Description Appends _Ch _Count times + *************************************************************************/ + CPVRTString& append(size_t _Count, const char _Ch); + + //template CPVRTString& append(InputIterator _First, InputIterator _Last); + + /*!*********************************************************************** + @Function assign + @Input _Ptr A string + @Returns Updated string + @Description Assigns the string to the string _Ptr + *************************************************************************/ + CPVRTString& assign(const char* _Ptr); + + /*!*********************************************************************** + @Function assign + @Input _Ptr A string + @Input _Count Length of _Ptr + @Returns Updated string + @Description Assigns the string to the string _Ptr + *************************************************************************/ + CPVRTString& assign(const char* _Ptr, size_t _Count); + + /*!*********************************************************************** + @Function assign + @Input _Str A string + @Returns Updated string + @Description Assigns the string to the string _Str + *************************************************************************/ + CPVRTString& assign(const CPVRTString& _Str); + + /*!*********************************************************************** + @Function assign + @Input _Str A string + @Input _Off First char to start assignment from + @Input _Count Length of _Str + @Returns Updated string + @Description Assigns the string to _Count characters in string _Str starting at _Off + *************************************************************************/ + CPVRTString& assign(const CPVRTString& _Str, size_t _Off, size_t _Count=npos); + + /*!*********************************************************************** + @Function assign + @Input _Ch A string + @Input _Count Number of times to repeat _Ch + @Returns Updated string + @Description Assigns the string to _Count copies of _Ch + *************************************************************************/ + CPVRTString& assign(size_t _Count, char _Ch); + + //template CPVRTString& assign(InputIterator _First, InputIterator _Last); + + //const_reference at(size_t _Off) const; + //reference at(size_t _Off); + + // const_iterator begin() const; + // iterator begin(); + + /*!*********************************************************************** + @Function c_str + @Returns const char* pointer of the string + @Description Returns a const char* pointer of the string + *************************************************************************/ + const char* c_str() const; + + /*!*********************************************************************** + @Function capacity + @Returns The size of the character array reserved + @Description Returns the size of the character array reserved + *************************************************************************/ + size_t capacity() const; + + /*!*********************************************************************** + @Function clear + @Description Clears the string + *************************************************************************/ + void clear(); + + /*!*********************************************************************** + @Function compare + @Input _Str A string to compare with + @Returns 0 if the strings match + @Description Compares the string with _Str + *************************************************************************/ + int compare(const CPVRTString& _Str) const; + + /*!*********************************************************************** + @Function compare + @Input _Pos1 Position to start comparing from + @Input _Num1 Number of chars to compare + @Input _Str A string to compare with + @Returns 0 if the strings match + @Description Compares the string with _Str + *************************************************************************/ + int compare(size_t _Pos1, size_t _Num1, const CPVRTString& _Str) const; + + /*!*********************************************************************** + @Function compare + @Input _Pos1 Position to start comparing from + @Input _Num1 Number of chars to compare + @Input _Str A string to compare with + @Input _Off Position in _Str to compare from + @Input _Count Number of chars in _Str to compare with + @Returns 0 if the strings match + @Description Compares the string with _Str + *************************************************************************/ + int compare(size_t _Pos1, size_t _Num1, const CPVRTString& _Str, size_t _Off, size_t _Count) const; + + /*!*********************************************************************** + @Function compare + @Input _Ptr A string to compare with + @Returns 0 if the strings match + @Description Compares the string with _Ptr + *************************************************************************/ + int compare(const char* _Ptr) const; + + /*!*********************************************************************** + @Function compare + @Input _Pos1 Position to start comparing from + @Input _Num1 Number of chars to compare + @Input _Ptr A string to compare with + @Returns 0 if the strings match + @Description Compares the string with _Ptr + *************************************************************************/ + int compare(size_t _Pos1, size_t _Num1, const char* _Ptr) const; + + /*!*********************************************************************** + @Function compare + @Input _Pos1 Position to start comparing from + @Input _Num1 Number of chars to compare + @Input _Ptr A string to compare with + @Input _Count Number of chars to compare + @Returns 0 if the strings match + @Description Compares the string with _Str + *************************************************************************/ + int compare(size_t _Pos1, size_t _Num1, const char* _Ptr, size_t _Count) const; + + /*!*********************************************************************** + @Function < + @Input _Str A string to compare with + @Returns True on success + @Description Less than operator + *************************************************************************/ + bool operator<(const CPVRTString & _Str) const; + + /*!*********************************************************************** + @Function == + @Input _Str A string to compare with + @Returns True if they match + @Description == Operator + *************************************************************************/ + bool operator==(const CPVRTString& _Str) const; + + /*!*********************************************************************** + @Function == + @Input _Ptr A string to compare with + @Returns True if they match + @Description == Operator + *************************************************************************/ + bool operator==(const char* const _Ptr) const; + + /*!*********************************************************************** + @Function != + @Input _Str A string to compare with + @Returns True if they don't match + @Description != Operator + *************************************************************************/ + bool operator!=(const CPVRTString& _Str) const; + + /*!*********************************************************************** + @Function != + @Input _Ptr A string to compare with + @Returns True if they don't match + @Description != Operator + *************************************************************************/ + bool operator!=(const char* const _Ptr) const; + + /*!*********************************************************************** + @Function copy + @Modified _Ptr A string to copy to + @Input _Count Size of _Ptr + @Input _Off Position to start copying from + @Returns Number of bytes copied + @Description Copies the string to _Ptr + *************************************************************************/ + size_t copy(char* _Ptr, size_t _Count, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function data + @Returns A const char* version of the string + @Description Returns a const char* version of the string + *************************************************************************/ + const char* data( ) const; + + /*!*********************************************************************** + @Function empty + @Returns True if the string is empty + @Description Returns true if the string is empty + *************************************************************************/ + bool empty() const; + + // const_iterator end() const; + // iterator end(); + + //iterator erase(iterator _First, iterator _Last); + //iterator erase(iterator _It); + + /*!*********************************************************************** + @Function erase + @Input _Pos The position to start erasing from + @Input _Count Number of chars to erase + @Returns An updated string + @Description Erases a portion of the string + *************************************************************************/ + CPVRTString& erase(size_t _Pos = 0, size_t _Count = npos); + + /*!*********************************************************************** + @Function substitute + @Input _src Character to search + @Input _subDes Character to substitute for + @Input _all Substitute all + @Returns An updated string + @Description Erases a portion of the string + *************************************************************************/ + CPVRTString& substitute(char _src,char _subDes, bool _all = true); + + /*!*********************************************************************** + @Function substitute + @Input _src Character to search + @Input _subDes Character to substitute for + @Input _all Substitute all + @Returns An updated string + @Description Erases a portion of the string + *************************************************************************/ + CPVRTString& substitute(const char* _src, const char* _subDes, bool _all = true); + + //size_t find(char _Ch, size_t _Off = 0) const; + //size_t find(const char* _Ptr, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find + @Input _Ptr String to search. + @Input _Off Offset to search from. + @Input _Count Number of characters in this string. + @Returns Position of the first matched string. + @Description Finds a substring within this string. + *************************************************************************/ + size_t find(const char* _Ptr, size_t _Off, size_t _Count) const; + + /*!*********************************************************************** + @Function find + @Input _Str String to search. + @Input _Off Offset to search from. + @Returns Position of the first matched string. + @Description Finds a substring within this string. + *************************************************************************/ + size_t find(const CPVRTString& _Str, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_first_not_of + @Input _Ch A char + @Input _Off Start position of the find + @Returns Position of the first char that is not _Ch + @Description Returns the position of the first char that is not _Ch + *************************************************************************/ + size_t find_first_not_of(char _Ch, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_first_not_of + @Input _Ptr A string + @Input _Off Start position of the find + @Returns Position of the first char that is not in _Ptr + @Description Returns the position of the first char that is not in _Ptr + *************************************************************************/ + size_t find_first_not_of(const char* _Ptr, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_first_not_of + @Input _Ptr A string + @Input _Off Start position of the find + @Input _Count Number of chars in _Ptr + @Returns Position of the first char that is not in _Ptr + @Description Returns the position of the first char that is not in _Ptr + *************************************************************************/ + size_t find_first_not_of(const char* _Ptr, size_t _Off, size_t _Count) const; + + /*!*********************************************************************** + @Function find_first_not_of + @Input _Str A string + @Input _Off Start position of the find + @Returns Position of the first char that is not in _Str + @Description Returns the position of the first char that is not in _Str + *************************************************************************/ + size_t find_first_not_of(const CPVRTString& _Str, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_first_of + @Input _Ch A char + @Input _Off Start position of the find + @Returns Position of the first char that is _Ch + @Description Returns the position of the first char that is _Ch + *************************************************************************/ + size_t find_first_of(char _Ch, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_first_of + @Input _Ptr A string + @Input _Off Start position of the find + @Returns Position of the first char that matches a char in _Ptr + @Description Returns the position of the first char that matches a char in _Ptr + *************************************************************************/ + size_t find_first_of(const char* _Ptr, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_first_of + @Input _Ptr A string + @Input _Off Start position of the find + @Input _Count Size of _Ptr + @Returns Position of the first char that matches a char in _Ptr + @Description Returns the position of the first char that matches a char in _Ptr + *************************************************************************/ + size_t find_first_of(const char* _Ptr, size_t _Off, size_t _Count) const; + + /*!*********************************************************************** + @Function find_first_ofn + @Input _Ptr A string + @Input _Off Start position of the find + @Input _Count Size of _Ptr + @Returns Position of the first char that matches a char in _Ptr + @Description Returns the position of the first char that matches all chars in _Ptr + *************************************************************************/ + size_t find_first_ofn(const char* _Ptr, size_t _Off, size_t _Count) const; + + + /*!*********************************************************************** + @Function find_first_of + @Input _Str A string + @Input _Off Start position of the find + @Returns Position of the first char that matches a char in _Str + @Description Returns the position of the first char that matches a char in _Str + *************************************************************************/ + size_t find_first_of(const CPVRTString& _Str, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_last_not_of + @Input _Ch A char + @Input _Off Start position of the find + @Returns Position of the last char that is not _Ch + @Description Returns the position of the last char that is not _Ch + *************************************************************************/ + size_t find_last_not_of(char _Ch, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_last_not_of + @Input _Ptr A string + @Input _Off Start position of the find + @Returns Position of the last char that is not in _Ptr + @Description Returns the position of the last char that is not in _Ptr + *************************************************************************/ + size_t find_last_not_of(const char* _Ptr, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_last_not_of + @Input _Ptr A string + @Input _Off Start position of the find + @Input _Count Length of _Ptr + @Returns Position of the last char that is not in _Ptr + @Description Returns the position of the last char that is not in _Ptr + *************************************************************************/ + size_t find_last_not_of(const char* _Ptr, size_t _Off, size_t _Count) const; + + /*!*********************************************************************** + @Function find_last_not_of + @Input _Str A string + @Input _Off Start position of the find + @Returns Position of the last char that is not in _Str + @Description Returns the position of the last char that is not in _Str + *************************************************************************/ + size_t find_last_not_of(const CPVRTString& _Str, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_last_of + @Input _Ch A char + @Input _Off Start position of the find + @Returns Position of the last char that is _Ch + @Description Returns the position of the last char that is _Ch + *************************************************************************/ + size_t find_last_of(char _Ch, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_last_of + @Input _Ptr A string + @Input _Off Start position of the find + @Returns Position of the last char that is in _Ptr + @Description Returns the position of the last char that is in _Ptr + *************************************************************************/ + size_t find_last_of(const char* _Ptr, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_last_of + @Input _Ptr A string + @Input _Off Start position of the find + @Input _Count Length of _Ptr + @Returns Position of the last char that is in _Ptr + @Description Returns the position of the last char that is in _Ptr + *************************************************************************/ + size_t find_last_of(const char* _Ptr, size_t _Off, size_t _Count) const; + + /*!*********************************************************************** + @Function find_last_of + @Input _Str A string + @Input _Off Start position of the find + @Returns Position of the last char that is in _Str + @Description Returns the position of the last char that is in _Str + *************************************************************************/ + size_t find_last_of(const CPVRTString& _Str, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_number_of + @Input _Ch A char + @Input _Off Start position of the find + @Returns Number of occurances of _Ch in the parent string. + @Description Returns the number of occurances of _Ch in the parent string. + *************************************************************************/ + size_t find_number_of(char _Ch, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_number_of + @Input _Ptr A string + @Input _Off Start position of the find + @Returns Number of occurances of _Ptr in the parent string. + @Description Returns the number of occurances of _Ptr in the parent string. + *************************************************************************/ + size_t find_number_of(const char* _Ptr, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_number_of + @Input _Ptr A string + @Input _Off Start position of the find + @Input _Count Size of _Ptr + @Returns Number of occurances of _Ptr in the parent string. + @Description Returns the number of occurances of _Ptr in the parent string. + *************************************************************************/ + size_t find_number_of(const char* _Ptr, size_t _Off, size_t _Count) const; + + /*!*********************************************************************** + @Function find_number_of + @Input _Str A string + @Input _Off Start position of the find + @Returns Number of occurances of _Str in the parent string. + @Description Returns the number of occurances of _Str in the parent string. + *************************************************************************/ + size_t find_number_of(const CPVRTString& _Str, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_next_occurance_of + @Input _Ch A char + @Input _Off Start position of the find + @Returns Next occurance of _Ch in the parent string. + @Description Returns the next occurance of _Ch in the parent string + after or at _Off. If not found, returns the length of the string. + *************************************************************************/ + int find_next_occurance_of(char _Ch, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_next_occurance_of + @Input _Ptr A string + @Input _Off Start position of the find + @Returns Next occurance of _Ptr in the parent string. + @Description Returns the next occurance of _Ptr in the parent string + after or at _Off. If not found, returns the length of the string. + *************************************************************************/ + int find_next_occurance_of(const char* _Ptr, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_next_occurance_of + @Input _Ptr A string + @Input _Off Start position of the find + @Input _Count Size of _Ptr + @Returns Next occurance of _Ptr in the parent string. + @Description Returns the next occurance of _Ptr in the parent string + after or at _Off. If not found, returns the length of the string. + *************************************************************************/ + int find_next_occurance_of(const char* _Ptr, size_t _Off, size_t _Count) const; + + /*!*********************************************************************** + @Function find_next_occurance_of + @Input _Str A string + @Input _Off Start position of the find + @Returns Next occurance of _Str in the parent string. + @Description Returns the next occurance of _Str in the parent string + after or at _Off. If not found, returns the length of the string. + *************************************************************************/ + int find_next_occurance_of(const CPVRTString& _Str, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_previous_occurance_of + @Input _Ch A char + @Input _Off Start position of the find + @Returns Previous occurance of _Ch in the parent string. + @Description Returns the previous occurance of _Ch in the parent string + before _Off. If not found, returns -1. + *************************************************************************/ + int find_previous_occurance_of(char _Ch, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_previous_occurance_of + @Input _Ptr A string + @Input _Off Start position of the find + @Returns Previous occurance of _Ptr in the parent string. + @Description Returns the previous occurance of _Ptr in the parent string + before _Off. If not found, returns -1. + *************************************************************************/ + int find_previous_occurance_of(const char* _Ptr, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function find_previous_occurance_of + @Input _Ptr A string + @Input _Off Start position of the find + @Input _Count Size of _Ptr + @Returns Previous occurance of _Ptr in the parent string. + @Description Returns the previous occurance of _Ptr in the parent string + before _Off. If not found, returns -1. + *************************************************************************/ + int find_previous_occurance_of(const char* _Ptr, size_t _Off, size_t _Count) const; + + /*!*********************************************************************** + @Function find_previous_occurance_of + @Input _Str A string + @Input _Off Start position of the find + @Returns Previous occurance of _Str in the parent string. + @Description Returns the previous occurance of _Str in the parent string + before _Off. If not found, returns -1. + *************************************************************************/ + int find_previous_occurance_of(const CPVRTString& _Str, size_t _Off = 0) const; + + /*!*********************************************************************** + @Function left + @Input iSize number of characters to return (excluding null character) + @Returns The leftmost 'iSize' characters of the string. + @Description Returns the leftmost characters of the string (excluding + the null character) in a new CPVRTString. If iSize is + larger than the string, a copy of the original string is returned. + *************************************************************************/ + CPVRTString left(size_t iSize) const; + + /*!*********************************************************************** + @Function right + @Input iSize number of characters to return (excluding null character) + @Returns The rightmost 'iSize' characters of the string. + @Description Returns the rightmost characters of the string (excluding + the null character) in a new CPVRTString. If iSize is + larger than the string, a copy of the original string is returned. + *************************************************************************/ + CPVRTString right(size_t iSize) const; + + //allocator_type get_allocator( ) const; + + //CPVRTString& insert(size_t _P0, const char* _Ptr); + //CPVRTString& insert(size_t _P0, const char* _Ptr, size_t _Count); + //CPVRTString& insert(size_t _P0, const CPVRTString& _Str); + //CPVRTString& insert(size_t _P0, const CPVRTString& _Str, size_t _Off, size_t _Count); + //CPVRTString& insert(size_t _P0, size_t _Count, char _Ch); + //iterator insert(iterator _It, char _Ch = char()); + //template void insert(iterator _It, InputIterator _First, InputIterator _Last); + //void insert(iterator _It, size_t _Count, char _Ch); + + /*!*********************************************************************** + @Function length + @Returns Length of the string + @Description Returns the length of the string + *************************************************************************/ + size_t length() const; + + /*!*********************************************************************** + @Function max_size + @Returns The maximum number of chars that the string can contain + @Description Returns the maximum number of chars that the string can contain + *************************************************************************/ + size_t max_size() const; + + /*!*********************************************************************** + @Function push_back + @Input _Ch A char to append + @Description Appends _Ch to the string + *************************************************************************/ + void push_back(char _Ch); + + // const_reverse_iterator rbegin() const; + // reverse_iterator rbegin(); + + // const_reverse_iterator rend() const; + // reverse_iterator rend(); + + //CPVRTString& replace(size_t _Pos1, size_t _Num1, const char* _Ptr); + //CPVRTString& replace(size_t _Pos1, size_t _Num1, const CPVRTString& _Str); + //CPVRTString& replace(size_t _Pos1, size_t _Num1, const char* _Ptr, size_t _Num2); + //CPVRTString& replace(size_t _Pos1, size_t _Num1, const CPVRTString& _Str, size_t _Pos2, size_t _Num2); + //CPVRTString& replace(size_t _Pos1, size_t _Num1, size_t _Count, char _Ch); + + //CPVRTString& replace(iterator _First0, iterator _Last0, const char* _Ptr); + //CPVRTString& replace(iterator _First0, iterator _Last0, const CPVRTString& _Str); + //CPVRTString& replace(iterator _First0, iterator _Last0, const char* _Ptr, size_t _Num2); + //CPVRTString& replace(iterator _First0, iterator _Last0, size_t _Num2, char _Ch); + //template CPVRTString& replace(iterator _First0, iterator _Last0, InputIterator _First, InputIterator _Last); + + /*!*********************************************************************** + @Function reserve + @Input _Count Size of string to reserve + @Description Reserves space for _Count number of chars + *************************************************************************/ + void reserve(size_t _Count = 0); + + /*!*********************************************************************** + @Function resize + @Input _Count Size of string to resize to + @Input _Ch Character to use to fill any additional space + @Description Resizes the string to _Count in length + *************************************************************************/ + void resize(size_t _Count, char _Ch = char()); + + //size_t rfind(char _Ch, size_t _Off = npos) const; + //size_t rfind(const char* _Ptr, size_t _Off = npos) const; + //size_t rfind(const char* _Ptr, size_t _Off = npos, size_t _Count) const; + //size_t rfind(const CPVRTString& _Str, size_t _Off = npos) const; + + /*!*********************************************************************** + @Function size + @Returns Size of the string + @Description Returns the size of the string + *************************************************************************/ + size_t size() const; + + /*!*********************************************************************** + @Function substr + @Input _Off Start of the substring + @Input _Count Length of the substring + @Returns A substring of the string + @Description Returns the size of the string + *************************************************************************/ + CPVRTString substr(size_t _Off = 0, size_t _Count = npos) const; + + /*!*********************************************************************** + @Function swap + @Input _Str A string to swap with + @Description Swaps the contents of the string with _Str + *************************************************************************/ + void swap(CPVRTString& _Str); + + /*!*********************************************************************** + @Function toLower + @Returns An updated string + @Description Converts the string to lower case + *************************************************************************/ + CPVRTString& toLower(); + + /*!*********************************************************************** + @Function toUpper + @Returns An updated string + @Description Converts the string to upper case + *************************************************************************/ + CPVRTString& toUpper(); + + /*!*********************************************************************** + @Function format + @Input pFormat A string containing the formating + @Returns A formatted string + @Description return the formatted string + ************************************************************************/ + CPVRTString format(const char *pFormat, ...); + + /*!*********************************************************************** + @Function += + @Input _Ch A char + @Returns An updated string + @Description += Operator + *************************************************************************/ + CPVRTString& operator+=(char _Ch); + + /*!*********************************************************************** + @Function += + @Input _Ptr A string + @Returns An updated string + @Description += Operator + *************************************************************************/ + CPVRTString& operator+=(const char* _Ptr); + + /*!*********************************************************************** + @Function += + @Input _Right A string + @Returns An updated string + @Description += Operator + *************************************************************************/ + CPVRTString& operator+=(const CPVRTString& _Right); + + /*!*********************************************************************** + @Function = + @Input _Ch A char + @Returns An updated string + @Description = Operator + *************************************************************************/ + CPVRTString& operator=(char _Ch); + + /*!*********************************************************************** + @Function = + @Input _Ptr A string + @Returns An updated string + @Description = Operator + *************************************************************************/ + CPVRTString& operator=(const char* _Ptr); + + /*!*********************************************************************** + @Function = + @Input _Right A string + @Returns An updated string + @Description = Operator + *************************************************************************/ + CPVRTString& operator=(const CPVRTString& _Right); + + /*!*********************************************************************** + @Function [] + @Input _Off An index into the string + @Returns A character + @Description [] Operator + *************************************************************************/ + const_reference operator[](size_t _Off) const; + + /*!*********************************************************************** + @Function [] + @Input _Off An index into the string + @Returns A character + @Description [] Operator + *************************************************************************/ + reference operator[](size_t _Off); + + /*!*********************************************************************** + @Function + + @Input _Left A string + @Input _Right A string + @Returns An updated string + @Description + Operator + *************************************************************************/ + friend CPVRTString operator+ (const CPVRTString& _Left, const CPVRTString& _Right); + + /*!*********************************************************************** + @Function + + @Input _Left A string + @Input _Right A string + @Returns An updated string + @Description + Operator + *************************************************************************/ + friend CPVRTString operator+ (const CPVRTString& _Left, const char* _Right); + + /*!*********************************************************************** + @Function + + @Input _Left A string + @Input _Right A string + @Returns An updated string + @Description + Operator + *************************************************************************/ + friend CPVRTString operator+ (const CPVRTString& _Left, const char _Right); + + /*!*********************************************************************** + @Function + + @Input _Left A string + @Input _Right A string + @Returns An updated string + @Description + Operator + *************************************************************************/ + friend CPVRTString operator+ (const char* _Left, const CPVRTString& _Right); + + + /*!*********************************************************************** + @Function + + @Input _Left A string + @Input _Right A string + @Returns An updated string + @Description + Operator + *************************************************************************/ + friend CPVRTString operator+ (const char _Left, const CPVRTString& _Right); + +protected: + char* m_pString; + size_t m_Size; + size_t m_Capacity; +}; + +/************************************************************************* +* MISCELLANEOUS UTILITY FUNCTIONS +*************************************************************************/ +/*!*********************************************************************** +@Function PVRTStringGetFileExtension +@Input strFilePath A string +@Returns Extension +@Description Extracts the file extension from a file path. +Returns an empty CPVRTString if no extension is found. +************************************************************************/ +CPVRTString PVRTStringGetFileExtension(const CPVRTString& strFilePath); + +/*!*********************************************************************** +@Function PVRTStringGetContainingDirectoryPath +@Input strFilePath A string +@Returns Directory +@Description Extracts the directory portion from a file path. +************************************************************************/ +CPVRTString PVRTStringGetContainingDirectoryPath(const CPVRTString& strFilePath); + +/*!*********************************************************************** +@Function PVRTStringGetFileName +@Input strFilePath A string +@Returns FileName +@Description Extracts the name and extension portion from a file path. +************************************************************************/ +CPVRTString PVRTStringGetFileName(const CPVRTString& strFilePath); + +/*!*********************************************************************** +@Function PVRTStringStripWhiteSpaceFromStartOf +@Input strLine A string +@Returns Result of the white space stripping +@Description strips white space characters from the beginning of a CPVRTString. +************************************************************************/ +CPVRTString PVRTStringStripWhiteSpaceFromStartOf(const CPVRTString& strLine); + +/*!*********************************************************************** +@Function PVRTStringStripWhiteSpaceFromEndOf +@Input strLine A string +@Returns Result of the white space stripping +@Description strips white space characters from the end of a CPVRTString. +************************************************************************/ +CPVRTString PVRTStringStripWhiteSpaceFromEndOf(const CPVRTString& strLine); + +/*!*********************************************************************** +@Function PVRTStringFromFormattedStr +@Input pFormat A string containing the formating +@Returns A formatted string +@Description Creates a formatted string +************************************************************************/ +CPVRTString PVRTStringFromFormattedStr(const char *pFormat, ...); + +#endif // _PVRTSTRING_H_ + + +/***************************************************************************** +End of file (PVRTString.h) +*****************************************************************************/ + + + + diff --git a/extern/pvrtextool/Include/PVRTTexture.h b/extern/pvrtextool/Include/PVRTTexture.h new file mode 100644 index 0000000..32ad588 --- /dev/null +++ b/extern/pvrtextool/Include/PVRTTexture.h @@ -0,0 +1,703 @@ +/****************************************************************************** + + @File PVRTTexture.h + + @Title PVRTTexture + + @Version @Version + + @Copyright Copyright (c) Imagination Technologies Limited. All Rights Reserved. Strictly Confidential. + + @Platform ANSI compatible + + @Description Texture loading. + +******************************************************************************/ +#ifndef _PVRTTEXTURE_H_ +#define _PVRTTEXTURE_H_ + +//ACS: PVRTGlobal.h was dragging in too much stuff (like windows.h & crtdbg.h) +// so I split the relevant stuff out into a new file, PVRTTypes.h +//#include "PVRTGlobal.h" +#include "PVRTTypes.h" + +/***************************************************************************** +* Texture related constants and enumerations. +*****************************************************************************/ +// V3 Header Identifiers. +const PVRTuint32 PVRTEX3_IDENT = 0x03525650; // 'P''V''R'3 +const PVRTuint32 PVRTEX3_IDENT_REV = 0x50565203; +// If endianness is backwards then PVR3 will read as 3RVP, hence why it is written as an int. + +//Current version texture identifiers +const PVRTuint32 PVRTEX_CURR_IDENT = PVRTEX3_IDENT; +const PVRTuint32 PVRTEX_CURR_IDENT_REV = PVRTEX3_IDENT_REV; + +// PVR Header file flags. Condition if true. If false, opposite is true unless specified. +const PVRTuint32 PVRTEX3_FILE_COMPRESSED = (1<<0); // Texture has been file compressed using PVRTexLib (currently unused) +const PVRTuint32 PVRTEX3_PREMULTIPLIED = (1<<1); // Texture has been premultiplied by alpha value. + +// Mip Map level specifier constants. Other levels are specified by 1,2...n +const PVRTint32 PVRTEX_TOPMIPLEVEL = 0; +const PVRTint32 PVRTEX_ALLMIPLEVELS = -1; //This is a special number used simply to return a total of all MIP levels when dealing with data sizes. + +//values for each meta data type that we know about. Texture arrays hinge on each surface being identical in all but content, including meta data. +//If the meta data varies even slightly then a new texture should be used. It is possible to write your own extension to get around this however. +enum EPVRTMetaData +{ + ePVRTMetaDataTextureAtlasCoords=0, + ePVRTMetaDataBumpData, + ePVRTMetaDataCubeMapOrder, + ePVRTMetaDataTextureOrientation, + ePVRTMetaDataBorderData, + ePVRTMetaDataPadding, + ePVRTMetaDataNumMetaDataTypes +}; + +enum EPVRTAxis +{ + ePVRTAxisX = 0, + ePVRTAxisY = 1, + ePVRTAxisZ = 2 +}; + +enum EPVRTOrientation +{ + ePVRTOrientLeft = 1< +class CPVRTMap; + + +/*!*********************************************************************** + @Function PVRTGetBitsPerPixel + @Input u64PixelFormat A PVR Pixel Format ID. + @Return const PVRTuint32 Number of bits per pixel. + @Description Returns the number of bits per pixel in a PVR Pixel Format + identifier. +*************************************************************************/ +PVRTuint32 PVRTGetBitsPerPixel(PVRTuint64 u64PixelFormat); + +/*!*********************************************************************** + @Function PVRTGetFormatMinDims + @Input u64PixelFormat A PVR Pixel Format ID. + @Modified minX Returns the minimum width. + @Modified minY Returns the minimum height. + @Modified minZ Returns the minimum depth. + @Description Gets the minimum dimensions (x,y,z) for a given pixel format. +*************************************************************************/ +void PVRTGetFormatMinDims(PVRTuint64 u64PixelFormat, PVRTuint32 &minX, PVRTuint32 &minY, PVRTuint32 &minZ); + +/*!*********************************************************************** + @Function PVRTConvertOldTextureHeaderToV3 + @Input LegacyHeader Legacy header for conversion. + @Modified NewHeader New header to output into. + @Modified pMetaData MetaData Map to output into. + @Description Converts a legacy texture header (V1 or V2) to a current + generation header (V3) +*************************************************************************/ +void PVRTConvertOldTextureHeaderToV3(const PVR_Texture_Header* LegacyHeader, PVRTextureHeaderV3& NewHeader, CPVRTMap >* pMetaData); + +/*!*********************************************************************** + @Function PVRTMapLegacyTextureEnumToNewFormat + @Input OldFormat Legacy Enumeration Value + @Modified newType New PixelType identifier. + @Modified newCSpace New ColourSpace + @Modified newChanType New Channel Type + @Modified isPreMult Whether format is pre-multiplied + @Description Maps a legacy enumeration value to the new PVR3 style format. +*************************************************************************/ +void PVRTMapLegacyTextureEnumToNewFormat(PVRTPixelType OldFormat, PVRTuint64& newType, EPVRTColourSpace& newCSpace, EPVRTVariableType& newChanType, bool& isPreMult); + +/*!*************************************************************************** +@Function PVRTTextureLoadTiled +@Modified pDst Texture to place the tiled data +@Input nWidthDst Width of destination texture +@Input nHeightDst Height of destination texture +@Input pSrc Texture to tile +@Input nWidthSrc Width of source texture +@Input nHeightSrc Height of source texture +@Input nElementSize Bytes per pixel +@Input bTwiddled True if the data is twiddled +@Description Needed by PVRTTextureTile() in the various PVRTTextureAPIs +*****************************************************************************/ +void PVRTTextureLoadTiled( + PVRTuint8 * const pDst, + const unsigned int nWidthDst, + const unsigned int nHeightDst, + const PVRTuint8 * const pSrc, + const unsigned int nWidthSrc, + const unsigned int nHeightSrc, + const unsigned int nElementSize, + const bool bTwiddled); + + +/*!*************************************************************************** +@Function PVRTTextureTwiddle +@Output a Twiddled value +@Input u Coordinate axis 0 +@Input v Coordinate axis 1 +@Description Combine a 2D coordinate into a twiddled value +*****************************************************************************/ +void PVRTTextureTwiddle(unsigned int &a, const unsigned int u, const unsigned int v); + +/*!*************************************************************************** +@Function PVRTTextureDeTwiddle +@Output u Coordinate axis 0 +@Output v Coordinate axis 1 +@Input a Twiddled value +@Description Extract 2D coordinates from a twiddled value. +*****************************************************************************/ +void PVRTTextureDeTwiddle(unsigned int &u, unsigned int &v, const unsigned int a); + +/*!*********************************************************************** +@Function PVRTGetTextureDataSize +@Input sTextureHeader Specifies the texture header. +@Input iMipLevel Specifies a mip level to check, 'PVRTEX_ALLMIPLEVELS' + can be passed to get the size of all MIP levels. +@Input bAllSurfaces Size of all surfaces is calculated if true, + only a single surface if false. +@Input bAllFaces Size of all faces is calculated if true, + only a single face if false. +@Return PVRTuint32 Size in BYTES of the specified texture area. +@Description Gets the size in BYTES of the texture, given various input + parameters. User can retrieve the size of either all + surfaces or a single surface, all faces or a single face and + all MIP-Maps or a single specified MIP level. +*************************************************************************/ +PVRTuint32 PVRTGetTextureDataSize(PVRTextureHeaderV3 sTextureHeader, PVRTint32 iMipLevel=PVRTEX_ALLMIPLEVELS, bool bAllSurfaces = true, bool bAllFaces = true); + +#endif /* _PVRTTEXTURE_H_ */ + +/***************************************************************************** +End of file (PVRTTexture.h) +*****************************************************************************/ + diff --git a/extern/pvrtextool/Include/PVRTTypes.h b/extern/pvrtextool/Include/PVRTTypes.h new file mode 100644 index 0000000..984ff86 --- /dev/null +++ b/extern/pvrtextool/Include/PVRTTypes.h @@ -0,0 +1,120 @@ +/****************************************************************************** + + @File PVRTTypes.h + + @Title PVRTTypes + + @Version + + @Copyright Copyright (c) Imagination Technologies Limited. All Rights Reserved. Strictly Confidential. + + @Platform ANSI compatible + + @Description Global enums and typedefs for PVRTools + +******************************************************************************/ + +//ACS: I split this out of PVRTGlobal.h + +#ifndef _PVRTTYPES_H_ +#define _PVRTTYPES_H_ + +/*!*************************************************************************** + Macros +*****************************************************************************/ +//#include + +// This macro is used to check at compile time that types are of a certain size +// If the size does not equal the expected size, this typedefs an array of size 0 +// which causes a compile error +#define PVRTSIZEASSERT(T, size) typedef int (sizeof_##T)[sizeof(T) == (size)] +#define PVRTCOMPILEASSERT(T, expr) typedef int (assert_##T)[expr] + + +/**************************************************************************** +** Integer types +****************************************************************************/ + +typedef char PVRTchar8; +typedef signed char PVRTint8; +typedef signed short PVRTint16; +typedef signed int PVRTint32; +typedef unsigned char PVRTuint8; +typedef unsigned short PVRTuint16; +typedef unsigned int PVRTuint32; + +typedef float PVRTfloat32; + +#if (defined(__int64) || defined(_WIN32)) +typedef signed __int64 PVRTint64; +typedef unsigned __int64 PVRTuint64; +#elif defined(TInt64) +typedef TInt64 PVRTint64; +typedef TUInt64 PVRTuint64; +#else +typedef signed long long PVRTint64; +typedef unsigned long long PVRTuint64; +#endif + +#if __SIZEOF_WCHAR_T__ == 4 || __WCHAR_MAX__ > 0x10000 + #define PVRTSIZEOFWCHAR 4 +#else + #define PVRTSIZEOFWCHAR 2 +#endif + +PVRTSIZEASSERT(PVRTchar8, 1); +PVRTSIZEASSERT(PVRTint8, 1); +PVRTSIZEASSERT(PVRTuint8, 1); +PVRTSIZEASSERT(PVRTint16, 2); +PVRTSIZEASSERT(PVRTuint16, 2); +PVRTSIZEASSERT(PVRTint32, 4); +PVRTSIZEASSERT(PVRTuint32, 4); +PVRTSIZEASSERT(PVRTint64, 8); +PVRTSIZEASSERT(PVRTuint64, 8); +PVRTSIZEASSERT(PVRTfloat32, 4); + +/*!************************************************************************** +@Enum ETextureFilter +@Brief Enum values for defining texture filtering +****************************************************************************/ +enum ETextureFilter +{ + eFilter_Nearest, + eFilter_Linear, + eFilter_None, + + eFilter_Size, + eFilter_Default = eFilter_Nearest, + eFilter_MipDefault = eFilter_None +}; + +/*!************************************************************************** +@Enum ETextureWrap +@Brief Enum values for defining texture wrapping +****************************************************************************/ +enum ETextureWrap +{ + eWrap_Clamp, + eWrap_Repeat, + + eWrap_Size, + eWrap_Default = eWrap_Repeat +}; + +/***************************************************************************** + ACS: Handle missing assert macros. + Maybe you needed to include PVRTGlobal.h after all? +*****************************************************************************/ +#ifndef _ASSERT +# define _ASSERT(X) +#endif +#ifndef _ASSERTE +# define _ASSERTE(X) +#endif + +#endif // _PVRTTYPES_H_ + +/***************************************************************************** + End of file +*****************************************************************************/ + diff --git a/extern/pvrtextool/Include/PVRTexture.h b/extern/pvrtextool/Include/PVRTexture.h new file mode 100644 index 0000000..df300e6 --- /dev/null +++ b/extern/pvrtextool/Include/PVRTexture.h @@ -0,0 +1,225 @@ +/****************************************************************************** + + @File PVRTexture.h + + @Title + + @Version + + @Copyright Copyright (c) Imagination Technologies Limited. All Rights Reserved. Strictly Confidential. + + @Platform + + @Description + +******************************************************************************/ +#ifndef _PVRTEXTURE_H +#define _PVRTEXTURE_H + +#include "PVRTextureDefines.h" +#include "PVRTextureHeader.h" +//ACS: removed unneccesary includes: +//#include "PVRTString.h" + +namespace pvrtexture +{ + class PVR_DLL CPVRTexture : public CPVRTextureHeader + { + public: + /******************************************************************************* + * Construction methods for a texture. + *******************************************************************************/ + /*!*********************************************************************** + @Function CPVRTexture + @Return CPVRTexture A new texture. + @Description Creates a new empty texture + *************************************************************************/ + CPVRTexture(); + /*!*********************************************************************** + @Function CPVRTexture + @Input sHeader + @Input pData + @Return CPVRTexture A new texture. + @Description Creates a new texture based on a texture header, + pre-allocating the correct amount of memory. If data is + supplied, it will be copied into memory. + *************************************************************************/ + CPVRTexture(const CPVRTextureHeader& sHeader, const void* pData=NULL); + + /*!*********************************************************************** + @Function CPVRTexture + @Input szFilePath + @Return CPVRTexture A new texture. + @Description Creates a new texture from a filepath. + *************************************************************************/ + CPVRTexture(const char* szFilePath); + + /*!*********************************************************************** + @Function CPVRTexture + @Input pTexture + @Return CPVRTexture A new texture. + @Description Creates a new texture from a pointer that includes a header + structure, meta data and texture data as laid out in a file. + This functionality is primarily for user defined file loading. + Header may be any version of pvr. + *************************************************************************/ + CPVRTexture( const void* pTexture ); + + /*!*********************************************************************** + @Function CPVRTexture + @Input texture + @Return CPVRTexture A new texture + @Description Creates a new texture as a copy of another. + *************************************************************************/ + CPVRTexture(const CPVRTexture& texture); + + /*!*********************************************************************** + @Function ~CPVRTexture + @Description Deconstructor for CPVRTextures. + *************************************************************************/ + ~CPVRTexture(); + + /*!*********************************************************************** + @Function operator= + @Input rhs + @Return CPVRTexture& This texture. + @Description Will copy the contents and information of another texture into this one. + *************************************************************************/ + CPVRTexture& operator=(const CPVRTexture& rhs); + + /******************************************************************************* + * Texture accessor functions - others are inherited from CPVRTextureHeader. + *******************************************************************************/ + /*!*********************************************************************** + @Function getDataPtr + @Input uiMIPLevel + @Input uiArrayMember + @Input uiFaceNumber + @Return void* Pointer to a location in the texture. + @Description Returns a pointer into the texture's data. + It is possible to specify an offset to specific array members, + faces and MIP Map levels. + *************************************************************************/ + void* getDataPtr(uint32 uiMIPLevel = 0, uint32 uiArrayMember = 0, uint32 uiFaceNumber = 0) const; + + /*!*********************************************************************** + @Function getHeader + @Return const CPVRTextureHeader& Returns the header only for this texture. + @Description Gets the header for this texture, allowing you to create a new + texture based on this one with some changes. Useful for passing + information about a texture without passing all of its data. + *************************************************************************/ + const CPVRTextureHeader& getHeader() const; + + /******************************************************************************* + * File io. + *******************************************************************************/ + + /*!*********************************************************************** + @Function setPaddedMetaData + @Input uiPadding + @Description When writing the texture out to a PVR file, it is often + desirable to pad the meta data so that the start of the + texture data aligns to a given boundary. + This function pads to a boundary value equal to "uiPadding". + For example setting uiPadding=8 will align the start of the + texture data to an 8 byte boundary. + Note - this should be called immediately before saving as + the value is worked out based on the current meta data size. + *************************************************************************/ + void addPaddingMetaData( uint32 uiPadding ); + + /*!*********************************************************************** + @Function saveFile + @Input filepath + @Return bool Whether the method succeeds or not. + @Description Writes out to a file, given a filename and path. + File type will be determined by the extension present in the string. + If no extension is present, PVR format will be selected. + Unsupported formats will result in failure. + *************************************************************************/ + bool saveFile(const CPVRTString& filepath) const; + + /*!*********************************************************************** + @Function saveFileLegacyPVR + @Input filepath + @Input eApi + @Return bool Whether the method succeeds or not. + @Description Writes out to a file, stripping any extensions specified + and appending .pvr. This function is for legacy support only + and saves out to PVR Version 2 file. The target api must be + specified in order to save to this format. + *************************************************************************/ + bool saveFileLegacyPVR(const CPVRTString& filepath, ELegacyApi eApi) const; + + private: + size_t m_stDataSize; // Size of the texture data. + uint8* m_pTextureData; // Pointer to texture data. + + /******************************************************************************* + * Private IO functions + *******************************************************************************/ + /*!*********************************************************************** + @Function loadPVRFile + @Input pTextureFile + @Description Loads a PVR file. + *************************************************************************/ + bool privateLoadPVRFile(FILE* pTextureFile); + + /*!*********************************************************************** + @Function privateSavePVRFile + @Input pTextureFile + @Description Saves a PVR File. + *************************************************************************/ + bool privateSavePVRFile(FILE* pTextureFile) const; + + /*!*********************************************************************** + @Function loadKTXFile + @Input pTextureFile + @Description Loads a KTX file. + *************************************************************************/ + bool privateLoadKTXFile(FILE* pTextureFile); + + /*!*********************************************************************** + @Function privateSaveKTXFile + @Input pTextureFile + @Description Saves a KTX File. + *************************************************************************/ + bool privateSaveKTXFile(FILE* pTextureFile) const; + + /*!*********************************************************************** + @Function loadDDSFile + @Input pTextureFile + @Description Loads a DDS file. + *************************************************************************/ + bool privateLoadDDSFile(FILE* pTextureFile); + bool privateLoadDDS10File(FILE* pTextureFile); + + /*!*********************************************************************** + @Function privateSaveDDSFile + @Input pTextureFile + @Description Saves a DDS File. + *************************************************************************/ + bool privateSaveDDSFile(FILE* pTextureFile) const; + + //Legacy IO + /*!*********************************************************************** + @Function privateSavePVRFile + @Input pTextureFile + @Input filename + @Description Saves a .h File. + *************************************************************************/ + bool privateSaveCHeaderFile(FILE* pTextureFile, CPVRTString filename) const; + + /*!*********************************************************************** + @Function privateSaveLegacyPVRFile + @Input pTextureFile + @Input eApi + @Description Saves a legacy PVR File - Uses version 2 file format. + *************************************************************************/ + bool privateSaveLegacyPVRFile(FILE* pTextureFile, ELegacyApi eApi) const; + }; +}; + +#endif //_PVRTEXTURE_H + diff --git a/extern/pvrtextool/Include/PVRTextureDefines.h b/extern/pvrtextool/Include/PVRTextureDefines.h new file mode 100644 index 0000000..ee6ba47 --- /dev/null +++ b/extern/pvrtextool/Include/PVRTextureDefines.h @@ -0,0 +1,109 @@ +/****************************************************************************** + + @File PVRTextureDefines.h + + @Title + + @Version + + @Copyright Copyright (c) Imagination Technologies Limited. All Rights Reserved. Strictly Confidential. + + @Platform + + @Description + +******************************************************************************/ +#ifndef _PVRTEXTURE_DEFINES_H +#define _PVRTEXTURE_DEFINES_H + +//To use the PVRTexLib .dll on Windows, you need to define _WINDLL_IMPORT +#ifndef PVR_DLL +#if defined(_WINDLL_EXPORT) +#define PVR_DLL __declspec(dllexport) +//Forward declaration of PVRTexture Header and CPVRTMap. This exports their interfaces for DLLs. +struct PVR_DLL PVRTextureHeaderV3; +template +class PVR_DLL CPVRTMap; +template +class PVR_DLL CPVRTArray; +#elif defined(_WINDLL_IMPORT) +#define PVR_DLL __declspec(dllimport) +//Forward declaration of PVRTexture Header and CPVRTMap. This imports their interfaces for DLLs. +struct PVR_DLL PVRTextureHeaderV3; +template +class PVR_DLL CPVRTMap; +template +class PVR_DLL CPVRTArray; +#else +#define PVR_DLL +#endif +#endif + + +#include "PVRTTexture.h" + +namespace pvrtexture +{ + /***************************************************************************** + * Type defines for standard variable sizes. + *****************************************************************************/ + typedef signed char int8; + typedef signed short int16; + typedef signed int int32; + typedef signed long long int64; + typedef unsigned char uint8; + typedef unsigned short uint16; + typedef unsigned int uint32; + typedef unsigned long long uint64; + + /***************************************************************************** + * Texture related constants and enumerations. + *****************************************************************************/ + enum ECompressorQuality + { + ePVRTCFast=0, + ePVRTCNormal, + ePVRTCHigh, + ePVRTCBest, + eNumPVRTCModes, + + eETCFast=0, + eETCFastPerceptual, + eETCSlow, + eETCSlowPerceptual, + eNumETCModes + }; + + enum EResizeMode + { + eResizeNearest, + eResizeLinear, + eResizeCubic, + eNumResizeModes + }; + + // Legacy - API enums. + enum ELegacyApi + { + eOGLES=1, + eOGLES2, + eD3DM, + eOGL, + eDX9, + eDX10, + eOVG, + eMGL, + }; + + /***************************************************************************** + * Useful macros. + *****************************************************************************/ + #define TEXOFFSET2D(x,y,width) ( ((x)+(y)*(width)) ) + #define TEXOFFSET3D(x,y,z,width,height) ( ((x)+(y)*(width)+(z)*(width)*(height)) ) + + /***************************************************************************** + * Useful typedef for Meta Data Maps + *****************************************************************************/ + typedef CPVRTMap > MetaDataMap; +}; +#endif //_PVRTEXTURE_DEFINES_H diff --git a/extern/pvrtextool/Include/PVRTextureFormat.h b/extern/pvrtextool/Include/PVRTextureFormat.h new file mode 100644 index 0000000..e0ff187 --- /dev/null +++ b/extern/pvrtextool/Include/PVRTextureFormat.h @@ -0,0 +1,90 @@ +/****************************************************************************** + + @File PVRTextureFormat.h + + @Title + + @Version + + @Copyright Copyright (c) Imagination Technologies Limited. All Rights Reserved. Strictly Confidential. + + @Platform + + @Description + +******************************************************************************/ +#ifndef _PVRT_PIXEL_FORMAT_H +#define _PVRT_PIXEL_FORMAT_H + +#include "PVRTextureDefines.h" +//ACS: removed unneccesary includes: +//#include "PVRTString.h" + +namespace pvrtexture +{ + //Channel Names + enum EChannelName + { + eNoChannel, + eRed, + eGreen, + eBlue, + eAlpha, + eLuminance, + eIntensity, + eUnspecified, + eNumChannels + }; + + //PixelType union + union PVR_DLL PixelType + { + /*!*********************************************************************** + @Function PixelType + @Return A new PixelType + @Description Creates an empty pixeltype. + *************************************************************************/ + PixelType(); + + /*!*********************************************************************** + @Function PixelType + @Input Type + @Return A new PixelType + @Description Initialises a new pixel type from a 64 bit integer value. + *************************************************************************/ + PixelType(uint64 Type); + + /*!*********************************************************************** + @Function PixelType + @Input C1Name + @Input C2Name + @Input C3Name + @Input C4Name + @Input C1Bits + @Input C2Bits + @Input C3Bits + @Input C4Bits + @Return A new PixelType + @Description Takes up to 4 characters (CnName) and 4 values (CnBits) + to create a new PixelType. Any unused channels should be set to 0. + For example: PixelType('r','g','b',0,8,8,8,0); + *************************************************************************/ + PixelType(uint8 C1Name, uint8 C2Name, uint8 C3Name, uint8 C4Name, uint8 C1Bits, uint8 C2Bits, uint8 C3Bits, uint8 C4Bits); + + struct PVR_DLL LowHigh + { + uint32 Low; + uint32 High; + } Part; + + uint64 PixelTypeID; + uint8 PixelTypeChar[8]; + }; + + static const PixelType PVRStandard8PixelType = PixelType('r','g','b','a',8,8,8,8); + static const PixelType PVRStandard16PixelType = PixelType('r','g','b','a',16,16,16,16); + static const PixelType PVRStandard32PixelType = PixelType('r','g','b','a',32,32,32,32); +} + +#endif + diff --git a/extern/pvrtextool/Include/PVRTextureHeader.h b/extern/pvrtextool/Include/PVRTextureHeader.h new file mode 100644 index 0000000..7fb7815 --- /dev/null +++ b/extern/pvrtextool/Include/PVRTextureHeader.h @@ -0,0 +1,603 @@ +/****************************************************************************** + + @File PVRTextureHeader.h + + @Title + + @Version + + @Copyright Copyright (c) Imagination Technologies Limited. All Rights Reserved. Strictly Confidential. + + @Platform + + @Description + +******************************************************************************/ +#ifndef _PVRTEXTURE_HEADER_H +#define _PVRTEXTURE_HEADER_H + +#include "PVRTextureDefines.h" +#include "PVRTextureFormat.h" +#include "PVRTMap.h" +#include "PVRTString.h" + +namespace pvrtexture +{ + //Wrapper class for PVRTextureHeaderV3, adds 'smart' accessor functions. + class PVR_DLL CPVRTextureHeader + { + protected: + PVRTextureHeaderV3 m_sHeader; //Texture header as laid out in a file. + mutable CPVRTMap > m_MetaData; //Map of all the meta data stored for a texture. + + public: + /******************************************************************************* + * Construction methods for a texture header. + *******************************************************************************/ + /*!*********************************************************************** + @Function CPVRTextureHeader + @Return CPVRTextureHeader A new texture header. + @Description Default constructor for a CPVRTextureHeader. Returns an empty header. + *************************************************************************/ + CPVRTextureHeader(); + + /*!*********************************************************************** + @Function CPVRTextureHeader + @Input fileHeader + @Input metaDataCount + @Input metaData + @Return CPVRTextureHeader A new texture header. + @Description Creates a new texture header from a PVRTextureHeaderV3, + and appends Meta data if any is supplied. + *************************************************************************/ + CPVRTextureHeader( PVRTextureHeaderV3 fileHeader, + uint32 metaDataCount=0, + MetaDataBlock* metaData=NULL); + + /*!*********************************************************************** + @Function CPVRTextureHeader + @Input u64PixelFormat + @Input u32Height + @Input u32Width + @Input u32Depth + @Input u32NumMipMaps + @Input u32NumArrayMembers + @Input u32NumFaces + @Input eColourSpace + @Input eChannelType + @Input bPreMultiplied + @Return CPVRTextureHeader A new texture header. + @Description Creates a new texture header based on individual header + variables. + *************************************************************************/ + CPVRTextureHeader( uint64 u64PixelFormat, + uint32 u32Height=1, + uint32 u32Width=1, + uint32 u32Depth=1, + uint32 u32NumMipMaps=1, + uint32 u32NumArrayMembers=1, + uint32 u32NumFaces=1, + EPVRTColourSpace eColourSpace=ePVRTCSpacelRGB, + EPVRTVariableType eChannelType=ePVRTVarTypeUnsignedByteNorm, + bool bPreMultiplied=false); + + /*!*********************************************************************** + @Function operator= + @Input rhs + @Return CPVRTextureHeader& This header. + @Description Will copy the contents and information of another header into this one. + *************************************************************************/ + CPVRTextureHeader& operator=(const CPVRTextureHeader& rhs); + + /******************************************************************************* + * Accessor Methods for a texture's properties - getters. + *******************************************************************************/ + + /*!*********************************************************************** + @Function getFileHeader + @Return PVRTextureHeaderV3 The file header. + @Description Gets the file header structure. + *************************************************************************/ + const PVRTextureHeaderV3 getFileHeader() const; + + /*!*********************************************************************** + @Function getPixelType + @Return PixelType 64-bit pixel type ID. + @Description Gets the 64-bit pixel type ID of the texture. + *************************************************************************/ + const PixelType getPixelType() const; + + /*!*********************************************************************** + @Function getBitsPerPixel + @Return uint32 Number of bits per pixel + @Description Gets the bits per pixel of the texture format. + *************************************************************************/ + const uint32 getBitsPerPixel() const; + + /*!*********************************************************************** + @Function getColourSpace + @Return EPVRTColourSpace enum representing colour space. + @Description Returns the colour space of the texture. + *************************************************************************/ + const EPVRTColourSpace getColourSpace() const; + + /*!*********************************************************************** + @Function getChannelType + @Return EPVRTVariableType enum representing the type of the texture. + @Description Returns the variable type that the texture's data is stored in. + *************************************************************************/ + const EPVRTVariableType getChannelType() const; + + /*!*********************************************************************** + @Function getWidth + @Input uiMipLevel MIP level that user is interested in. + @Return uint32 Width of the specified MIP-Map level. + @Description Gets the width of the user specified MIP-Map + level for the texture + *************************************************************************/ + const uint32 getWidth(uint32 uiMipLevel=PVRTEX_TOPMIPLEVEL) const; + + /*!*********************************************************************** + @Function getHeight + @Input uiMipLevel MIP level that user is interested in. + @Return uint32 Height of the specified MIP-Map level. + @Description Gets the height of the user specified MIP-Map + level for the texture + *************************************************************************/ + const uint32 getHeight(uint32 uiMipLevel=PVRTEX_TOPMIPLEVEL) const; + + /*!*********************************************************************** + @Function getDepth + @Input uiMipLevel MIP level that user is interested in. + @Return Depth of the specified MIP-Map level. + @Description Gets the depth of the user specified MIP-Map + level for the texture + *************************************************************************/ + const uint32 getDepth(uint32 uiMipLevel=PVRTEX_TOPMIPLEVEL) const; + + /*!*********************************************************************** + @Function getTextureSize + @Input iMipLevel Specifies a MIP level to check, + 'PVRTEX_ALLMIPLEVELS' can be passed to get + the size of all MIP levels. + @Input bAllSurfaces Size of all surfaces is calculated if true, + only a single surface if false. + @Input bAllFaces Size of all faces is calculated if true, + only a single face if false. + @Return uint32 Size in PIXELS of the specified texture area. + @Description Gets the size in PIXELS of the texture, given various input + parameters. User can retrieve the total size of either all + surfaces or a single surface, all faces or a single face and + all MIP-Maps or a single specified MIP level. All of these + *************************************************************************/ + const uint32 getTextureSize(int32 iMipLevel=PVRTEX_ALLMIPLEVELS, bool bAllSurfaces = true, bool bAllFaces = true) const; + + /*!*********************************************************************** + @Function getDataSize + @Input iMipLevel Specifies a mip level to check, + 'PVRTEX_ALLMIPLEVELS' can be passed to get + the size of all MIP levels. + @Input bAllSurfaces Size of all surfaces is calculated if true, + only a single surface if false. + @Input bAllFaces Size of all faces is calculated if true, + only a single face if false. + @Return uint32 Size in BYTES of the specified texture area. + @Description Gets the size in BYTES of the texture, given various input + parameters. User can retrieve the size of either all + surfaces or a single surface, all faces or a single face + and all MIP-Maps or a single specified MIP level. + *************************************************************************/ + const uint32 getDataSize(int32 iMipLevel=PVRTEX_ALLMIPLEVELS, bool bAllSurfaces = true, bool bAllFaces = true) const; + + /*!*********************************************************************** + @Function getNumArrayMembers + @Return uint32 Number of array members in this texture. + @Description Gets the number of array members stored in this texture. + *************************************************************************/ + const uint32 getNumArrayMembers() const; + + /*!*********************************************************************** + @Function getNumMIPLevels + @Return uint32 Number of MIP-Map levels in this texture. + @Description Gets the number of MIP-Map levels stored in this texture. + *************************************************************************/ + const uint32 getNumMIPLevels() const; + + /*!*********************************************************************** + @Function getNumFaces + @Return uint32 Number of faces in this texture. + @Description Gets the number of faces stored in this texture. + *************************************************************************/ + const uint32 getNumFaces() const; + + /*!*********************************************************************** + @Function getOrientation + @Input axis EPVRTAxis type specifying the axis to examine. + @Return EPVRTOrientation Enum orientation of the axis. + @Description Gets the data orientation for this texture. + *************************************************************************/ + const EPVRTOrientation getOrientation(EPVRTAxis axis) const; + + /*!*********************************************************************** + @Function isFileCompressed + @Return bool True if it is file compressed. + @Description Returns whether or not the texture is compressed using + PVRTexLib's FILE compression - this is independent of + any texture compression. + *************************************************************************/ + const bool isFileCompressed() const; + + /*!*********************************************************************** + @Function isPreMultiplied + @Return bool True if texture is premultiplied. + @Description Returns whether or not the texture's colour has been + pre-multiplied by the alpha values. + *************************************************************************/ + const bool isPreMultiplied() const; + + /*!*********************************************************************** + @Function getMetaDataSize + @Return const uint32 Size, in bytes, of the meta data stored in the header. + @Description Returns the total size of the meta data stored in the header. + This includes the size of all information stored in all MetaDataBlocks. + *************************************************************************/ + const uint32 getMetaDataSize() const; + + /*!*********************************************************************** + @Function getOGLFormat + @Modified internalformat + @Modified format + @Modified type + @Description Gets the OpenGL equivalent values of internal format, format + and type for this texture. This will return any supported + OpenGL texture values, it is up to the user to decide if + these are valid for their current platform. + *************************************************************************/ + const void getOGLFormat(uint32& internalformat, uint32& format, uint32& type) const; + + /*!*********************************************************************** + @Function getOGLESFormat + @Modified internalformat + @Modified format + @Modified type + @Description Gets the OpenGLES equivalent values of internal format, + format and type for this texture. This will return any + supported OpenGLES texture values, it is up to the user + to decide if these are valid for their current platform. + *************************************************************************/ + const void getOGLESFormat(uint32& internalformat, uint32& format, uint32& type) const; + + /*!*********************************************************************** + @Function getD3DFormat + @Return const uint32 + @Description Gets the D3DFormat (up to DirectX 9 and Direct 3D Mobile) + equivalent values for this texture. This will return any + supported D3D texture formats, it is up to the user to + decide if this is valid for their current platform. + *************************************************************************/ + const uint32 getD3DFormat() const; + + /*!*********************************************************************** + @Function getDXGIFormat + @Return const uint32 + @Description Gets the DXGIFormat (DirectX 10 onward) equivalent values + for this texture. This will return any supported DX texture + formats, it is up to the user to decide if this is valid + for their current platform. + *************************************************************************/ + const uint32 getDXGIFormat() const; + + /*!*********************************************************************** + * Accessor Methods for a texture's properties - setters. + *************************************************************************/ + + /*!*********************************************************************** + @Function setPixelFormat + @Input uPixelFormat The format of the pixel. + @Description Sets the pixel format for this texture. + *************************************************************************/ + void setPixelFormat(PixelType uPixelFormat); + + /*!*********************************************************************** + @Function setColourSpace + @Input eColourSpace A colour space enum. + @Description Sets the colour space for this texture. Default is lRGB. + *************************************************************************/ + void setColourSpace(EPVRTColourSpace eColourSpace); + + /*!*********************************************************************** + @Function setChannelType + @Input eVarType A variable type enum. + @Description Sets the variable type for the channels in this texture. + *************************************************************************/ + void setChannelType(EPVRTVariableType eVarType); + + /*!*********************************************************************** + @Function setOGLFormat + @Input internalformat + @Input format + @Input type + @Return bool Whether the format is valid or not. + @Description Sets the format of the texture to PVRTexLib's internal + representation of the OGL format. + *************************************************************************/ + bool setOGLFormat(const uint32& internalformat, const uint32& format, const uint32& type); + + /*!*********************************************************************** + @Function setOGLESFormat + @Input internalformat + @Input format + @Input type + @Return bool Whether the format is valid or not. + @Description Sets the format of the texture to PVRTexLib's internal + representation of the OGLES format. + *************************************************************************/ + bool setOGLESFormat(const uint32& internalformat, const uint32& format, const uint32& type); + + /*!*********************************************************************** + @Function setD3DFormat + @Return bool Whether the format is valid or not. + @Description Sets the format of the texture to PVRTexLib's internal + representation of the D3D format. + *************************************************************************/ + bool setD3DFormat(const uint32& DWORD_D3D_FORMAT); + + /*!*********************************************************************** + @Function setDXGIFormat + @Return bool Whether the format is valid or not. + @Description Sets the format of the texture to PVRTexLib's internal + representation of the DXGI format. + *************************************************************************/ + bool setDXGIFormat(const uint32& DWORD_DXGI_FORMAT); + + /*!*********************************************************************** + @Function setWidth + @Input newWidth The new width. + @Description Sets the width. + *************************************************************************/ + void setWidth(uint32 newWidth); + + /*!*********************************************************************** + @Function setHeight + @Input newHeight The new height. + @Description Sets the height. + *************************************************************************/ + void setHeight(uint32 newHeight); + + /*!*********************************************************************** + @Function setDepth + @Input newDepth The new depth. + @Description Sets the depth. + *************************************************************************/ + void setDepth(uint32 newDepth); + + /*!*********************************************************************** + @Function setNumArrayMembers + @Input newNumMembers The new number of members in this array. + @Description Sets the depth. + *************************************************************************/ + void setNumArrayMembers(uint32 newNumMembers); + + /*!*********************************************************************** + @Function setNumMIPLevels + @Input newNumMIPLevels New number of MIP-Map levels. + @Description Sets the number of MIP-Map levels in this texture. + *************************************************************************/ + void setNumMIPLevels(uint32 newNumMIPLevels); + + /*!*********************************************************************** + @Function setNumFaces + @Input newNumFaces New number of faces for this texture. + @Description Sets the number of faces stored in this texture. + *************************************************************************/ + void setNumFaces(uint32 newNumFaces); + + /*!*********************************************************************** + @Function setOrientation + @Input eAxisOrientation Enum specifying axis and orientation. + @Description Sets the data orientation for a given axis in this texture. + *************************************************************************/ + void setOrientation(EPVRTOrientation eAxisOrientation); + + /*!*********************************************************************** + @Function setIsFileCompressed + @Input isFileCompressed Sets file compression to true/false. + @Description Sets whether or not the texture is compressed using + PVRTexLib's FILE compression - this is independent of + any texture compression. Currently unsupported. + *************************************************************************/ + void setIsFileCompressed(bool isFileCompressed); + + /*!*********************************************************************** + @Function isPreMultiplied + @Return isPreMultiplied Sets if texture is premultiplied. + @Description Sets whether or not the texture's colour has been + pre-multiplied by the alpha values. + *************************************************************************/ + void setIsPreMultiplied(bool isPreMultiplied); + + /*!*********************************************************************** + Meta Data functions - Getters. + *************************************************************************/ + + /*!*********************************************************************** + @Function isBumpMap + @Return bool True if it is a bump map. + @Description Returns whether the texture is a bump map or not. + *************************************************************************/ + const bool isBumpMap() const; + + /*!*********************************************************************** + @Function getBumpMapScale + @Return float Returns the bump map scale. + @Description Gets the bump map scaling value for this texture. If the + texture is not a bump map, 0.0f is returned. If the + texture is a bump map but no meta data is stored to + specify its scale, then 1.0f is returned. + *************************************************************************/ + const float getBumpMapScale() const; + + /*!*********************************************************************** + @Function getBumpMapOrder + @Return CPVRTString Returns bump map order relative to rgba. + @Description Gets the bump map channel order relative to rgba. For + example, an RGB texture with bumps mapped to XYZ returns + 'xyz'. A BGR texture with bumps in the order ZYX will also + return 'xyz' as the mapping is the same: R=X, G=Y, B=Z. + If the letter 'h' is present in the string, it means that + the height map has been stored here. + Other characters are possible if the bump map was created + manually, but PVRTexLib will ignore these characters. They + are returned simply for completeness. + *************************************************************************/ + const CPVRTString getBumpMapOrder() const; + + /*!*********************************************************************** + @Function getNumTextureAtlasMembers + @Return int Returns number of sub textures defined by meta data. + @Description Works out the number of possible texture atlas members in + the texture based on the w/h/d and the data size. + TODO: Is this the right way to do things? Should I return number of floats? Or just data size? Hmm. Also need to make it TWO floats per dimension, and also possibly a rotated value. Not sure. + *************************************************************************/ + const int getNumTextureAtlasMembers() const; + + /*!*********************************************************************** + @Function getTextureAtlasData + @Return float* Returns a pointer directly to the texture atlas data. + @Description Returns a pointer to the texture atlas data. + TODO: Maybe I should return a copy rather than the original. + *************************************************************************/ + const float* getTextureAtlasData() const; + + /*!*********************************************************************** + @Function getCubeMapOrder + @Return CPVRTString Returns cube map order. + @Description Gets the cube map face order. Returned string will be in + the form "ZzXxYy" with capitals representing positive and + small letters representing negative. I.e. Z=Z-Positive, + z=Z-Negative. + *************************************************************************/ + const CPVRTString getCubeMapOrder() const; + + /*!*********************************************************************** + @Function getBorder + @Input uiBorderWidth + @Input uiBorderHeight + @Input uiBorderDepth + @Description Obtains the border size in each dimension for this texture. + *************************************************************************/ + void getBorder(uint32& uiBorderWidth, uint32& uiBorderHeight, uint32& uiBorderDepth) const; + + /*!*********************************************************************** + @Function getMetaData + @Input DevFOURCC + @Input u32Key + @Return pvrtexture::MetaDataBlock A copy of the meta data from the texture. + @Description Returns a block of meta data from the texture. If the meta data doesn't exist, a block with data size 0 will be returned. + *************************************************************************/ + MetaDataBlock getMetaData(uint32 DevFOURCC, uint32 u32Key) const; + + /*!*********************************************************************** + @Function hasMetaData + @Input DevFOURCC + @Input u32Key + @Return bool Whether or not the meta data bock specified exists + @Description Returns whether or not the specified meta data exists as + part of this texture header. + *************************************************************************/ + bool hasMetaData(uint32 DevFOURCC, uint32 u32Key) const; + + /*!*********************************************************************** + @Function getMetaDataMap + @Return MetaDataMap* A direct pointer to the MetaData map. + @Description A pointer directly to the Meta Data Map, to allow users to read out data. + *************************************************************************/ + const MetaDataMap* const getMetaDataMap() const; + + /*!*********************************************************************** + Meta Data functions - Setters. + *************************************************************************/ + + /*!*********************************************************************** + @Function setBumpMap + @Input bumpScale Floating point "height" value to scale the bump map. + @Input bumpOrder Up to 4 character string, with values x,y,z,h in + some combination. Not all values need to be present. + Denotes channel order; x,y,z refer to the + corresponding axes, h indicates presence of the + original height map. It is possible to have only some + of these values rather than all. For example if 'h' + is present alone it will be considered a height map. + The values should be presented in RGBA order, regardless + of the texture format, so a zyxh order in a bgra texture + should still be passed as 'xyzh'. Capitals are allowed. + Any character stored here that is not one of x,y,z,h + or a NULL character will be ignored when PVRTexLib + reads the data, but will be preserved. This is useful + if you wish to define a custom data channel for instance. + In these instances PVRTexLib will assume it is simply + colour data. + @Description Sets a texture's bump map data. + *************************************************************************/ + void setBumpMap(float bumpScale, CPVRTString bumpOrder="xyz"); + + /*!*********************************************************************** + @Function setTextureAtlas + @Input pAtlasData Pointer to an array of atlas data. + @Input dataSize Number of floats that the data pointer contains. + @Description Sets the texture atlas coordinate meta data for later display. + It is up to the user to make sure that this texture atlas + data actually makes sense in the context of the header. It is + suggested that the "generateTextureAtlas" method in the tools + is used to create a texture atlas, manually setting one up is + possible but should be done with care. + *************************************************************************/ + void setTextureAtlas(float* pAtlasData, uint32 dataSize); + + /*!*********************************************************************** + @Function setCubeMapOrder + @Input cubeMapOrder Up to 6 character string, with values + x,X,y,Y,z,Z in some combination. Not all + values need to be present. Denotes face + order; Capitals refer to positive axis + positions and small letters refer to + negative axis positions. E.g. x=X-Negative, + X=X-Positive. It is possible to have only + some of these values rather than all, as + long as they are NULL terminated. + NB: Values past the 6th character are not read. + @Description Sets a texture's bump map data. + *************************************************************************/ + void setCubeMapOrder(CPVRTString cubeMapOrder="XxYyZz"); + + /*!*********************************************************************** + @Function setBorder + @Input uiBorderWidth + @Input uiBorderHeight + @Input uiBorderDepth + @Return void + @Description Sets a texture's border size data. This value is subtracted + from the current texture height/width/depth to get the valid + texture data. + *************************************************************************/ + void setBorder(uint32 uiBorderWidth, uint32 uiBorderHeight, uint32 uiBorderDepth); + + /*!*********************************************************************** + @Function addMetaData + @Input MetaBlock Meta data block to be added. + @Description Adds an arbitrary piece of meta data. + *************************************************************************/ + void addMetaData(const MetaDataBlock& MetaBlock); + + /*!*********************************************************************** + @Function removeMetaData + @Input DevFourCC + @Input u32Key + @Return void + @Description Removes a specified piece of meta data, if it exists. + *************************************************************************/ + void removeMetaData(const uint32& DevFourCC, const uint32& u32Key); + }; +}; + +#endif diff --git a/extern/pvrtextool/Include/PVRTextureUtilities.h b/extern/pvrtextool/Include/PVRTextureUtilities.h new file mode 100644 index 0000000..0d5050a --- /dev/null +++ b/extern/pvrtextool/Include/PVRTextureUtilities.h @@ -0,0 +1,171 @@ +/****************************************************************************** + + @File PVRTextureUtilities.h + + @Title + + @Version + + @Copyright Copyright (c) Imagination Technologies Limited. All Rights Reserved. Strictly Confidential. + + @Platform + + @Description + +******************************************************************************/ +#ifndef _PVRTEXTURE_UTILITIES_H +#define _PVRTEXTURE_UTILITIES_H + +#include "PVRTextureFormat.h" +#include "PVRTexture.h" + +namespace pvrtexture +{ + /*!*********************************************************************** + @Function Resize + @Input sTexture + @Input u32NewWidth + @Input u32NewHeight + @Input u32NewDepth + @Input eResizeMode + @Return bool Whether the method succeeds or not. + @Description Resizes the texture to new specified dimensions. Filtering + mode is specified with "eResizeMode". + *************************************************************************/ + bool PVR_DLL Resize(CPVRTexture& sTexture, const uint32& u32NewWidth, const uint32& u32NewHeight, const uint32& u32NewDepth, const EResizeMode eResizeMode); + + /*!*********************************************************************** + @Function Rotate90 + @Input sTexture + @Input eRotationAxis + @Input bForward + @Return bool Whether the method succeeds or not. + @Description Rotates a texture by 90 degrees around the given axis. bForward controls direction of rotation. + *************************************************************************/ + bool PVR_DLL Rotate90(CPVRTexture& sTexture, const EPVRTAxis eRotationAxis, const bool bForward); + + /*!*********************************************************************** + @Function Flip + @Input sTexture + @Input eFlipDirection + @Return bool Whether the method succeeds or not. + @Description Flips a texture in a given direction. + *************************************************************************/ + bool PVR_DLL Flip(CPVRTexture& sTexture, const EPVRTAxis eFlipDirection); + + /*!*********************************************************************** + @Function Border + @Input sTexture + @Input uiBorderX + @Input uiBorderY + @Input uiBorderZ + @Return bool Whether the method succeeds or not. + @Description Adds a user specified border to the texture. + *************************************************************************/ + bool PVR_DLL Border(CPVRTexture& sTexture, uint32 uiBorderX, uint32 uiBorderY, uint32 uiBorderZ); + + /*!*********************************************************************** + @Function PreMultiplyAlpha + @Input sTexture + @Return bool Whether the method succeeds or not. + @Description Pre-multiplies a texture's colours by its alpha values. + *************************************************************************/ + bool PVR_DLL PreMultiplyAlpha(CPVRTexture& sTexture); + + /*!*********************************************************************** + @Function Bleed + @Input sTexture + @Return bool Whether the method succeeds or not. + @Description Allows a texture's colours to run into any fully transparent areas. + *************************************************************************/ + bool PVR_DLL Bleed(CPVRTexture& sTexture); + + /*!*********************************************************************** + @Function SetChannels + @Input sTexture + @Input uiNumChannelSets + @Input eChannels + @Input pValues + @Return bool Whether the method succeeds or not. + @Description Sets the specified number of channels to values specified in pValues. + *************************************************************************/ + bool PVR_DLL SetChannels(CPVRTexture& sTexture, uint32 uiNumChannelSets, EChannelName *eChannels, uint32 *pValues); + bool PVR_DLL SetChannelsFloat(CPVRTexture& sTexture, uint32 uiNumChannelSets, EChannelName *eChannels, float *pValues); + + /*!*********************************************************************** + @Function CopyChannels + @Input sTexture + @Input sTextureSource + @Input uiNumChannelCopies + @Input eChannels + @Input eChannelsSource + @Return bool Whether the method succeeds or not. + @Description Copies the specified channels from sTextureSource into sTexture. + sTextureSource is not modified so it is possible to use the + same texture as both input and output. When using the same + texture as source and destination, channels are preserved + between swaps (e.g. copying Red to Green and then Green to Red + will result in the two channels trading places correctly). + Channels in eChannels are set to the value of the channels + in eChannelSource. + *************************************************************************/ + bool PVR_DLL CopyChannels(CPVRTexture& sTexture, const CPVRTexture& sTextureSource, uint32 uiNumChannelCopies, EChannelName *eChannels, EChannelName *eChannelsSource); + + /*!*********************************************************************** + @Function GenerateNormalMap + @Input sTexture + @Input fScale + @Input sChannelOrder + @Return bool Whether the method succeeds or not. + @Description Generates a Normal Map from a given height map. + Assumes the red channel has the height values. + By default outputs to red/green/blue = x/y/z, + this can be overridden by specifying a channel + order in sChannelOrder. The channels specified + will output to red/green/blue/alpha in that order. + So "xyzh" maps x to red, y to green, z to blue + and h to alpha. 'h' is used to specify that the + original height map data should be preserved in + the given channel. + *************************************************************************/ + bool PVR_DLL GenerateNormalMap(CPVRTexture& sTexture, const float fScale, CPVRTString sChannelOrder); + + /*!*********************************************************************** + @Function GenerateMIPMaps + @Input sTexture + @Input eFilterMode + @Input uiMIPMapsToDo + @Return bool Whether the method succeeds or not. + @Description Generates MIPMaps for a source texture. Default is to + create a complete MIPMap chain, however this can be + overridden with uiMIPMapsToDo. + *************************************************************************/ + bool PVR_DLL GenerateMIPMaps(CPVRTexture& sTexture, const EResizeMode eFilterMode, const uint32 uiMIPMapsToDo=PVRTEX_ALLMIPLEVELS); + + /*!*********************************************************************** + @Function ColourMIPMaps + @Input sTexture + @Return bool Whether the method succeeds or not. + @Description Colours a texture's MIPMap levels with artificial colours + for debugging. MIP levels are coloured in the order: + Red, Green, Blue, Cyan, Magenta and Yellow + in a repeating pattern. + *************************************************************************/ + bool PVR_DLL ColourMIPMaps(CPVRTexture& sTexture); + + /*!*********************************************************************** + @Function Transcode + @Input sTexture + @Input ptFormat + @Input eChannelType + @Input eColourspace + @Input eQuality + @Input bDoDither + @Return bool Whether the method succeeds or not. + @Description Transcodes a texture from its original format into a newly specified format. + Will either quantise or dither to lower precisions based on bDoDither. + uiQuality specifies the quality for PVRTC and ETC compression. + *************************************************************************/ + bool PVR_DLL Transcode(CPVRTexture& sTexture, const PixelType ptFormat, const EPVRTVariableType eChannelType, const EPVRTColourSpace eColourspace, const ECompressorQuality eQuality=ePVRTCNormal, const bool bDoDither=false); +}; +#endif //_PVRTEXTURE_UTILTIES_H diff --git a/extern/pvrtextool/Include/PVRTextureVersion.h b/extern/pvrtextool/Include/PVRTextureVersion.h new file mode 100644 index 0000000..6c98c84 --- /dev/null +++ b/extern/pvrtextool/Include/PVRTextureVersion.h @@ -0,0 +1,23 @@ +/****************************************************************************** + + @File PVRTextureVersion.h + + @Title + + @Version + + @Copyright Copyright (c) Imagination Technologies Limited. All Rights Reserved. Strictly Confidential. + + @Platform ANSI + + @Description Texture processing routines. + +******************************************************************************/ +#ifndef PVRTEXLIBVERSION_H +#define PVRTEXLIBVERSION_H +#define PVRTLMAJORVERSION 4 +#define PVRTLMINORVERSION 2 +#define PVRTLSTRINGVERSION "4.2" +#define PVRTLVERSIONDESCRIPTOR "" //"BETA" //"ALPHA" //"ENGINEERING DROP" +#endif + diff --git a/extern/pvrtextool/OSX_x86/Static/libPVRTexLib.a b/extern/pvrtextool/OSX_x86/Static/libPVRTexLib.a new file mode 100644 index 0000000000000000000000000000000000000000..e8c3acd2d787d85915ecad336c7df332d35e8b5f GIT binary patch literal 2270032 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

KIJDP)i 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

qa+d*O>1s99R!(rgr-rHy-MmUi!2$cARgL7b&s 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%0AN@5cP`Ye(VM@fy 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�U3M>|&5 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%0DCqaTAm=BpZeYvx6RUF?YC$+z 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≥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 {K_$ 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