From 6bffdf80a1495d52fb7c89c5c860f6b1ca28ea0d Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Sun, 24 May 2020 21:19:33 +0200 Subject: [PATCH] - finally managed to merge in the original texture system commit. Game compiles and runs but transparency doesn't work yet. # Conflicts: # source/CMakeLists.txt # source/core/menu/menu.cpp # source/core/textures/buildtiles.cpp --- source/CMakeLists.txt | 47 +- source/build/include/mdsprite.h | 3 +- source/build/src/animvpx.cpp | 2 +- source/build/src/mdsprite.cpp | 13 +- source/build/src/palette.cpp | 1 + source/common/textures/animtexture.cpp | 13 +- source/common/textures/animtexture.h | 2 +- .../textures/formats/multipatchtexture.h | 25 + source/common/textures/hires/hqnx/common.h | 141 + source/common/textures/hires/hqnx/hq2x.cpp | 2808 +++++++++ source/common/textures/hires/hqnx/hq3x.cpp | 3786 ++++++++++++ source/common/textures/hires/hqnx/hq4x.cpp | 5232 ++++++++++++++++ source/common/textures/hires/hqnx/hqx.h | 55 + source/common/textures/hires/hqnx/init.cpp | 38 + .../textures/hires/hqnx_asm/hq2x_asm.cpp | 2943 +++++++++ .../textures/hires/hqnx_asm/hq3x_asm.cpp | 3843 ++++++++++++ .../textures/hires/hqnx_asm/hq4x_asm.cpp | 5419 +++++++++++++++++ .../common/textures/hires/hqnx_asm/hqnx_asm.h | 234 + .../hires/hqnx_asm/hqnx_asm_Image.cpp | 111 + .../textures/hires/hqnx_asm/hqnx_asm_Image.h | 77 + source/common/textures/hires/hqresize.cpp | 511 ++ source/common/textures/hires/xbr/xbrz.cpp | 1364 +++++ source/common/textures/hires/xbr/xbrz.h | 79 + .../common/textures/hires/xbr/xbrz_config.h | 35 + .../textures/hires/xbr/xbrz_config_old.h | 40 + source/common/textures/hires/xbr/xbrz_old.cpp | 1300 ++++ source/common/textures/hires/xbr/xbrz_old.h | 87 + source/common/textures/hires/xbr/xbrz_tools.h | 270 + source/common/textures/hw_material.cpp | 410 ++ source/common/textures/hw_material.h | 140 + source/common/textures/image.h | 6 +- .../{core => common}/textures/imagehelpers.h | 0 .../textures/imagetexture.cpp | 0 .../textures/multipatchtexturebuilder.cpp | 927 +++ .../textures/skyboxtexture.cpp | 27 +- .../{core => common}/textures/skyboxtexture.h | 10 +- source/common/textures/texture.cpp | 4 +- source/common/textures/texturemanager.cpp | 20 +- source/common/textures/texturemanager.h | 2 - source/common/textures/textures.h | 1 - source/core/fonts/font.cpp | 4 +- source/core/menu/menu.cpp | 10 +- source/core/menu/menudef.cpp | 9 +- .../rendering/gl/renderer/gl_renderer.cpp | 2 +- source/core/textures/buildtiles.cpp | 70 +- source/core/textures/imagehelpers.cpp | 46 - source/glbackend/gl_texture.cpp | 17 + source/platform/win32/i_system.cpp | 4 +- 48 files changed, 30050 insertions(+), 138 deletions(-) create mode 100644 source/common/textures/hires/hqnx/common.h create mode 100644 source/common/textures/hires/hqnx/hq2x.cpp create mode 100644 source/common/textures/hires/hqnx/hq3x.cpp create mode 100644 source/common/textures/hires/hqnx/hq4x.cpp create mode 100644 source/common/textures/hires/hqnx/hqx.h create mode 100644 source/common/textures/hires/hqnx/init.cpp create mode 100644 source/common/textures/hires/hqnx_asm/hq2x_asm.cpp create mode 100644 source/common/textures/hires/hqnx_asm/hq3x_asm.cpp create mode 100644 source/common/textures/hires/hqnx_asm/hq4x_asm.cpp create mode 100644 source/common/textures/hires/hqnx_asm/hqnx_asm.h create mode 100644 source/common/textures/hires/hqnx_asm/hqnx_asm_Image.cpp create mode 100644 source/common/textures/hires/hqnx_asm/hqnx_asm_Image.h create mode 100644 source/common/textures/hires/hqresize.cpp create mode 100644 source/common/textures/hires/xbr/xbrz.cpp create mode 100644 source/common/textures/hires/xbr/xbrz.h create mode 100644 source/common/textures/hires/xbr/xbrz_config.h create mode 100644 source/common/textures/hires/xbr/xbrz_config_old.h create mode 100644 source/common/textures/hires/xbr/xbrz_old.cpp create mode 100644 source/common/textures/hires/xbr/xbrz_old.h create mode 100644 source/common/textures/hires/xbr/xbrz_tools.h create mode 100644 source/common/textures/hw_material.cpp create mode 100644 source/common/textures/hw_material.h rename source/{core => common}/textures/imagehelpers.h (100%) rename source/{core => common}/textures/imagetexture.cpp (100%) create mode 100644 source/common/textures/multipatchtexturebuilder.cpp rename source/{core => common}/textures/skyboxtexture.cpp (77%) rename source/{core => common}/textures/skyboxtexture.h (77%) delete mode 100644 source/core/textures/imagehelpers.cpp diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index 4c0f37268..9a1487747 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -542,6 +542,24 @@ else() set( OTHER_SYSTEM_SOURCES ${PLAT_WIN32_SOURCES} ${PLAT_OSX_SOURCES} ${PLAT_COCOA_SOURCES} ) endif() +if( HAVE_MMX ) + add_definitions( -DHAVE_MMX=1 ) + + set( SYSTEM_SOURCES ${SYSTEM_SOURCES} + common/textures/hires/hqnx_asm/hq2x_asm.cpp + common/textures/hires/hqnx_asm/hq3x_asm.cpp + common/textures/hires/hqnx_asm/hq4x_asm.cpp + common/textures/hires/hqnx_asm/hqnx_asm_Image.cpp) + + if( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) + set_source_files_properties( + common/textures/hires/hqnx_asm/hq2x_asm.cpp + common/textures/hires/hqnx_asm/hq3x_asm.cpp + common/textures/hires/hqnx_asm/hq4x_asm.cpp + common/textures/hires/hqresize.cpp + PROPERTIES COMPILE_FLAGS "-mmmx" ) + endif( ZD_CMAKE_COMPILER_IS_GNUCXX_COMPATIBLE ) +endif( HAVE_MMX ) if( HAVE_PARALLEL_FOR ) add_definitions( -DHAVE_PARALLEL_FOR=1 ) @@ -611,6 +629,9 @@ file( GLOB HEADER_FILES common/objects/*.h common/filesystem/*.h common/textures/*.h + common/textures/hires/hqnx/*.h + common/textures/hires/hqnx_asm/*.h + common/textures/hires/xbr/*.h common/thirdparty/*.h common/thirdparty/rapidjson/*.h common/thirdparty/math/*h @@ -660,6 +681,12 @@ set( VM_JIT_SOURCES set( FASTMATH_SOURCES common/rendering/gl_load/gl_load.c + common/textures/hires/hqnx/init.cpp + common/textures/hires/hqnx/hq2x.cpp + common/textures/hires/hqnx/hq3x.cpp + common/textures/hires/hqnx/hq4x.cpp + common/textures/hires/xbr/xbrz.cpp + common/textures/hires/xbr/xbrz_old.cpp # This should better be made a library subproject later, once things are working. libsmackerdec/src/BitReader.cpp libsmackerdec/src/FileStream.cpp @@ -749,18 +776,23 @@ set (PCH_SOURCES common/thirdparty/sfmt/SFMT.cpp common/textures/texture.cpp common/textures/animtexture.cpp + common/textures/hw_ihwtexture.cpp + common/textures/hw_material.cpp common/fonts/v_text.cpp common/textures/bitmap.cpp common/textures/m_png.cpp + common/textures/texture.cpp common/textures/image.cpp + common/textures/imagetexture.cpp common/textures/texturemanager.cpp - common/textures/hw_ihwtexture.cpp - common/textures/formats/fontchars.cpp + common/textures/multipatchtexturebuilder.cpp + common/textures/skyboxtexture.cpp common/textures/formats/automaptexture.cpp common/textures/formats/brightmaptexture.cpp common/textures/formats/buildtexture.cpp common/textures/formats/ddstexture.cpp common/textures/formats/flattexture.cpp + common/textures/formats/fontchars.cpp common/textures/formats/imgztexture.cpp common/textures/formats/jpegtexture.cpp common/textures/formats/md5check.cpp @@ -773,6 +805,7 @@ set (PCH_SOURCES common/textures/formats/shadertexture.cpp common/textures/formats/tgatexture.cpp common/textures/formats/stbtexture.cpp + common/textures/hires/hqresize.cpp common/console/c_commandline.cpp common/console/c_buttons.cpp common/console/c_bind.cpp @@ -842,8 +875,7 @@ set (PCH_SOURCES core/textures/buildtiles.cpp - core/textures/imagetexture.cpp - core/textures/imagehelpers.cpp + #core/textures/texture.cpp core/music/s_advsound.cpp @@ -982,6 +1014,7 @@ include_directories( common/thirdparty common/textures common/textures/formats + common/textures/hires common/filesystem common/utility common/console @@ -1119,7 +1152,11 @@ source_group("Common\\Scripting\\VM" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE source_group("Common\\Rendering" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/rendering/.+") source_group("Common\\Rendering\\OpenGL Loader" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/rendering/gl_load/.+") source_group("Common\\Textures" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/textures/.+") -source_group("Common\\Textures\\Formats" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/textures/formats.+") +source_group("Common\\Textures\\Hires" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/textures/hires/.+") +source_group("Common\\Textures\\Hires\\HQ Resize" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/textures/hires/hqnx/.+") +source_group("Common\\Textures\\Hires\\HQ Resize MMX version" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/textures/hires/hqnx_asm/.+") +source_group("Common\\Textures\\Hires\\XBRZ" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/textures/hires/xbr/.+") +source_group("Common\\Textures\\Formats" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/textures/formats/.+") source_group("Common\\Third Party" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/thirdparty/.+") source_group("Common\\Third Party\\Math" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/thirdparty/math/.+") source_group("Common\\Third Party\\RapidJSON" REGULAR_EXPRESSION "^${CMAKE_CURRENT_SOURCE_DIR}/common/thirdparty/rapidjson/.+") diff --git a/source/build/include/mdsprite.h b/source/build/include/mdsprite.h index f2bda8907..40bd5a6ce 100644 --- a/source/build/include/mdsprite.h +++ b/source/build/include/mdsprite.h @@ -15,6 +15,7 @@ #define IDP3_MAGIC 0x33504449 class FTexture; +class FHardwareTexture; struct mdmodel_t { @@ -36,7 +37,7 @@ struct mdskinmap_t { uint8_t palette, flags, filler[2]; // Build palette number, flags the same as hightiles int32_t skinnum, surfnum; // Skin identifier, surface number - FTexture* texture; + FTextureID texture; mdskinmap_t* next; float param, specpower, specfactor; }; diff --git a/source/build/src/animvpx.cpp b/source/build/src/animvpx.cpp index 40fda518d..ca6cc4f6e 100644 --- a/source/build/src/animvpx.cpp +++ b/source/build/src/animvpx.cpp @@ -49,7 +49,7 @@ void VPXTexture::SetFrame(const void *data_, int width, int height) Width = width; Height = height; data = data_; - DeleteHardwareTextures(); + SystemTextures.Clean(true, true); } //=========================================================================== diff --git a/source/build/src/mdsprite.cpp b/source/build/src/mdsprite.cpp index d0f8593d8..cd483d7d9 100644 --- a/source/build/src/mdsprite.cpp +++ b/source/build/src/mdsprite.cpp @@ -17,6 +17,7 @@ #include "bitmap.h" #include "v_video.h" #include "flatvertices.h" +#include "texturemanager.h" #include "../../glbackend/glbackend.h" static int32_t curextra=MAXTILES; @@ -395,8 +396,8 @@ int32_t md_defineskin(int32_t modelid, const char *skinfn, int32_t palnum, int32 sk->param = param; sk->specpower = specpower; sk->specfactor = specfactor; - sk->texture = TileFiles.GetTexture(skinfn); - if (!sk->texture) + sk->texture = TexMan.CheckForTexture(skinfn, ETextureType::Any); + if (!sk->texture.isValid()) { Printf("Unable to load %s as model skin\n", skinfn); } @@ -481,7 +482,7 @@ FTexture *mdloadskin(idmodel_t *m, int32_t number, int32_t pal, int32_t surf, bo { if (exact) *exact = true; //Printf("Using exact match skin (pal=%d,skinnum=%d,surfnum=%d) %s\n",pal,number,surf,skinfile); - return sk->texture; + return TexMan.GetTexture(sk->texture); } //If no match, give highest priority to number, then pal.. (Parkar's request, 02/27/2005) else if ((sk->palette == 0) && (sk->skinnum == number) && (sk->surfnum == surf) && (i < 5)) { i = 5; skzero = sk; } @@ -500,7 +501,7 @@ FTexture *mdloadskin(idmodel_t *m, int32_t number, int32_t pal, int32_t surf, bo { //Printf("Using def skin 0,0 as fallback, pal=%d\n", pal); if (exact) *exact = false; - return skzero->texture; + return TexMan.GetTexture(skzero->texture); } else return nullptr; @@ -883,8 +884,8 @@ static md2model_t *md2load(FileReader & fil, const char *filnam) if (m->numskins > 0) { FStringf fn("%s%s", m->basepath, m->skinfn); - sk->texture = TileFiles.GetTexture(fn); - if (!sk->texture) + sk->texture = TexMan.CheckForTexture(fn, ETextureType::Any); + if (!sk->texture.isValid()) { Printf("Unable to load %s as model skin\n", m->skinfn); } diff --git a/source/build/src/palette.cpp b/source/build/src/palette.cpp index 5b5075d90..a849cf7ce 100644 --- a/source/build/src/palette.cpp +++ b/source/build/src/palette.cpp @@ -20,6 +20,7 @@ #include "palutil.h" #include "colormatcher.h" #include "m_swap.h" +#include "v_colortables.h" #include "../../glbackend/glbackend.h" // FString is a nice and convenient way to have automatically managed shared storage. diff --git a/source/common/textures/animtexture.cpp b/source/common/textures/animtexture.cpp index fecb8028b..4dbf2d111 100644 --- a/source/common/textures/animtexture.cpp +++ b/source/common/textures/animtexture.cpp @@ -40,18 +40,17 @@ // //========================================================================== -void AnimTexture::SetSize(int width, int height) +void AnimTexture::SetFrameSize(int width, int height) { - Width = width; - Height = height; + FTexture::SetSize(width, height); Image.Resize(width*height); } void AnimTexture::SetFrame(const uint8_t *palette, const void *data_) { memcpy(Palette, palette, 768); - memcpy(Image.Data(), data_, Width*Height); - DeleteHardwareTextures(); + memcpy(Image.Data(), data_, Width * Height); + SystemTextures.Clean(true, true); } //=========================================================================== @@ -101,8 +100,8 @@ AnimTextures::~AnimTextures() void AnimTextures::SetSize(int width, int height) { - tex[0]->SetSize(width, height); - tex[1]->SetSize(width, height); + tex[0]->SetFrameSize(width, height); + tex[1]->SetFrameSize(width, height); } void AnimTextures::SetFrame(const uint8_t *palette, const void* data) diff --git a/source/common/textures/animtexture.h b/source/common/textures/animtexture.h index e568a3cd2..668671a9d 100644 --- a/source/common/textures/animtexture.h +++ b/source/common/textures/animtexture.h @@ -9,7 +9,7 @@ class AnimTexture : public FTexture TArray Image; public: AnimTexture() = default; - void SetSize(int width, int height); + void SetFrameSize(int width, int height); void SetFrame(const uint8_t *palette, const void* data); virtual FBitmap GetBgraBitmap(const PalEntry* remap, int* trans) override; }; diff --git a/source/common/textures/formats/multipatchtexture.h b/source/common/textures/formats/multipatchtexture.h index 4030675e7..f9f5913e1 100644 --- a/source/common/textures/formats/multipatchtexture.h +++ b/source/common/textures/formats/multipatchtexture.h @@ -118,3 +118,28 @@ struct BuildInfo }; + +class FMultipatchTextureBuilder +{ + FTextureManager &TexMan; + TArray BuiltTextures; + void(*progressFunc)(); + void(*checkForHacks)(BuildInfo&); + + void MakeTexture(BuildInfo &buildinfo, ETextureType usetype); + + void BuildTexture(const void *texdef, FPatchLookup *patchlookup, int maxpatchnum, bool strife, int deflumpnum, ETextureType usetyoe); + void AddTexturesLump(const void *lumpdata, int lumpsize, int deflumpnum, int patcheslump, int firstdup, bool texture1); + + void ParsePatch(FScanner &sc, BuildInfo &info, TexPart &part, TexInit &init); + void ResolvePatches(BuildInfo &buildinfo); + +public: + FMultipatchTextureBuilder(FTextureManager &texMan, void(*progressFunc_)(), void(*checkForHacks_)(BuildInfo &)) : TexMan(texMan), progressFunc(progressFunc_), checkForHacks(checkForHacks_) + { + } + + void AddTexturesLumps(int lump1, int lump2, int patcheslump); + void ParseTexture(FScanner &sc, ETextureType usetype); + void ResolveAllPatches(); +}; diff --git a/source/common/textures/hires/hqnx/common.h b/source/common/textures/hires/hqnx/common.h new file mode 100644 index 000000000..fa807df77 --- /dev/null +++ b/source/common/textures/hires/hqnx/common.h @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2003 Maxim Stepin ( maxst@hiend3d.com ) + * + * Copyright (C) 2010 Cameron Zemek ( grom@zeminvaders.net) + * Copyright (C) 2011 Francois Gannaz + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef __HQX_COMMON_H_ +#define __HQX_COMMON_H_ + +#include +#include + +#define MASK_2 0x0000FF00 +#define MASK_13 0x00FF00FF +#define MASK_RGB 0x00FFFFFF +#define MASK_ALPHA 0xFF000000 + +#define Ymask 0x00FF0000 +#define Umask 0x0000FF00 +#define Vmask 0x000000FF +#define trY 0x00300000 +#define trU 0x00000700 +#define trV 0x00000006 + +/* RGB to YUV lookup table */ +extern uint32_t *RGBtoYUV; + +static inline uint32_t rgb_to_yuv(uint32_t c) +{ + // Mask against MASK_RGB to discard the alpha channel + return RGBtoYUV[MASK_RGB & c]; +} + +/* Test if there is difference in color */ +static inline int yuv_diff(uint32_t yuv1, uint32_t yuv2) { + return (( abs((int32_t)(yuv1 & Ymask) - (int32_t)(yuv2 & Ymask)) > trY ) || + ( abs((int32_t)(yuv1 & Umask) - (int32_t)(yuv2 & Umask)) > trU ) || + ( abs((int32_t)(yuv1 & Vmask) - (int32_t)(yuv2 & Vmask)) > trV ) ); +} + +static inline int Diff(uint32_t c1, uint32_t c2) +{ + return yuv_diff(rgb_to_yuv(c1), rgb_to_yuv(c2)); +} + +/* Interpolate functions */ +static inline uint32_t Interpolate_2(uint32_t c1, int w1, uint32_t c2, int w2, int s) +{ + if (c1 == c2) { + return c1; + } + return + (((((c1 & MASK_ALPHA) >> 24) * w1 + ((c2 & MASK_ALPHA) >> 24) * w2) << (24-s)) & MASK_ALPHA) + + ((((c1 & MASK_2) * w1 + (c2 & MASK_2) * w2) >> s) & MASK_2) + + ((((c1 & MASK_13) * w1 + (c2 & MASK_13) * w2) >> s) & MASK_13); +} + +static inline uint32_t Interpolate_3(uint32_t c1, int w1, uint32_t c2, int w2, uint32_t c3, int w3, int s) +{ + return + (((((c1 & MASK_ALPHA) >> 24) * w1 + ((c2 & MASK_ALPHA) >> 24) * w2 + ((c3 & MASK_ALPHA) >> 24) * w3) << (24-s)) & MASK_ALPHA) + + ((((c1 & MASK_2) * w1 + (c2 & MASK_2) * w2 + (c3 & MASK_2) * w3) >> s) & MASK_2) + + ((((c1 & MASK_13) * w1 + (c2 & MASK_13) * w2 + (c3 & MASK_13) * w3) >> s) & MASK_13); +} + +static inline uint32_t Interp1(uint32_t c1, uint32_t c2) +{ + //(c1*3+c2) >> 2; + return Interpolate_2(c1, 3, c2, 1, 2); +} + +static inline uint32_t Interp2(uint32_t c1, uint32_t c2, uint32_t c3) +{ + //(c1*2+c2+c3) >> 2; + return Interpolate_3(c1, 2, c2, 1, c3, 1, 2); +} + +static inline uint32_t Interp3(uint32_t c1, uint32_t c2) +{ + //(c1*7+c2)/8; + return Interpolate_2(c1, 7, c2, 1, 3); +} + +static inline uint32_t Interp4(uint32_t c1, uint32_t c2, uint32_t c3) +{ + //(c1*2+(c2+c3)*7)/16; + return Interpolate_3(c1, 2, c2, 7, c3, 7, 4); +} + +static inline uint32_t Interp5(uint32_t c1, uint32_t c2) +{ + //(c1+c2) >> 1; + return Interpolate_2(c1, 1, c2, 1, 1); +} + +static inline uint32_t Interp6(uint32_t c1, uint32_t c2, uint32_t c3) +{ + //(c1*5+c2*2+c3)/8; + return Interpolate_3(c1, 5, c2, 2, c3, 1, 3); +} + +static inline uint32_t Interp7(uint32_t c1, uint32_t c2, uint32_t c3) +{ + //(c1*6+c2+c3)/8; + return Interpolate_3(c1, 6, c2, 1, c3, 1, 3); +} + +static inline uint32_t Interp8(uint32_t c1, uint32_t c2) +{ + //(c1*5+c2*3)/8; + return Interpolate_2(c1, 5, c2, 3, 3); +} + +static inline uint32_t Interp9(uint32_t c1, uint32_t c2, uint32_t c3) +{ + //(c1*2+(c2+c3)*3)/8; + return Interpolate_3(c1, 2, c2, 3, c3, 3, 3); +} + +static inline uint32_t Interp10(uint32_t c1, uint32_t c2, uint32_t c3) +{ + //(c1*14+c2+c3)/16; + return Interpolate_3(c1, 14, c2, 1, c3, 1, 4); +} + +#endif diff --git a/source/common/textures/hires/hqnx/hq2x.cpp b/source/common/textures/hires/hqnx/hq2x.cpp new file mode 100644 index 000000000..637d1fb78 --- /dev/null +++ b/source/common/textures/hires/hqnx/hq2x.cpp @@ -0,0 +1,2808 @@ +/* + * Copyright (C) 2003 Maxim Stepin ( maxst@hiend3d.com ) + * + * Copyright (C) 2010 Cameron Zemek ( grom@zeminvaders.net) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "common.h" +#include "hqx.h" + +#define PIXEL00_0 *dp = w[5]; +#define PIXEL00_10 *dp = Interp1(w[5], w[1]); +#define PIXEL00_11 *dp = Interp1(w[5], w[4]); +#define PIXEL00_12 *dp = Interp1(w[5], w[2]); +#define PIXEL00_20 *dp = Interp2(w[5], w[4], w[2]); +#define PIXEL00_21 *dp = Interp2(w[5], w[1], w[2]); +#define PIXEL00_22 *dp = Interp2(w[5], w[1], w[4]); +#define PIXEL00_60 *dp = Interp6(w[5], w[2], w[4]); +#define PIXEL00_61 *dp = Interp6(w[5], w[4], w[2]); +#define PIXEL00_70 *dp = Interp7(w[5], w[4], w[2]); +#define PIXEL00_90 *dp = Interp9(w[5], w[4], w[2]); +#define PIXEL00_100 *dp = Interp10(w[5], w[4], w[2]); +#define PIXEL01_0 *(dp+1) = w[5]; +#define PIXEL01_10 *(dp+1) = Interp1(w[5], w[3]); +#define PIXEL01_11 *(dp+1) = Interp1(w[5], w[2]); +#define PIXEL01_12 *(dp+1) = Interp1(w[5], w[6]); +#define PIXEL01_20 *(dp+1) = Interp2(w[5], w[2], w[6]); +#define PIXEL01_21 *(dp+1) = Interp2(w[5], w[3], w[6]); +#define PIXEL01_22 *(dp+1) = Interp2(w[5], w[3], w[2]); +#define PIXEL01_60 *(dp+1) = Interp6(w[5], w[6], w[2]); +#define PIXEL01_61 *(dp+1) = Interp6(w[5], w[2], w[6]); +#define PIXEL01_70 *(dp+1) = Interp7(w[5], w[2], w[6]); +#define PIXEL01_90 *(dp+1) = Interp9(w[5], w[2], w[6]); +#define PIXEL01_100 *(dp+1) = Interp10(w[5], w[2], w[6]); +#define PIXEL10_0 *(dp+dpL) = w[5]; +#define PIXEL10_10 *(dp+dpL) = Interp1(w[5], w[7]); +#define PIXEL10_11 *(dp+dpL) = Interp1(w[5], w[8]); +#define PIXEL10_12 *(dp+dpL) = Interp1(w[5], w[4]); +#define PIXEL10_20 *(dp+dpL) = Interp2(w[5], w[8], w[4]); +#define PIXEL10_21 *(dp+dpL) = Interp2(w[5], w[7], w[4]); +#define PIXEL10_22 *(dp+dpL) = Interp2(w[5], w[7], w[8]); +#define PIXEL10_60 *(dp+dpL) = Interp6(w[5], w[4], w[8]); +#define PIXEL10_61 *(dp+dpL) = Interp6(w[5], w[8], w[4]); +#define PIXEL10_70 *(dp+dpL) = Interp7(w[5], w[8], w[4]); +#define PIXEL10_90 *(dp+dpL) = Interp9(w[5], w[8], w[4]); +#define PIXEL10_100 *(dp+dpL) = Interp10(w[5], w[8], w[4]); +#define PIXEL11_0 *(dp+dpL+1) = w[5]; +#define PIXEL11_10 *(dp+dpL+1) = Interp1(w[5], w[9]); +#define PIXEL11_11 *(dp+dpL+1) = Interp1(w[5], w[6]); +#define PIXEL11_12 *(dp+dpL+1) = Interp1(w[5], w[8]); +#define PIXEL11_20 *(dp+dpL+1) = Interp2(w[5], w[6], w[8]); +#define PIXEL11_21 *(dp+dpL+1) = Interp2(w[5], w[9], w[8]); +#define PIXEL11_22 *(dp+dpL+1) = Interp2(w[5], w[9], w[6]); +#define PIXEL11_60 *(dp+dpL+1) = Interp6(w[5], w[8], w[6]); +#define PIXEL11_61 *(dp+dpL+1) = Interp6(w[5], w[6], w[8]); +#define PIXEL11_70 *(dp+dpL+1) = Interp7(w[5], w[6], w[8]); +#define PIXEL11_90 *(dp+dpL+1) = Interp9(w[5], w[6], w[8]); +#define PIXEL11_100 *(dp+dpL+1) = Interp10(w[5], w[6], w[8]); + +HQX_API void HQX_CALLCONV hq2x_32_rb( uint32_t * sp, uint32_t srb, uint32_t * dp, uint32_t drb, int Xres, int Yres ) +{ + int i, j, k; + int prevline, nextline; + uint32_t w[10]; + int dpL = (drb >> 2); + int spL = (srb >> 2); + uint8_t *sRowP = (uint8_t *) sp; + uint8_t *dRowP = (uint8_t *) dp; + uint32_t yuv1, yuv2; + + // +----+----+----+ + // | | | | + // | w1 | w2 | w3 | + // +----+----+----+ + // | | | | + // | w4 | w5 | w6 | + // +----+----+----+ + // | | | | + // | w7 | w8 | w9 | + // +----+----+----+ + + for (j=0; j0) prevline = -spL; else prevline = 0; + if (j0) + { + w[1] = *(sp + prevline - 1); + w[4] = *(sp - 1); + w[7] = *(sp + nextline - 1); + } + else + { + w[1] = w[2]; + w[4] = w[5]; + w[7] = w[8]; + } + + if (i> 2); + int spL = (srb >> 2); + uint8_t *sRowP = (uint8_t *) sp; + uint8_t *dRowP = (uint8_t *) dp; + uint32_t yuv1, yuv2; + + // +----+----+----+ + // | | | | + // | w1 | w2 | w3 | + // +----+----+----+ + // | | | | + // | w4 | w5 | w6 | + // +----+----+----+ + // | | | | + // | w7 | w8 | w9 | + // +----+----+----+ + + for (j=0; j0) prevline = -spL; else prevline = 0; + if (j0) + { + w[1] = *(sp + prevline - 1); + w[4] = *(sp - 1); + w[7] = *(sp + nextline - 1); + } + else + { + w[1] = w[2]; + w[4] = w[5]; + w[7] = w[8]; + } + + if (i> 2); + int spL = (srb >> 2); + uint8_t *sRowP = (uint8_t *) sp; + uint8_t *dRowP = (uint8_t *) dp; + uint32_t yuv1, yuv2; + + // +----+----+----+ + // | | | | + // | w1 | w2 | w3 | + // +----+----+----+ + // | | | | + // | w4 | w5 | w6 | + // +----+----+----+ + // | | | | + // | w7 | w8 | w9 | + // +----+----+----+ + + for (j=0; j0) prevline = -spL; else prevline = 0; + if (j0) + { + w[1] = *(sp + prevline - 1); + w[4] = *(sp - 1); + w[7] = *(sp + nextline - 1); + } + else + { + w[1] = w[2]; + w[4] = w[5]; + w[7] = w[8]; + } + + if (i + +#if defined( __GNUC__ ) + #ifdef __MINGW32__ + #define HQX_CALLCONV __stdcall + #else + #define HQX_CALLCONV + #endif +#else + #define HQX_CALLCONV +#endif + +#if 0 //defined(_WIN32) + #ifdef DLL_EXPORT + #define HQX_API __declspec(dllexport) + #else + #define HQX_API __declspec(dllimport) + #endif +#else + #define HQX_API +#endif + +HQX_API void HQX_CALLCONV hqxInit(void); +HQX_API void HQX_CALLCONV hq2x_32( uint32_t * src, uint32_t * dest, int width, int height ); +HQX_API void HQX_CALLCONV hq3x_32( uint32_t * src, uint32_t * dest, int width, int height ); +HQX_API void HQX_CALLCONV hq4x_32( uint32_t * src, uint32_t * dest, int width, int height ); + +HQX_API void HQX_CALLCONV hq2x_32_rb( uint32_t * src, uint32_t src_rowBytes, uint32_t * dest, uint32_t dest_rowBytes, int width, int height ); +HQX_API void HQX_CALLCONV hq3x_32_rb( uint32_t * src, uint32_t src_rowBytes, uint32_t * dest, uint32_t dest_rowBytes, int width, int height ); +HQX_API void HQX_CALLCONV hq4x_32_rb( uint32_t * src, uint32_t src_rowBytes, uint32_t * dest, uint32_t dest_rowBytes, int width, int height ); + +#endif diff --git a/source/common/textures/hires/hqnx/init.cpp b/source/common/textures/hires/hqnx/init.cpp new file mode 100644 index 000000000..0e8c2db1c --- /dev/null +++ b/source/common/textures/hires/hqnx/init.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2010 Cameron Zemek ( grom@zeminvaders.net) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "hqx.h" + +uint32_t *RGBtoYUV; +uint32_t YUV1, YUV2; + +HQX_API void HQX_CALLCONV hqxInit(void) +{ + /* Initalize RGB to YUV lookup table */ + uint32_t c, r, g, b, y, u, v; + RGBtoYUV = new uint32_t[16777216]; + for (c = 0; c < 16777215; c++) { + r = (c & 0xFF0000) >> 16; + g = (c & 0x00FF00) >> 8; + b = c & 0x0000FF; + y = (uint32_t)(0.299*r + 0.587*g + 0.114*b); + u = (uint32_t)(-0.169*r - 0.331*g + 0.5*b) + 128; + v = (uint32_t)(0.5*r - 0.419*g - 0.081*b) + 128; + RGBtoYUV[c] = (y << 16) + (u << 8) + v; + } +} diff --git a/source/common/textures/hires/hqnx_asm/hq2x_asm.cpp b/source/common/textures/hires/hqnx_asm/hq2x_asm.cpp new file mode 100644 index 000000000..65e460377 --- /dev/null +++ b/source/common/textures/hires/hqnx_asm/hq2x_asm.cpp @@ -0,0 +1,2943 @@ +//hq2x filter demo program +//---------------------------------------------------------- +//Copyright (C) 2003 MaxSt ( maxst@hiend3d.com ) +//Copyright (C) 2012-2014 Alexey Lysiuk +// +//This program is free software; you can redistribute it and/or +//modify it under the terms of the GNU Lesser General Public +//License as published by the Free Software Foundation; either +//version 2.1 of the License, or (at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +//Lesser General Public License for more details. +// +//You should have received a copy of the GNU Lesser General Public +//License along with this program; if not, write to the Free Software +//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#include "hqnx_asm.h" + +namespace HQnX_asm +{ + +extern int LUT16to32[65536*2]; +extern int RGBtoYUV[65536*2]; + +static const hq_vec const3 = hq_vec::expand(0x0003); +static const hq_vec const5 = hq_vec::expand(0x0005); +static const hq_vec const6 = hq_vec::expand(0x0006); +static const hq_vec const14 = hq_vec::expand(0x000E); + +inline void Interp1(unsigned char * pc, int c1, int c2) +{ + //*((int*)pc) = (c1*3+c2)/4; + + hq_vec result = hq_vec::load(c1); + + result *= const3; + result += hq_vec::load(c2); + result >> 2; + + result.store(pc); +} + +inline void Interp2(unsigned char * pc, int c1, int c2, int c3) +{ + //*((int*)pc) = (c1*2+c2+c3) >> 2; + + hq_vec result = hq_vec::load(c1); + + result << 1; + result += hq_vec::load(c2); + result += hq_vec::load(c3); + result >> 2; + + result.store(pc); +} + +inline void Interp5(unsigned char * pc, int c1, int c2) +{ + //*((int*)pc) = (c1+c2)/2; + + hq_vec result = hq_vec::load(c1); + + result += hq_vec::load(c2); + result >> 1; + + result.store(pc); +} + +inline void Interp6(unsigned char * pc, int c1, int c2, int c3) +{ + //*((int*)pc) = (c1*5+c2*2+c3)/8; + + hq_vec result = hq_vec::load(c1); + + result *= const5; + result += hq_vec::load(c2) << 1; + result += hq_vec::load(c3); + result >> 3; + + result.store(pc); +} + +inline void Interp7(unsigned char * pc, int c1, int c2, int c3) +{ + //*((int*)pc) = (c1*6+c2+c3)/8; + + hq_vec result = hq_vec::load(c1); + + result *= const6; + result += hq_vec::load(c2); + result += hq_vec::load(c3); + result >> 3; + + result.store(pc); +} + +inline void Interp9(unsigned char * pc, int c1, int c2, int c3) +{ + //*((int*)pc) = (c1*2+(c2+c3)*3)/8; + + hq_vec result = hq_vec::load(c2); + + result += hq_vec::load(c3); + result *= const3; + result += hq_vec::load(c1) << 1; + result >> 3; + + result.store(pc); +} + +inline void Interp10(unsigned char * pc, int c1, int c2, int c3) +{ + //*((int*)pc) = (c1*14+c2+c3)/16; + + hq_vec result = hq_vec::load(c1); + + result *= const14; + result += hq_vec::load(c2); + result += hq_vec::load(c3); + result >> 4; + + result.store(pc); +} + +#define PIXEL00_0 *((int*)(pOut)) = c[5]; +#define PIXEL00_10 Interp1(pOut, c[5], c[1]); +#define PIXEL00_11 Interp1(pOut, c[5], c[4]); +#define PIXEL00_12 Interp1(pOut, c[5], c[2]); +#define PIXEL00_20 Interp2(pOut, c[5], c[4], c[2]); +#define PIXEL00_21 Interp2(pOut, c[5], c[1], c[2]); +#define PIXEL00_22 Interp2(pOut, c[5], c[1], c[4]); +#define PIXEL00_60 Interp6(pOut, c[5], c[2], c[4]); +#define PIXEL00_61 Interp6(pOut, c[5], c[4], c[2]); +#define PIXEL00_70 Interp7(pOut, c[5], c[4], c[2]); +#define PIXEL00_90 Interp9(pOut, c[5], c[4], c[2]); +#define PIXEL00_100 Interp10(pOut, c[5], c[4], c[2]); +#define PIXEL01_0 *((int*)(pOut+4)) = c[5]; +#define PIXEL01_10 Interp1(pOut+4, c[5], c[3]); +#define PIXEL01_11 Interp1(pOut+4, c[5], c[2]); +#define PIXEL01_12 Interp1(pOut+4, c[5], c[6]); +#define PIXEL01_20 Interp2(pOut+4, c[5], c[2], c[6]); +#define PIXEL01_21 Interp2(pOut+4, c[5], c[3], c[6]); +#define PIXEL01_22 Interp2(pOut+4, c[5], c[3], c[2]); +#define PIXEL01_60 Interp6(pOut+4, c[5], c[6], c[2]); +#define PIXEL01_61 Interp6(pOut+4, c[5], c[2], c[6]); +#define PIXEL01_70 Interp7(pOut+4, c[5], c[2], c[6]); +#define PIXEL01_90 Interp9(pOut+4, c[5], c[2], c[6]); +#define PIXEL01_100 Interp10(pOut+4, c[5], c[2], c[6]); +#define PIXEL10_0 *((int*)(pOut+BpL)) = c[5]; +#define PIXEL10_10 Interp1(pOut+BpL, c[5], c[7]); +#define PIXEL10_11 Interp1(pOut+BpL, c[5], c[8]); +#define PIXEL10_12 Interp1(pOut+BpL, c[5], c[4]); +#define PIXEL10_20 Interp2(pOut+BpL, c[5], c[8], c[4]); +#define PIXEL10_21 Interp2(pOut+BpL, c[5], c[7], c[4]); +#define PIXEL10_22 Interp2(pOut+BpL, c[5], c[7], c[8]); +#define PIXEL10_60 Interp6(pOut+BpL, c[5], c[4], c[8]); +#define PIXEL10_61 Interp6(pOut+BpL, c[5], c[8], c[4]); +#define PIXEL10_70 Interp7(pOut+BpL, c[5], c[8], c[4]); +#define PIXEL10_90 Interp9(pOut+BpL, c[5], c[8], c[4]); +#define PIXEL10_100 Interp10(pOut+BpL, c[5], c[8], c[4]); +#define PIXEL11_0 *((int*)(pOut+BpL+4)) = c[5]; +#define PIXEL11_10 Interp1(pOut+BpL+4, c[5], c[9]); +#define PIXEL11_11 Interp1(pOut+BpL+4, c[5], c[6]); +#define PIXEL11_12 Interp1(pOut+BpL+4, c[5], c[8]); +#define PIXEL11_20 Interp2(pOut+BpL+4, c[5], c[6], c[8]); +#define PIXEL11_21 Interp2(pOut+BpL+4, c[5], c[9], c[8]); +#define PIXEL11_22 Interp2(pOut+BpL+4, c[5], c[9], c[6]); +#define PIXEL11_60 Interp6(pOut+BpL+4, c[5], c[8], c[6]); +#define PIXEL11_61 Interp6(pOut+BpL+4, c[5], c[6], c[8]); +#define PIXEL11_70 Interp7(pOut+BpL+4, c[5], c[6], c[8]); +#define PIXEL11_90 Interp9(pOut+BpL+4, c[5], c[6], c[8]); +#define PIXEL11_100 Interp10(pOut+BpL+4, c[5], c[6], c[8]); + + +bool Diff(const unsigned int, const unsigned int); + +void DLL hq2x_32( int * pIn, unsigned char * pOut, int Xres, int Yres, int BpL ) +{ + int i, j, k; + int w[10]; + unsigned int c[10]; + + // +----+----+----+ + // | | | | + // | w1 | w2 | w3 | + // +----+----+----+ + // | | | | + // | w4 | w5 | w6 | + // +----+----+----+ + // | | | | + // | w7 | w8 | w9 | + // +----+----+----+ + + for (j=0; j0) + { + w[1] = *(pIn - Xres - 1); + } + else + { + w[1] = 0; + } + + w[2] = *(pIn - Xres); + + if (i0) + { + w[4] = *(pIn - 1); + } + else + { + w[4] = 0; + } + + w[5] = *(pIn); + if (i0) + { + w[7] = *(pIn + Xres - 1); + } + else + { + w[7] = 0; + } + + w[8] = *(pIn + Xres); + if (i> 2; + + result.store(pc); +} + +inline void Interp2(unsigned char * pc, int c1, int c2, int c3) +{ +// *((int*)pc) = (c1*2+c2+c3)/4; + + hq_vec result = hq_vec::load(c1); + + result << 1; + result += hq_vec::load(c2); + result += hq_vec::load(c3); + result >> 2; + + result.store(pc); +} + +inline void Interp3(unsigned char * pc, int c1, int c2) +{ + //*((int*)pc) = (c1*7+c2)/8; + + hq_vec result = hq_vec::load(c1); + + result *= const7; + result += hq_vec::load(c2); + result >> 3; + + result.store(pc); +} + +inline void Interp4(unsigned char * pc, int c1, int c2, int c3) +{ + //*((int*)pc) = (c1*2+(c2+c3)*7)/16; + + hq_vec result = hq_vec::load(c2); + + result += hq_vec::load(c3); + result *= const7; + result += hq_vec::load(c1) << 1; + result >> 4; + + result.store(pc); +} + +inline void Interp5(unsigned char * pc, int c1, int c2) +{ + //*((int*)pc) = (c1+c2)/2; + + hq_vec result = hq_vec::load(c1); + + result += hq_vec::load(c2); + result >> 1; + + result.store(pc); +} + +#define PIXEL00_1M Interp1(pOut, c[5], c[1]); +#define PIXEL00_1U Interp1(pOut, c[5], c[2]); +#define PIXEL00_1L Interp1(pOut, c[5], c[4]); +#define PIXEL00_2 Interp2(pOut, c[5], c[4], c[2]); +#define PIXEL00_4 Interp4(pOut, c[5], c[4], c[2]); +#define PIXEL00_5 Interp5(pOut, c[4], c[2]); +#define PIXEL00_C *((int*)(pOut)) = c[5]; + +#define PIXEL01_1 Interp1(pOut+4, c[5], c[2]); +#define PIXEL01_3 Interp3(pOut+4, c[5], c[2]); +#define PIXEL01_6 Interp1(pOut+4, c[2], c[5]); +#define PIXEL01_C *((int*)(pOut+4)) = c[5]; + +#define PIXEL02_1M Interp1(pOut+8, c[5], c[3]); +#define PIXEL02_1U Interp1(pOut+8, c[5], c[2]); +#define PIXEL02_1R Interp1(pOut+8, c[5], c[6]); +#define PIXEL02_2 Interp2(pOut+8, c[5], c[2], c[6]); +#define PIXEL02_4 Interp4(pOut+8, c[5], c[2], c[6]); +#define PIXEL02_5 Interp5(pOut+8, c[2], c[6]); +#define PIXEL02_C *((int*)(pOut+8)) = c[5]; + +#define PIXEL10_1 Interp1(pOut+BpL, c[5], c[4]); +#define PIXEL10_3 Interp3(pOut+BpL, c[5], c[4]); +#define PIXEL10_6 Interp1(pOut+BpL, c[4], c[5]); +#define PIXEL10_C *((int*)(pOut+BpL)) = c[5]; + +#define PIXEL11 *((int*)(pOut+BpL+4)) = c[5]; + +#define PIXEL12_1 Interp1(pOut+BpL+8, c[5], c[6]); +#define PIXEL12_3 Interp3(pOut+BpL+8, c[5], c[6]); +#define PIXEL12_6 Interp1(pOut+BpL+8, c[6], c[5]); +#define PIXEL12_C *((int*)(pOut+BpL+8)) = c[5]; + +#define PIXEL20_1M Interp1(pOut+BpL+BpL, c[5], c[7]); +#define PIXEL20_1D Interp1(pOut+BpL+BpL, c[5], c[8]); +#define PIXEL20_1L Interp1(pOut+BpL+BpL, c[5], c[4]); +#define PIXEL20_2 Interp2(pOut+BpL+BpL, c[5], c[8], c[4]); +#define PIXEL20_4 Interp4(pOut+BpL+BpL, c[5], c[8], c[4]); +#define PIXEL20_5 Interp5(pOut+BpL+BpL, c[8], c[4]); +#define PIXEL20_C *((int*)(pOut+BpL+BpL)) = c[5]; + +#define PIXEL21_1 Interp1(pOut+BpL+BpL+4, c[5], c[8]); +#define PIXEL21_3 Interp3(pOut+BpL+BpL+4, c[5], c[8]); +#define PIXEL21_6 Interp1(pOut+BpL+BpL+4, c[8], c[5]); +#define PIXEL21_C *((int*)(pOut+BpL+BpL+4)) = c[5]; + +#define PIXEL22_1M Interp1(pOut+BpL+BpL+8, c[5], c[9]); +#define PIXEL22_1D Interp1(pOut+BpL+BpL+8, c[5], c[8]); +#define PIXEL22_1R Interp1(pOut+BpL+BpL+8, c[5], c[6]); +#define PIXEL22_2 Interp2(pOut+BpL+BpL+8, c[5], c[6], c[8]); +#define PIXEL22_4 Interp4(pOut+BpL+BpL+8, c[5], c[6], c[8]); +#define PIXEL22_5 Interp5(pOut+BpL+BpL+8, c[6], c[8]); +#define PIXEL22_C *((int*)(pOut+BpL+BpL+8)) = c[5]; + +bool Diff(const unsigned int, const unsigned int); + +void DLL hq3x_32( int * pIn, unsigned char * pOut, int Xres, int Yres, int BpL ) +{ + int i, j, k; + int w[10]; + unsigned int c[10]; + + // +----+----+----+ + // | | | | + // | w1 | w2 | w3 | + // +----+----+----+ + // | | | | + // | w4 | w5 | w6 | + // +----+----+----+ + // | | | | + // | w7 | w8 | w9 | + // +----+----+----+ + + for (j=0; j0) w[1] = *(pIn - Xres - 1); else w[1] = 0; + w[2] = *(pIn - Xres); + if (i0) w[4] = *(pIn - 1); else w[4] = 0; + w[5] = *(pIn); + if (i0) w[7] = *(pIn + Xres - 1); else w[7] = 0; + w[8] = *(pIn + Xres); + if (i> 2; + + result.store(pc); +} + +inline void Interp2(unsigned char * pc, int c1, int c2, int c3) +{ +// *((int*)pc) = (c1*2+c2+c3)/4; + + hq_vec result = hq_vec::load(c1); + + result << 1; + result += hq_vec::load(c2); + result += hq_vec::load(c3); + result >> 2; + + result.store(pc); +} + +inline void Interp3(unsigned char * pc, int c1, int c2) +{ + //*((int*)pc) = (c1*7+c2)/8; + + hq_vec result = hq_vec::load(c1); + + result *= const7; + result += hq_vec::load(c2); + result >> 3; + + result.store(pc); +} + +inline void Interp5(unsigned char * pc, int c1, int c2) +{ + //*((int*)pc) = (c1+c2)/2; + + hq_vec result = hq_vec::load(c1); + + result += hq_vec::load(c2); + result >> 1; + + result.store(pc); +} + +inline void Interp6(unsigned char * pc, int c1, int c2, int c3) +{ + //*((int*)pc) = (c1*5+c2*2+c3)/8; + + hq_vec result = hq_vec::load(c1); + + result *= const5; + result += hq_vec::load(c2) << 1; + result += hq_vec::load(c3); + result >> 3; + + result.store(pc); +} + +inline void Interp7(unsigned char * pc, int c1, int c2, int c3) +{ + //*((int*)pc) = (c1*6+c2+c3)/8; + + hq_vec result = hq_vec::load(c1); + + result *= const6; + result += hq_vec::load(c2); + result += hq_vec::load(c3); + result >> 3; + + result.store(pc); +} + +inline void Interp8(unsigned char * pc, int c1, int c2) +{ + //*((int*)pc) = (c1*5+c2*3)/8; + + hq_vec result = hq_vec::load(c1); + + result *= const5; + result += hq_vec::load(c2) * const3; + result >> 3; + + result.store(pc); +} + +#define PIXEL00_0 *((int*)(pOut)) = c[5]; +#define PIXEL00_11 Interp1(pOut, c[5], c[4]); +#define PIXEL00_12 Interp1(pOut, c[5], c[2]); +#define PIXEL00_20 Interp2(pOut, c[5], c[2], c[4]); +#define PIXEL00_50 Interp5(pOut, c[2], c[4]); +#define PIXEL00_80 Interp8(pOut, c[5], c[1]); +#define PIXEL00_81 Interp8(pOut, c[5], c[4]); +#define PIXEL00_82 Interp8(pOut, c[5], c[2]); +#define PIXEL01_0 *((int*)(pOut+4)) = c[5]; +#define PIXEL01_10 Interp1(pOut+4, c[5], c[1]); +#define PIXEL01_12 Interp1(pOut+4, c[5], c[2]); +#define PIXEL01_14 Interp1(pOut+4, c[2], c[5]); +#define PIXEL01_21 Interp2(pOut+4, c[2], c[5], c[4]); +#define PIXEL01_31 Interp3(pOut+4, c[5], c[4]); +#define PIXEL01_50 Interp5(pOut+4, c[2], c[5]); +#define PIXEL01_60 Interp6(pOut+4, c[5], c[2], c[4]); +#define PIXEL01_61 Interp6(pOut+4, c[5], c[2], c[1]); +#define PIXEL01_82 Interp8(pOut+4, c[5], c[2]); +#define PIXEL01_83 Interp8(pOut+4, c[2], c[4]); +#define PIXEL02_0 *((int*)(pOut+8)) = c[5]; +#define PIXEL02_10 Interp1(pOut+8, c[5], c[3]); +#define PIXEL02_11 Interp1(pOut+8, c[5], c[2]); +#define PIXEL02_13 Interp1(pOut+8, c[2], c[5]); +#define PIXEL02_21 Interp2(pOut+8, c[2], c[5], c[6]); +#define PIXEL02_32 Interp3(pOut+8, c[5], c[6]); +#define PIXEL02_50 Interp5(pOut+8, c[2], c[5]); +#define PIXEL02_60 Interp6(pOut+8, c[5], c[2], c[6]); +#define PIXEL02_61 Interp6(pOut+8, c[5], c[2], c[3]); +#define PIXEL02_81 Interp8(pOut+8, c[5], c[2]); +#define PIXEL02_83 Interp8(pOut+8, c[2], c[6]); +#define PIXEL03_0 *((int*)(pOut+12)) = c[5]; +#define PIXEL03_11 Interp1(pOut+12, c[5], c[2]); +#define PIXEL03_12 Interp1(pOut+12, c[5], c[6]); +#define PIXEL03_20 Interp2(pOut+12, c[5], c[2], c[6]); +#define PIXEL03_50 Interp5(pOut+12, c[2], c[6]); +#define PIXEL03_80 Interp8(pOut+12, c[5], c[3]); +#define PIXEL03_81 Interp8(pOut+12, c[5], c[2]); +#define PIXEL03_82 Interp8(pOut+12, c[5], c[6]); +#define PIXEL10_0 *((int*)(pOut+BpL)) = c[5]; +#define PIXEL10_10 Interp1(pOut+BpL, c[5], c[1]); +#define PIXEL10_11 Interp1(pOut+BpL, c[5], c[4]); +#define PIXEL10_13 Interp1(pOut+BpL, c[4], c[5]); +#define PIXEL10_21 Interp2(pOut+BpL, c[4], c[5], c[2]); +#define PIXEL10_32 Interp3(pOut+BpL, c[5], c[2]); +#define PIXEL10_50 Interp5(pOut+BpL, c[4], c[5]); +#define PIXEL10_60 Interp6(pOut+BpL, c[5], c[4], c[2]); +#define PIXEL10_61 Interp6(pOut+BpL, c[5], c[4], c[1]); +#define PIXEL10_81 Interp8(pOut+BpL, c[5], c[4]); +#define PIXEL10_83 Interp8(pOut+BpL, c[4], c[2]); +#define PIXEL11_0 *((int*)(pOut+BpL+4)) = c[5]; +#define PIXEL11_30 Interp3(pOut+BpL+4, c[5], c[1]); +#define PIXEL11_31 Interp3(pOut+BpL+4, c[5], c[4]); +#define PIXEL11_32 Interp3(pOut+BpL+4, c[5], c[2]); +#define PIXEL11_70 Interp7(pOut+BpL+4, c[5], c[4], c[2]); +#define PIXEL12_0 *((int*)(pOut+BpL+8)) = c[5]; +#define PIXEL12_30 Interp3(pOut+BpL+8, c[5], c[3]); +#define PIXEL12_31 Interp3(pOut+BpL+8, c[5], c[2]); +#define PIXEL12_32 Interp3(pOut+BpL+8, c[5], c[6]); +#define PIXEL12_70 Interp7(pOut+BpL+8, c[5], c[6], c[2]); +#define PIXEL13_0 *((int*)(pOut+BpL+12)) = c[5]; +#define PIXEL13_10 Interp1(pOut+BpL+12, c[5], c[3]); +#define PIXEL13_12 Interp1(pOut+BpL+12, c[5], c[6]); +#define PIXEL13_14 Interp1(pOut+BpL+12, c[6], c[5]); +#define PIXEL13_21 Interp2(pOut+BpL+12, c[6], c[5], c[2]); +#define PIXEL13_31 Interp3(pOut+BpL+12, c[5], c[2]); +#define PIXEL13_50 Interp5(pOut+BpL+12, c[6], c[5]); +#define PIXEL13_60 Interp6(pOut+BpL+12, c[5], c[6], c[2]); +#define PIXEL13_61 Interp6(pOut+BpL+12, c[5], c[6], c[3]); +#define PIXEL13_82 Interp8(pOut+BpL+12, c[5], c[6]); +#define PIXEL13_83 Interp8(pOut+BpL+12, c[6], c[2]); +#define PIXEL20_0 *((int*)(pOut+BpL+BpL)) = c[5]; +#define PIXEL20_10 Interp1(pOut+BpL+BpL, c[5], c[7]); +#define PIXEL20_12 Interp1(pOut+BpL+BpL, c[5], c[4]); +#define PIXEL20_14 Interp1(pOut+BpL+BpL, c[4], c[5]); +#define PIXEL20_21 Interp2(pOut+BpL+BpL, c[4], c[5], c[8]); +#define PIXEL20_31 Interp3(pOut+BpL+BpL, c[5], c[8]); +#define PIXEL20_50 Interp5(pOut+BpL+BpL, c[4], c[5]); +#define PIXEL20_60 Interp6(pOut+BpL+BpL, c[5], c[4], c[8]); +#define PIXEL20_61 Interp6(pOut+BpL+BpL, c[5], c[4], c[7]); +#define PIXEL20_82 Interp8(pOut+BpL+BpL, c[5], c[4]); +#define PIXEL20_83 Interp8(pOut+BpL+BpL, c[4], c[8]); +#define PIXEL21_0 *((int*)(pOut+BpL+BpL+4)) = c[5]; +#define PIXEL21_30 Interp3(pOut+BpL+BpL+4, c[5], c[7]); +#define PIXEL21_31 Interp3(pOut+BpL+BpL+4, c[5], c[8]); +#define PIXEL21_32 Interp3(pOut+BpL+BpL+4, c[5], c[4]); +#define PIXEL21_70 Interp7(pOut+BpL+BpL+4, c[5], c[4], c[8]); +#define PIXEL22_0 *((int*)(pOut+BpL+BpL+8)) = c[5]; +#define PIXEL22_30 Interp3(pOut+BpL+BpL+8, c[5], c[9]); +#define PIXEL22_31 Interp3(pOut+BpL+BpL+8, c[5], c[6]); +#define PIXEL22_32 Interp3(pOut+BpL+BpL+8, c[5], c[8]); +#define PIXEL22_70 Interp7(pOut+BpL+BpL+8, c[5], c[6], c[8]); +#define PIXEL23_0 *((int*)(pOut+BpL+BpL+12)) = c[5]; +#define PIXEL23_10 Interp1(pOut+BpL+BpL+12, c[5], c[9]); +#define PIXEL23_11 Interp1(pOut+BpL+BpL+12, c[5], c[6]); +#define PIXEL23_13 Interp1(pOut+BpL+BpL+12, c[6], c[5]); +#define PIXEL23_21 Interp2(pOut+BpL+BpL+12, c[6], c[5], c[8]); +#define PIXEL23_32 Interp3(pOut+BpL+BpL+12, c[5], c[8]); +#define PIXEL23_50 Interp5(pOut+BpL+BpL+12, c[6], c[5]); +#define PIXEL23_60 Interp6(pOut+BpL+BpL+12, c[5], c[6], c[8]); +#define PIXEL23_61 Interp6(pOut+BpL+BpL+12, c[5], c[6], c[9]); +#define PIXEL23_81 Interp8(pOut+BpL+BpL+12, c[5], c[6]); +#define PIXEL23_83 Interp8(pOut+BpL+BpL+12, c[6], c[8]); +#define PIXEL30_0 *((int*)(pOut+BpL+BpL+BpL)) = c[5]; +#define PIXEL30_11 Interp1(pOut+BpL+BpL+BpL, c[5], c[8]); +#define PIXEL30_12 Interp1(pOut+BpL+BpL+BpL, c[5], c[4]); +#define PIXEL30_20 Interp2(pOut+BpL+BpL+BpL, c[5], c[8], c[4]); +#define PIXEL30_50 Interp5(pOut+BpL+BpL+BpL, c[8], c[4]); +#define PIXEL30_80 Interp8(pOut+BpL+BpL+BpL, c[5], c[7]); +#define PIXEL30_81 Interp8(pOut+BpL+BpL+BpL, c[5], c[8]); +#define PIXEL30_82 Interp8(pOut+BpL+BpL+BpL, c[5], c[4]); +#define PIXEL31_0 *((int*)(pOut+BpL+BpL+BpL+4)) = c[5]; +#define PIXEL31_10 Interp1(pOut+BpL+BpL+BpL+4, c[5], c[7]); +#define PIXEL31_11 Interp1(pOut+BpL+BpL+BpL+4, c[5], c[8]); +#define PIXEL31_13 Interp1(pOut+BpL+BpL+BpL+4, c[8], c[5]); +#define PIXEL31_21 Interp2(pOut+BpL+BpL+BpL+4, c[8], c[5], c[4]); +#define PIXEL31_32 Interp3(pOut+BpL+BpL+BpL+4, c[5], c[4]); +#define PIXEL31_50 Interp5(pOut+BpL+BpL+BpL+4, c[8], c[5]); +#define PIXEL31_60 Interp6(pOut+BpL+BpL+BpL+4, c[5], c[8], c[4]); +#define PIXEL31_61 Interp6(pOut+BpL+BpL+BpL+4, c[5], c[8], c[7]); +#define PIXEL31_81 Interp8(pOut+BpL+BpL+BpL+4, c[5], c[8]); +#define PIXEL31_83 Interp8(pOut+BpL+BpL+BpL+4, c[8], c[4]); +#define PIXEL32_0 *((int*)(pOut+BpL+BpL+BpL+8)) = c[5]; +#define PIXEL32_10 Interp1(pOut+BpL+BpL+BpL+8, c[5], c[9]); +#define PIXEL32_12 Interp1(pOut+BpL+BpL+BpL+8, c[5], c[8]); +#define PIXEL32_14 Interp1(pOut+BpL+BpL+BpL+8, c[8], c[5]); +#define PIXEL32_21 Interp2(pOut+BpL+BpL+BpL+8, c[8], c[5], c[6]); +#define PIXEL32_31 Interp3(pOut+BpL+BpL+BpL+8, c[5], c[6]); +#define PIXEL32_50 Interp5(pOut+BpL+BpL+BpL+8, c[8], c[5]); +#define PIXEL32_60 Interp6(pOut+BpL+BpL+BpL+8, c[5], c[8], c[6]); +#define PIXEL32_61 Interp6(pOut+BpL+BpL+BpL+8, c[5], c[8], c[9]); +#define PIXEL32_82 Interp8(pOut+BpL+BpL+BpL+8, c[5], c[8]); +#define PIXEL32_83 Interp8(pOut+BpL+BpL+BpL+8, c[8], c[6]); +#define PIXEL33_0 *((int*)(pOut+BpL+BpL+BpL+12)) = c[5]; +#define PIXEL33_11 Interp1(pOut+BpL+BpL+BpL+12, c[5], c[6]); +#define PIXEL33_12 Interp1(pOut+BpL+BpL+BpL+12, c[5], c[8]); +#define PIXEL33_20 Interp2(pOut+BpL+BpL+BpL+12, c[5], c[8], c[6]); +#define PIXEL33_50 Interp5(pOut+BpL+BpL+BpL+12, c[8], c[6]); +#define PIXEL33_80 Interp8(pOut+BpL+BpL+BpL+12, c[5], c[9]); +#define PIXEL33_81 Interp8(pOut+BpL+BpL+BpL+12, c[5], c[6]); +#define PIXEL33_82 Interp8(pOut+BpL+BpL+BpL+12, c[5], c[8]); + +bool Diff(const unsigned int rgb1, const unsigned int rgb2) +{ + if (rgb1 == rgb2) + { + return false; + } + + static const hq_vec THRESHOLD = 0x00300706; + + const hq_vec yuv1 = RGBtoYUV[rgb1]; + const hq_vec yuv2 = RGBtoYUV[rgb2]; + + const hq_vec delta1 = yuv1 - yuv2; + const hq_vec delta2 = yuv2 - yuv1; + + const hq_vec delta = delta1 | delta2; + const hq_vec result = delta - THRESHOLD; + + return 0 != result; +} + +void DLL hq4x_32( int * pIn, unsigned char * pOut, int Xres, int Yres, int BpL ) +{ + int i, j, k; + int w[10]; + int c[10]; + + // +----+----+----+ + // | | | | + // | w1 | w2 | w3 | + // +----+----+----+ + // | | | | + // | w4 | w5 | w6 | + // +----+----+----+ + // | | | | + // | w7 | w8 | w9 | + // +----+----+----+ + + for (j = 0; j < Yres; j++) + { + for (i = 0; i < Xres; i++) + { + if (j == 0) + { + w[1] = 0; + w[2] = 0; + w[3] = 0; + } + else + { + if (i > 0) + w[1] = *(pIn - Xres - 1); + else + w[1] = 0; + + w[2] = *(pIn - Xres); + + if (i < Xres - 1) + w[3] = *(pIn - Xres + 1); + else + w[3] = 0; + } + + if (i > 0) + w[4] = *(pIn - 1); + else + w[4] = 0; + + w[5] = *(pIn); + + if (i < Xres - 1) + w[6] = *(pIn + 1); + else + w[6] = 0; + + if (j == Yres - 1) + { + w[7] = 0; + w[8] = 0; + w[9] = 0; + } + else + { + if (i > 0) + w[7] = *(pIn + Xres - 1); + else + w[7] = 0; + + w[8] = *(pIn + Xres); + + if (i < Xres-1) + w[9] = *(pIn + Xres + 1); + else + w[9] = 0; + } + + int pattern = 0; + + if ( Diff(w[5],w[1]) ) pattern |= 0x0001; + if ( Diff(w[5],w[2]) ) pattern |= 0x0002; + if ( Diff(w[5],w[3]) ) pattern |= 0x0004; + if ( Diff(w[5],w[4]) ) pattern |= 0x0008; + if ( Diff(w[5],w[6]) ) pattern |= 0x0010; + if ( Diff(w[5],w[7]) ) pattern |= 0x0020; + if ( Diff(w[5],w[8]) ) pattern |= 0x0040; + if ( Diff(w[5],w[9]) ) pattern |= 0x0080; + + for (k=1; k<=9; k++) + c[k] = LUT16to32[w[k]]; + + switch (pattern) + { + case 0: + case 1: + case 4: + case 32: + case 128: + case 5: + case 132: + case 160: + case 33: + case 129: + case 36: + case 133: + case 164: + case 161: + case 37: + case 165: + { + PIXEL00_20 + PIXEL01_60 + PIXEL02_60 + PIXEL03_20 + PIXEL10_60 + PIXEL11_70 + PIXEL12_70 + PIXEL13_60 + PIXEL20_60 + PIXEL21_70 + PIXEL22_70 + PIXEL23_60 + PIXEL30_20 + PIXEL31_60 + PIXEL32_60 + PIXEL33_20 + break; + } + case 2: + case 34: + case 130: + case 162: + { + PIXEL00_80 + PIXEL01_10 + PIXEL02_10 + PIXEL03_80 + PIXEL10_61 + PIXEL11_30 + PIXEL12_30 + PIXEL13_61 + PIXEL20_60 + PIXEL21_70 + PIXEL22_70 + PIXEL23_60 + PIXEL30_20 + PIXEL31_60 + PIXEL32_60 + PIXEL33_20 + break; + } + case 16: + case 17: + case 48: + case 49: + { + PIXEL00_20 + PIXEL01_60 + PIXEL02_61 + PIXEL03_80 + PIXEL10_60 + PIXEL11_70 + PIXEL12_30 + PIXEL13_10 + PIXEL20_60 + PIXEL21_70 + PIXEL22_30 + PIXEL23_10 + PIXEL30_20 + PIXEL31_60 + PIXEL32_61 + PIXEL33_80 + break; + } + case 64: + case 65: + case 68: + case 69: + { + PIXEL00_20 + PIXEL01_60 + PIXEL02_60 + PIXEL03_20 + PIXEL10_60 + PIXEL11_70 + PIXEL12_70 + PIXEL13_60 + PIXEL20_61 + PIXEL21_30 + PIXEL22_30 + PIXEL23_61 + PIXEL30_80 + PIXEL31_10 + PIXEL32_10 + PIXEL33_80 + break; + } + case 8: + case 12: + case 136: + case 140: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_60 + PIXEL03_20 + PIXEL10_10 + PIXEL11_30 + PIXEL12_70 + PIXEL13_60 + PIXEL20_10 + PIXEL21_30 + PIXEL22_70 + PIXEL23_60 + PIXEL30_80 + PIXEL31_61 + PIXEL32_60 + PIXEL33_20 + break; + } + case 3: + case 35: + case 131: + case 163: + { + PIXEL00_81 + PIXEL01_31 + PIXEL02_10 + PIXEL03_80 + PIXEL10_81 + PIXEL11_31 + PIXEL12_30 + PIXEL13_61 + PIXEL20_60 + PIXEL21_70 + PIXEL22_70 + PIXEL23_60 + PIXEL30_20 + PIXEL31_60 + PIXEL32_60 + PIXEL33_20 + break; + } + case 6: + case 38: + case 134: + case 166: + { + PIXEL00_80 + PIXEL01_10 + PIXEL02_32 + PIXEL03_82 + PIXEL10_61 + PIXEL11_30 + PIXEL12_32 + PIXEL13_82 + PIXEL20_60 + PIXEL21_70 + PIXEL22_70 + PIXEL23_60 + PIXEL30_20 + PIXEL31_60 + PIXEL32_60 + PIXEL33_20 + break; + } + case 20: + case 21: + case 52: + case 53: + { + PIXEL00_20 + PIXEL01_60 + PIXEL02_81 + PIXEL03_81 + PIXEL10_60 + PIXEL11_70 + PIXEL12_31 + PIXEL13_31 + PIXEL20_60 + PIXEL21_70 + PIXEL22_30 + PIXEL23_10 + PIXEL30_20 + PIXEL31_60 + PIXEL32_61 + PIXEL33_80 + break; + } + case 144: + case 145: + case 176: + case 177: + { + PIXEL00_20 + PIXEL01_60 + PIXEL02_61 + PIXEL03_80 + PIXEL10_60 + PIXEL11_70 + PIXEL12_30 + PIXEL13_10 + PIXEL20_60 + PIXEL21_70 + PIXEL22_32 + PIXEL23_32 + PIXEL30_20 + PIXEL31_60 + PIXEL32_82 + PIXEL33_82 + break; + } + case 192: + case 193: + case 196: + case 197: + { + PIXEL00_20 + PIXEL01_60 + PIXEL02_60 + PIXEL03_20 + PIXEL10_60 + PIXEL11_70 + PIXEL12_70 + PIXEL13_60 + PIXEL20_61 + PIXEL21_30 + PIXEL22_31 + PIXEL23_81 + PIXEL30_80 + PIXEL31_10 + PIXEL32_31 + PIXEL33_81 + break; + } + case 96: + case 97: + case 100: + case 101: + { + PIXEL00_20 + PIXEL01_60 + PIXEL02_60 + PIXEL03_20 + PIXEL10_60 + PIXEL11_70 + PIXEL12_70 + PIXEL13_60 + PIXEL20_82 + PIXEL21_32 + PIXEL22_30 + PIXEL23_61 + PIXEL30_82 + PIXEL31_32 + PIXEL32_10 + PIXEL33_80 + break; + } + case 40: + case 44: + case 168: + case 172: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_60 + PIXEL03_20 + PIXEL10_10 + PIXEL11_30 + PIXEL12_70 + PIXEL13_60 + PIXEL20_31 + PIXEL21_31 + PIXEL22_70 + PIXEL23_60 + PIXEL30_81 + PIXEL31_81 + PIXEL32_60 + PIXEL33_20 + break; + } + case 9: + case 13: + case 137: + case 141: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_60 + PIXEL03_20 + PIXEL10_32 + PIXEL11_32 + PIXEL12_70 + PIXEL13_60 + PIXEL20_10 + PIXEL21_30 + PIXEL22_70 + PIXEL23_60 + PIXEL30_80 + PIXEL31_61 + PIXEL32_60 + PIXEL33_20 + break; + } + case 18: + case 50: + { + PIXEL00_80 + PIXEL01_10 + if (Diff(w[2], w[6])) + { + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_10 + } + else + { + PIXEL02_50 + PIXEL03_50 + PIXEL12_0 + PIXEL13_50 + } + PIXEL10_61 + PIXEL11_30 + PIXEL20_60 + PIXEL21_70 + PIXEL22_30 + PIXEL23_10 + PIXEL30_20 + PIXEL31_60 + PIXEL32_61 + PIXEL33_80 + break; + } + case 80: + case 81: + { + PIXEL00_20 + PIXEL01_60 + PIXEL02_61 + PIXEL03_80 + PIXEL10_60 + PIXEL11_70 + PIXEL12_30 + PIXEL13_10 + PIXEL20_61 + PIXEL21_30 + if (Diff(w[6], w[8])) + { + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + } + else + { + PIXEL22_0 + PIXEL23_50 + PIXEL32_50 + PIXEL33_50 + } + PIXEL30_80 + PIXEL31_10 + break; + } + case 72: + case 76: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_60 + PIXEL03_20 + PIXEL10_10 + PIXEL11_30 + PIXEL12_70 + PIXEL13_60 + if (Diff(w[8], w[4])) + { + PIXEL20_10 + PIXEL21_30 + PIXEL30_80 + PIXEL31_10 + } + else + { + PIXEL20_50 + PIXEL21_0 + PIXEL30_50 + PIXEL31_50 + } + PIXEL22_30 + PIXEL23_61 + PIXEL32_10 + PIXEL33_80 + break; + } + case 10: + case 138: + { + if (Diff(w[4], w[2])) + { + PIXEL00_80 + PIXEL01_10 + PIXEL10_10 + PIXEL11_30 + } + else + { + PIXEL00_50 + PIXEL01_50 + PIXEL10_50 + PIXEL11_0 + } + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_61 + PIXEL20_10 + PIXEL21_30 + PIXEL22_70 + PIXEL23_60 + PIXEL30_80 + PIXEL31_61 + PIXEL32_60 + PIXEL33_20 + break; + } + case 66: + { + PIXEL00_80 + PIXEL01_10 + PIXEL02_10 + PIXEL03_80 + PIXEL10_61 + PIXEL11_30 + PIXEL12_30 + PIXEL13_61 + PIXEL20_61 + PIXEL21_30 + PIXEL22_30 + PIXEL23_61 + PIXEL30_80 + PIXEL31_10 + PIXEL32_10 + PIXEL33_80 + break; + } + case 24: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_61 + PIXEL03_80 + PIXEL10_10 + PIXEL11_30 + PIXEL12_30 + PIXEL13_10 + PIXEL20_10 + PIXEL21_30 + PIXEL22_30 + PIXEL23_10 + PIXEL30_80 + PIXEL31_61 + PIXEL32_61 + PIXEL33_80 + break; + } + case 7: + case 39: + case 135: + { + PIXEL00_81 + PIXEL01_31 + PIXEL02_32 + PIXEL03_82 + PIXEL10_81 + PIXEL11_31 + PIXEL12_32 + PIXEL13_82 + PIXEL20_60 + PIXEL21_70 + PIXEL22_70 + PIXEL23_60 + PIXEL30_20 + PIXEL31_60 + PIXEL32_60 + PIXEL33_20 + break; + } + case 148: + case 149: + case 180: + { + PIXEL00_20 + PIXEL01_60 + PIXEL02_81 + PIXEL03_81 + PIXEL10_60 + PIXEL11_70 + PIXEL12_31 + PIXEL13_31 + PIXEL20_60 + PIXEL21_70 + PIXEL22_32 + PIXEL23_32 + PIXEL30_20 + PIXEL31_60 + PIXEL32_82 + PIXEL33_82 + break; + } + case 224: + case 228: + case 225: + { + PIXEL00_20 + PIXEL01_60 + PIXEL02_60 + PIXEL03_20 + PIXEL10_60 + PIXEL11_70 + PIXEL12_70 + PIXEL13_60 + PIXEL20_82 + PIXEL21_32 + PIXEL22_31 + PIXEL23_81 + PIXEL30_82 + PIXEL31_32 + PIXEL32_31 + PIXEL33_81 + break; + } + case 41: + case 169: + case 45: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_60 + PIXEL03_20 + PIXEL10_32 + PIXEL11_32 + PIXEL12_70 + PIXEL13_60 + PIXEL20_31 + PIXEL21_31 + PIXEL22_70 + PIXEL23_60 + PIXEL30_81 + PIXEL31_81 + PIXEL32_60 + PIXEL33_20 + break; + } + case 22: + case 54: + { + PIXEL00_80 + PIXEL01_10 + if (Diff(w[2], w[6])) + { + PIXEL02_0 + PIXEL03_0 + PIXEL13_0 + } + else + { + PIXEL02_50 + PIXEL03_50 + PIXEL13_50 + } + PIXEL10_61 + PIXEL11_30 + PIXEL12_0 + PIXEL20_60 + PIXEL21_70 + PIXEL22_30 + PIXEL23_10 + PIXEL30_20 + PIXEL31_60 + PIXEL32_61 + PIXEL33_80 + break; + } + case 208: + case 209: + { + PIXEL00_20 + PIXEL01_60 + PIXEL02_61 + PIXEL03_80 + PIXEL10_60 + PIXEL11_70 + PIXEL12_30 + PIXEL13_10 + PIXEL20_61 + PIXEL21_30 + PIXEL22_0 + if (Diff(w[6], w[8])) + { + PIXEL23_0 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL23_50 + PIXEL32_50 + PIXEL33_50 + } + PIXEL30_80 + PIXEL31_10 + break; + } + case 104: + case 108: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_60 + PIXEL03_20 + PIXEL10_10 + PIXEL11_30 + PIXEL12_70 + PIXEL13_60 + if (Diff(w[8], w[4])) + { + PIXEL20_0 + PIXEL30_0 + PIXEL31_0 + } + else + { + PIXEL20_50 + PIXEL30_50 + PIXEL31_50 + } + PIXEL21_0 + PIXEL22_30 + PIXEL23_61 + PIXEL32_10 + PIXEL33_80 + break; + } + case 11: + case 139: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL10_0 + } + else + { + PIXEL00_50 + PIXEL01_50 + PIXEL10_50 + } + PIXEL02_10 + PIXEL03_80 + PIXEL11_0 + PIXEL12_30 + PIXEL13_61 + PIXEL20_10 + PIXEL21_30 + PIXEL22_70 + PIXEL23_60 + PIXEL30_80 + PIXEL31_61 + PIXEL32_60 + PIXEL33_20 + break; + } + case 19: + case 51: + { + if (Diff(w[2], w[6])) + { + PIXEL00_81 + PIXEL01_31 + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_10 + } + else + { + PIXEL00_12 + PIXEL01_14 + PIXEL02_83 + PIXEL03_50 + PIXEL12_70 + PIXEL13_21 + } + PIXEL10_81 + PIXEL11_31 + PIXEL20_60 + PIXEL21_70 + PIXEL22_30 + PIXEL23_10 + PIXEL30_20 + PIXEL31_60 + PIXEL32_61 + PIXEL33_80 + break; + } + case 146: + case 178: + { + PIXEL00_80 + PIXEL01_10 + if (Diff(w[2], w[6])) + { + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_10 + PIXEL23_32 + PIXEL33_82 + } + else + { + PIXEL02_21 + PIXEL03_50 + PIXEL12_70 + PIXEL13_83 + PIXEL23_13 + PIXEL33_11 + } + PIXEL10_61 + PIXEL11_30 + PIXEL20_60 + PIXEL21_70 + PIXEL22_32 + PIXEL30_20 + PIXEL31_60 + PIXEL32_82 + break; + } + case 84: + case 85: + { + PIXEL00_20 + PIXEL01_60 + PIXEL02_81 + if (Diff(w[6], w[8])) + { + PIXEL03_81 + PIXEL13_31 + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + } + else + { + PIXEL03_12 + PIXEL13_14 + PIXEL22_70 + PIXEL23_83 + PIXEL32_21 + PIXEL33_50 + } + PIXEL10_60 + PIXEL11_70 + PIXEL12_31 + PIXEL20_61 + PIXEL21_30 + PIXEL30_80 + PIXEL31_10 + break; + } + case 112: + case 113: + { + PIXEL00_20 + PIXEL01_60 + PIXEL02_61 + PIXEL03_80 + PIXEL10_60 + PIXEL11_70 + PIXEL12_30 + PIXEL13_10 + PIXEL20_82 + PIXEL21_32 + if (Diff(w[6], w[8])) + { + PIXEL22_30 + PIXEL23_10 + PIXEL30_82 + PIXEL31_32 + PIXEL32_10 + PIXEL33_80 + } + else + { + PIXEL22_70 + PIXEL23_21 + PIXEL30_11 + PIXEL31_13 + PIXEL32_83 + PIXEL33_50 + } + break; + } + case 200: + case 204: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_60 + PIXEL03_20 + PIXEL10_10 + PIXEL11_30 + PIXEL12_70 + PIXEL13_60 + if (Diff(w[8], w[4])) + { + PIXEL20_10 + PIXEL21_30 + PIXEL30_80 + PIXEL31_10 + PIXEL32_31 + PIXEL33_81 + } + else + { + PIXEL20_21 + PIXEL21_70 + PIXEL30_50 + PIXEL31_83 + PIXEL32_14 + PIXEL33_12 + } + PIXEL22_31 + PIXEL23_81 + break; + } + case 73: + case 77: + { + if (Diff(w[8], w[4])) + { + PIXEL00_82 + PIXEL10_32 + PIXEL20_10 + PIXEL21_30 + PIXEL30_80 + PIXEL31_10 + } + else + { + PIXEL00_11 + PIXEL10_13 + PIXEL20_83 + PIXEL21_70 + PIXEL30_50 + PIXEL31_21 + } + PIXEL01_82 + PIXEL02_60 + PIXEL03_20 + PIXEL11_32 + PIXEL12_70 + PIXEL13_60 + PIXEL22_30 + PIXEL23_61 + PIXEL32_10 + PIXEL33_80 + break; + } + case 42: + case 170: + { + if (Diff(w[4], w[2])) + { + PIXEL00_80 + PIXEL01_10 + PIXEL10_10 + PIXEL11_30 + PIXEL20_31 + PIXEL30_81 + } + else + { + PIXEL00_50 + PIXEL01_21 + PIXEL10_83 + PIXEL11_70 + PIXEL20_14 + PIXEL30_12 + } + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_61 + PIXEL21_31 + PIXEL22_70 + PIXEL23_60 + PIXEL31_81 + PIXEL32_60 + PIXEL33_20 + break; + } + case 14: + case 142: + { + if (Diff(w[4], w[2])) + { + PIXEL00_80 + PIXEL01_10 + PIXEL02_32 + PIXEL03_82 + PIXEL10_10 + PIXEL11_30 + } + else + { + PIXEL00_50 + PIXEL01_83 + PIXEL02_13 + PIXEL03_11 + PIXEL10_21 + PIXEL11_70 + } + PIXEL12_32 + PIXEL13_82 + PIXEL20_10 + PIXEL21_30 + PIXEL22_70 + PIXEL23_60 + PIXEL30_80 + PIXEL31_61 + PIXEL32_60 + PIXEL33_20 + break; + } + case 67: + { + PIXEL00_81 + PIXEL01_31 + PIXEL02_10 + PIXEL03_80 + PIXEL10_81 + PIXEL11_31 + PIXEL12_30 + PIXEL13_61 + PIXEL20_61 + PIXEL21_30 + PIXEL22_30 + PIXEL23_61 + PIXEL30_80 + PIXEL31_10 + PIXEL32_10 + PIXEL33_80 + break; + } + case 70: + { + PIXEL00_80 + PIXEL01_10 + PIXEL02_32 + PIXEL03_82 + PIXEL10_61 + PIXEL11_30 + PIXEL12_32 + PIXEL13_82 + PIXEL20_61 + PIXEL21_30 + PIXEL22_30 + PIXEL23_61 + PIXEL30_80 + PIXEL31_10 + PIXEL32_10 + PIXEL33_80 + break; + } + case 28: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_81 + PIXEL03_81 + PIXEL10_10 + PIXEL11_30 + PIXEL12_31 + PIXEL13_31 + PIXEL20_10 + PIXEL21_30 + PIXEL22_30 + PIXEL23_10 + PIXEL30_80 + PIXEL31_61 + PIXEL32_61 + PIXEL33_80 + break; + } + case 152: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_61 + PIXEL03_80 + PIXEL10_10 + PIXEL11_30 + PIXEL12_30 + PIXEL13_10 + PIXEL20_10 + PIXEL21_30 + PIXEL22_32 + PIXEL23_32 + PIXEL30_80 + PIXEL31_61 + PIXEL32_82 + PIXEL33_82 + break; + } + case 194: + { + PIXEL00_80 + PIXEL01_10 + PIXEL02_10 + PIXEL03_80 + PIXEL10_61 + PIXEL11_30 + PIXEL12_30 + PIXEL13_61 + PIXEL20_61 + PIXEL21_30 + PIXEL22_31 + PIXEL23_81 + PIXEL30_80 + PIXEL31_10 + PIXEL32_31 + PIXEL33_81 + break; + } + case 98: + { + PIXEL00_80 + PIXEL01_10 + PIXEL02_10 + PIXEL03_80 + PIXEL10_61 + PIXEL11_30 + PIXEL12_30 + PIXEL13_61 + PIXEL20_82 + PIXEL21_32 + PIXEL22_30 + PIXEL23_61 + PIXEL30_82 + PIXEL31_32 + PIXEL32_10 + PIXEL33_80 + break; + } + case 56: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_61 + PIXEL03_80 + PIXEL10_10 + PIXEL11_30 + PIXEL12_30 + PIXEL13_10 + PIXEL20_31 + PIXEL21_31 + PIXEL22_30 + PIXEL23_10 + PIXEL30_81 + PIXEL31_81 + PIXEL32_61 + PIXEL33_80 + break; + } + case 25: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_61 + PIXEL03_80 + PIXEL10_32 + PIXEL11_32 + PIXEL12_30 + PIXEL13_10 + PIXEL20_10 + PIXEL21_30 + PIXEL22_30 + PIXEL23_10 + PIXEL30_80 + PIXEL31_61 + PIXEL32_61 + PIXEL33_80 + break; + } + case 26: + case 31: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL10_0 + } + else + { + PIXEL00_50 + PIXEL01_50 + PIXEL10_50 + } + if (Diff(w[2], w[6])) + { + PIXEL02_0 + PIXEL03_0 + PIXEL13_0 + } + else + { + PIXEL02_50 + PIXEL03_50 + PIXEL13_50 + } + PIXEL11_0 + PIXEL12_0 + PIXEL20_10 + PIXEL21_30 + PIXEL22_30 + PIXEL23_10 + PIXEL30_80 + PIXEL31_61 + PIXEL32_61 + PIXEL33_80 + break; + } + case 82: + case 214: + { + PIXEL00_80 + PIXEL01_10 + if (Diff(w[2], w[6])) + { + PIXEL02_0 + PIXEL03_0 + PIXEL13_0 + } + else + { + PIXEL02_50 + PIXEL03_50 + PIXEL13_50 + } + PIXEL10_61 + PIXEL11_30 + PIXEL12_0 + PIXEL20_61 + PIXEL21_30 + PIXEL22_0 + if (Diff(w[6], w[8])) + { + PIXEL23_0 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL23_50 + PIXEL32_50 + PIXEL33_50 + } + PIXEL30_80 + PIXEL31_10 + break; + } + case 88: + case 248: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_61 + PIXEL03_80 + PIXEL10_10 + PIXEL11_30 + PIXEL12_30 + PIXEL13_10 + if (Diff(w[8], w[4])) + { + PIXEL20_0 + PIXEL30_0 + PIXEL31_0 + } + else + { + PIXEL20_50 + PIXEL30_50 + PIXEL31_50 + } + PIXEL21_0 + PIXEL22_0 + if (Diff(w[6], w[8])) + { + PIXEL23_0 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL23_50 + PIXEL32_50 + PIXEL33_50 + } + break; + } + case 74: + case 107: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL10_0 + } + else + { + PIXEL00_50 + PIXEL01_50 + PIXEL10_50 + } + PIXEL02_10 + PIXEL03_80 + PIXEL11_0 + PIXEL12_30 + PIXEL13_61 + if (Diff(w[8], w[4])) + { + PIXEL20_0 + PIXEL30_0 + PIXEL31_0 + } + else + { + PIXEL20_50 + PIXEL30_50 + PIXEL31_50 + } + PIXEL21_0 + PIXEL22_30 + PIXEL23_61 + PIXEL32_10 + PIXEL33_80 + break; + } + case 27: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL10_0 + } + else + { + PIXEL00_50 + PIXEL01_50 + PIXEL10_50 + } + PIXEL02_10 + PIXEL03_80 + PIXEL11_0 + PIXEL12_30 + PIXEL13_10 + PIXEL20_10 + PIXEL21_30 + PIXEL22_30 + PIXEL23_10 + PIXEL30_80 + PIXEL31_61 + PIXEL32_61 + PIXEL33_80 + break; + } + case 86: + { + PIXEL00_80 + PIXEL01_10 + if (Diff(w[2], w[6])) + { + PIXEL02_0 + PIXEL03_0 + PIXEL13_0 + } + else + { + PIXEL02_50 + PIXEL03_50 + PIXEL13_50 + } + PIXEL10_61 + PIXEL11_30 + PIXEL12_0 + PIXEL20_61 + PIXEL21_30 + PIXEL22_30 + PIXEL23_10 + PIXEL30_80 + PIXEL31_10 + PIXEL32_10 + PIXEL33_80 + break; + } + case 216: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_61 + PIXEL03_80 + PIXEL10_10 + PIXEL11_30 + PIXEL12_30 + PIXEL13_10 + PIXEL20_10 + PIXEL21_30 + PIXEL22_0 + if (Diff(w[6], w[8])) + { + PIXEL23_0 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL23_50 + PIXEL32_50 + PIXEL33_50 + } + PIXEL30_80 + PIXEL31_10 + break; + } + case 106: + { + PIXEL00_80 + PIXEL01_10 + PIXEL02_10 + PIXEL03_80 + PIXEL10_10 + PIXEL11_30 + PIXEL12_30 + PIXEL13_61 + if (Diff(w[8], w[4])) + { + PIXEL20_0 + PIXEL30_0 + PIXEL31_0 + } + else + { + PIXEL20_50 + PIXEL30_50 + PIXEL31_50 + } + PIXEL21_0 + PIXEL22_30 + PIXEL23_61 + PIXEL32_10 + PIXEL33_80 + break; + } + case 30: + { + PIXEL00_80 + PIXEL01_10 + if (Diff(w[2], w[6])) + { + PIXEL02_0 + PIXEL03_0 + PIXEL13_0 + } + else + { + PIXEL02_50 + PIXEL03_50 + PIXEL13_50 + } + PIXEL10_10 + PIXEL11_30 + PIXEL12_0 + PIXEL20_10 + PIXEL21_30 + PIXEL22_30 + PIXEL23_10 + PIXEL30_80 + PIXEL31_61 + PIXEL32_61 + PIXEL33_80 + break; + } + case 210: + { + PIXEL00_80 + PIXEL01_10 + PIXEL02_10 + PIXEL03_80 + PIXEL10_61 + PIXEL11_30 + PIXEL12_30 + PIXEL13_10 + PIXEL20_61 + PIXEL21_30 + PIXEL22_0 + if (Diff(w[6], w[8])) + { + PIXEL23_0 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL23_50 + PIXEL32_50 + PIXEL33_50 + } + PIXEL30_80 + PIXEL31_10 + break; + } + case 120: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_61 + PIXEL03_80 + PIXEL10_10 + PIXEL11_30 + PIXEL12_30 + PIXEL13_10 + if (Diff(w[8], w[4])) + { + PIXEL20_0 + PIXEL30_0 + PIXEL31_0 + } + else + { + PIXEL20_50 + PIXEL30_50 + PIXEL31_50 + } + PIXEL21_0 + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + break; + } + case 75: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL10_0 + } + else + { + PIXEL00_50 + PIXEL01_50 + PIXEL10_50 + } + PIXEL02_10 + PIXEL03_80 + PIXEL11_0 + PIXEL12_30 + PIXEL13_61 + PIXEL20_10 + PIXEL21_30 + PIXEL22_30 + PIXEL23_61 + PIXEL30_80 + PIXEL31_10 + PIXEL32_10 + PIXEL33_80 + break; + } + case 29: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_81 + PIXEL03_81 + PIXEL10_32 + PIXEL11_32 + PIXEL12_31 + PIXEL13_31 + PIXEL20_10 + PIXEL21_30 + PIXEL22_30 + PIXEL23_10 + PIXEL30_80 + PIXEL31_61 + PIXEL32_61 + PIXEL33_80 + break; + } + case 198: + { + PIXEL00_80 + PIXEL01_10 + PIXEL02_32 + PIXEL03_82 + PIXEL10_61 + PIXEL11_30 + PIXEL12_32 + PIXEL13_82 + PIXEL20_61 + PIXEL21_30 + PIXEL22_31 + PIXEL23_81 + PIXEL30_80 + PIXEL31_10 + PIXEL32_31 + PIXEL33_81 + break; + } + case 184: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_61 + PIXEL03_80 + PIXEL10_10 + PIXEL11_30 + PIXEL12_30 + PIXEL13_10 + PIXEL20_31 + PIXEL21_31 + PIXEL22_32 + PIXEL23_32 + PIXEL30_81 + PIXEL31_81 + PIXEL32_82 + PIXEL33_82 + break; + } + case 99: + { + PIXEL00_81 + PIXEL01_31 + PIXEL02_10 + PIXEL03_80 + PIXEL10_81 + PIXEL11_31 + PIXEL12_30 + PIXEL13_61 + PIXEL20_82 + PIXEL21_32 + PIXEL22_30 + PIXEL23_61 + PIXEL30_82 + PIXEL31_32 + PIXEL32_10 + PIXEL33_80 + break; + } + case 57: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_61 + PIXEL03_80 + PIXEL10_32 + PIXEL11_32 + PIXEL12_30 + PIXEL13_10 + PIXEL20_31 + PIXEL21_31 + PIXEL22_30 + PIXEL23_10 + PIXEL30_81 + PIXEL31_81 + PIXEL32_61 + PIXEL33_80 + break; + } + case 71: + { + PIXEL00_81 + PIXEL01_31 + PIXEL02_32 + PIXEL03_82 + PIXEL10_81 + PIXEL11_31 + PIXEL12_32 + PIXEL13_82 + PIXEL20_61 + PIXEL21_30 + PIXEL22_30 + PIXEL23_61 + PIXEL30_80 + PIXEL31_10 + PIXEL32_10 + PIXEL33_80 + break; + } + case 156: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_81 + PIXEL03_81 + PIXEL10_10 + PIXEL11_30 + PIXEL12_31 + PIXEL13_31 + PIXEL20_10 + PIXEL21_30 + PIXEL22_32 + PIXEL23_32 + PIXEL30_80 + PIXEL31_61 + PIXEL32_82 + PIXEL33_82 + break; + } + case 226: + { + PIXEL00_80 + PIXEL01_10 + PIXEL02_10 + PIXEL03_80 + PIXEL10_61 + PIXEL11_30 + PIXEL12_30 + PIXEL13_61 + PIXEL20_82 + PIXEL21_32 + PIXEL22_31 + PIXEL23_81 + PIXEL30_82 + PIXEL31_32 + PIXEL32_31 + PIXEL33_81 + break; + } + case 60: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_81 + PIXEL03_81 + PIXEL10_10 + PIXEL11_30 + PIXEL12_31 + PIXEL13_31 + PIXEL20_31 + PIXEL21_31 + PIXEL22_30 + PIXEL23_10 + PIXEL30_81 + PIXEL31_81 + PIXEL32_61 + PIXEL33_80 + break; + } + case 195: + { + PIXEL00_81 + PIXEL01_31 + PIXEL02_10 + PIXEL03_80 + PIXEL10_81 + PIXEL11_31 + PIXEL12_30 + PIXEL13_61 + PIXEL20_61 + PIXEL21_30 + PIXEL22_31 + PIXEL23_81 + PIXEL30_80 + PIXEL31_10 + PIXEL32_31 + PIXEL33_81 + break; + } + case 102: + { + PIXEL00_80 + PIXEL01_10 + PIXEL02_32 + PIXEL03_82 + PIXEL10_61 + PIXEL11_30 + PIXEL12_32 + PIXEL13_82 + PIXEL20_82 + PIXEL21_32 + PIXEL22_30 + PIXEL23_61 + PIXEL30_82 + PIXEL31_32 + PIXEL32_10 + PIXEL33_80 + break; + } + case 153: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_61 + PIXEL03_80 + PIXEL10_32 + PIXEL11_32 + PIXEL12_30 + PIXEL13_10 + PIXEL20_10 + PIXEL21_30 + PIXEL22_32 + PIXEL23_32 + PIXEL30_80 + PIXEL31_61 + PIXEL32_82 + PIXEL33_82 + break; + } + case 58: + { + if (Diff(w[4], w[2])) + { + PIXEL00_80 + PIXEL01_10 + PIXEL10_10 + PIXEL11_30 + } + else + { + PIXEL00_20 + PIXEL01_12 + PIXEL10_11 + PIXEL11_0 + } + if (Diff(w[2], w[6])) + { + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_10 + } + else + { + PIXEL02_11 + PIXEL03_20 + PIXEL12_0 + PIXEL13_12 + } + PIXEL20_31 + PIXEL21_31 + PIXEL22_30 + PIXEL23_10 + PIXEL30_81 + PIXEL31_81 + PIXEL32_61 + PIXEL33_80 + break; + } + case 83: + { + PIXEL00_81 + PIXEL01_31 + if (Diff(w[2], w[6])) + { + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_10 + } + else + { + PIXEL02_11 + PIXEL03_20 + PIXEL12_0 + PIXEL13_12 + } + PIXEL10_81 + PIXEL11_31 + PIXEL20_61 + PIXEL21_30 + if (Diff(w[6], w[8])) + { + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + } + else + { + PIXEL22_0 + PIXEL23_11 + PIXEL32_12 + PIXEL33_20 + } + PIXEL30_80 + PIXEL31_10 + break; + } + case 92: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_81 + PIXEL03_81 + PIXEL10_10 + PIXEL11_30 + PIXEL12_31 + PIXEL13_31 + if (Diff(w[8], w[4])) + { + PIXEL20_10 + PIXEL21_30 + PIXEL30_80 + PIXEL31_10 + } + else + { + PIXEL20_12 + PIXEL21_0 + PIXEL30_20 + PIXEL31_11 + } + if (Diff(w[6], w[8])) + { + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + } + else + { + PIXEL22_0 + PIXEL23_11 + PIXEL32_12 + PIXEL33_20 + } + break; + } + case 202: + { + if (Diff(w[4], w[2])) + { + PIXEL00_80 + PIXEL01_10 + PIXEL10_10 + PIXEL11_30 + } + else + { + PIXEL00_20 + PIXEL01_12 + PIXEL10_11 + PIXEL11_0 + } + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_61 + if (Diff(w[8], w[4])) + { + PIXEL20_10 + PIXEL21_30 + PIXEL30_80 + PIXEL31_10 + } + else + { + PIXEL20_12 + PIXEL21_0 + PIXEL30_20 + PIXEL31_11 + } + PIXEL22_31 + PIXEL23_81 + PIXEL32_31 + PIXEL33_81 + break; + } + case 78: + { + if (Diff(w[4], w[2])) + { + PIXEL00_80 + PIXEL01_10 + PIXEL10_10 + PIXEL11_30 + } + else + { + PIXEL00_20 + PIXEL01_12 + PIXEL10_11 + PIXEL11_0 + } + PIXEL02_32 + PIXEL03_82 + PIXEL12_32 + PIXEL13_82 + if (Diff(w[8], w[4])) + { + PIXEL20_10 + PIXEL21_30 + PIXEL30_80 + PIXEL31_10 + } + else + { + PIXEL20_12 + PIXEL21_0 + PIXEL30_20 + PIXEL31_11 + } + PIXEL22_30 + PIXEL23_61 + PIXEL32_10 + PIXEL33_80 + break; + } + case 154: + { + if (Diff(w[4], w[2])) + { + PIXEL00_80 + PIXEL01_10 + PIXEL10_10 + PIXEL11_30 + } + else + { + PIXEL00_20 + PIXEL01_12 + PIXEL10_11 + PIXEL11_0 + } + if (Diff(w[2], w[6])) + { + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_10 + } + else + { + PIXEL02_11 + PIXEL03_20 + PIXEL12_0 + PIXEL13_12 + } + PIXEL20_10 + PIXEL21_30 + PIXEL22_32 + PIXEL23_32 + PIXEL30_80 + PIXEL31_61 + PIXEL32_82 + PIXEL33_82 + break; + } + case 114: + { + PIXEL00_80 + PIXEL01_10 + if (Diff(w[2], w[6])) + { + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_10 + } + else + { + PIXEL02_11 + PIXEL03_20 + PIXEL12_0 + PIXEL13_12 + } + PIXEL10_61 + PIXEL11_30 + PIXEL20_82 + PIXEL21_32 + if (Diff(w[6], w[8])) + { + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + } + else + { + PIXEL22_0 + PIXEL23_11 + PIXEL32_12 + PIXEL33_20 + } + PIXEL30_82 + PIXEL31_32 + break; + } + case 89: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_61 + PIXEL03_80 + PIXEL10_32 + PIXEL11_32 + PIXEL12_30 + PIXEL13_10 + if (Diff(w[8], w[4])) + { + PIXEL20_10 + PIXEL21_30 + PIXEL30_80 + PIXEL31_10 + } + else + { + PIXEL20_12 + PIXEL21_0 + PIXEL30_20 + PIXEL31_11 + } + if (Diff(w[6], w[8])) + { + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + } + else + { + PIXEL22_0 + PIXEL23_11 + PIXEL32_12 + PIXEL33_20 + } + break; + } + case 90: + { + if (Diff(w[4], w[2])) + { + PIXEL00_80 + PIXEL01_10 + PIXEL10_10 + PIXEL11_30 + } + else + { + PIXEL00_20 + PIXEL01_12 + PIXEL10_11 + PIXEL11_0 + } + if (Diff(w[2], w[6])) + { + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_10 + } + else + { + PIXEL02_11 + PIXEL03_20 + PIXEL12_0 + PIXEL13_12 + } + if (Diff(w[8], w[4])) + { + PIXEL20_10 + PIXEL21_30 + PIXEL30_80 + PIXEL31_10 + } + else + { + PIXEL20_12 + PIXEL21_0 + PIXEL30_20 + PIXEL31_11 + } + if (Diff(w[6], w[8])) + { + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + } + else + { + PIXEL22_0 + PIXEL23_11 + PIXEL32_12 + PIXEL33_20 + } + break; + } + case 55: + case 23: + { + if (Diff(w[2], w[6])) + { + PIXEL00_81 + PIXEL01_31 + PIXEL02_0 + PIXEL03_0 + PIXEL12_0 + PIXEL13_0 + } + else + { + PIXEL00_12 + PIXEL01_14 + PIXEL02_83 + PIXEL03_50 + PIXEL12_70 + PIXEL13_21 + } + PIXEL10_81 + PIXEL11_31 + PIXEL20_60 + PIXEL21_70 + PIXEL22_30 + PIXEL23_10 + PIXEL30_20 + PIXEL31_60 + PIXEL32_61 + PIXEL33_80 + break; + } + case 182: + case 150: + { + PIXEL00_80 + PIXEL01_10 + if (Diff(w[2], w[6])) + { + PIXEL02_0 + PIXEL03_0 + PIXEL12_0 + PIXEL13_0 + PIXEL23_32 + PIXEL33_82 + } + else + { + PIXEL02_21 + PIXEL03_50 + PIXEL12_70 + PIXEL13_83 + PIXEL23_13 + PIXEL33_11 + } + PIXEL10_61 + PIXEL11_30 + PIXEL20_60 + PIXEL21_70 + PIXEL22_32 + PIXEL30_20 + PIXEL31_60 + PIXEL32_82 + break; + } + case 213: + case 212: + { + PIXEL00_20 + PIXEL01_60 + PIXEL02_81 + if (Diff(w[6], w[8])) + { + PIXEL03_81 + PIXEL13_31 + PIXEL22_0 + PIXEL23_0 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL03_12 + PIXEL13_14 + PIXEL22_70 + PIXEL23_83 + PIXEL32_21 + PIXEL33_50 + } + PIXEL10_60 + PIXEL11_70 + PIXEL12_31 + PIXEL20_61 + PIXEL21_30 + PIXEL30_80 + PIXEL31_10 + break; + } + case 241: + case 240: + { + PIXEL00_20 + PIXEL01_60 + PIXEL02_61 + PIXEL03_80 + PIXEL10_60 + PIXEL11_70 + PIXEL12_30 + PIXEL13_10 + PIXEL20_82 + PIXEL21_32 + if (Diff(w[6], w[8])) + { + PIXEL22_0 + PIXEL23_0 + PIXEL30_82 + PIXEL31_32 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL22_70 + PIXEL23_21 + PIXEL30_11 + PIXEL31_13 + PIXEL32_83 + PIXEL33_50 + } + break; + } + case 236: + case 232: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_60 + PIXEL03_20 + PIXEL10_10 + PIXEL11_30 + PIXEL12_70 + PIXEL13_60 + if (Diff(w[8], w[4])) + { + PIXEL20_0 + PIXEL21_0 + PIXEL30_0 + PIXEL31_0 + PIXEL32_31 + PIXEL33_81 + } + else + { + PIXEL20_21 + PIXEL21_70 + PIXEL30_50 + PIXEL31_83 + PIXEL32_14 + PIXEL33_12 + } + PIXEL22_31 + PIXEL23_81 + break; + } + case 109: + case 105: + { + if (Diff(w[8], w[4])) + { + PIXEL00_82 + PIXEL10_32 + PIXEL20_0 + PIXEL21_0 + PIXEL30_0 + PIXEL31_0 + } + else + { + PIXEL00_11 + PIXEL10_13 + PIXEL20_83 + PIXEL21_70 + PIXEL30_50 + PIXEL31_21 + } + PIXEL01_82 + PIXEL02_60 + PIXEL03_20 + PIXEL11_32 + PIXEL12_70 + PIXEL13_60 + PIXEL22_30 + PIXEL23_61 + PIXEL32_10 + PIXEL33_80 + break; + } + case 171: + case 43: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL10_0 + PIXEL11_0 + PIXEL20_31 + PIXEL30_81 + } + else + { + PIXEL00_50 + PIXEL01_21 + PIXEL10_83 + PIXEL11_70 + PIXEL20_14 + PIXEL30_12 + } + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_61 + PIXEL21_31 + PIXEL22_70 + PIXEL23_60 + PIXEL31_81 + PIXEL32_60 + PIXEL33_20 + break; + } + case 143: + case 15: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL02_32 + PIXEL03_82 + PIXEL10_0 + PIXEL11_0 + } + else + { + PIXEL00_50 + PIXEL01_83 + PIXEL02_13 + PIXEL03_11 + PIXEL10_21 + PIXEL11_70 + } + PIXEL12_32 + PIXEL13_82 + PIXEL20_10 + PIXEL21_30 + PIXEL22_70 + PIXEL23_60 + PIXEL30_80 + PIXEL31_61 + PIXEL32_60 + PIXEL33_20 + break; + } + case 124: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_81 + PIXEL03_81 + PIXEL10_10 + PIXEL11_30 + PIXEL12_31 + PIXEL13_31 + if (Diff(w[8], w[4])) + { + PIXEL20_0 + PIXEL30_0 + PIXEL31_0 + } + else + { + PIXEL20_50 + PIXEL30_50 + PIXEL31_50 + } + PIXEL21_0 + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + break; + } + case 203: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL10_0 + } + else + { + PIXEL00_50 + PIXEL01_50 + PIXEL10_50 + } + PIXEL02_10 + PIXEL03_80 + PIXEL11_0 + PIXEL12_30 + PIXEL13_61 + PIXEL20_10 + PIXEL21_30 + PIXEL22_31 + PIXEL23_81 + PIXEL30_80 + PIXEL31_10 + PIXEL32_31 + PIXEL33_81 + break; + } + case 62: + { + PIXEL00_80 + PIXEL01_10 + if (Diff(w[2], w[6])) + { + PIXEL02_0 + PIXEL03_0 + PIXEL13_0 + } + else + { + PIXEL02_50 + PIXEL03_50 + PIXEL13_50 + } + PIXEL10_10 + PIXEL11_30 + PIXEL12_0 + PIXEL20_31 + PIXEL21_31 + PIXEL22_30 + PIXEL23_10 + PIXEL30_81 + PIXEL31_81 + PIXEL32_61 + PIXEL33_80 + break; + } + case 211: + { + PIXEL00_81 + PIXEL01_31 + PIXEL02_10 + PIXEL03_80 + PIXEL10_81 + PIXEL11_31 + PIXEL12_30 + PIXEL13_10 + PIXEL20_61 + PIXEL21_30 + PIXEL22_0 + if (Diff(w[6], w[8])) + { + PIXEL23_0 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL23_50 + PIXEL32_50 + PIXEL33_50 + } + PIXEL30_80 + PIXEL31_10 + break; + } + case 118: + { + PIXEL00_80 + PIXEL01_10 + if (Diff(w[2], w[6])) + { + PIXEL02_0 + PIXEL03_0 + PIXEL13_0 + } + else + { + PIXEL02_50 + PIXEL03_50 + PIXEL13_50 + } + PIXEL10_61 + PIXEL11_30 + PIXEL12_0 + PIXEL20_82 + PIXEL21_32 + PIXEL22_30 + PIXEL23_10 + PIXEL30_82 + PIXEL31_32 + PIXEL32_10 + PIXEL33_80 + break; + } + case 217: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_61 + PIXEL03_80 + PIXEL10_32 + PIXEL11_32 + PIXEL12_30 + PIXEL13_10 + PIXEL20_10 + PIXEL21_30 + PIXEL22_0 + if (Diff(w[6], w[8])) + { + PIXEL23_0 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL23_50 + PIXEL32_50 + PIXEL33_50 + } + PIXEL30_80 + PIXEL31_10 + break; + } + case 110: + { + PIXEL00_80 + PIXEL01_10 + PIXEL02_32 + PIXEL03_82 + PIXEL10_10 + PIXEL11_30 + PIXEL12_32 + PIXEL13_82 + if (Diff(w[8], w[4])) + { + PIXEL20_0 + PIXEL30_0 + PIXEL31_0 + } + else + { + PIXEL20_50 + PIXEL30_50 + PIXEL31_50 + } + PIXEL21_0 + PIXEL22_30 + PIXEL23_61 + PIXEL32_10 + PIXEL33_80 + break; + } + case 155: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL10_0 + } + else + { + PIXEL00_50 + PIXEL01_50 + PIXEL10_50 + } + PIXEL02_10 + PIXEL03_80 + PIXEL11_0 + PIXEL12_30 + PIXEL13_10 + PIXEL20_10 + PIXEL21_30 + PIXEL22_32 + PIXEL23_32 + PIXEL30_80 + PIXEL31_61 + PIXEL32_82 + PIXEL33_82 + break; + } + case 188: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_81 + PIXEL03_81 + PIXEL10_10 + PIXEL11_30 + PIXEL12_31 + PIXEL13_31 + PIXEL20_31 + PIXEL21_31 + PIXEL22_32 + PIXEL23_32 + PIXEL30_81 + PIXEL31_81 + PIXEL32_82 + PIXEL33_82 + break; + } + case 185: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_61 + PIXEL03_80 + PIXEL10_32 + PIXEL11_32 + PIXEL12_30 + PIXEL13_10 + PIXEL20_31 + PIXEL21_31 + PIXEL22_32 + PIXEL23_32 + PIXEL30_81 + PIXEL31_81 + PIXEL32_82 + PIXEL33_82 + break; + } + case 61: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_81 + PIXEL03_81 + PIXEL10_32 + PIXEL11_32 + PIXEL12_31 + PIXEL13_31 + PIXEL20_31 + PIXEL21_31 + PIXEL22_30 + PIXEL23_10 + PIXEL30_81 + PIXEL31_81 + PIXEL32_61 + PIXEL33_80 + break; + } + case 157: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_81 + PIXEL03_81 + PIXEL10_32 + PIXEL11_32 + PIXEL12_31 + PIXEL13_31 + PIXEL20_10 + PIXEL21_30 + PIXEL22_32 + PIXEL23_32 + PIXEL30_80 + PIXEL31_61 + PIXEL32_82 + PIXEL33_82 + break; + } + case 103: + { + PIXEL00_81 + PIXEL01_31 + PIXEL02_32 + PIXEL03_82 + PIXEL10_81 + PIXEL11_31 + PIXEL12_32 + PIXEL13_82 + PIXEL20_82 + PIXEL21_32 + PIXEL22_30 + PIXEL23_61 + PIXEL30_82 + PIXEL31_32 + PIXEL32_10 + PIXEL33_80 + break; + } + case 227: + { + PIXEL00_81 + PIXEL01_31 + PIXEL02_10 + PIXEL03_80 + PIXEL10_81 + PIXEL11_31 + PIXEL12_30 + PIXEL13_61 + PIXEL20_82 + PIXEL21_32 + PIXEL22_31 + PIXEL23_81 + PIXEL30_82 + PIXEL31_32 + PIXEL32_31 + PIXEL33_81 + break; + } + case 230: + { + PIXEL00_80 + PIXEL01_10 + PIXEL02_32 + PIXEL03_82 + PIXEL10_61 + PIXEL11_30 + PIXEL12_32 + PIXEL13_82 + PIXEL20_82 + PIXEL21_32 + PIXEL22_31 + PIXEL23_81 + PIXEL30_82 + PIXEL31_32 + PIXEL32_31 + PIXEL33_81 + break; + } + case 199: + { + PIXEL00_81 + PIXEL01_31 + PIXEL02_32 + PIXEL03_82 + PIXEL10_81 + PIXEL11_31 + PIXEL12_32 + PIXEL13_82 + PIXEL20_61 + PIXEL21_30 + PIXEL22_31 + PIXEL23_81 + PIXEL30_80 + PIXEL31_10 + PIXEL32_31 + PIXEL33_81 + break; + } + case 220: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_81 + PIXEL03_81 + PIXEL10_10 + PIXEL11_30 + PIXEL12_31 + PIXEL13_31 + if (Diff(w[8], w[4])) + { + PIXEL20_10 + PIXEL21_30 + PIXEL30_80 + PIXEL31_10 + } + else + { + PIXEL20_12 + PIXEL21_0 + PIXEL30_20 + PIXEL31_11 + } + PIXEL22_0 + if (Diff(w[6], w[8])) + { + PIXEL23_0 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL23_50 + PIXEL32_50 + PIXEL33_50 + } + break; + } + case 158: + { + if (Diff(w[4], w[2])) + { + PIXEL00_80 + PIXEL01_10 + PIXEL10_10 + PIXEL11_30 + } + else + { + PIXEL00_20 + PIXEL01_12 + PIXEL10_11 + PIXEL11_0 + } + if (Diff(w[2], w[6])) + { + PIXEL02_0 + PIXEL03_0 + PIXEL13_0 + } + else + { + PIXEL02_50 + PIXEL03_50 + PIXEL13_50 + } + PIXEL12_0 + PIXEL20_10 + PIXEL21_30 + PIXEL22_32 + PIXEL23_32 + PIXEL30_80 + PIXEL31_61 + PIXEL32_82 + PIXEL33_82 + break; + } + case 234: + { + if (Diff(w[4], w[2])) + { + PIXEL00_80 + PIXEL01_10 + PIXEL10_10 + PIXEL11_30 + } + else + { + PIXEL00_20 + PIXEL01_12 + PIXEL10_11 + PIXEL11_0 + } + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_61 + if (Diff(w[8], w[4])) + { + PIXEL20_0 + PIXEL30_0 + PIXEL31_0 + } + else + { + PIXEL20_50 + PIXEL30_50 + PIXEL31_50 + } + PIXEL21_0 + PIXEL22_31 + PIXEL23_81 + PIXEL32_31 + PIXEL33_81 + break; + } + case 242: + { + PIXEL00_80 + PIXEL01_10 + if (Diff(w[2], w[6])) + { + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_10 + } + else + { + PIXEL02_11 + PIXEL03_20 + PIXEL12_0 + PIXEL13_12 + } + PIXEL10_61 + PIXEL11_30 + PIXEL20_82 + PIXEL21_32 + PIXEL22_0 + if (Diff(w[6], w[8])) + { + PIXEL23_0 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL23_50 + PIXEL32_50 + PIXEL33_50 + } + PIXEL30_82 + PIXEL31_32 + break; + } + case 59: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL10_0 + } + else + { + PIXEL00_50 + PIXEL01_50 + PIXEL10_50 + } + if (Diff(w[2], w[6])) + { + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_10 + } + else + { + PIXEL02_11 + PIXEL03_20 + PIXEL12_0 + PIXEL13_12 + } + PIXEL11_0 + PIXEL20_31 + PIXEL21_31 + PIXEL22_30 + PIXEL23_10 + PIXEL30_81 + PIXEL31_81 + PIXEL32_61 + PIXEL33_80 + break; + } + case 121: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_61 + PIXEL03_80 + PIXEL10_32 + PIXEL11_32 + PIXEL12_30 + PIXEL13_10 + if (Diff(w[8], w[4])) + { + PIXEL20_0 + PIXEL30_0 + PIXEL31_0 + } + else + { + PIXEL20_50 + PIXEL30_50 + PIXEL31_50 + } + PIXEL21_0 + if (Diff(w[6], w[8])) + { + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + } + else + { + PIXEL22_0 + PIXEL23_11 + PIXEL32_12 + PIXEL33_20 + } + break; + } + case 87: + { + PIXEL00_81 + PIXEL01_31 + if (Diff(w[2], w[6])) + { + PIXEL02_0 + PIXEL03_0 + PIXEL13_0 + } + else + { + PIXEL02_50 + PIXEL03_50 + PIXEL13_50 + } + PIXEL10_81 + PIXEL11_31 + PIXEL12_0 + PIXEL20_61 + PIXEL21_30 + if (Diff(w[6], w[8])) + { + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + } + else + { + PIXEL22_0 + PIXEL23_11 + PIXEL32_12 + PIXEL33_20 + } + PIXEL30_80 + PIXEL31_10 + break; + } + case 79: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL10_0 + } + else + { + PIXEL00_50 + PIXEL01_50 + PIXEL10_50 + } + PIXEL02_32 + PIXEL03_82 + PIXEL11_0 + PIXEL12_32 + PIXEL13_82 + if (Diff(w[8], w[4])) + { + PIXEL20_10 + PIXEL21_30 + PIXEL30_80 + PIXEL31_10 + } + else + { + PIXEL20_12 + PIXEL21_0 + PIXEL30_20 + PIXEL31_11 + } + PIXEL22_30 + PIXEL23_61 + PIXEL32_10 + PIXEL33_80 + break; + } + case 122: + { + if (Diff(w[4], w[2])) + { + PIXEL00_80 + PIXEL01_10 + PIXEL10_10 + PIXEL11_30 + } + else + { + PIXEL00_20 + PIXEL01_12 + PIXEL10_11 + PIXEL11_0 + } + if (Diff(w[2], w[6])) + { + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_10 + } + else + { + PIXEL02_11 + PIXEL03_20 + PIXEL12_0 + PIXEL13_12 + } + if (Diff(w[8], w[4])) + { + PIXEL20_0 + PIXEL30_0 + PIXEL31_0 + } + else + { + PIXEL20_50 + PIXEL30_50 + PIXEL31_50 + } + PIXEL21_0 + if (Diff(w[6], w[8])) + { + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + } + else + { + PIXEL22_0 + PIXEL23_11 + PIXEL32_12 + PIXEL33_20 + } + break; + } + case 94: + { + if (Diff(w[4], w[2])) + { + PIXEL00_80 + PIXEL01_10 + PIXEL10_10 + PIXEL11_30 + } + else + { + PIXEL00_20 + PIXEL01_12 + PIXEL10_11 + PIXEL11_0 + } + if (Diff(w[2], w[6])) + { + PIXEL02_0 + PIXEL03_0 + PIXEL13_0 + } + else + { + PIXEL02_50 + PIXEL03_50 + PIXEL13_50 + } + PIXEL12_0 + if (Diff(w[8], w[4])) + { + PIXEL20_10 + PIXEL21_30 + PIXEL30_80 + PIXEL31_10 + } + else + { + PIXEL20_12 + PIXEL21_0 + PIXEL30_20 + PIXEL31_11 + } + if (Diff(w[6], w[8])) + { + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + } + else + { + PIXEL22_0 + PIXEL23_11 + PIXEL32_12 + PIXEL33_20 + } + break; + } + case 218: + { + if (Diff(w[4], w[2])) + { + PIXEL00_80 + PIXEL01_10 + PIXEL10_10 + PIXEL11_30 + } + else + { + PIXEL00_20 + PIXEL01_12 + PIXEL10_11 + PIXEL11_0 + } + if (Diff(w[2], w[6])) + { + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_10 + } + else + { + PIXEL02_11 + PIXEL03_20 + PIXEL12_0 + PIXEL13_12 + } + if (Diff(w[8], w[4])) + { + PIXEL20_10 + PIXEL21_30 + PIXEL30_80 + PIXEL31_10 + } + else + { + PIXEL20_12 + PIXEL21_0 + PIXEL30_20 + PIXEL31_11 + } + PIXEL22_0 + if (Diff(w[6], w[8])) + { + PIXEL23_0 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL23_50 + PIXEL32_50 + PIXEL33_50 + } + break; + } + case 91: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL10_0 + } + else + { + PIXEL00_50 + PIXEL01_50 + PIXEL10_50 + } + if (Diff(w[2], w[6])) + { + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_10 + } + else + { + PIXEL02_11 + PIXEL03_20 + PIXEL12_0 + PIXEL13_12 + } + PIXEL11_0 + if (Diff(w[8], w[4])) + { + PIXEL20_10 + PIXEL21_30 + PIXEL30_80 + PIXEL31_10 + } + else + { + PIXEL20_12 + PIXEL21_0 + PIXEL30_20 + PIXEL31_11 + } + if (Diff(w[6], w[8])) + { + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + } + else + { + PIXEL22_0 + PIXEL23_11 + PIXEL32_12 + PIXEL33_20 + } + break; + } + case 229: + { + PIXEL00_20 + PIXEL01_60 + PIXEL02_60 + PIXEL03_20 + PIXEL10_60 + PIXEL11_70 + PIXEL12_70 + PIXEL13_60 + PIXEL20_82 + PIXEL21_32 + PIXEL22_31 + PIXEL23_81 + PIXEL30_82 + PIXEL31_32 + PIXEL32_31 + PIXEL33_81 + break; + } + case 167: + { + PIXEL00_81 + PIXEL01_31 + PIXEL02_32 + PIXEL03_82 + PIXEL10_81 + PIXEL11_31 + PIXEL12_32 + PIXEL13_82 + PIXEL20_60 + PIXEL21_70 + PIXEL22_70 + PIXEL23_60 + PIXEL30_20 + PIXEL31_60 + PIXEL32_60 + PIXEL33_20 + break; + } + case 173: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_60 + PIXEL03_20 + PIXEL10_32 + PIXEL11_32 + PIXEL12_70 + PIXEL13_60 + PIXEL20_31 + PIXEL21_31 + PIXEL22_70 + PIXEL23_60 + PIXEL30_81 + PIXEL31_81 + PIXEL32_60 + PIXEL33_20 + break; + } + case 181: + { + PIXEL00_20 + PIXEL01_60 + PIXEL02_81 + PIXEL03_81 + PIXEL10_60 + PIXEL11_70 + PIXEL12_31 + PIXEL13_31 + PIXEL20_60 + PIXEL21_70 + PIXEL22_32 + PIXEL23_32 + PIXEL30_20 + PIXEL31_60 + PIXEL32_82 + PIXEL33_82 + break; + } + case 186: + { + if (Diff(w[4], w[2])) + { + PIXEL00_80 + PIXEL01_10 + PIXEL10_10 + PIXEL11_30 + } + else + { + PIXEL00_20 + PIXEL01_12 + PIXEL10_11 + PIXEL11_0 + } + if (Diff(w[2], w[6])) + { + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_10 + } + else + { + PIXEL02_11 + PIXEL03_20 + PIXEL12_0 + PIXEL13_12 + } + PIXEL20_31 + PIXEL21_31 + PIXEL22_32 + PIXEL23_32 + PIXEL30_81 + PIXEL31_81 + PIXEL32_82 + PIXEL33_82 + break; + } + case 115: + { + PIXEL00_81 + PIXEL01_31 + if (Diff(w[2], w[6])) + { + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_10 + } + else + { + PIXEL02_11 + PIXEL03_20 + PIXEL12_0 + PIXEL13_12 + } + PIXEL10_81 + PIXEL11_31 + PIXEL20_82 + PIXEL21_32 + if (Diff(w[6], w[8])) + { + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + } + else + { + PIXEL22_0 + PIXEL23_11 + PIXEL32_12 + PIXEL33_20 + } + PIXEL30_82 + PIXEL31_32 + break; + } + case 93: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_81 + PIXEL03_81 + PIXEL10_32 + PIXEL11_32 + PIXEL12_31 + PIXEL13_31 + if (Diff(w[8], w[4])) + { + PIXEL20_10 + PIXEL21_30 + PIXEL30_80 + PIXEL31_10 + } + else + { + PIXEL20_12 + PIXEL21_0 + PIXEL30_20 + PIXEL31_11 + } + if (Diff(w[6], w[8])) + { + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + } + else + { + PIXEL22_0 + PIXEL23_11 + PIXEL32_12 + PIXEL33_20 + } + break; + } + case 206: + { + if (Diff(w[4], w[2])) + { + PIXEL00_80 + PIXEL01_10 + PIXEL10_10 + PIXEL11_30 + } + else + { + PIXEL00_20 + PIXEL01_12 + PIXEL10_11 + PIXEL11_0 + } + PIXEL02_32 + PIXEL03_82 + PIXEL12_32 + PIXEL13_82 + if (Diff(w[8], w[4])) + { + PIXEL20_10 + PIXEL21_30 + PIXEL30_80 + PIXEL31_10 + } + else + { + PIXEL20_12 + PIXEL21_0 + PIXEL30_20 + PIXEL31_11 + } + PIXEL22_31 + PIXEL23_81 + PIXEL32_31 + PIXEL33_81 + break; + } + case 205: + case 201: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_60 + PIXEL03_20 + PIXEL10_32 + PIXEL11_32 + PIXEL12_70 + PIXEL13_60 + if (Diff(w[8], w[4])) + { + PIXEL20_10 + PIXEL21_30 + PIXEL30_80 + PIXEL31_10 + } + else + { + PIXEL20_12 + PIXEL21_0 + PIXEL30_20 + PIXEL31_11 + } + PIXEL22_31 + PIXEL23_81 + PIXEL32_31 + PIXEL33_81 + break; + } + case 174: + case 46: + { + if (Diff(w[4], w[2])) + { + PIXEL00_80 + PIXEL01_10 + PIXEL10_10 + PIXEL11_30 + } + else + { + PIXEL00_20 + PIXEL01_12 + PIXEL10_11 + PIXEL11_0 + } + PIXEL02_32 + PIXEL03_82 + PIXEL12_32 + PIXEL13_82 + PIXEL20_31 + PIXEL21_31 + PIXEL22_70 + PIXEL23_60 + PIXEL30_81 + PIXEL31_81 + PIXEL32_60 + PIXEL33_20 + break; + } + case 179: + case 147: + { + PIXEL00_81 + PIXEL01_31 + if (Diff(w[2], w[6])) + { + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_10 + } + else + { + PIXEL02_11 + PIXEL03_20 + PIXEL12_0 + PIXEL13_12 + } + PIXEL10_81 + PIXEL11_31 + PIXEL20_60 + PIXEL21_70 + PIXEL22_32 + PIXEL23_32 + PIXEL30_20 + PIXEL31_60 + PIXEL32_82 + PIXEL33_82 + break; + } + case 117: + case 116: + { + PIXEL00_20 + PIXEL01_60 + PIXEL02_81 + PIXEL03_81 + PIXEL10_60 + PIXEL11_70 + PIXEL12_31 + PIXEL13_31 + PIXEL20_82 + PIXEL21_32 + if (Diff(w[6], w[8])) + { + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + } + else + { + PIXEL22_0 + PIXEL23_11 + PIXEL32_12 + PIXEL33_20 + } + PIXEL30_82 + PIXEL31_32 + break; + } + case 189: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_81 + PIXEL03_81 + PIXEL10_32 + PIXEL11_32 + PIXEL12_31 + PIXEL13_31 + PIXEL20_31 + PIXEL21_31 + PIXEL22_32 + PIXEL23_32 + PIXEL30_81 + PIXEL31_81 + PIXEL32_82 + PIXEL33_82 + break; + } + case 231: + { + PIXEL00_81 + PIXEL01_31 + PIXEL02_32 + PIXEL03_82 + PIXEL10_81 + PIXEL11_31 + PIXEL12_32 + PIXEL13_82 + PIXEL20_82 + PIXEL21_32 + PIXEL22_31 + PIXEL23_81 + PIXEL30_82 + PIXEL31_32 + PIXEL32_31 + PIXEL33_81 + break; + } + case 126: + { + PIXEL00_80 + PIXEL01_10 + if (Diff(w[2], w[6])) + { + PIXEL02_0 + PIXEL03_0 + PIXEL13_0 + } + else + { + PIXEL02_50 + PIXEL03_50 + PIXEL13_50 + } + PIXEL10_10 + PIXEL11_30 + PIXEL12_0 + if (Diff(w[8], w[4])) + { + PIXEL20_0 + PIXEL30_0 + PIXEL31_0 + } + else + { + PIXEL20_50 + PIXEL30_50 + PIXEL31_50 + } + PIXEL21_0 + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + break; + } + case 219: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL10_0 + } + else + { + PIXEL00_50 + PIXEL01_50 + PIXEL10_50 + } + PIXEL02_10 + PIXEL03_80 + PIXEL11_0 + PIXEL12_30 + PIXEL13_10 + PIXEL20_10 + PIXEL21_30 + PIXEL22_0 + if (Diff(w[6], w[8])) + { + PIXEL23_0 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL23_50 + PIXEL32_50 + PIXEL33_50 + } + PIXEL30_80 + PIXEL31_10 + break; + } + case 125: + { + if (Diff(w[8], w[4])) + { + PIXEL00_82 + PIXEL10_32 + PIXEL20_0 + PIXEL21_0 + PIXEL30_0 + PIXEL31_0 + } + else + { + PIXEL00_11 + PIXEL10_13 + PIXEL20_83 + PIXEL21_70 + PIXEL30_50 + PIXEL31_21 + } + PIXEL01_82 + PIXEL02_81 + PIXEL03_81 + PIXEL11_32 + PIXEL12_31 + PIXEL13_31 + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + break; + } + case 221: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_81 + if (Diff(w[6], w[8])) + { + PIXEL03_81 + PIXEL13_31 + PIXEL22_0 + PIXEL23_0 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL03_12 + PIXEL13_14 + PIXEL22_70 + PIXEL23_83 + PIXEL32_21 + PIXEL33_50 + } + PIXEL10_32 + PIXEL11_32 + PIXEL12_31 + PIXEL20_10 + PIXEL21_30 + PIXEL30_80 + PIXEL31_10 + break; + } + case 207: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL02_32 + PIXEL03_82 + PIXEL10_0 + PIXEL11_0 + } + else + { + PIXEL00_50 + PIXEL01_83 + PIXEL02_13 + PIXEL03_11 + PIXEL10_21 + PIXEL11_70 + } + PIXEL12_32 + PIXEL13_82 + PIXEL20_10 + PIXEL21_30 + PIXEL22_31 + PIXEL23_81 + PIXEL30_80 + PIXEL31_10 + PIXEL32_31 + PIXEL33_81 + break; + } + case 238: + { + PIXEL00_80 + PIXEL01_10 + PIXEL02_32 + PIXEL03_82 + PIXEL10_10 + PIXEL11_30 + PIXEL12_32 + PIXEL13_82 + if (Diff(w[8], w[4])) + { + PIXEL20_0 + PIXEL21_0 + PIXEL30_0 + PIXEL31_0 + PIXEL32_31 + PIXEL33_81 + } + else + { + PIXEL20_21 + PIXEL21_70 + PIXEL30_50 + PIXEL31_83 + PIXEL32_14 + PIXEL33_12 + } + PIXEL22_31 + PIXEL23_81 + break; + } + case 190: + { + PIXEL00_80 + PIXEL01_10 + if (Diff(w[2], w[6])) + { + PIXEL02_0 + PIXEL03_0 + PIXEL12_0 + PIXEL13_0 + PIXEL23_32 + PIXEL33_82 + } + else + { + PIXEL02_21 + PIXEL03_50 + PIXEL12_70 + PIXEL13_83 + PIXEL23_13 + PIXEL33_11 + } + PIXEL10_10 + PIXEL11_30 + PIXEL20_31 + PIXEL21_31 + PIXEL22_32 + PIXEL30_81 + PIXEL31_81 + PIXEL32_82 + break; + } + case 187: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL10_0 + PIXEL11_0 + PIXEL20_31 + PIXEL30_81 + } + else + { + PIXEL00_50 + PIXEL01_21 + PIXEL10_83 + PIXEL11_70 + PIXEL20_14 + PIXEL30_12 + } + PIXEL02_10 + PIXEL03_80 + PIXEL12_30 + PIXEL13_10 + PIXEL21_31 + PIXEL22_32 + PIXEL23_32 + PIXEL31_81 + PIXEL32_82 + PIXEL33_82 + break; + } + case 243: + { + PIXEL00_81 + PIXEL01_31 + PIXEL02_10 + PIXEL03_80 + PIXEL10_81 + PIXEL11_31 + PIXEL12_30 + PIXEL13_10 + PIXEL20_82 + PIXEL21_32 + if (Diff(w[6], w[8])) + { + PIXEL22_0 + PIXEL23_0 + PIXEL30_82 + PIXEL31_32 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL22_70 + PIXEL23_21 + PIXEL30_11 + PIXEL31_13 + PIXEL32_83 + PIXEL33_50 + } + break; + } + case 119: + { + if (Diff(w[2], w[6])) + { + PIXEL00_81 + PIXEL01_31 + PIXEL02_0 + PIXEL03_0 + PIXEL12_0 + PIXEL13_0 + } + else + { + PIXEL00_12 + PIXEL01_14 + PIXEL02_83 + PIXEL03_50 + PIXEL12_70 + PIXEL13_21 + } + PIXEL10_81 + PIXEL11_31 + PIXEL20_82 + PIXEL21_32 + PIXEL22_30 + PIXEL23_10 + PIXEL30_82 + PIXEL31_32 + PIXEL32_10 + PIXEL33_80 + break; + } + case 237: + case 233: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_60 + PIXEL03_20 + PIXEL10_32 + PIXEL11_32 + PIXEL12_70 + PIXEL13_60 + PIXEL20_0 + PIXEL21_0 + PIXEL22_31 + PIXEL23_81 + if (Diff(w[8], w[4])) + { + PIXEL30_0 + } + else + { + PIXEL30_20 + } + PIXEL31_0 + PIXEL32_31 + PIXEL33_81 + break; + } + case 175: + case 47: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_0 + PIXEL02_32 + PIXEL03_82 + PIXEL10_0 + PIXEL11_0 + PIXEL12_32 + PIXEL13_82 + PIXEL20_31 + PIXEL21_31 + PIXEL22_70 + PIXEL23_60 + PIXEL30_81 + PIXEL31_81 + PIXEL32_60 + PIXEL33_20 + break; + } + case 183: + case 151: + { + PIXEL00_81 + PIXEL01_31 + PIXEL02_0 + if (Diff(w[2], w[6])) + { + PIXEL03_0 + } + else + { + PIXEL03_20 + } + PIXEL10_81 + PIXEL11_31 + PIXEL12_0 + PIXEL13_0 + PIXEL20_60 + PIXEL21_70 + PIXEL22_32 + PIXEL23_32 + PIXEL30_20 + PIXEL31_60 + PIXEL32_82 + PIXEL33_82 + break; + } + case 245: + case 244: + { + PIXEL00_20 + PIXEL01_60 + PIXEL02_81 + PIXEL03_81 + PIXEL10_60 + PIXEL11_70 + PIXEL12_31 + PIXEL13_31 + PIXEL20_82 + PIXEL21_32 + PIXEL22_0 + PIXEL23_0 + PIXEL30_82 + PIXEL31_32 + PIXEL32_0 + if (Diff(w[6], w[8])) + { + PIXEL33_0 + } + else + { + PIXEL33_20 + } + break; + } + case 250: + { + PIXEL00_80 + PIXEL01_10 + PIXEL02_10 + PIXEL03_80 + PIXEL10_10 + PIXEL11_30 + PIXEL12_30 + PIXEL13_10 + if (Diff(w[8], w[4])) + { + PIXEL20_0 + PIXEL30_0 + PIXEL31_0 + } + else + { + PIXEL20_50 + PIXEL30_50 + PIXEL31_50 + } + PIXEL21_0 + PIXEL22_0 + if (Diff(w[6], w[8])) + { + PIXEL23_0 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL23_50 + PIXEL32_50 + PIXEL33_50 + } + break; + } + case 123: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL10_0 + } + else + { + PIXEL00_50 + PIXEL01_50 + PIXEL10_50 + } + PIXEL02_10 + PIXEL03_80 + PIXEL11_0 + PIXEL12_30 + PIXEL13_10 + if (Diff(w[8], w[4])) + { + PIXEL20_0 + PIXEL30_0 + PIXEL31_0 + } + else + { + PIXEL20_50 + PIXEL30_50 + PIXEL31_50 + } + PIXEL21_0 + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + break; + } + case 95: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL10_0 + } + else + { + PIXEL00_50 + PIXEL01_50 + PIXEL10_50 + } + if (Diff(w[2], w[6])) + { + PIXEL02_0 + PIXEL03_0 + PIXEL13_0 + } + else + { + PIXEL02_50 + PIXEL03_50 + PIXEL13_50 + } + PIXEL11_0 + PIXEL12_0 + PIXEL20_10 + PIXEL21_30 + PIXEL22_30 + PIXEL23_10 + PIXEL30_80 + PIXEL31_10 + PIXEL32_10 + PIXEL33_80 + break; + } + case 222: + { + PIXEL00_80 + PIXEL01_10 + if (Diff(w[2], w[6])) + { + PIXEL02_0 + PIXEL03_0 + PIXEL13_0 + } + else + { + PIXEL02_50 + PIXEL03_50 + PIXEL13_50 + } + PIXEL10_10 + PIXEL11_30 + PIXEL12_0 + PIXEL20_10 + PIXEL21_30 + PIXEL22_0 + if (Diff(w[6], w[8])) + { + PIXEL23_0 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL23_50 + PIXEL32_50 + PIXEL33_50 + } + PIXEL30_80 + PIXEL31_10 + break; + } + case 252: + { + PIXEL00_80 + PIXEL01_61 + PIXEL02_81 + PIXEL03_81 + PIXEL10_10 + PIXEL11_30 + PIXEL12_31 + PIXEL13_31 + if (Diff(w[8], w[4])) + { + PIXEL20_0 + PIXEL30_0 + PIXEL31_0 + } + else + { + PIXEL20_50 + PIXEL30_50 + PIXEL31_50 + } + PIXEL21_0 + PIXEL22_0 + PIXEL23_0 + PIXEL32_0 + if (Diff(w[6], w[8])) + { + PIXEL33_0 + } + else + { + PIXEL33_20 + } + break; + } + case 249: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_61 + PIXEL03_80 + PIXEL10_32 + PIXEL11_32 + PIXEL12_30 + PIXEL13_10 + PIXEL20_0 + PIXEL21_0 + PIXEL22_0 + if (Diff(w[6], w[8])) + { + PIXEL23_0 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL23_50 + PIXEL32_50 + PIXEL33_50 + } + if (Diff(w[8], w[4])) + { + PIXEL30_0 + } + else + { + PIXEL30_20 + } + PIXEL31_0 + break; + } + case 235: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL10_0 + } + else + { + PIXEL00_50 + PIXEL01_50 + PIXEL10_50 + } + PIXEL02_10 + PIXEL03_80 + PIXEL11_0 + PIXEL12_30 + PIXEL13_61 + PIXEL20_0 + PIXEL21_0 + PIXEL22_31 + PIXEL23_81 + if (Diff(w[8], w[4])) + { + PIXEL30_0 + } + else + { + PIXEL30_20 + } + PIXEL31_0 + PIXEL32_31 + PIXEL33_81 + break; + } + case 111: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_0 + PIXEL02_32 + PIXEL03_82 + PIXEL10_0 + PIXEL11_0 + PIXEL12_32 + PIXEL13_82 + if (Diff(w[8], w[4])) + { + PIXEL20_0 + PIXEL30_0 + PIXEL31_0 + } + else + { + PIXEL20_50 + PIXEL30_50 + PIXEL31_50 + } + PIXEL21_0 + PIXEL22_30 + PIXEL23_61 + PIXEL32_10 + PIXEL33_80 + break; + } + case 63: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_0 + if (Diff(w[2], w[6])) + { + PIXEL02_0 + PIXEL03_0 + PIXEL13_0 + } + else + { + PIXEL02_50 + PIXEL03_50 + PIXEL13_50 + } + PIXEL10_0 + PIXEL11_0 + PIXEL12_0 + PIXEL20_31 + PIXEL21_31 + PIXEL22_30 + PIXEL23_10 + PIXEL30_81 + PIXEL31_81 + PIXEL32_61 + PIXEL33_80 + break; + } + case 159: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL10_0 + } + else + { + PIXEL00_50 + PIXEL01_50 + PIXEL10_50 + } + PIXEL02_0 + if (Diff(w[2], w[6])) + { + PIXEL03_0 + } + else + { + PIXEL03_20 + } + PIXEL11_0 + PIXEL12_0 + PIXEL13_0 + PIXEL20_10 + PIXEL21_30 + PIXEL22_32 + PIXEL23_32 + PIXEL30_80 + PIXEL31_61 + PIXEL32_82 + PIXEL33_82 + break; + } + case 215: + { + PIXEL00_81 + PIXEL01_31 + PIXEL02_0 + if (Diff(w[2], w[6])) + { + PIXEL03_0 + } + else + { + PIXEL03_20 + } + PIXEL10_81 + PIXEL11_31 + PIXEL12_0 + PIXEL13_0 + PIXEL20_61 + PIXEL21_30 + PIXEL22_0 + if (Diff(w[6], w[8])) + { + PIXEL23_0 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL23_50 + PIXEL32_50 + PIXEL33_50 + } + PIXEL30_80 + PIXEL31_10 + break; + } + case 246: + { + PIXEL00_80 + PIXEL01_10 + if (Diff(w[2], w[6])) + { + PIXEL02_0 + PIXEL03_0 + PIXEL13_0 + } + else + { + PIXEL02_50 + PIXEL03_50 + PIXEL13_50 + } + PIXEL10_61 + PIXEL11_30 + PIXEL12_0 + PIXEL20_82 + PIXEL21_32 + PIXEL22_0 + PIXEL23_0 + PIXEL30_82 + PIXEL31_32 + PIXEL32_0 + if (Diff(w[6], w[8])) + { + PIXEL33_0 + } + else + { + PIXEL33_20 + } + break; + } + case 254: + { + PIXEL00_80 + PIXEL01_10 + if (Diff(w[2], w[6])) + { + PIXEL02_0 + PIXEL03_0 + PIXEL13_0 + } + else + { + PIXEL02_50 + PIXEL03_50 + PIXEL13_50 + } + PIXEL10_10 + PIXEL11_30 + PIXEL12_0 + if (Diff(w[8], w[4])) + { + PIXEL20_0 + PIXEL30_0 + PIXEL31_0 + } + else + { + PIXEL20_50 + PIXEL30_50 + PIXEL31_50 + } + PIXEL21_0 + PIXEL22_0 + PIXEL23_0 + PIXEL32_0 + if (Diff(w[6], w[8])) + { + PIXEL33_0 + } + else + { + PIXEL33_20 + } + break; + } + case 253: + { + PIXEL00_82 + PIXEL01_82 + PIXEL02_81 + PIXEL03_81 + PIXEL10_32 + PIXEL11_32 + PIXEL12_31 + PIXEL13_31 + PIXEL20_0 + PIXEL21_0 + PIXEL22_0 + PIXEL23_0 + if (Diff(w[8], w[4])) + { + PIXEL30_0 + } + else + { + PIXEL30_20 + } + PIXEL31_0 + PIXEL32_0 + if (Diff(w[6], w[8])) + { + PIXEL33_0 + } + else + { + PIXEL33_20 + } + break; + } + case 251: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL10_0 + } + else + { + PIXEL00_50 + PIXEL01_50 + PIXEL10_50 + } + PIXEL02_10 + PIXEL03_80 + PIXEL11_0 + PIXEL12_30 + PIXEL13_10 + PIXEL20_0 + PIXEL21_0 + PIXEL22_0 + if (Diff(w[6], w[8])) + { + PIXEL23_0 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL23_50 + PIXEL32_50 + PIXEL33_50 + } + if (Diff(w[8], w[4])) + { + PIXEL30_0 + } + else + { + PIXEL30_20 + } + PIXEL31_0 + break; + } + case 239: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_0 + PIXEL02_32 + PIXEL03_82 + PIXEL10_0 + PIXEL11_0 + PIXEL12_32 + PIXEL13_82 + PIXEL20_0 + PIXEL21_0 + PIXEL22_31 + PIXEL23_81 + if (Diff(w[8], w[4])) + { + PIXEL30_0 + } + else + { + PIXEL30_20 + } + PIXEL31_0 + PIXEL32_31 + PIXEL33_81 + break; + } + case 127: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_0 + if (Diff(w[2], w[6])) + { + PIXEL02_0 + PIXEL03_0 + PIXEL13_0 + } + else + { + PIXEL02_50 + PIXEL03_50 + PIXEL13_50 + } + PIXEL10_0 + PIXEL11_0 + PIXEL12_0 + if (Diff(w[8], w[4])) + { + PIXEL20_0 + PIXEL30_0 + PIXEL31_0 + } + else + { + PIXEL20_50 + PIXEL30_50 + PIXEL31_50 + } + PIXEL21_0 + PIXEL22_30 + PIXEL23_10 + PIXEL32_10 + PIXEL33_80 + break; + } + case 191: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_0 + PIXEL02_0 + if (Diff(w[2], w[6])) + { + PIXEL03_0 + } + else + { + PIXEL03_20 + } + PIXEL10_0 + PIXEL11_0 + PIXEL12_0 + PIXEL13_0 + PIXEL20_31 + PIXEL21_31 + PIXEL22_32 + PIXEL23_32 + PIXEL30_81 + PIXEL31_81 + PIXEL32_82 + PIXEL33_82 + break; + } + case 223: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_0 + PIXEL10_0 + } + else + { + PIXEL00_50 + PIXEL01_50 + PIXEL10_50 + } + PIXEL02_0 + if (Diff(w[2], w[6])) + { + PIXEL03_0 + } + else + { + PIXEL03_20 + } + PIXEL11_0 + PIXEL12_0 + PIXEL13_0 + PIXEL20_10 + PIXEL21_30 + PIXEL22_0 + if (Diff(w[6], w[8])) + { + PIXEL23_0 + PIXEL32_0 + PIXEL33_0 + } + else + { + PIXEL23_50 + PIXEL32_50 + PIXEL33_50 + } + PIXEL30_80 + PIXEL31_10 + break; + } + case 247: + { + PIXEL00_81 + PIXEL01_31 + PIXEL02_0 + if (Diff(w[2], w[6])) + { + PIXEL03_0 + } + else + { + PIXEL03_20 + } + PIXEL10_81 + PIXEL11_31 + PIXEL12_0 + PIXEL13_0 + PIXEL20_82 + PIXEL21_32 + PIXEL22_0 + PIXEL23_0 + PIXEL30_82 + PIXEL31_32 + PIXEL32_0 + if (Diff(w[6], w[8])) + { + PIXEL33_0 + } + else + { + PIXEL33_20 + } + break; + } + case 255: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_0 + PIXEL02_0 + if (Diff(w[2], w[6])) + { + PIXEL03_0 + } + else + { + PIXEL03_20 + } + PIXEL10_0 + PIXEL11_0 + PIXEL12_0 + PIXEL13_0 + PIXEL20_0 + PIXEL21_0 + PIXEL22_0 + PIXEL23_0 + if (Diff(w[8], w[4])) + { + PIXEL30_0 + } + else + { + PIXEL30_20 + } + PIXEL31_0 + PIXEL32_0 + if (Diff(w[6], w[8])) + { + PIXEL33_0 + } + else + { + PIXEL33_20 + } + break; + } + } + pIn++; // next source pixel (just increment since it's an int*) + pOut += 16; // skip 4 pixels (4 bytes * 4 pixels) + } + pOut += BpL; // skip next 3 rows + pOut += BpL; + pOut += BpL; + } + hq_vec::reset(); +} + +void DLL InitLUTs() +{ + int i, j, k, r, g, b, Y, u, v; + +#if 0 // colorOutlines() after hqresize + for (i=0; i<65536; i++) + LUT16to32[i] = 0x00404040; + for (i=0; i<65536; i++) + LUT16to32[i+65536] = 0xFF000000 + ((i & 0xF800) << 8) + ((i & 0x07E0) << 5) + ((i & 0x001F) << 3); +#else // colorOutlines() before hqresize + for (i=0; i<65536; i++) + LUT16to32[i] = ((i & 0xF800) << 8) + ((i & 0x07E0) << 5) + ((i & 0x001F) << 3); + for (i=0; i<65536; i++) + LUT16to32[i+65536] = 0xFF000000 + LUT16to32[i]; +#endif + + for (i=0; i<65536; i++) + RGBtoYUV[i] = 0xFF000000; + + for (i=0; i<32; i++) + for (j=0; j<64; j++) + for (k=0; k<32; k++) + { + r = i << 3; + g = j << 2; + b = k << 3; + Y = (r + g + b) >> 2; + u = 128 + ((r - b) >> 2); + v = 128 + ((-r + 2*g -b)>>3); + RGBtoYUV[ 65536 + (i << 11) + (j << 5) + k ] = (Y<<16) + (u<<8) + v; + } +} + +/* +int DLL hq4x_32 ( CImage &ImageIn, CImage &ImageOut ) +{ + if ( ImageIn.Convert32To17() != 0 ) + { + printf( "ERROR: conversion to 17 bit failed\n" ); + return 1; + } + + if ( ImageOut.Init( ImageIn.m_Xres*4, ImageIn.m_Yres*4, 32 ) != 0 ) + { + printf( "ERROR: ImageOut.Init()\n" ); + return 1; + }; + + InitLUTs(); + hq4x_32( (int*)ImageIn.m_pBitmap, ImageOut.m_pBitmap, ImageIn.m_Xres, ImageIn.m_Yres, ImageOut.m_Xres*4 ); + + printf( "\nOK\n" ); + return 0; +} +*/ + +} \ No newline at end of file diff --git a/source/common/textures/hires/hqnx_asm/hqnx_asm.h b/source/common/textures/hires/hqnx_asm/hqnx_asm.h new file mode 100644 index 000000000..9ced349fa --- /dev/null +++ b/source/common/textures/hires/hqnx_asm/hqnx_asm.h @@ -0,0 +1,234 @@ +//hqnx filter library +//---------------------------------------------------------- +//Copyright (C) 2003 MaxSt ( maxst@hiend3d.com ) +//Copyright (C) 2009 Benjamin Berkels +//Copyright (C) 2012-2014 Alexey Lysiuk +// +//This program is free software; you can redistribute it and/or +//modify it under the terms of the GNU Lesser General Public +//License as published by the Free Software Foundation; either +//version 2.1 of the License, or (at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +//Lesser General Public License for more details. +// +//You should have received a copy of the GNU Lesser General Public +//License along with this program; if not, write to the Free Software +//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#ifndef __HQNX_H__ +#define __HQNX_H__ + +#ifdef _MSC_VER +#pragma warning(disable:4799) +#endif // _MSC_VER + +#include "hqnx_asm_Image.h" + +// IMPORTANT NOTE! +// The following is not a generic vectorized math class +// Each member function or overloaded operator does specific task to simplify client code +// To re-implement this class for different platform you need check very carefully +// the Intel C++ Intrinsic Reference at http://software.intel.com/file/18072/ + +#if defined _MSC_VER && defined _M_X64 + +// Implementation via SSE2 intrinsics +// MSVC doesn't support MMX intrinsics on x64 + +#include + +class hq_vec +{ +public: + hq_vec(const int value) + : m_value(_mm_cvtsi32_si128(value)) + { + } + + static hq_vec load(const int source) + { + return _mm_unpacklo_epi8(_mm_cvtsi32_si128(source), _mm_cvtsi32_si128(0)); + } + + static hq_vec expand(const short source) + { + return _mm_set_epi16(source, source, source, source, source, source, source, source); + } + + void store(unsigned char* const destination) const + { + *reinterpret_cast(destination) = _mm_cvtsi128_si32(_mm_packus_epi16(m_value, _mm_cvtsi32_si128(0))); + } + + static void reset() + { + } + + hq_vec& operator+=(const hq_vec& right) + { + m_value = _mm_add_epi16(m_value, right.m_value); + return *this; + } + + hq_vec& operator*=(const hq_vec& right) + { + m_value = _mm_mullo_epi16(m_value, right.m_value); + return *this; + } + + hq_vec& operator<<(const int count) + { + m_value = _mm_sll_epi16(m_value, _mm_cvtsi32_si128(count)); + return *this; + } + + hq_vec& operator>>(const int count) + { + m_value = _mm_srl_epi16(m_value, _mm_cvtsi32_si128(count)); + return *this; + } + +private: + __m128i m_value; + + hq_vec(const __m128i value) + : m_value(value) + { + } + + friend hq_vec operator- (const hq_vec&, const hq_vec&); + friend hq_vec operator* (const hq_vec&, const hq_vec&); + friend hq_vec operator| (const hq_vec&, const hq_vec&); + friend bool operator!=(const int, const hq_vec&); +}; + +inline hq_vec operator-(const hq_vec& left, const hq_vec& right) +{ + return _mm_subs_epu8(left.m_value, right.m_value); +} + +inline hq_vec operator*(const hq_vec& left, const hq_vec& right) +{ + return _mm_mullo_epi16(left.m_value, right.m_value); +} + +inline hq_vec operator|(const hq_vec& left, const hq_vec& right) +{ + return _mm_or_si128(left.m_value, right.m_value); +} + +inline bool operator!=(const int left, const hq_vec& right) +{ + return left != _mm_cvtsi128_si32(right.m_value); +} + +#else // _M_X64 + +// Implementation via MMX intrinsics + +#include + +class hq_vec +{ +public: + hq_vec(const int value) + : m_value(_mm_cvtsi32_si64(value)) + { + } + + static hq_vec load(const int source) + { + return _mm_unpacklo_pi8(_mm_cvtsi32_si64(source), _mm_cvtsi32_si64(0)); + } + + static hq_vec expand(const short source) + { + return _mm_set_pi16(source, source, source, source); + } + + void store(unsigned char* const destination) const + { + *reinterpret_cast(destination) = _mm_cvtsi64_si32(_mm_packs_pu16(m_value, _mm_cvtsi32_si64(0))); + } + + static void reset() + { + _mm_empty(); + } + + hq_vec& operator+=(const hq_vec& right) + { + m_value = _mm_add_pi16(m_value, right.m_value); + return *this; + } + + hq_vec& operator*=(const hq_vec& right) + { + m_value = _mm_mullo_pi16(m_value, right.m_value); + return *this; + } + + hq_vec& operator<<(const int count) + { + m_value = _mm_sll_pi16(m_value, _mm_cvtsi32_si64(count)); + return *this; + } + + hq_vec& operator>>(const int count) + { + m_value = _mm_srl_pi16(m_value, _mm_cvtsi32_si64(count)); + return *this; + } + +private: + __m64 m_value; + + hq_vec(const __m64 value) + : m_value(value) + { + } + + friend hq_vec operator- (const hq_vec&, const hq_vec&); + friend hq_vec operator* (const hq_vec&, const hq_vec&); + friend hq_vec operator| (const hq_vec&, const hq_vec&); + friend bool operator!=(const int, const hq_vec&); +}; + +inline hq_vec operator-(const hq_vec& left, const hq_vec& right) +{ + return _mm_subs_pu8(left.m_value, right.m_value); +} + +inline hq_vec operator*(const hq_vec& left, const hq_vec& right) +{ + return _mm_mullo_pi16(left.m_value, right.m_value); +} + +inline hq_vec operator|(const hq_vec& left, const hq_vec& right) +{ + return _mm_or_si64(left.m_value, right.m_value); +} + +inline bool operator!=(const int left, const hq_vec& right) +{ + return left != _mm_cvtsi64_si32(right.m_value); +} + +#endif // _MSC_VER && _M_X64 + +namespace HQnX_asm +{ +void DLL hq2x_32( int * pIn, unsigned char * pOut, int Xres, int Yres, int BpL ); +void DLL hq3x_32( int * pIn, unsigned char * pOut, int Xres, int Yres, int BpL ); +void DLL hq4x_32( int * pIn, unsigned char * pOut, int Xres, int Yres, int BpL ); +int DLL hq4x_32 ( CImage &ImageIn, CImage &ImageOut ); + +void DLL InitLUTs(); + +} + + +#endif //__HQNX_H__ \ No newline at end of file diff --git a/source/common/textures/hires/hqnx_asm/hqnx_asm_Image.cpp b/source/common/textures/hires/hqnx_asm/hqnx_asm_Image.cpp new file mode 100644 index 000000000..31adda874 --- /dev/null +++ b/source/common/textures/hires/hqnx_asm/hqnx_asm_Image.cpp @@ -0,0 +1,111 @@ +//CImage class - loading and saving BMP and TGA files +//---------------------------------------------------------- +//Copyright (C) 2003 MaxSt ( maxst@hiend3d.com ) +// +//This program is free software; you can redistribute it and/or +//modify it under the terms of the GNU Lesser General Public +//License as published by the Free Software Foundation; either +//version 2.1 of the License, or (at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +//Lesser General Public License for more details. +// +//You should have received a copy of the GNU Lesser General Public +//License along with this program; if not, write to the Free Software +//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#include +#include +#include "hqnx_asm_Image.h" + +#ifndef _MSC_VER +#define _stricmp strcasecmp +#endif + +namespace HQnX_asm +{ + +DLL CImage::CImage() +{ + m_Xres = m_Yres = m_NumPixel = 0; + m_pBitmap = NULL; +} + +DLL CImage::~CImage() +{ + Destroy(); +} + +int DLL CImage::Init( int X, int Y, unsigned short BitPerPixel ) +{ + if (m_pBitmap != NULL) + free(m_pBitmap); + + m_Xres = X; + m_Yres = Y; + m_BitPerPixel = BitPerPixel<=8 ? 8 : BitPerPixel<=16 ? 16 : BitPerPixel<=24 ? 24 : 32; + m_BytePerPixel = m_BitPerPixel >> 3; + m_NumPixel = m_Xres*m_Yres; + int size = m_NumPixel*((m_BitPerPixel+7)/8); + m_pBitmap=(unsigned char *)malloc(size); + return (m_pBitmap != NULL) ? 0 : 1; +} + +int DLL CImage::SetImage(unsigned char *img, int width, int height, int bpp) +{ + Init(width, height, bpp); + + memcpy(m_pBitmap, img, m_NumPixel * m_BytePerPixel); + + return 0; +} + +int DLL CImage::Destroy() +{ + if (m_pBitmap) + { + free(m_pBitmap); + m_pBitmap = NULL; + } + m_Xres = 0; + m_Yres = 0; + m_NumPixel = 0; + m_BitPerPixel = 0; + return 0; +} + +int DLL CImage::Convert32To17( void ) +{ + int nRes = eConvUnknownFormat; + + if ( m_BitPerPixel == 32 ) + { + if ( m_pBitmap != NULL ) + { + unsigned char * pTemp8 = m_pBitmap; + unsigned int * pTemp32 = (unsigned int *)m_pBitmap; + unsigned int a, r, g, b; + for ( int i=0; i> 3; + g = (*(pTemp8++)) >> 2; + r = (*(pTemp8++)) >> 3; + a = *(pTemp8++); + *pTemp32 = (r << 11) + (g << 5) + b + (a > 127 ? 0x10000 : 0); + pTemp32++; + } + } + else + nRes = eConvSourceMemory; + + nRes = 0; + } + + return nRes; +} + + + +} \ No newline at end of file diff --git a/source/common/textures/hires/hqnx_asm/hqnx_asm_Image.h b/source/common/textures/hires/hqnx_asm/hqnx_asm_Image.h new file mode 100644 index 000000000..918b904fe --- /dev/null +++ b/source/common/textures/hires/hqnx_asm/hqnx_asm_Image.h @@ -0,0 +1,77 @@ +//CImage class - loading and saving BMP and TGA files +//---------------------------------------------------------- +//Copyright (C) 2003 MaxSt ( maxst@hiend3d.com ) +// +//This program is free software; you can redistribute it and/or +//modify it under the terms of the GNU Lesser General Public +//License as published by the Free Software Foundation; either +//version 2.1 of the License, or (at your option) any later version. +// +//This program is distributed in the hope that it will be useful, +//but WITHOUT ANY WARRANTY; without even the implied warranty of +//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +//Lesser General Public License for more details. +// +//You should have received a copy of the GNU Lesser General Public +//License along with this program; if not, write to the Free Software +//Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +//#ifdef WIN32 +//#define DLL __declspec(dllexport) +//#else +#define DLL +//#endif + +#include +#pragma once +#ifdef _MSC_VER +#pragma warning(disable: 4103) +#endif // _MSC_VER +#pragma pack(1) + +namespace HQnX_asm +{ + +typedef struct { unsigned char b, g, r; } _BGR; +typedef struct { unsigned char b, g, r, a; } _BGRA; + +class CImage +{ + public: + DLL CImage(); + DLL ~CImage(); + + enum CImageErrors + { + eConvUnknownFormat = 10, + eConvSourceMemory = 11, + eConvDestMemory = 12, + + }; + + + public: + int DLL Init( int Xres, int Yres, unsigned short BitPerPixel ); + int DLL SetImage(unsigned char *img, int width, int height, int bpp); + int DLL Destroy(); + int DLL Convert32To17( void ); + + private: + + public: + int m_Xres, m_Yres; + unsigned short m_BitPerPixel; + unsigned short m_BytePerPixel; + unsigned char * m_pBitmap; + _BGR m_Pal[256]; + + private: + int m_NumPixel; + FILE * f; + int m_nCount; + char m_cBuf[32768]; +}; + +#pragma pack() + +} \ No newline at end of file diff --git a/source/common/textures/hires/hqresize.cpp b/source/common/textures/hires/hqresize.cpp new file mode 100644 index 000000000..e04be8262 --- /dev/null +++ b/source/common/textures/hires/hqresize.cpp @@ -0,0 +1,511 @@ +/* +** gl_hqresize.cpp +** Contains high quality upsampling functions. +** So far Scale2x/3x/4x as described in http://scale2x.sourceforge.net/ +** are implemented. +** +**--------------------------------------------------------------------------- +** Copyright 2008 Benjamin Berkels +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +*/ + +#include "c_cvars.h" +#include "hqnx/hqx.h" +#ifdef HAVE_MMX +#include "hqnx_asm/hqnx_asm.h" +#endif +#include "xbr/xbrz.h" +#include "xbr/xbrz_old.h" +#include "parallel_for.h" +#include "textures.h" +#include "texturemanager.h" +#include "printf.h" + +EXTERN_CVAR(Int, gl_texture_hqresizemult) +CUSTOM_CVAR(Int, gl_texture_hqresizemode, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) +{ + if (self < 0 || self > 6) + self = 0; + if ((gl_texture_hqresizemult > 4) && (self < 4) && (self > 0)) + gl_texture_hqresizemult = 4; + TexMan.FlushAll(); +} + +CUSTOM_CVAR(Int, gl_texture_hqresizemult, 1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) +{ + if (self < 1 || self > 6) + self = 1; + if ((self > 4) && (gl_texture_hqresizemode < 4) && (gl_texture_hqresizemode > 0)) + self = 4; + TexMan.FlushAll(); +} + +CUSTOM_CVAR(Int, gl_texture_hqresize_maxinputsize, 512, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) +{ + if (self > 1024) self = 1024; + TexMan.FlushAll(); +} + +CUSTOM_CVAR(Int, gl_texture_hqresize_targets, 7, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) +{ + TexMan.FlushAll(); +} + +CVAR (Flag, gl_texture_hqresize_textures, gl_texture_hqresize_targets, 1); +CVAR (Flag, gl_texture_hqresize_sprites, gl_texture_hqresize_targets, 2); +CVAR (Flag, gl_texture_hqresize_fonts, gl_texture_hqresize_targets, 4); + +CVAR(Bool, gl_texture_hqresize_multithread, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG); + +CUSTOM_CVAR(Int, gl_texture_hqresize_mt_width, 16, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (self < 2) self = 2; + if (self > 1024) self = 1024; +} + +CUSTOM_CVAR(Int, gl_texture_hqresize_mt_height, 4, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (self < 2) self = 2; + if (self > 1024) self = 1024; +} + +CVAR(Int, xbrz_colorformat, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) + +static void xbrzApplyOptions() +{ + if (gl_texture_hqresizemult != 0 && (gl_texture_hqresizemode == 4 || gl_texture_hqresizemode == 5)) + { + if (xbrz_colorformat == 0) + { + Printf("Changing xBRZ options requires a restart when buffered color format is used.\n" + "To avoid this at cost of scaling performance, set xbrz_colorformat CVAR to non-zero value."); + } + else + { + TexMan.FlushAll(); + } + } +} + +#define XBRZ_CVAR(NAME, VALUE) \ + CUSTOM_CVAR(Float, xbrz_##NAME, VALUE, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_NOINITCALL) { xbrzApplyOptions(); } + +XBRZ_CVAR(luminanceweight, 1.f) +XBRZ_CVAR(equalcolortolerance, 30.f) +XBRZ_CVAR(centerdirectionbias, 4.f) +XBRZ_CVAR(dominantdirectionthreshold, 3.6f) +XBRZ_CVAR(steepdirectionthreshold, 2.2f) + +#undef XBRZ_CVAR + +static void scale2x ( uint32_t* inputBuffer, uint32_t* outputBuffer, int inWidth, int inHeight ) +{ + const int width = 2* inWidth; + const int height = 2 * inHeight; + + for ( int i = 0; i < inWidth; ++i ) + { + const int iMinus = (i > 0) ? (i-1) : 0; + const int iPlus = (i < inWidth - 1 ) ? (i+1) : i; + for ( int j = 0; j < inHeight; ++j ) + { + const int jMinus = (j > 0) ? (j-1) : 0; + const int jPlus = (j < inHeight - 1 ) ? (j+1) : j; + const uint32_t A = inputBuffer[ iMinus +inWidth*jMinus]; + const uint32_t B = inputBuffer[ iMinus +inWidth*j ]; + const uint32_t C = inputBuffer[ iMinus +inWidth*jPlus]; + const uint32_t D = inputBuffer[ i +inWidth*jMinus]; + const uint32_t E = inputBuffer[ i +inWidth*j ]; + const uint32_t F = inputBuffer[ i +inWidth*jPlus]; + const uint32_t G = inputBuffer[ iPlus +inWidth*jMinus]; + const uint32_t H = inputBuffer[ iPlus +inWidth*j ]; + const uint32_t I = inputBuffer[ iPlus +inWidth*jPlus]; + if (B != H && D != F) { + outputBuffer[2*i + width*2*j ] = D == B ? D : E; + outputBuffer[2*i + width*(2*j+1)] = B == F ? F : E; + outputBuffer[2*i+1 + width*2*j ] = D == H ? D : E; + outputBuffer[2*i+1 + width*(2*j+1)] = H == F ? F : E; + } else { + outputBuffer[2*i + width*2*j ] = E; + outputBuffer[2*i + width*(2*j+1)] = E; + outputBuffer[2*i+1 + width*2*j ] = E; + outputBuffer[2*i+1 + width*(2*j+1)] = E; + } + } + } +} + +static void scale3x ( uint32_t* inputBuffer, uint32_t* outputBuffer, int inWidth, int inHeight ) +{ + const int width = 3* inWidth; + const int height = 3 * inHeight; + + for ( int i = 0; i < inWidth; ++i ) + { + const int iMinus = (i > 0) ? (i-1) : 0; + const int iPlus = (i < inWidth - 1 ) ? (i+1) : i; + for ( int j = 0; j < inHeight; ++j ) + { + const int jMinus = (j > 0) ? (j-1) : 0; + const int jPlus = (j < inHeight - 1 ) ? (j+1) : j; + const uint32_t A = inputBuffer[ iMinus +inWidth*jMinus]; + const uint32_t B = inputBuffer[ iMinus +inWidth*j ]; + const uint32_t C = inputBuffer[ iMinus +inWidth*jPlus]; + const uint32_t D = inputBuffer[ i +inWidth*jMinus]; + const uint32_t E = inputBuffer[ i +inWidth*j ]; + const uint32_t F = inputBuffer[ i +inWidth*jPlus]; + const uint32_t G = inputBuffer[ iPlus +inWidth*jMinus]; + const uint32_t H = inputBuffer[ iPlus +inWidth*j ]; + const uint32_t I = inputBuffer[ iPlus +inWidth*jPlus]; + if (B != H && D != F) { + outputBuffer[3*i + width*3*j ] = D == B ? D : E; + outputBuffer[3*i + width*(3*j+1)] = (D == B && E != C) || (B == F && E != A) ? B : E; + outputBuffer[3*i + width*(3*j+2)] = B == F ? F : E; + outputBuffer[3*i+1 + width*3*j ] = (D == B && E != G) || (D == H && E != A) ? D : E; + outputBuffer[3*i+1 + width*(3*j+1)] = E; + outputBuffer[3*i+1 + width*(3*j+2)] = (B == F && E != I) || (H == F && E != C) ? F : E; + outputBuffer[3*i+2 + width*3*j ] = D == H ? D : E; + outputBuffer[3*i+2 + width*(3*j+1)] = (D == H && E != I) || (H == F && E != G) ? H : E; + outputBuffer[3*i+2 + width*(3*j+2)] = H == F ? F : E; + } else { + outputBuffer[3*i + width*3*j ] = E; + outputBuffer[3*i + width*(3*j+1)] = E; + outputBuffer[3*i + width*(3*j+2)] = E; + outputBuffer[3*i+1 + width*3*j ] = E; + outputBuffer[3*i+1 + width*(3*j+1)] = E; + outputBuffer[3*i+1 + width*(3*j+2)] = E; + outputBuffer[3*i+2 + width*3*j ] = E; + outputBuffer[3*i+2 + width*(3*j+1)] = E; + outputBuffer[3*i+2 + width*(3*j+2)] = E; + } + } + } +} + +static void scale4x ( uint32_t* inputBuffer, uint32_t* outputBuffer, int inWidth, int inHeight ) +{ + int width = 2* inWidth; + int height = 2 * inHeight; + uint32_t * buffer2x = new uint32_t[width*height]; + + scale2x ( reinterpret_cast ( inputBuffer ), reinterpret_cast ( buffer2x ), inWidth, inHeight ); + width *= 2; + height *= 2; + scale2x ( reinterpret_cast ( buffer2x ), reinterpret_cast ( outputBuffer ), 2*inWidth, 2*inHeight ); + delete[] buffer2x; +} + +static unsigned char *scaleNxHelper( void (*scaleNxFunction) ( uint32_t* , uint32_t* , int , int), + const int N, + unsigned char *inputBuffer, + const int inWidth, + const int inHeight, + int &outWidth, + int &outHeight ) +{ + outWidth = N * inWidth; + outHeight = N *inHeight; + unsigned char * newBuffer = new unsigned char[outWidth*outHeight*4]; + + scaleNxFunction ( reinterpret_cast ( inputBuffer ), reinterpret_cast ( newBuffer ), inWidth, inHeight ); + delete[] inputBuffer; + return newBuffer; +} + +static unsigned char *normalNx(const int N, + unsigned char *inputBuffer, + const int inWidth, + const int inHeight, + int &outWidth, + int &outHeight ) +{ + outWidth = N * inWidth; + outHeight = N *inHeight; + unsigned char * newBuffer = new unsigned char[outWidth*outHeight*4]; + + uint32_t *const inBuffer = reinterpret_cast(inputBuffer); + uint32_t *const outBuffer = reinterpret_cast(newBuffer); + + for (int y = 0; y < inHeight; ++y) + { + const int inRowPos = inWidth * y; + const int outRowPos = outWidth * N * y; + + for (int x = 0; x < inWidth; ++x) + { + std::fill_n(&outBuffer[outRowPos + N * x], N, inBuffer[inRowPos + x]); + } + + for (int c = 1; c < N; ++c) + { + std::copy_n(&outBuffer[outRowPos], outWidth, &outBuffer[outRowPos + outWidth * c]); + } + } + + delete[] inputBuffer; + return newBuffer; +} + +#ifdef HAVE_MMX +static unsigned char *hqNxAsmHelper( void (*hqNxFunction) ( int*, unsigned char*, int, int, int ), + const int N, + unsigned char *inputBuffer, + const int inWidth, + const int inHeight, + int &outWidth, + int &outHeight ) +{ + outWidth = N * inWidth; + outHeight = N *inHeight; + + static int initdone = false; + + if (!initdone) + { + HQnX_asm::InitLUTs(); + initdone = true; + } + + HQnX_asm::CImage cImageIn; + cImageIn.SetImage(inputBuffer, inWidth, inHeight, 32); + cImageIn.Convert32To17(); + + unsigned char * newBuffer = new unsigned char[outWidth*outHeight*4]; + hqNxFunction( reinterpret_cast(cImageIn.m_pBitmap), newBuffer, cImageIn.m_Xres, cImageIn.m_Yres, outWidth*4 ); + delete[] inputBuffer; + return newBuffer; +} +#endif + +static unsigned char *hqNxHelper( void (HQX_CALLCONV *hqNxFunction) ( unsigned*, unsigned*, int, int ), + const int N, + unsigned char *inputBuffer, + const int inWidth, + const int inHeight, + int &outWidth, + int &outHeight ) +{ + static int initdone = false; + + if (!initdone) + { + hqxInit(); + initdone = true; + } + outWidth = N * inWidth; + outHeight = N *inHeight; + + unsigned char * newBuffer = new unsigned char[outWidth*outHeight*4]; + hqNxFunction( reinterpret_cast(inputBuffer), reinterpret_cast(newBuffer), inWidth, inHeight ); + delete[] inputBuffer; + return newBuffer; +} + + +template +void xbrzSetupConfig(ConfigType& cfg); + +template <> +void xbrzSetupConfig(xbrz::ScalerCfg& cfg) +{ + cfg.luminanceWeight = xbrz_luminanceweight; + cfg.equalColorTolerance = xbrz_equalcolortolerance; + cfg.centerDirectionBias = xbrz_centerdirectionbias; + cfg.dominantDirectionThreshold = xbrz_dominantdirectionthreshold; + cfg.steepDirectionThreshold = xbrz_steepdirectionthreshold; +} + +template <> +void xbrzSetupConfig(xbrz_old::ScalerCfg& cfg) +{ + cfg.luminanceWeight_ = xbrz_luminanceweight; + cfg.equalColorTolerance_ = xbrz_equalcolortolerance; + cfg.dominantDirectionThreshold = xbrz_dominantdirectionthreshold; + cfg.steepDirectionThreshold = xbrz_steepdirectionthreshold; +} + +template +static unsigned char *xbrzHelper( void (*xbrzFunction) ( size_t, const uint32_t*, uint32_t*, int, int, xbrz::ColorFormat, const ConfigType&, int, int ), + const int N, + unsigned char *inputBuffer, + const int inWidth, + const int inHeight, + int &outWidth, + int &outHeight ) +{ + outWidth = N * inWidth; + outHeight = N *inHeight; + + unsigned char * newBuffer = new unsigned char[outWidth*outHeight*4]; + + const int thresholdWidth = gl_texture_hqresize_mt_width; + const int thresholdHeight = gl_texture_hqresize_mt_height; + + ConfigType cfg; + xbrzSetupConfig(cfg); + + const xbrz::ColorFormat colorFormat = xbrz_colorformat == 0 + ? xbrz::ColorFormat::ARGB + : xbrz::ColorFormat::ARGB_UNBUFFERED; + + if (gl_texture_hqresize_multithread + && inWidth > thresholdWidth + && inHeight > thresholdHeight) + { + parallel_for(inHeight, thresholdHeight, [=, &cfg](int sliceY) + { + xbrzFunction(N, reinterpret_cast(inputBuffer), reinterpret_cast(newBuffer), + inWidth, inHeight, colorFormat, cfg, sliceY, sliceY + thresholdHeight); + }); + } + else + { + xbrzFunction(N, reinterpret_cast(inputBuffer), reinterpret_cast(newBuffer), + inWidth, inHeight, colorFormat, cfg, 0, std::numeric_limits::max()); + } + + delete[] inputBuffer; + return newBuffer; +} + +static void xbrzOldScale(size_t factor, const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, xbrz::ColorFormat colFmt, const xbrz_old::ScalerCfg& cfg, int yFirst, int yLast) +{ + xbrz_old::scale(factor, src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); +} + + +//=========================================================================== +// +// [BB] Upsamples the texture in texbuffer.mBuffer, frees texbuffer.mBuffer and returns +// the upsampled buffer. +// +//=========================================================================== +void FTexture::CreateUpsampledTextureBuffer(FTextureBuffer &texbuffer, bool hasAlpha, bool checkonly) +{ + // [BB] Make sure that inWidth and inHeight denote the size of + // the returned buffer even if we don't upsample the input buffer. + int inWidth = texbuffer.mWidth; + int inHeight = texbuffer.mHeight; + + // [BB] Don't resample if width * height of the input texture is bigger than gl_texture_hqresize_maxinputsize squared. + const int maxInputSize = gl_texture_hqresize_maxinputsize; + if (inWidth * inHeight > maxInputSize * maxInputSize) + return; + + // [BB] Don't try to upsample textures based off FCanvasTexture. (This should never get here in the first place!) + if (bHasCanvas) + return; + + // already scaled? + if (Scale.X >= 2 && Scale.Y >= 2) + return; + + switch (UseType) + { + case ETextureType::Sprite: + case ETextureType::SkinSprite: + if (!(gl_texture_hqresize_targets & 2)) return; + break; + + case ETextureType::FontChar: + if (!(gl_texture_hqresize_targets & 4)) return; + break; + + default: + if (!(gl_texture_hqresize_targets & 1)) return; + break; + } + + int type = gl_texture_hqresizemode; + int mult = gl_texture_hqresizemult; +#ifdef HAVE_MMX + // hqNx MMX does not preserve the alpha channel so fall back to C-version for such textures + if (hasAlpha && type == 3) + { + type = 2; + } +#endif + // These checks are to ensure consistency of the content ID. + if (mult < 2 || mult > 6 || type < 1 || type > 6) return; + if (type < 4 && mult > 4) mult = 4; + + if (!checkonly) + { + if (type == 1) + { + if (mult == 2) + texbuffer.mBuffer = scaleNxHelper(&scale2x, 2, texbuffer.mBuffer, inWidth, inHeight, texbuffer.mWidth, texbuffer.mHeight); + else if (mult == 3) + texbuffer.mBuffer = scaleNxHelper(&scale3x, 3, texbuffer.mBuffer, inWidth, inHeight, texbuffer.mWidth, texbuffer.mHeight); + else if (mult == 4) + texbuffer.mBuffer = scaleNxHelper(&scale4x, 4, texbuffer.mBuffer, inWidth, inHeight, texbuffer.mWidth, texbuffer.mHeight); + else return; + } + else if (type == 2) + { + if (mult == 2) + texbuffer.mBuffer = hqNxHelper(&hq2x_32, 2, texbuffer.mBuffer, inWidth, inHeight, texbuffer.mWidth, texbuffer.mHeight); + else if (mult == 3) + texbuffer.mBuffer = hqNxHelper(&hq3x_32, 3, texbuffer.mBuffer, inWidth, inHeight, texbuffer.mWidth, texbuffer.mHeight); + else if (mult == 4) + texbuffer.mBuffer = hqNxHelper(&hq4x_32, 4, texbuffer.mBuffer, inWidth, inHeight, texbuffer.mWidth, texbuffer.mHeight); + else return; + } +#ifdef HAVE_MMX + else if (type == 3) + { + if (mult == 2) + texbuffer.mBuffer = hqNxAsmHelper(&HQnX_asm::hq2x_32, 2, texbuffer.mBuffer, inWidth, inHeight, texbuffer.mWidth, texbuffer.mHeight); + else if (mult == 3) + texbuffer.mBuffer = hqNxAsmHelper(&HQnX_asm::hq3x_32, 3, texbuffer.mBuffer, inWidth, inHeight, texbuffer.mWidth, texbuffer.mHeight); + else if (mult == 4) + texbuffer.mBuffer = hqNxAsmHelper(&HQnX_asm::hq4x_32, 4, texbuffer.mBuffer, inWidth, inHeight, texbuffer.mWidth, texbuffer.mHeight); + else return; + } +#endif + else if (type == 4) + texbuffer.mBuffer = xbrzHelper(xbrz::scale, mult, texbuffer.mBuffer, inWidth, inHeight, texbuffer.mWidth, texbuffer.mHeight); + else if (type == 5) + texbuffer.mBuffer = xbrzHelper(xbrzOldScale, mult, texbuffer.mBuffer, inWidth, inHeight, texbuffer.mWidth, texbuffer.mHeight); + else if (type == 6) + texbuffer.mBuffer = normalNx(mult, texbuffer.mBuffer, inWidth, inHeight, texbuffer.mWidth, texbuffer.mHeight); + else + return; + } + else + { + texbuffer.mWidth *= mult; + texbuffer.mHeight *= mult; + } + // Encode the scaling method in the content ID. + FContentIdBuilder contentId; + contentId.id = texbuffer.mContentId; + contentId.scaler = type; + contentId.scalefactor = mult; + texbuffer.mContentId = contentId.id; +} diff --git a/source/common/textures/hires/xbr/xbrz.cpp b/source/common/textures/hires/xbr/xbrz.cpp new file mode 100644 index 000000000..cd0b7b030 --- /dev/null +++ b/source/common/textures/hires/xbr/xbrz.cpp @@ -0,0 +1,1364 @@ +// **************************************************************************** +// * This file is part of the xBRZ project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// * * +// * Additionally and as a special exception, the author gives permission * +// * to link the code of this program with the following libraries * +// * (or with modified versions that use the same licenses), and distribute * +// * linked combinations including the two: MAME, FreeFileSync, Snes9x, ePSXe * +// * You must obey the GNU General Public License in all respects for all of * +// * the code used other than MAME, FreeFileSync, Snes9x, ePSXe. * +// * If you modify this file, you may extend this exception to your version * +// * of the file, but you are not obligated to do so. If you do not wish to * +// * do so, delete this exception statement from your version. * +// **************************************************************************** + +#include "xbrz.h" +#include +#include +#include +#include //std::sqrt +#include "xbrz_tools.h" + +using namespace xbrz; + + +namespace +{ +template inline +uint32_t gradientRGB(uint32_t pixFront, uint32_t pixBack) //blend front color with opacity M / N over opaque background: https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending +{ + static_assert(0 < M && M < N && N <= 1000, ""); + + auto calcColor = [](unsigned char colFront, unsigned char colBack) -> unsigned char { return (colFront * M + colBack * (N - M)) / N; }; + + return makePixel(calcColor(getRed (pixFront), getRed (pixBack)), + calcColor(getGreen(pixFront), getGreen(pixBack)), + calcColor(getBlue (pixFront), getBlue (pixBack))); +} + + +template inline +uint32_t gradientARGB(uint32_t pixFront, uint32_t pixBack) //find intermediate color between two colors with alpha channels (=> NO alpha blending!!!) +{ + static_assert(0 < M && M < N && N <= 1000, ""); + + const unsigned int weightFront = getAlpha(pixFront) * M; + const unsigned int weightBack = getAlpha(pixBack) * (N - M); + const unsigned int weightSum = weightFront + weightBack; + if (weightSum == 0) + return 0; + + auto calcColor = [=](unsigned char colFront, unsigned char colBack) + { + return static_cast((colFront * weightFront + colBack * weightBack) / weightSum); + }; + + return makePixel(static_cast(weightSum / N), + calcColor(getRed (pixFront), getRed (pixBack)), + calcColor(getGreen(pixFront), getGreen(pixBack)), + calcColor(getBlue (pixFront), getBlue (pixBack))); +} + + +//inline +//double fastSqrt(double n) +//{ +// __asm //speeds up xBRZ by about 9% compared to std::sqrt which internally uses the same assembler instructions but adds some "fluff" +// { +// fld n +// fsqrt +// } +//} +// + + +#ifdef _MSC_VER + #define FORCE_INLINE __forceinline +#elif defined __GNUC__ + #define FORCE_INLINE __attribute__((always_inline)) inline +#else + #define FORCE_INLINE inline +#endif + + +enum RotationDegree //clock-wise +{ + ROT_0, + ROT_90, + ROT_180, + ROT_270 +}; + +//calculate input matrix coordinates after rotation at compile time +template +struct MatrixRotation; + +template +struct MatrixRotation +{ + static const size_t I_old = I; + static const size_t J_old = J; +}; + +template //(i, j) = (row, col) indices, N = size of (square) matrix +struct MatrixRotation +{ + static const size_t I_old = N - 1 - MatrixRotation(rotDeg - 1), I, J, N>::J_old; //old coordinates before rotation! + static const size_t J_old = MatrixRotation(rotDeg - 1), I, J, N>::I_old; // +}; + + +template +class OutputMatrix +{ +public: + OutputMatrix(uint32_t* out, int outWidth) : //access matrix area, top-left at position "out" for image with given width + out_(out), + outWidth_(outWidth) {} + + template + uint32_t& ref() const + { + static const size_t I_old = MatrixRotation::I_old; + static const size_t J_old = MatrixRotation::J_old; + return *(out_ + J_old + I_old * outWidth_); + } + +private: + uint32_t* out_; + const int outWidth_; +}; + + +template inline +T square(T value) { return value * value; } + + +#if 0 +inline +double distRGB(uint32_t pix1, uint32_t pix2) +{ + const double r_diff = static_cast(getRed (pix1)) - getRed (pix2); + const double g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); + const double b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); + + //euklidean RGB distance + return std::sqrt(square(r_diff) + square(g_diff) + square(b_diff)); +} +#endif + + +inline +double distYCbCr(uint32_t pix1, uint32_t pix2, double lumaWeight) +{ + //https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion + //YCbCr conversion is a matrix multiplication => take advantage of linearity by subtracting first! + const int r_diff = static_cast(getRed (pix1)) - getRed (pix2); //we may delay division by 255 to after matrix multiplication + const int g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); // + const int b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); //substraction for int is noticeable faster than for double! + + //const double k_b = 0.0722; //ITU-R BT.709 conversion + //const double k_r = 0.2126; // + const double k_b = 0.0593; //ITU-R BT.2020 conversion + const double k_r = 0.2627; // + const double k_g = 1 - k_b - k_r; + + const double scale_b = 0.5 / (1 - k_b); + const double scale_r = 0.5 / (1 - k_r); + + const double y = k_r * r_diff + k_g * g_diff + k_b * b_diff; //[!], analog YCbCr! + const double c_b = scale_b * (b_diff - y); + const double c_r = scale_r * (r_diff - y); + + //we skip division by 255 to have similar range like other distance functions + return std::sqrt(square(lumaWeight * y) + square(c_b) + square(c_r)); +} + + +inline +double distYCbCrBuffered(uint32_t pix1, uint32_t pix2) +{ + //30% perf boost compared to plain distYCbCr()! + //consumes 64 MB memory; using double is only 2% faster, but takes 128 MB + static const std::vector diffToDist = [] + { + std::vector tmp; + + for (uint32_t i = 0; i < 256 * 256 * 256; ++i) //startup time: 114 ms on Intel Core i5 (four cores) + { + const int r_diff = static_cast(getByte<2>(i)) * 2; + const int g_diff = static_cast(getByte<1>(i)) * 2; + const int b_diff = static_cast(getByte<0>(i)) * 2; + + const double k_b = 0.0593; //ITU-R BT.2020 conversion + const double k_r = 0.2627; // + const double k_g = 1 - k_b - k_r; + + const double scale_b = 0.5 / (1 - k_b); + const double scale_r = 0.5 / (1 - k_r); + + const double y = k_r * r_diff + k_g * g_diff + k_b * b_diff; //[!], analog YCbCr! + const double c_b = scale_b * (b_diff - y); + const double c_r = scale_r * (r_diff - y); + + tmp.push_back(static_cast(std::sqrt(square(y) + square(c_b) + square(c_r)))); + } + return tmp; + }(); + + //if (pix1 == pix2) -> 8% perf degradation! + // return 0; + //if (pix1 < pix2) + // std::swap(pix1, pix2); -> 30% perf degradation!!! + + const int r_diff = static_cast(getRed (pix1)) - getRed (pix2); + const int g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); + const int b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); + + const size_t index = (static_cast(r_diff / 2) << 16) | //slightly reduce precision (division by 2) to squeeze value into single byte + (static_cast(g_diff / 2) << 8) | + (static_cast(b_diff / 2)); + +#if 0 //attention: the following calculation creates an asymmetric color distance!!! (e.g. r_diff=46 will be unpacked as 45, but r_diff=-46 unpacks to -47 + const size_t index = (((r_diff + 0xFF) / 2) << 16) | //slightly reduce precision (division by 2) to squeeze value into single byte + (((g_diff + 0xFF) / 2) << 8) | + (( b_diff + 0xFF) / 2); +#endif + return diffToDist[index]; +} + + +#if defined _MSC_VER && !defined NDEBUG + const int debugPixelX = -1; + const int debugPixelY = 58; + + thread_local bool breakIntoDebugger = false; +#endif + + +enum BlendType +{ + BLEND_NONE = 0, + BLEND_NORMAL, //a normal indication to blend + BLEND_DOMINANT, //a strong indication to blend + //attention: BlendType must fit into the value range of 2 bit!!! +}; + +struct BlendResult +{ + BlendType + /**/blend_f, blend_g, + /**/blend_j, blend_k; +}; + + +struct Kernel_3x3 +{ + uint32_t + a, b, c, + d, e, f, + g, h, i; +}; + +struct Kernel_4x4 //kernel for preprocessing step +{ + uint32_t + a, b, c, // + e, f, g, // support reinterpret_cast from Kernel_4x4 => Kernel_3x3 + i, j, k, // + m, n, o, + d, h, l, p; +}; + +/* input kernel area naming convention: +----------------- +| A | B | C | D | +|---|---|---|---| +| E | F | G | H | evaluate the four corners between F, G, J, K +|---|---|---|---| input pixel is at position F +| I | J | K | L | +|---|---|---|---| +| M | N | O | P | +----------------- +*/ +template +FORCE_INLINE //detect blend direction +BlendResult preProcessCorners(const Kernel_4x4& ker, const xbrz::ScalerCfg& cfg) //result: F, G, J, K corners of "GradientType" +{ +#if defined _MSC_VER && !defined NDEBUG + if (breakIntoDebugger) + __debugbreak(); //__asm int 3; +#endif + + BlendResult result = {}; + + if ((ker.f == ker.g && + ker.j == ker.k) || + (ker.f == ker.j && + ker.g == ker.k)) + return result; + + auto dist = [&](uint32_t pix1, uint32_t pix2) { return ColorDistance::dist(pix1, pix2, cfg.luminanceWeight); }; + + double jg = dist(ker.i, ker.f) + dist(ker.f, ker.c) + dist(ker.n, ker.k) + dist(ker.k, ker.h) + cfg.centerDirectionBias * dist(ker.j, ker.g); + double fk = dist(ker.e, ker.j) + dist(ker.j, ker.o) + dist(ker.b, ker.g) + dist(ker.g, ker.l) + cfg.centerDirectionBias * dist(ker.f, ker.k); + + if (jg < fk) //test sample: 70% of values max(jg, fk) / min(jg, fk) are between 1.1 and 3.7 with median being 1.8 + { + const bool dominantGradient = cfg.dominantDirectionThreshold * jg < fk; + if (ker.f != ker.g && ker.f != ker.j) + result.blend_f = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + + if (ker.k != ker.j && ker.k != ker.g) + result.blend_k = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + } + else if (fk < jg) + { + const bool dominantGradient = cfg.dominantDirectionThreshold * fk < jg; + if (ker.j != ker.f && ker.j != ker.k) + result.blend_j = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + + if (ker.g != ker.f && ker.g != ker.k) + result.blend_g = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + } + return result; +} + +#define DEF_GETTER(x) template uint32_t inline get_##x(const Kernel_3x3& ker) { return ker.x; } +//we cannot and NEED NOT write "ker.##x" since ## concatenates preprocessor tokens but "." is not a token +DEF_GETTER(a) DEF_GETTER(b) DEF_GETTER(c) +DEF_GETTER(d) DEF_GETTER(e) DEF_GETTER(f) +DEF_GETTER(g) DEF_GETTER(h) DEF_GETTER(i) +#undef DEF_GETTER + +#define DEF_GETTER(x, y) template <> inline uint32_t get_##x(const Kernel_3x3& ker) { return ker.y; } +DEF_GETTER(a, g) DEF_GETTER(b, d) DEF_GETTER(c, a) +DEF_GETTER(d, h) DEF_GETTER(e, e) DEF_GETTER(f, b) +DEF_GETTER(g, i) DEF_GETTER(h, f) DEF_GETTER(i, c) +#undef DEF_GETTER + +#define DEF_GETTER(x, y) template <> inline uint32_t get_##x(const Kernel_3x3& ker) { return ker.y; } +DEF_GETTER(a, i) DEF_GETTER(b, h) DEF_GETTER(c, g) +DEF_GETTER(d, f) DEF_GETTER(e, e) DEF_GETTER(f, d) +DEF_GETTER(g, c) DEF_GETTER(h, b) DEF_GETTER(i, a) +#undef DEF_GETTER + +#define DEF_GETTER(x, y) template <> inline uint32_t get_##x(const Kernel_3x3& ker) { return ker.y; } +DEF_GETTER(a, c) DEF_GETTER(b, f) DEF_GETTER(c, i) +DEF_GETTER(d, b) DEF_GETTER(e, e) DEF_GETTER(f, h) +DEF_GETTER(g, a) DEF_GETTER(h, d) DEF_GETTER(i, g) +#undef DEF_GETTER + + +//compress four blend types into a single byte +//inline BlendType getTopL (unsigned char b) { return static_cast(0x3 & b); } +inline BlendType getTopR (unsigned char b) { return static_cast(0x3 & (b >> 2)); } +inline BlendType getBottomR(unsigned char b) { return static_cast(0x3 & (b >> 4)); } +inline BlendType getBottomL(unsigned char b) { return static_cast(0x3 & (b >> 6)); } + +inline void clearAddTopL(unsigned char& b, BlendType bt) { b = static_cast(bt); } +inline void addTopR (unsigned char& b, BlendType bt) { b |= (bt << 2); } //buffer is assumed to be initialized before preprocessing! +inline void addBottomR (unsigned char& b, BlendType bt) { b |= (bt << 4); } //e.g. via clearAddTopL() +inline void addBottomL (unsigned char& b, BlendType bt) { b |= (bt << 6); } // + +inline bool blendingNeeded(unsigned char b) +{ + static_assert(BLEND_NONE == 0, ""); + return b != 0; +} + +template inline +unsigned char rotateBlendInfo(unsigned char b) { return b; } +template <> inline unsigned char rotateBlendInfo(unsigned char b) { return ((b << 2) | (b >> 6)) & 0xff; } +template <> inline unsigned char rotateBlendInfo(unsigned char b) { return ((b << 4) | (b >> 4)) & 0xff; } +template <> inline unsigned char rotateBlendInfo(unsigned char b) { return ((b << 6) | (b >> 2)) & 0xff; } + + +/* input kernel area naming convention: +------------- +| A | B | C | +|---|---|---| +| D | E | F | input pixel is at position E +|---|---|---| +| G | H | I | +------------- +*/ +template +FORCE_INLINE //perf: quite worth it! +void blendPixel(const Kernel_3x3& ker, + uint32_t* target, int trgWidth, + unsigned char blendInfo, //result of preprocessing all four corners of pixel "e" + const xbrz::ScalerCfg& cfg) +{ + //#define a get_a(ker) +#define b get_b(ker) +#define c get_c(ker) +#define d get_d(ker) +#define e get_e(ker) +#define f get_f(ker) +#define g get_g(ker) +#define h get_h(ker) +#define i get_i(ker) + +#if defined _MSC_VER && !defined NDEBUG + if (breakIntoDebugger) + __debugbreak(); //__asm int 3; +#endif + + const unsigned char blend = rotateBlendInfo(blendInfo); + + if (getBottomR(blend) >= BLEND_NORMAL) + { + auto eq = [&](uint32_t pix1, uint32_t pix2) { return ColorDistance::dist(pix1, pix2, cfg.luminanceWeight) < cfg.equalColorTolerance; }; + auto dist = [&](uint32_t pix1, uint32_t pix2) { return ColorDistance::dist(pix1, pix2, cfg.luminanceWeight); }; + + const bool doLineBlend = [&]() -> bool + { + if (getBottomR(blend) >= BLEND_DOMINANT) + return true; + + //make sure there is no second blending in an adjacent rotation for this pixel: handles insular pixels, mario eyes + if (getTopR(blend) != BLEND_NONE && !eq(e, g)) //but support double-blending for 90° corners + return false; + if (getBottomL(blend) != BLEND_NONE && !eq(e, c)) + return false; + + //no full blending for L-shapes; blend corner only (handles "mario mushroom eyes") + if (!eq(e, i) && eq(g, h) && eq(h, i) && eq(i, f) && eq(f, c)) + return false; + + return true; + }(); + + const uint32_t px = dist(e, f) <= dist(e, h) ? f : h; //choose most similar color + + OutputMatrix out(target, trgWidth); + + if (doLineBlend) + { + const double fg = dist(f, g); //test sample: 70% of values max(fg, hc) / min(fg, hc) are between 1.1 and 3.7 with median being 1.9 + const double hc = dist(h, c); // + + const bool haveShallowLine = cfg.steepDirectionThreshold * fg <= hc && e != g && d != g; + const bool haveSteepLine = cfg.steepDirectionThreshold * hc <= fg && e != c && b != c; + + if (haveShallowLine) + { + if (haveSteepLine) + Scaler::blendLineSteepAndShallow(px, out); + else + Scaler::blendLineShallow(px, out); + } + else + { + if (haveSteepLine) + Scaler::blendLineSteep(px, out); + else + Scaler::blendLineDiagonal(px, out); + } + } + else + Scaler::blendCorner(px, out); + } + + //#undef a +#undef b +#undef c +#undef d +#undef e +#undef f +#undef g +#undef h +#undef i +} + + +class OobReaderTransparent +{ +public: + OobReaderTransparent(const uint32_t* src, int srcWidth, int srcHeight, int y) : + s_m1(0 <= y - 1 && y - 1 < srcHeight ? src + srcWidth * (y - 1) : nullptr), + s_0 (0 <= y && y < srcHeight ? src + srcWidth * y : nullptr), + s_p1(0 <= y + 1 && y + 1 < srcHeight ? src + srcWidth * (y + 1) : nullptr), + s_p2(0 <= y + 2 && y + 2 < srcHeight ? src + srcWidth * (y + 2) : nullptr), + srcWidth_(srcWidth) {} + + void readDhlp(Kernel_4x4& ker, int x) const //(x, y) is at kernel position F + { + const int x_p2 = x + 2; + + if (0 <= x_p2 && x_p2 < srcWidth_) + { + ker.d = s_m1 ? s_m1[x_p2] : 0; + ker.h = s_0 ? s_0 [x_p2] : 0; + ker.l = s_p1 ? s_p1[x_p2] : 0; + ker.p = s_p2 ? s_p2[x_p2] : 0; + } + else + { + ker.d = 0; + ker.h = 0; + ker.l = 0; + ker.p = 0; + } + } + +private: + const uint32_t* const s_m1; + const uint32_t* const s_0; + const uint32_t* const s_p1; + const uint32_t* const s_p2; + const int srcWidth_; +}; + + +template +constexpr inline T xbrz_clamp(const T in, const T min, const T max) +{ + return in <= min ? min : in >= max ? max : in; +} + +class OobReaderDuplicate +{ +public: + OobReaderDuplicate(const uint32_t* src, int srcWidth, int srcHeight, int y) : + s_m1(src + srcWidth * xbrz_clamp(y - 1, 0, srcHeight - 1)), + s_0 (src + srcWidth * xbrz_clamp(y, 0, srcHeight - 1)), + s_p1(src + srcWidth * xbrz_clamp(y + 1, 0, srcHeight - 1)), + s_p2(src + srcWidth * xbrz_clamp(y + 2, 0, srcHeight - 1)), + srcWidth_(srcWidth) {} + + void readDhlp(Kernel_4x4& ker, int x) const //(x, y) is at kernel position F + { + const int x_p2 = xbrz_clamp(x + 2, 0, srcWidth_ - 1); + ker.d = s_m1[x_p2]; + ker.h = s_0 [x_p2]; + ker.l = s_p1[x_p2]; + ker.p = s_p2[x_p2]; + } + +private: + const uint32_t* const s_m1; + const uint32_t* const s_0; + const uint32_t* const s_p1; + const uint32_t* const s_p2; + const int srcWidth_; +}; + + +template //scaler policy: see "Scaler2x" reference implementation +void scaleImage(const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, const xbrz::ScalerCfg& cfg, int yFirst, int yLast) +{ + yFirst = std::max(yFirst, 0); + yLast = std::min(yLast, srcHeight); + if (yFirst >= yLast || srcWidth <= 0) + return; + + const int trgWidth = srcWidth * Scaler::scale; + + //(ab)use space of "sizeof(uint32_t) * srcWidth * Scaler::scale" at the end of the image as temporary + //buffer for "on the fly preprocessing" without risk of accidental overwriting before accessing + unsigned char* const preProcBuf = reinterpret_cast(trg + yLast * Scaler::scale * trgWidth) - srcWidth; + + //initialize preprocessing buffer for first row of current stripe: detect upper left and right corner blending + //this cannot be optimized for adjacent processing stripes; we must not allow for a memory race condition! + { + const OobReader oobReader(src, srcWidth, srcHeight, yFirst - 1); + + //initialize at position x = -1 + Kernel_4x4 ker4 = {}; + oobReader.readDhlp(ker4, -4); //hack: read a, e, i, m at x = -1 + ker4.a = ker4.d; + ker4.e = ker4.h; + ker4.i = ker4.l; + ker4.m = ker4.p; + + oobReader.readDhlp(ker4, -3); + ker4.b = ker4.d; + ker4.f = ker4.h; + ker4.j = ker4.l; + ker4.n = ker4.p; + + oobReader.readDhlp(ker4, -2); + ker4.c = ker4.d; + ker4.g = ker4.h; + ker4.k = ker4.l; + ker4.o = ker4.p; + + oobReader.readDhlp(ker4, -1); + + { + const BlendResult res = preProcessCorners(ker4, cfg); + clearAddTopL(preProcBuf[0], res.blend_k); //set 1st known corner for (0, yFirst) + } + + for (int x = 0; x < srcWidth; ++x) + { + ker4.a = ker4.b; //shift previous kernel to the left + ker4.e = ker4.f; // ----------------- + ker4.i = ker4.j; // | A | B | C | D | + ker4.m = ker4.n; // |---|---|---|---| + /**/ // | E | F | G | H | (x, yFirst - 1) is at position F + ker4.b = ker4.c; // |---|---|---|---| + ker4.f = ker4.g; // | I | J | K | L | + ker4.j = ker4.k; // |---|---|---|---| + ker4.n = ker4.o; // | M | N | O | P | + /**/ // ----------------- + ker4.c = ker4.d; + ker4.g = ker4.h; + ker4.k = ker4.l; + ker4.o = ker4.p; + + oobReader.readDhlp(ker4, x); + + /* preprocessing blend result: + --------- + | F | G | evaluate corner between F, G, J, K + |---+---| current input pixel is at position F + | J | K | + --------- */ + const BlendResult res = preProcessCorners(ker4, cfg); + addTopR(preProcBuf[x], res.blend_j); //set 2nd known corner for (x, yFirst) + + if (x + 1 < srcWidth) + clearAddTopL(preProcBuf[x + 1], res.blend_k); //set 1st known corner for (x + 1, yFirst) + } + } + //------------------------------------------------------------------------------------ + + for (int y = yFirst; y < yLast; ++y) + { + uint32_t* out = trg + Scaler::scale * y * trgWidth; //consider MT "striped" access + + const OobReader oobReader(src, srcWidth, srcHeight, y); + + //initialize at position x = -1 + Kernel_4x4 ker4 = {}; + oobReader.readDhlp(ker4, -4); //hack: read a, e, i, m at x = -1 + ker4.a = ker4.d; + ker4.e = ker4.h; + ker4.i = ker4.l; + ker4.m = ker4.p; + + oobReader.readDhlp(ker4, -3); + ker4.b = ker4.d; + ker4.f = ker4.h; + ker4.j = ker4.l; + ker4.n = ker4.p; + + oobReader.readDhlp(ker4, -2); + ker4.c = ker4.d; + ker4.g = ker4.h; + ker4.k = ker4.l; + ker4.o = ker4.p; + + oobReader.readDhlp(ker4, -1); + + unsigned char blend_xy1 = 0; //corner blending for current (x, y + 1) position + { + const BlendResult res = preProcessCorners(ker4, cfg); + clearAddTopL(blend_xy1, res.blend_k); //set 1st known corner for (0, y + 1) and buffer for use on next column + + addBottomL(preProcBuf[0], res.blend_g); //set 3rd known corner for (0, y) + } + + for (int x = 0; x < srcWidth; ++x, out += Scaler::scale) + { +#if defined _MSC_VER && !defined NDEBUG + breakIntoDebugger = debugPixelX == x && debugPixelY == y; +#endif + ker4.a = ker4.b; //shift previous kernel to the left + ker4.e = ker4.f; // ----------------- + ker4.i = ker4.j; // | A | B | C | D | + ker4.m = ker4.n; // |---|---|---|---| + /**/ // | E | F | G | H | (x, y) is at position F + ker4.b = ker4.c; // |---|---|---|---| + ker4.f = ker4.g; // | I | J | K | L | + ker4.j = ker4.k; // |---|---|---|---| + ker4.n = ker4.o; // | M | N | O | P | + /**/ // ----------------- + ker4.c = ker4.d; + ker4.g = ker4.h; + ker4.k = ker4.l; + ker4.o = ker4.p; + + oobReader.readDhlp(ker4, x); + + //evaluate the four corners on bottom-right of current pixel + unsigned char blend_xy = preProcBuf[x]; //for current (x, y) position + { + /* preprocessing blend result: + --------- + | F | G | evaluate corner between F, G, J, K + |---+---| current input pixel is at position F + | J | K | + --------- */ + const BlendResult res = preProcessCorners(ker4, cfg); + addBottomR(blend_xy, res.blend_f); //all four corners of (x, y) have been determined at this point due to processing sequence! + + addTopR(blend_xy1, res.blend_j); //set 2nd known corner for (x, y + 1) + preProcBuf[x] = blend_xy1; //store on current buffer position for use on next row + + if (x + 1 < srcWidth) + { + //blend_xy1 -> blend_x1y1 + clearAddTopL(blend_xy1, res.blend_k); //set 1st known corner for (x + 1, y + 1) and buffer for use on next column + + addBottomL(preProcBuf[x + 1], res.blend_g); //set 3rd known corner for (x + 1, y) + } + } + + //fill block of size scale * scale with the given color + fillBlock(out, trgWidth * sizeof(uint32_t), ker4.f, Scaler::scale, Scaler::scale); + //place *after* preprocessing step, to not overwrite the results while processing the last pixel! + + //blend all four corners of current pixel + if (blendingNeeded(blend_xy)) + { + const auto& ker3 = reinterpret_cast(ker4); //"The Things We Do for Perf" + blendPixel(ker3, out, trgWidth, blend_xy, cfg); + blendPixel(ker3, out, trgWidth, blend_xy, cfg); + blendPixel(ker3, out, trgWidth, blend_xy, cfg); + blendPixel(ker3, out, trgWidth, blend_xy, cfg); + } + } + } +} + +//------------------------------------------------------------------------------------ + +template +struct Scaler2x : public ColorGradient +{ + static const int scale = 2; + + template //bring template function into scope for GCC + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) { ColorGradient::template alphaGrad(pixBack, pixFront); } + + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<1, 0>(), col); + alphaGrad<1, 4>(out.template ref<0, 1>(), col); + alphaGrad<5, 6>(out.template ref<1, 1>(), col); //[!] fixes 7/8 used in xBR + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 2>(out.template ref<1, 1>(), col); + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaGrad<21, 100>(out.template ref<1, 1>(), col); //exact: 1 - pi/4 = 0.2146018366 + } +}; + + +template +struct Scaler3x : public ColorGradient +{ + static const int scale = 3; + + template //bring template function into scope for GCC + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) { ColorGradient::template alphaGrad(pixBack, pixFront); } + + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + + alphaGrad<3, 4>(out.template ref(), col); + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col); + + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + out.template ref<2, scale - 1>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<2, 0>(), col); + alphaGrad<1, 4>(out.template ref<0, 2>(), col); + alphaGrad<3, 4>(out.template ref<2, 1>(), col); + alphaGrad<3, 4>(out.template ref<1, 2>(), col); + out.template ref<2, 2>() = col; + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 8>(out.template ref<1, 2>(), col); //conflict with other rotations for this odd scale + alphaGrad<1, 8>(out.template ref<2, 1>(), col); + alphaGrad<7, 8>(out.template ref<2, 2>(), col); // + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaGrad<45, 100>(out.template ref<2, 2>(), col); //exact: 0.4545939598 + //alphaGrad<7, 256>(out.template ref<2, 1>(), col); //0.02826017254 -> negligible + avoid conflicts with other rotations for this odd scale + //alphaGrad<7, 256>(out.template ref<1, 2>(), col); //0.02826017254 + } +}; + + +template +struct Scaler4x : public ColorGradient +{ + static const int scale = 4; + + template //bring template function into scope for GCC + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) { ColorGradient::template alphaGrad(pixBack, pixFront); } + + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + + alphaGrad<3, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col); + + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + alphaGrad<3, 4>(out.template ref<3, scale - 2>(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<3, 4>(out.template ref<3, 1>(), col); + alphaGrad<3, 4>(out.template ref<1, 3>(), col); + alphaGrad<1, 4>(out.template ref<3, 0>(), col); + alphaGrad<1, 4>(out.template ref<0, 3>(), col); + + alphaGrad<1, 3>(out.template ref<2, 2>(), col); //[!] fixes 1/4 used in xBR + + out.template ref<3, 3>() = col; + out.template ref<3, 2>() = col; + out.template ref<2, 3>() = col; + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 2>(out.template ref(), col); + alphaGrad<1, 2>(out.template ref(), col); + out.template ref() = col; + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaGrad<68, 100>(out.template ref<3, 3>(), col); //exact: 0.6848532563 + alphaGrad< 9, 100>(out.template ref<3, 2>(), col); //0.08677704501 + alphaGrad< 9, 100>(out.template ref<2, 3>(), col); //0.08677704501 + } +}; + + +template +struct Scaler5x : public ColorGradient +{ + static const int scale = 5; + + template //bring template function into scope for GCC + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) { ColorGradient::template alphaGrad(pixBack, pixFront); } + + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + + alphaGrad<3, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col); + alphaGrad<1, 4>(out.template ref<4, scale - 3>(), col); + + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + alphaGrad<3, 4>(out.template ref<3, scale - 2>(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + out.template ref<4, scale - 1>() = col; + out.template ref<4, scale - 2>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col); + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + + alphaGrad<2, 3>(out.template ref<3, 3>(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + out.template ref<4, scale - 1>() = col; + + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 8>(out.template ref(), col); //conflict with other rotations for this odd scale + alphaGrad<1, 8>(out.template ref(), col); + alphaGrad<1, 8>(out.template ref(), col); // + + alphaGrad<7, 8>(out.template ref<4, 3>(), col); + alphaGrad<7, 8>(out.template ref<3, 4>(), col); + + out.template ref<4, 4>() = col; + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaGrad<86, 100>(out.template ref<4, 4>(), col); //exact: 0.8631434088 + alphaGrad<23, 100>(out.template ref<4, 3>(), col); //0.2306749731 + alphaGrad<23, 100>(out.template ref<3, 4>(), col); //0.2306749731 + //alphaGrad<1, 64>(out.template ref<4, 2>(), col); //0.01676812367 -> negligible + avoid conflicts with other rotations for this odd scale + //alphaGrad<1, 64>(out.template ref<2, 4>(), col); //0.01676812367 + } +}; + + +template +struct Scaler6x : public ColorGradient +{ + static const int scale = 6; + + template //bring template function into scope for GCC + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) { ColorGradient::template alphaGrad(pixBack, pixFront); } + + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + + alphaGrad<3, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col); + alphaGrad<1, 4>(out.template ref<4, scale - 3>(), col); + + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + alphaGrad<3, 4>(out.template ref<3, scale - 2>(), col); + alphaGrad<3, 4>(out.template ref<5, scale - 3>(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + out.template ref<4, scale - 1>() = col; + out.template ref<5, scale - 1>() = col; + + out.template ref<4, scale - 2>() = col; + out.template ref<5, scale - 2>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 4>(out.template ref<0, scale - 1>(), col); + alphaGrad<1, 4>(out.template ref<2, scale - 2>(), col); + alphaGrad<3, 4>(out.template ref<1, scale - 1>(), col); + alphaGrad<3, 4>(out.template ref<3, scale - 2>(), col); + + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<1, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + alphaGrad<3, 4>(out.template ref(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + out.template ref<4, scale - 1>() = col; + out.template ref<5, scale - 1>() = col; + + out.template ref<4, scale - 2>() = col; + out.template ref<5, scale - 2>() = col; + + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaGrad<1, 2>(out.template ref(), col); + alphaGrad<1, 2>(out.template ref(), col); + alphaGrad<1, 2>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaGrad<97, 100>(out.template ref<5, 5>(), col); //exact: 0.9711013910 + alphaGrad<42, 100>(out.template ref<4, 5>(), col); //0.4236372243 + alphaGrad<42, 100>(out.template ref<5, 4>(), col); //0.4236372243 + alphaGrad< 6, 100>(out.template ref<5, 3>(), col); //0.05652034508 + alphaGrad< 6, 100>(out.template ref<3, 5>(), col); //0.05652034508 + } +}; + +//------------------------------------------------------------------------------------ + +struct ColorDistanceRGB +{ + static double dist(uint32_t pix1, uint32_t pix2, double luminanceWeight) + { + return distYCbCrBuffered(pix1, pix2); + + //if (pix1 == pix2) //about 4% perf boost + // return 0; + //return distYCbCr(pix1, pix2, luminanceWeight); + } +}; + +struct ColorDistanceARGB +{ + static double dist(uint32_t pix1, uint32_t pix2, double luminanceWeight) + { + const double a1 = getAlpha(pix1) / 255.0 ; + const double a2 = getAlpha(pix2) / 255.0 ; + /* + Requirements for a color distance handling alpha channel: with a1, a2 in [0, 1] + + 1. if a1 = a2, distance should be: a1 * distYCbCr() + 2. if a1 = 0, distance should be: a2 * distYCbCr(black, white) = a2 * 255 + 3. if a1 = 1, ??? maybe: 255 * (1 - a2) + a2 * distYCbCr() + */ + + //return std::min(a1, a2) * distYCbCrBuffered(pix1, pix2) + 255 * abs(a1 - a2); + //=> following code is 15% faster: + const double d = distYCbCrBuffered(pix1, pix2); + if (a1 < a2) + return a1 * d + 255 * (a2 - a1); + else + return a2 * d + 255 * (a1 - a2); + + //alternative? return std::sqrt(a1 * a2 * square(distYCbCrBuffered(pix1, pix2)) + square(255 * (a1 - a2))); + } +}; + + +struct ColorDistanceUnbufferedARGB +{ + static double dist(uint32_t pix1, uint32_t pix2, double luminanceWeight) + { + const double a1 = getAlpha(pix1) / 255.0 ; + const double a2 = getAlpha(pix2) / 255.0 ; + + const double d = distYCbCr(pix1, pix2, luminanceWeight); + if (a1 < a2) + return a1 * d + 255 * (a2 - a1); + else + return a2 * d + 255 * (a1 - a2); + } +}; + + +struct ColorGradientRGB +{ + template + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) + { + pixBack = gradientRGB(pixFront, pixBack); + } +}; + +struct ColorGradientARGB +{ + template + static void alphaGrad(uint32_t& pixBack, uint32_t pixFront) + { + pixBack = gradientARGB(pixFront, pixBack); + } +}; +} + + +void xbrz::scale(size_t factor, const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, ColorFormat colFmt, const xbrz::ScalerCfg& cfg, int yFirst, int yLast) +{ + if (factor == 1) + { + std::copy(src + yFirst * srcWidth, src + yLast * srcWidth, trg); + return; + } + + static_assert(SCALE_FACTOR_MAX == 6, ""); + switch (colFmt) + { + case ColorFormat::RGB: + switch (factor) + { + case 2: + return scaleImage, ColorDistanceRGB, OobReaderDuplicate>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 3: + return scaleImage, ColorDistanceRGB, OobReaderDuplicate>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 4: + return scaleImage, ColorDistanceRGB, OobReaderDuplicate>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 5: + return scaleImage, ColorDistanceRGB, OobReaderDuplicate>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 6: + return scaleImage, ColorDistanceRGB, OobReaderDuplicate>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + } + break; + + case ColorFormat::ARGB: + switch (factor) + { + case 2: + return scaleImage, ColorDistanceARGB, OobReaderTransparent>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 3: + return scaleImage, ColorDistanceARGB, OobReaderTransparent>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 4: + return scaleImage, ColorDistanceARGB, OobReaderTransparent>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 5: + return scaleImage, ColorDistanceARGB, OobReaderTransparent>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 6: + return scaleImage, ColorDistanceARGB, OobReaderTransparent>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + } + break; + + case ColorFormat::ARGB_UNBUFFERED: + switch (factor) + { + case 2: + return scaleImage, ColorDistanceUnbufferedARGB, OobReaderTransparent>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 3: + return scaleImage, ColorDistanceUnbufferedARGB, OobReaderTransparent>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 4: + return scaleImage, ColorDistanceUnbufferedARGB, OobReaderTransparent>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 5: + return scaleImage, ColorDistanceUnbufferedARGB, OobReaderTransparent>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 6: + return scaleImage, ColorDistanceUnbufferedARGB, OobReaderTransparent>(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + } + break; + } + assert(false); +} + + +bool xbrz::equalColorTest(uint32_t col1, uint32_t col2, ColorFormat colFmt, double luminanceWeight, double equalColorTolerance) +{ + switch (colFmt) + { + case ColorFormat::RGB: + return ColorDistanceRGB::dist(col1, col2, luminanceWeight) < equalColorTolerance; + case ColorFormat::ARGB: + return ColorDistanceARGB::dist(col1, col2, luminanceWeight) < equalColorTolerance; + case ColorFormat::ARGB_UNBUFFERED: + return ColorDistanceUnbufferedARGB::dist(col1, col2, luminanceWeight) < equalColorTolerance; + } + assert(false); + return false; +} + + +void xbrz::bilinearScale(const uint32_t* src, int srcWidth, int srcHeight, + /**/ uint32_t* trg, int trgWidth, int trgHeight) +{ + bilinearScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t), + trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t), + 0, trgHeight, [](uint32_t pix) { return pix; }); +} + + +void xbrz::nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, + /**/ uint32_t* trg, int trgWidth, int trgHeight) +{ + nearestNeighborScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t), + trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t), + 0, trgHeight, [](uint32_t pix) { return pix; }); +} + + +#if 0 +//#include +void bilinearScaleCpu(const uint32_t* src, int srcWidth, int srcHeight, + /**/ uint32_t* trg, int trgWidth, int trgHeight) +{ + const int TASK_GRANULARITY = 16; + + concurrency::task_group tg; + + for (int i = 0; i < trgHeight; i += TASK_GRANULARITY) + tg.run([=] + { + const int iLast = std::min(i + TASK_GRANULARITY, trgHeight); + xbrz::bilinearScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t), + trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t), + i, iLast, [](uint32_t pix) { return pix; }); + }); + tg.wait(); +} + + +//Perf: AMP vs CPU: merely ~10% shorter runtime (scaling 1280x800 -> 1920x1080) +//#include +void bilinearScaleAmp(const uint32_t* src, int srcWidth, int srcHeight, //throw concurrency::runtime_exception + /**/ uint32_t* trg, int trgWidth, int trgHeight) +{ + //C++ AMP reference: https://msdn.microsoft.com/en-us/library/hh289390.aspx + //introduction to C++ AMP: https://msdn.microsoft.com/en-us/magazine/hh882446.aspx + using namespace concurrency; + //TODO: pitch + + if (srcHeight <= 0 || srcWidth <= 0) return; + + const float scaleX = static_cast(trgWidth ) / srcWidth; + const float scaleY = static_cast(trgHeight) / srcHeight; + + array_view srcView(srcHeight, srcWidth, src); + array_view< uint32_t, 2> trgView(trgHeight, trgWidth, trg); + trgView.discard_data(); + + parallel_for_each(trgView.extent, [=](index<2> idx) restrict(amp) //throw ? + { + const int y = idx[0]; + const int x = idx[1]; + //Perf notes: + // -> float-based calculation is (almost) 2x as fas as double! + // -> no noticeable improvement via tiling: https://msdn.microsoft.com/en-us/magazine/hh882447.aspx + // -> no noticeable improvement with restrict(amp,cpu) + // -> iterating over y-axis only is significantly slower! + // -> pre-calculating x,y-dependent variables in a buffer + array_view<> is ~ 20 % slower! + const int y1 = srcHeight * y / trgHeight; + int y2 = y1 + 1; + if (y2 == srcHeight) --y2; + + const float yy1 = y / scaleY - y1; + const float y2y = 1 - yy1; + //------------------------------------- + const int x1 = srcWidth * x / trgWidth; + int x2 = x1 + 1; + if (x2 == srcWidth) --x2; + + const float xx1 = x / scaleX - x1; + const float x2x = 1 - xx1; + //------------------------------------- + const float x2xy2y = x2x * y2y; + const float xx1y2y = xx1 * y2y; + const float x2xyy1 = x2x * yy1; + const float xx1yy1 = xx1 * yy1; + + auto interpolate = [=](int offset) + { + /* + https://en.wikipedia.org/wiki/Bilinear_interpolation + (c11(x2 - x) + c21(x - x1)) * (y2 - y ) + + (c12(x2 - x) + c22(x - x1)) * (y - y1) + */ + const auto c11 = (srcView(y1, x1) >> (8 * offset)) & 0xff; + const auto c21 = (srcView(y1, x2) >> (8 * offset)) & 0xff; + const auto c12 = (srcView(y2, x1) >> (8 * offset)) & 0xff; + const auto c22 = (srcView(y2, x2) >> (8 * offset)) & 0xff; + + return c11 * x2xy2y + c21 * xx1y2y + + c12 * x2xyy1 + c22 * xx1yy1; + }; + + const float bi = interpolate(0); + const float gi = interpolate(1); + const float ri = interpolate(2); + const float ai = interpolate(3); + + const auto b = static_cast(bi + 0.5f); + const auto g = static_cast(gi + 0.5f); + const auto r = static_cast(ri + 0.5f); + const auto a = static_cast(ai + 0.5f); + + trgView(y, x) = (a << 24) | (r << 16) | (g << 8) | b; + }); + trgView.synchronize(); //throw ? +} +#endif \ No newline at end of file diff --git a/source/common/textures/hires/xbr/xbrz.h b/source/common/textures/hires/xbr/xbrz.h new file mode 100644 index 000000000..492fb43ad --- /dev/null +++ b/source/common/textures/hires/xbr/xbrz.h @@ -0,0 +1,79 @@ +// **************************************************************************** +// * This file is part of the xBRZ project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// * * +// * Additionally and as a special exception, the author gives permission * +// * to link the code of this program with the following libraries * +// * (or with modified versions that use the same licenses), and distribute * +// * linked combinations including the two: MAME, FreeFileSync, Snes9x, ePSXe * +// * You must obey the GNU General Public License in all respects for all of * +// * the code used other than MAME, FreeFileSync, Snes9x, ePSXe. * +// * If you modify this file, you may extend this exception to your version * +// * of the file, but you are not obligated to do so. If you do not wish to * +// * do so, delete this exception statement from your version. * +// **************************************************************************** + +#ifndef XBRZ_HEADER_3847894708239054 +#define XBRZ_HEADER_3847894708239054 + +#include //size_t +#include //uint32_t +#include +#include "xbrz_config.h" + + +namespace xbrz +{ +/* +------------------------------------------------------------------------- +| xBRZ: "Scale by rules" - high quality image upscaling filter by Zenju | +------------------------------------------------------------------------- +using a modified approach of xBR: +http://board.byuu.org/viewtopic.php?f=10&t=2248 +- new rule set preserving small image features +- highly optimized for performance +- support alpha channel +- support multithreading +- support 64-bit architectures +- support processing image slices +- support scaling up to 6xBRZ +*/ + +enum class ColorFormat //from high bits -> low bits, 8 bit per channel +{ + RGB, //8 bit for each red, green, blue, upper 8 bits unused + ARGB, //including alpha channel, BGRA byte order on little-endian machines + ARGB_UNBUFFERED, //like ARGB, but without the one-time buffer creation overhead (ca. 100 - 300 ms) at the expense of a slightly slower scaling time +}; + +const int SCALE_FACTOR_MAX = 6; + +/* +-> map source (srcWidth * srcHeight) to target (scale * width x scale * height) image, optionally processing a half-open slice of rows [yFirst, yLast) only +-> if your emulator changes only a few image slices during each cycle (e.g. DOSBox) then there's no need to run xBRZ on the complete image: + Just make sure you enlarge the source image slice by 2 rows on top and 2 on bottom (this is the additional range the xBRZ algorithm is using during analysis) + CAVEAT: If there are multiple changed slices, make sure they do not overlap after adding these additional rows in order to avoid a memory race condition + in the target image data if you are using multiple threads for processing each enlarged slice! + +THREAD-SAFETY: - parts of the same image may be scaled by multiple threads as long as the [yFirst, yLast) ranges do not overlap! + - there is a minor inefficiency for the first row of a slice, so avoid processing single rows only; suggestion: process at least 8-16 rows +*/ +void scale(size_t factor, //valid range: 2 - SCALE_FACTOR_MAX + const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, + ColorFormat colFmt, + const ScalerCfg& cfg = ScalerCfg(), + int yFirst = 0, int yLast = std::numeric_limits::max()); //slice of source image + +void bilinearScale(const uint32_t* src, int srcWidth, int srcHeight, + /**/ uint32_t* trg, int trgWidth, int trgHeight); + +void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, + /**/ uint32_t* trg, int trgWidth, int trgHeight); + + +//parameter tuning +bool equalColorTest(uint32_t col1, uint32_t col2, ColorFormat colFmt, double luminanceWeight, double equalColorTolerance); +} + +#endif diff --git a/source/common/textures/hires/xbr/xbrz_config.h b/source/common/textures/hires/xbr/xbrz_config.h new file mode 100644 index 000000000..fcfda99ab --- /dev/null +++ b/source/common/textures/hires/xbr/xbrz_config.h @@ -0,0 +1,35 @@ +// **************************************************************************** +// * This file is part of the xBRZ project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// * * +// * Additionally and as a special exception, the author gives permission * +// * to link the code of this program with the following libraries * +// * (or with modified versions that use the same licenses), and distribute * +// * linked combinations including the two: MAME, FreeFileSync, Snes9x, ePSXe * +// * You must obey the GNU General Public License in all respects for all of * +// * the code used other than MAME, FreeFileSync, Snes9x, ePSXe. * +// * If you modify this file, you may extend this exception to your version * +// * of the file, but you are not obligated to do so. If you do not wish to * +// * do so, delete this exception statement from your version. * +// **************************************************************************** + +#ifndef XBRZ_CONFIG_HEADER_284578425345 +#define XBRZ_CONFIG_HEADER_284578425345 + +//do NOT include any headers here! used by xBRZ_dll!!! + +namespace xbrz +{ +struct ScalerCfg +{ + double luminanceWeight = 1; + double equalColorTolerance = 30; + double centerDirectionBias = 4; + double dominantDirectionThreshold = 3.6; + double steepDirectionThreshold = 2.2; + double newTestAttribute = 0; //unused; test new parameters +}; +} + +#endif diff --git a/source/common/textures/hires/xbr/xbrz_config_old.h b/source/common/textures/hires/xbr/xbrz_config_old.h new file mode 100644 index 000000000..885dff9f8 --- /dev/null +++ b/source/common/textures/hires/xbr/xbrz_config_old.h @@ -0,0 +1,40 @@ +// **************************************************************************** +// * This file is part of the HqMAME project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// * * +// * Additionally and as a special exception, the author gives permission * +// * to link the code of this program with the MAME library (or with modified * +// * versions of MAME that use the same license as MAME), and distribute * +// * linked combinations including the two. You must obey the GNU General * +// * Public License in all respects for all of the code used other than MAME. * +// * If you modify this file, you may extend this exception to your version * +// * of the file, but you are not obligated to do so. If you do not wish to * +// * do so, delete this exception statement from your version. * +// **************************************************************************** + +#ifndef __XBRZ_CONFIG_OLD_HEADER_INCLUDED__ +#define __XBRZ_CONFIG_OLD_HEADER_INCLUDED__ + +//do NOT include any headers here! used by xBRZ_dll!!! + +namespace xbrz_old +{ +struct ScalerCfg +{ + ScalerCfg() : + luminanceWeight_(1), + equalColorTolerance_(30), + dominantDirectionThreshold(3.6), + steepDirectionThreshold(2.2), + newTestAttribute_(0) {} + + double luminanceWeight_; + double equalColorTolerance_; + double dominantDirectionThreshold; + double steepDirectionThreshold; + double newTestAttribute_; //unused; test new parameters +}; +} + +#endif \ No newline at end of file diff --git a/source/common/textures/hires/xbr/xbrz_old.cpp b/source/common/textures/hires/xbr/xbrz_old.cpp new file mode 100644 index 000000000..8eb19bb80 --- /dev/null +++ b/source/common/textures/hires/xbr/xbrz_old.cpp @@ -0,0 +1,1300 @@ +// **************************************************************************** +// * This file is part of the HqMAME project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// * * +// * Additionally and as a special exception, the author gives permission * +// * to link the code of this program with the MAME library (or with modified * +// * versions of MAME that use the same license as MAME), and distribute * +// * linked combinations including the two. You must obey the GNU General * +// * Public License in all respects for all of the code used other than MAME. * +// * If you modify this file, you may extend this exception to your version * +// * of the file, but you are not obligated to do so. If you do not wish to * +// * do so, delete this exception statement from your version. * +// **************************************************************************** + +#include "xbrz_old.h" + +#include +#include +#include + +namespace +{ +template inline +unsigned char getByte(uint32_t val) { return static_cast((val >> (8 * N)) & 0xff); } + +inline unsigned char getRed (uint32_t val) { return getByte<2>(val); } +inline unsigned char getGreen(uint32_t val) { return getByte<1>(val); } +inline unsigned char getBlue (uint32_t val) { return getByte<0>(val); } + +template inline +T abs(T value) +{ + static_assert(std::is_signed::value, ""); + return value < 0 ? -value : value; +} + +const uint32_t redMask = 0xff0000; +const uint32_t greenMask = 0x00ff00; +const uint32_t blueMask = 0x0000ff; + +template inline +void alphaBlend(uint32_t& dst, uint32_t col) //blend color over destination with opacity N / M +{ + static_assert(N < 256, "possible overflow of (col & redMask) * N"); + static_assert(M < 256, "possible overflow of (col & redMask ) * N + (dst & redMask ) * (M - N)"); + static_assert(0 < N && N < M, ""); + + static const uint32_t ALPHA_MASK = 0xFF000000; + static const uint32_t ALPHA_SHIFT = 24; + + static const uint32_t FULL_OPAQUE = 0xFF; + + const uint32_t colAlpha = col >> ALPHA_SHIFT; + const uint32_t dstAlpha = dst >> ALPHA_SHIFT; + + // Overflow is ignored intentionally! + + const uint32_t alpha = (FULL_OPAQUE == colAlpha && FULL_OPAQUE == dstAlpha) + ? ALPHA_MASK + : ALPHA_MASK & ((colAlpha * N + dstAlpha * (M - N)) / M << ALPHA_SHIFT); + + dst = (redMask & ((col & redMask ) * N + (dst & redMask ) * (M - N)) / M) | //this works because 8 upper bits are free + (greenMask & ((col & greenMask) * N + (dst & greenMask) * (M - N)) / M) | + (blueMask & ((col & blueMask ) * N + (dst & blueMask ) * (M - N)) / M) | + alpha; +} + + +//inline +//double fastSqrt(double n) +//{ +// __asm //speeds up xBRZ by about 9% compared to std::sqrt +// { +// fld n +// fsqrt +// } +//} +// + + +inline +uint32_t alphaBlend2(uint32_t pix1, uint32_t pix2, double alpha) +{ + return (redMask & static_cast((pix1 & redMask ) * alpha + (pix2 & redMask ) * (1 - alpha))) | + (greenMask & static_cast((pix1 & greenMask) * alpha + (pix2 & greenMask) * (1 - alpha))) | + (blueMask & static_cast((pix1 & blueMask ) * alpha + (pix2 & blueMask ) * (1 - alpha))); +} + + +uint32_t* byteAdvance( uint32_t* ptr, int bytes) { return reinterpret_cast< uint32_t*>(reinterpret_cast< char*>(ptr) + bytes); } +const uint32_t* byteAdvance(const uint32_t* ptr, int bytes) { return reinterpret_cast(reinterpret_cast(ptr) + bytes); } + + +//fill block with the given color +inline +void fillBlock(uint32_t* trg, int pitch, uint32_t col, int blockWidth, int blockHeight) +{ + //for (int y = 0; y < blockHeight; ++y, trg = byteAdvance(trg, pitch)) + // std::fill(trg, trg + blockWidth, col); + + for (int y = 0; y < blockHeight; ++y, trg = byteAdvance(trg, pitch)) + for (int x = 0; x < blockWidth; ++x) + trg[x] = col; +} + +inline +void fillBlock(uint32_t* trg, int pitch, uint32_t col, int n) { fillBlock(trg, pitch, col, n, n); } + + +#ifdef _MSC_VER +#define FORCE_INLINE __forceinline +#elif defined __GNUC__ +#define FORCE_INLINE __attribute__((always_inline)) inline +#else +#define FORCE_INLINE inline +#endif + + +enum RotationDegree //clock-wise +{ + ROT_0, + ROT_90, + ROT_180, + ROT_270 +}; + +//calculate input matrix coordinates after rotation at compile time +template +struct MatrixRotation; + +template +struct MatrixRotation +{ + static const size_t I_old = I; + static const size_t J_old = J; +}; + +template //(i, j) = (row, col) indices, N = size of (square) matrix +struct MatrixRotation +{ + static const size_t I_old = N - 1 - MatrixRotation(rotDeg - 1), I, J, N>::J_old; //old coordinates before rotation! + static const size_t J_old = MatrixRotation(rotDeg - 1), I, J, N>::I_old; // +}; + + +template +class OutputMatrix +{ +public: + OutputMatrix(uint32_t* out, int outWidth) : //access matrix area, top-left at position "out" for image with given width + out_(out), + outWidth_(outWidth) {} + + template + uint32_t& ref() const + { + static const size_t I_old = MatrixRotation::I_old; + static const size_t J_old = MatrixRotation::J_old; + return *(out_ + J_old + I_old * outWidth_); + } + +private: + uint32_t* out_; + const int outWidth_; +}; + + +template inline +T square(T value) { return value * value; } + + +/* +inline +void rgbtoLuv(uint32_t c, double& L, double& u, double& v) +{ + //http://www.easyrgb.com/index.php?X=MATH&H=02#text2 + double r = getRed (c) / 255.0; + double g = getGreen(c) / 255.0; + double b = getBlue (c) / 255.0; + + if ( r > 0.04045 ) + r = std::pow(( ( r + 0.055 ) / 1.055 ) , 2.4); + else + r /= 12.92; + if ( g > 0.04045 ) + g = std::pow(( ( g + 0.055 ) / 1.055 ) , 2.4); + else + g /= 12.92; + if ( b > 0.04045 ) + b = std::pow(( ( b + 0.055 ) / 1.055 ) , 2.4); + else + b /= 12.92; + + r *= 100; + g *= 100; + b *= 100; + + double x = 0.4124564 * r + 0.3575761 * g + 0.1804375 * b; + double y = 0.2126729 * r + 0.7151522 * g + 0.0721750 * b; + double z = 0.0193339 * r + 0.1191920 * g + 0.9503041 * b; + //--------------------- + double var_U = 4 * x / ( x + 15 * y + 3 * z ); + double var_V = 9 * y / ( x + 15 * y + 3 * z ); + double var_Y = y / 100; + + if ( var_Y > 0.008856 ) var_Y = std::pow(var_Y , 1.0/3 ); + else var_Y = 7.787 * var_Y + 16.0 / 116; + + const double ref_X = 95.047; //Observer= 2 degree, Illuminant= D65 + const double ref_Y = 100.000; + const double ref_Z = 108.883; + + const double ref_U = ( 4 * ref_X ) / ( ref_X + ( 15 * ref_Y ) + ( 3 * ref_Z ) ); + const double ref_V = ( 9 * ref_Y ) / ( ref_X + ( 15 * ref_Y ) + ( 3 * ref_Z ) ); + + L = ( 116 * var_Y ) - 16; + u = 13 * L * ( var_U - ref_U ); + v = 13 * L * ( var_V - ref_V ); +} +*/ + +inline +void rgbtoLab(uint32_t c, unsigned char& L, signed char& A, signed char& B) +{ + //code: http://www.easyrgb.com/index.php?X=MATH + //test: http://www.workwithcolor.com/color-converter-01.htm + //------RGB to XYZ------ + double r = getRed (c) / 255.0; + double g = getGreen(c) / 255.0; + double b = getBlue (c) / 255.0; + + r = r > 0.04045 ? std::pow(( r + 0.055 ) / 1.055, 2.4) : r / 12.92; + r = g > 0.04045 ? std::pow(( g + 0.055 ) / 1.055, 2.4) : g / 12.92; + r = b > 0.04045 ? std::pow(( b + 0.055 ) / 1.055, 2.4) : b / 12.92; + + r *= 100; + g *= 100; + b *= 100; + + double x = 0.4124564 * r + 0.3575761 * g + 0.1804375 * b; + double y = 0.2126729 * r + 0.7151522 * g + 0.0721750 * b; + double z = 0.0193339 * r + 0.1191920 * g + 0.9503041 * b; + //------XYZ to Lab------ + const double refX = 95.047; // + const double refY = 100.000; //Observer= 2 degree, Illuminant= D65 + const double refZ = 108.883; // + double var_X = x / refX; + double var_Y = y / refY; + double var_Z = z / refZ; + + var_X = var_X > 0.008856 ? std::pow(var_X, 1.0 / 3) : 7.787 * var_X + 4.0 / 29; + var_Y = var_Y > 0.008856 ? std::pow(var_Y, 1.0 / 3) : 7.787 * var_Y + 4.0 / 29; + var_Z = var_Z > 0.008856 ? std::pow(var_Z, 1.0 / 3) : 7.787 * var_Z + 4.0 / 29; + + L = static_cast(116 * var_Y - 16); + A = static_cast< signed char>(500 * (var_X - var_Y)); + B = static_cast< signed char>(200 * (var_Y - var_Z)); +}; + + +inline +double distLAB(uint32_t pix1, uint32_t pix2) +{ + unsigned char L1 = 0; //[0, 100] + signed char a1 = 0; //[-128, 127] + signed char b1 = 0; //[-128, 127] + rgbtoLab(pix1, L1, a1, b1); + + unsigned char L2 = 0; + signed char a2 = 0; + signed char b2 = 0; + rgbtoLab(pix2, L2, a2, b2); + + //----------------------------- + //http://www.easyrgb.com/index.php?X=DELT + + //Delta E/CIE76 + return std::sqrt(square(1.0 * L1 - L2) + + square(1.0 * a1 - a2) + + square(1.0 * b1 - b2)); +} + + +/* +inline +void rgbtoHsl(uint32_t c, double& h, double& s, double& l) +{ + //http://www.easyrgb.com/index.php?X=MATH&H=18#text18 + const int r = getRed (c); + const int g = getGreen(c); + const int b = getBlue (c); + + const int varMin = numeric::min(r, g, b); + const int varMax = numeric::max(r, g, b); + const int delMax = varMax - varMin; + + l = (varMax + varMin) / 2.0 / 255.0; + + if (delMax == 0) //gray, no chroma... + { + h = 0; + s = 0; + } + else + { + s = l < 0.5 ? + delMax / (1.0 * varMax + varMin) : + delMax / (2.0 * 255 - varMax - varMin); + + double delR = ((varMax - r) / 6.0 + delMax / 2.0) / delMax; + double delG = ((varMax - g) / 6.0 + delMax / 2.0) / delMax; + double delB = ((varMax - b) / 6.0 + delMax / 2.0) / delMax; + + if (r == varMax) + h = delB - delG; + else if (g == varMax) + h = 1 / 3.0 + delR - delB; + else if (b == varMax) + h = 2 / 3.0 + delG - delR; + + if (h < 0) + h += 1; + if (h > 1) + h -= 1; + } +} + +inline +double distHSL(uint32_t pix1, uint32_t pix2, double lightningWeight) +{ + double h1 = 0; + double s1 = 0; + double l1 = 0; + rgbtoHsl(pix1, h1, s1, l1); + double h2 = 0; + double s2 = 0; + double l2 = 0; + rgbtoHsl(pix2, h2, s2, l2); + + //HSL is in cylindric coordinatates where L represents height, S radius, H angle, + //however we interpret the cylinder as a bi-conic solid with top/bottom radius 0, middle radius 1 + assert(0 <= h1 && h1 <= 1); + assert(0 <= h2 && h2 <= 1); + + double r1 = l1 < 0.5 ? + l1 * 2 : + 2 - l1 * 2; + + double x1 = r1 * s1 * std::cos(h1 * 2 * numeric::pi); + double y1 = r1 * s1 * std::sin(h1 * 2 * numeric::pi); + double z1 = l1; + + double r2 = l2 < 0.5 ? + l2 * 2 : + 2 - l2 * 2; + + double x2 = r2 * s2 * std::cos(h2 * 2 * numeric::pi); + double y2 = r2 * s2 * std::sin(h2 * 2 * numeric::pi); + double z2 = l2; + + return 255 * std::sqrt(square(x1 - x2) + square(y1 - y2) + square(lightningWeight * (z1 - z2))); +} +*/ + + +inline +double distRGB(uint32_t pix1, uint32_t pix2) +{ + const double r_diff = static_cast(getRed (pix1)) - getRed (pix2); + const double g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); + const double b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); + + //euklidean RGB distance + return std::sqrt(square(r_diff) + square(g_diff) + square(b_diff)); +} + + +inline +double distNonLinearRGB(uint32_t pix1, uint32_t pix2) +{ + //non-linear rgb: http://www.compuphase.com/cmetric.htm + const double r_diff = static_cast(getRed (pix1)) - getRed (pix2); + const double g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); + const double b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); + + const double r_avg = (static_cast(getRed(pix1)) + getRed(pix2)) / 2; + return std::sqrt((2 + r_avg / 255) * square(r_diff) + 4 * square(g_diff) + (2 + (255 - r_avg) / 255) * square(b_diff)); +} + + +inline +double distYCbCr(uint32_t pix1, uint32_t pix2, double lumaWeight) +{ + //http://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.601_conversion + //YCbCr conversion is a matrix multiplication => take advantage of linearity by subtracting first! + const int r_diff = static_cast(getRed (pix1)) - getRed (pix2); //we may delay division by 255 to after matrix multiplication + const int g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); // + const int b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); //substraction for int is noticeable faster than for double! + + const double k_b = 0.0722; //ITU-R BT.709 conversion + const double k_r = 0.2126; // + const double k_g = 1 - k_b - k_r; + + const double scale_b = 0.5 / (1 - k_b); + const double scale_r = 0.5 / (1 - k_r); + + const double y = k_r * r_diff + k_g * g_diff + k_b * b_diff; //[!], analog YCbCr! + const double c_b = scale_b * (b_diff - y); + const double c_r = scale_r * (r_diff - y); + + //we skip division by 255 to have similar range like other distance functions + return std::sqrt(square(lumaWeight * y) + square(c_b) + square(c_r)); +} + + +inline +double distYUV(uint32_t pix1, uint32_t pix2, double luminanceWeight) +{ + //perf: it's not worthwhile to buffer the YUV-conversion, the direct code is faster by ~ 6% + //since RGB -> YUV conversion is essentially a matrix multiplication, we can calculate the RGB diff before the conversion (distributive property) + const double r_diff = static_cast(getRed (pix1)) - getRed (pix2); + const double g_diff = static_cast(getGreen(pix1)) - getGreen(pix2); + const double b_diff = static_cast(getBlue (pix1)) - getBlue (pix2); + + //http://en.wikipedia.org/wiki/YUV#Conversion_to.2Ffrom_RGB + const double w_b = 0.114; + const double w_r = 0.299; + const double w_g = 1 - w_r - w_b; + + const double u_max = 0.436; + const double v_max = 0.615; + + const double scale_u = u_max / (1 - w_b); + const double scale_v = v_max / (1 - w_r); + + double y = w_r * r_diff + w_g * g_diff + w_b * b_diff;//value range: 255 * [-1, 1] + double u = scale_u * (b_diff - y); //value range: 255 * 2 * u_max * [-1, 1] + double v = scale_v * (r_diff - y); //value range: 255 * 2 * v_max * [-1, 1] + +#ifndef NDEBUG + const double eps = 0.5; +#endif + assert(std::abs(y) <= 255 + eps); + assert(std::abs(u) <= 255 * 2 * u_max + eps); + assert(std::abs(v) <= 255 * 2 * v_max + eps); + + return std::sqrt(square(luminanceWeight * y) + square(u) + square(v)); +} + + +inline +double colorDist(uint32_t pix1, uint32_t pix2, double luminanceWeight) +{ + if (pix1 == pix2) //about 8% perf boost + return 0; + + //return distHSL(pix1, pix2, luminanceWeight); + //return distRGB(pix1, pix2); + //return distLAB(pix1, pix2); + //return distNonLinearRGB(pix1, pix2); + //return distYUV(pix1, pix2, luminanceWeight); + + return distYCbCr(pix1, pix2, luminanceWeight); +} + + +enum BlendType +{ + BLEND_NONE = 0, + BLEND_NORMAL, //a normal indication to blend + BLEND_DOMINANT, //a strong indication to blend + //attention: BlendType must fit into the value range of 2 bit!!! +}; + +struct BlendResult +{ + BlendType + /**/blend_f, blend_g, + /**/blend_j, blend_k; +}; + + +struct Kernel_4x4 //kernel for preprocessing step +{ + uint32_t + /**/a, b, c, d, + /**/e, f, g, h, + /**/i, j, k, l, + /**/m, n, o, p; +}; + +/* +input kernel area naming convention: +----------------- +| A | B | C | D | +----|---|---|---| +| E | F | G | H | //evalute the four corners between F, G, J, K +----|---|---|---| //input pixel is at position F +| I | J | K | L | +----|---|---|---| +| M | N | O | P | +----------------- +*/ +FORCE_INLINE //detect blend direction +BlendResult preProcessCorners(const Kernel_4x4& ker, const xbrz_old::ScalerCfg& cfg) //result: F, G, J, K corners of "GradientType" +{ + BlendResult result = {}; + + if ((ker.f == ker.g && + ker.j == ker.k) || + (ker.f == ker.j && + ker.g == ker.k)) + return result; + + auto dist = [&](uint32_t col1, uint32_t col2) { return colorDist(col1, col2, cfg.luminanceWeight_); }; + + const int weight = 4; + double jg = dist(ker.i, ker.f) + dist(ker.f, ker.c) + dist(ker.n, ker.k) + dist(ker.k, ker.h) + weight * dist(ker.j, ker.g); + double fk = dist(ker.e, ker.j) + dist(ker.j, ker.o) + dist(ker.b, ker.g) + dist(ker.g, ker.l) + weight * dist(ker.f, ker.k); + + if (jg < fk) //test sample: 70% of values max(jg, fk) / min(jg, fk) are between 1.1 and 3.7 with median being 1.8 + { + const bool dominantGradient = cfg.dominantDirectionThreshold * jg < fk; + if (ker.f != ker.g && ker.f != ker.j) + result.blend_f = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + + if (ker.k != ker.j && ker.k != ker.g) + result.blend_k = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + } + else if (fk < jg) + { + const bool dominantGradient = cfg.dominantDirectionThreshold * fk < jg; + if (ker.j != ker.f && ker.j != ker.k) + result.blend_j = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + + if (ker.g != ker.f && ker.g != ker.k) + result.blend_g = dominantGradient ? BLEND_DOMINANT : BLEND_NORMAL; + } + return result; +} + +struct Kernel_3x3 +{ + uint32_t + /**/a, b, c, + /**/d, e, f, + /**/g, h, i; +}; + +#define DEF_GETTER(x) template uint32_t inline get_##x(const Kernel_3x3& ker) { return ker.x; } +//we cannot and NEED NOT write "ker.##x" since ## concatenates preprocessor tokens but "." is not a token +DEF_GETTER(a) DEF_GETTER(b) DEF_GETTER(c) +DEF_GETTER(d) DEF_GETTER(e) DEF_GETTER(f) +DEF_GETTER(g) DEF_GETTER(h) DEF_GETTER(i) +#undef DEF_GETTER + +#define DEF_GETTER(x, y) template <> inline uint32_t get_##x(const Kernel_3x3& ker) { return ker.y; } +DEF_GETTER(a, g) DEF_GETTER(b, d) DEF_GETTER(c, a) +DEF_GETTER(d, h) DEF_GETTER(e, e) DEF_GETTER(f, b) +DEF_GETTER(g, i) DEF_GETTER(h, f) DEF_GETTER(i, c) +#undef DEF_GETTER + +#define DEF_GETTER(x, y) template <> inline uint32_t get_##x(const Kernel_3x3& ker) { return ker.y; } +DEF_GETTER(a, i) DEF_GETTER(b, h) DEF_GETTER(c, g) +DEF_GETTER(d, f) DEF_GETTER(e, e) DEF_GETTER(f, d) +DEF_GETTER(g, c) DEF_GETTER(h, b) DEF_GETTER(i, a) +#undef DEF_GETTER + +#define DEF_GETTER(x, y) template <> inline uint32_t get_##x(const Kernel_3x3& ker) { return ker.y; } +DEF_GETTER(a, c) DEF_GETTER(b, f) DEF_GETTER(c, i) +DEF_GETTER(d, b) DEF_GETTER(e, e) DEF_GETTER(f, h) +DEF_GETTER(g, a) DEF_GETTER(h, d) DEF_GETTER(i, g) +#undef DEF_GETTER + + +//compress four blend types into a single byte +inline BlendType getTopL (unsigned char b) { return static_cast(0x3 & b); } +inline BlendType getTopR (unsigned char b) { return static_cast(0x3 & (b >> 2)); } +inline BlendType getBottomR(unsigned char b) { return static_cast(0x3 & (b >> 4)); } +inline BlendType getBottomL(unsigned char b) { return static_cast(0x3 & (b >> 6)); } + +inline void setTopL (unsigned char& b, BlendType bt) { b |= bt; } //buffer is assumed to be initialized before preprocessing! +inline void setTopR (unsigned char& b, BlendType bt) { b |= (bt << 2); } +inline void setBottomR(unsigned char& b, BlendType bt) { b |= (bt << 4); } +inline void setBottomL(unsigned char& b, BlendType bt) { b |= (bt << 6); } + +inline bool blendingNeeded(unsigned char b) { return b != 0; } + +template inline +unsigned char rotateBlendInfo(unsigned char b) { return b; } +template <> inline unsigned char rotateBlendInfo(unsigned char b) { return ((b << 2) | (b >> 6)) & 0xff; } +template <> inline unsigned char rotateBlendInfo(unsigned char b) { return ((b << 4) | (b >> 4)) & 0xff; } +template <> inline unsigned char rotateBlendInfo(unsigned char b) { return ((b << 6) | (b >> 2)) & 0xff; } + + +#ifndef NDEBUG +int debugPixelX = -1; +int debugPixelY = 84; +bool breakIntoDebugger = false; +#endif + + +/* +input kernel area naming convention: +------------- +| A | B | C | +----|---|---| +| D | E | F | //input pixel is at position E +----|---|---| +| G | H | I | +------------- +*/ +template +FORCE_INLINE //perf: quite worth it! +void scalePixel(const Kernel_3x3& ker, + uint32_t* target, int trgWidth, + unsigned char blendInfo, //result of preprocessing all four corners of pixel "e" + const xbrz_old::ScalerCfg& cfg) +{ +#define a get_a(ker) +#define b get_b(ker) +#define c get_c(ker) +#define d get_d(ker) +#define e get_e(ker) +#define f get_f(ker) +#define g get_g(ker) +#define h get_h(ker) +#define i get_i(ker) + +#if 0 //#ifndef NDEBUG + if (breakIntoDebugger) + __debugbreak(); //__asm int 3; +#endif + + const unsigned char blend = rotateBlendInfo(blendInfo); + + if (getBottomR(blend) >= BLEND_NORMAL) + { + auto eq = [&](uint32_t col1, uint32_t col2) { return colorDist(col1, col2, cfg.luminanceWeight_) < cfg.equalColorTolerance_; }; + auto dist = [&](uint32_t col1, uint32_t col2) { return colorDist(col1, col2, cfg.luminanceWeight_); }; + + const bool doLineBlend = [&]() -> bool + { + if (getBottomR(blend) >= BLEND_DOMINANT) + return true; + + //make sure there is no second blending in an adjacent rotation for this pixel: handles insular pixels, mario eyes + if (getTopR(blend) != BLEND_NONE && !eq(e, g)) //but support double-blending for 90 degree corners + return false; + if (getBottomL(blend) != BLEND_NONE && !eq(e, c)) + return false; + + //no full blending for L-shapes; blend corner only (handles "mario mushroom eyes") + if (eq(g, h) && eq(h , i) && eq(i, f) && eq(f, c) && !eq(e, i)) + return false; + + return true; + }(); + + const uint32_t px = dist(e, f) <= dist(e, h) ? f : h; //choose most similar color + + OutputMatrix out(target, trgWidth); + + if (doLineBlend) + { + const double fg = dist(f, g); //test sample: 70% of values max(fg, hc) / min(fg, hc) are between 1.1 and 3.7 with median being 1.9 + const double hc = dist(h, c); // + + const bool haveShallowLine = cfg.steepDirectionThreshold * fg <= hc && e != g && d != g; + const bool haveSteepLine = cfg.steepDirectionThreshold * hc <= fg && e != c && b != c; + + if (haveShallowLine) + { + if (haveSteepLine) + Scaler::blendLineSteepAndShallow(px, out); + else + Scaler::blendLineShallow(px, out); + } + else + { + if (haveSteepLine) + Scaler::blendLineSteep(px, out); + else + Scaler::blendLineDiagonal(px,out); + } + } + else + Scaler::blendCorner(px, out); + } + +#undef a +#undef b +#undef c +#undef d +#undef e +#undef f +#undef g +#undef h +#undef i +} + + +template //scaler policy: see "Scaler2x" reference implementation +void scaleImage(const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, const xbrz_old::ScalerCfg& cfg, int yFirst, int yLast) +{ + yFirst = std::max(yFirst, 0); + yLast = std::min(yLast, srcHeight); + if (yFirst >= yLast || srcWidth <= 0) + return; + + const int trgWidth = srcWidth * Scaler::scale; + + //"use" space at the end of the image as temporary buffer for "on the fly preprocessing": we even could use larger area of + //"sizeof(uint32_t) * srcWidth * (yLast - yFirst)" bytes without risk of accidental overwriting before accessing + const int bufferSize = srcWidth; + unsigned char* preProcBuffer = reinterpret_cast(trg + yLast * Scaler::scale * trgWidth) - bufferSize; + std::fill(preProcBuffer, preProcBuffer + bufferSize, 0); + static_assert(BLEND_NONE == 0, ""); + + //initialize preprocessing buffer for first row: detect upper left and right corner blending + //this cannot be optimized for adjacent processing stripes; we must not allow for a memory race condition! + if (yFirst > 0) + { + const int y = yFirst - 1; + + const uint32_t* s_m1 = src + srcWidth * std::max(y - 1, 0); + const uint32_t* s_0 = src + srcWidth * y; //center line + const uint32_t* s_p1 = src + srcWidth * std::min(y + 1, srcHeight - 1); + const uint32_t* s_p2 = src + srcWidth * std::min(y + 2, srcHeight - 1); + + for (int x = 0; x < srcWidth; ++x) + { + const int x_m1 = std::max(x - 1, 0); + const int x_p1 = std::min(x + 1, srcWidth - 1); + const int x_p2 = std::min(x + 2, srcWidth - 1); + + Kernel_4x4 ker = {}; //perf: initialization is negligable + ker.a = s_m1[x_m1]; //read sequentially from memory as far as possible + ker.b = s_m1[x]; + ker.c = s_m1[x_p1]; + ker.d = s_m1[x_p2]; + + ker.e = s_0[x_m1]; + ker.f = s_0[x]; + ker.g = s_0[x_p1]; + ker.h = s_0[x_p2]; + + ker.i = s_p1[x_m1]; + ker.j = s_p1[x]; + ker.k = s_p1[x_p1]; + ker.l = s_p1[x_p2]; + + ker.m = s_p2[x_m1]; + ker.n = s_p2[x]; + ker.o = s_p2[x_p1]; + ker.p = s_p2[x_p2]; + + const BlendResult res = preProcessCorners(ker, cfg); + /* + preprocessing blend result: + --------- + | F | G | //evalute corner between F, G, J, K + ----|---| //input pixel is at position F + | J | K | + --------- + */ + setTopR(preProcBuffer[x], res.blend_j); + + if (x + 1 < srcWidth) + setTopL(preProcBuffer[x + 1], res.blend_k); + } + } + //------------------------------------------------------------------------------------ + + for (int y = yFirst; y < yLast; ++y) + { + uint32_t* out = trg + Scaler::scale * y * trgWidth; //consider MT "striped" access + + const uint32_t* s_m1 = src + srcWidth * std::max(y - 1, 0); + const uint32_t* s_0 = src + srcWidth * y; //center line + const uint32_t* s_p1 = src + srcWidth * std::min(y + 1, srcHeight - 1); + const uint32_t* s_p2 = src + srcWidth * std::min(y + 2, srcHeight - 1); + + unsigned char blend_xy1 = 0; //corner blending for current (x, y + 1) position + + for (int x = 0; x < srcWidth; ++x, out += Scaler::scale) + { +#ifndef NDEBUG + breakIntoDebugger = debugPixelX == x && debugPixelY == y; +#endif + //all those bounds checks have only insignificant impact on performance! + const int x_m1 = std::max(x - 1, 0); //perf: prefer array indexing to additional pointers! + const int x_p1 = std::min(x + 1, srcWidth - 1); + const int x_p2 = std::min(x + 2, srcWidth - 1); + + //evaluate the four corners on bottom-right of current pixel + unsigned char blend_xy = 0; //for current (x, y) position + { + Kernel_4x4 ker = {}; //perf: initialization is negligable + ker.a = s_m1[x_m1]; //read sequentially from memory as far as possible + ker.b = s_m1[x]; + ker.c = s_m1[x_p1]; + ker.d = s_m1[x_p2]; + + ker.e = s_0[x_m1]; + ker.f = s_0[x]; + ker.g = s_0[x_p1]; + ker.h = s_0[x_p2]; + + ker.i = s_p1[x_m1]; + ker.j = s_p1[x]; + ker.k = s_p1[x_p1]; + ker.l = s_p1[x_p2]; + + ker.m = s_p2[x_m1]; + ker.n = s_p2[x]; + ker.o = s_p2[x_p1]; + ker.p = s_p2[x_p2]; + + const BlendResult res = preProcessCorners(ker, cfg); + /* + preprocessing blend result: + --------- + | F | G | //evalute corner between F, G, J, K + ----|---| //current input pixel is at position F + | J | K | + --------- + */ + blend_xy = preProcBuffer[x]; + setBottomR(blend_xy, res.blend_f); //all four corners of (x, y) have been determined at this point due to processing sequence! + + setTopR(blend_xy1, res.blend_j); //set 2nd known corner for (x, y + 1) + preProcBuffer[x] = blend_xy1; //store on current buffer position for use on next row + + blend_xy1 = 0; + setTopL(blend_xy1, res.blend_k); //set 1st known corner for (x + 1, y + 1) and buffer for use on next column + + if (x + 1 < srcWidth) //set 3rd known corner for (x + 1, y) + setBottomL(preProcBuffer[x + 1], res.blend_g); + } + + //fill block of size scale * scale with the given color + fillBlock(out, trgWidth * sizeof(uint32_t), s_0[x], Scaler::scale); //place *after* preprocessing step, to not overwrite the results while processing the the last pixel! + + //blend four corners of current pixel + if (blendingNeeded(blend_xy)) //good 20% perf-improvement + { + Kernel_3x3 ker = {}; //perf: initialization is negligable + + ker.a = s_m1[x_m1]; //read sequentially from memory as far as possible + ker.b = s_m1[x]; + ker.c = s_m1[x_p1]; + + ker.d = s_0[x_m1]; + ker.e = s_0[x]; + ker.f = s_0[x_p1]; + + ker.g = s_p1[x_m1]; + ker.h = s_p1[x]; + ker.i = s_p1[x_p1]; + + scalePixel(ker, out, trgWidth, blend_xy, cfg); + scalePixel(ker, out, trgWidth, blend_xy, cfg); + scalePixel(ker, out, trgWidth, blend_xy, cfg); + scalePixel(ker, out, trgWidth, blend_xy, cfg); + } + } + } +} + + +struct Scaler2x +{ + static const int scale = 2; + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<3, 4>(out.template ref(), col); + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref<0, scale - 1>(), col); + alphaBlend<3, 4>(out.template ref<1, scale - 1>(), col); + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref<1, 0>(), col); + alphaBlend<1, 4>(out.template ref<0, 1>(), col); + alphaBlend<5, 6>(out.template ref<1, 1>(), col); //[!] fixes 7/8 used in xBR + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 2>(out.template ref<1, 1>(), col); + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaBlend<21, 100>(out.template ref<1, 1>(), col); //exact: 1 - pi/4 = 0.2146018366 + } +}; + + +struct Scaler3x +{ + static const int scale = 3; + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<1, 4>(out.template ref(), col); + + alphaBlend<3, 4>(out.template ref(), col); + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref<0, scale - 1>(), col); + alphaBlend<1, 4>(out.template ref<2, scale - 2>(), col); + + alphaBlend<3, 4>(out.template ref<1, scale - 1>(), col); + out.template ref<2, scale - 1>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref<2, 0>(), col); + alphaBlend<1, 4>(out.template ref<0, 2>(), col); + alphaBlend<3, 4>(out.template ref<2, 1>(), col); + alphaBlend<3, 4>(out.template ref<1, 2>(), col); + out.template ref<2, 2>() = col; + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 8>(out.template ref<1, 2>(), col); + alphaBlend<1, 8>(out.template ref<2, 1>(), col); + alphaBlend<7, 8>(out.template ref<2, 2>(), col); + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaBlend<45, 100>(out.template ref<2, 2>(), col); //exact: 0.4545939598 + //alphaBlend<14, 1000>(out.template ref<2, 1>(), col); //0.01413008627 -> negligable + //alphaBlend<14, 1000>(out.template ref<1, 2>(), col); //0.01413008627 + } +}; + + +struct Scaler4x +{ + static const int scale = 4; + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<1, 4>(out.template ref(), col); + + alphaBlend<3, 4>(out.template ref(), col); + alphaBlend<3, 4>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref<0, scale - 1>(), col); + alphaBlend<1, 4>(out.template ref<2, scale - 2>(), col); + + alphaBlend<3, 4>(out.template ref<1, scale - 1>(), col); + alphaBlend<3, 4>(out.template ref<3, scale - 2>(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<3, 4>(out.template ref<3, 1>(), col); + alphaBlend<3, 4>(out.template ref<1, 3>(), col); + alphaBlend<1, 4>(out.template ref<3, 0>(), col); + alphaBlend<1, 4>(out.template ref<0, 3>(), col); + alphaBlend<1, 3>(out.template ref<2, 2>(), col); //[!] fixes 1/4 used in xBR + out.template ref<3, 3>() = out.template ref<3, 2>() = out.template ref<2, 3>() = col; + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 2>(out.template ref(), col); + alphaBlend<1, 2>(out.template ref(), col); + out.template ref() = col; + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaBlend<68, 100>(out.template ref<3, 3>(), col); //exact: 0.6848532563 + alphaBlend< 9, 100>(out.template ref<3, 2>(), col); //0.08677704501 + alphaBlend< 9, 100>(out.template ref<2, 3>(), col); //0.08677704501 + } +}; + + +struct Scaler5x +{ + static const int scale = 5; + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<1, 4>(out.template ref(), col); + + alphaBlend<3, 4>(out.template ref(), col); + alphaBlend<3, 4>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref<0, scale - 1>(), col); + alphaBlend<1, 4>(out.template ref<2, scale - 2>(), col); + alphaBlend<1, 4>(out.template ref<4, scale - 3>(), col); + + alphaBlend<3, 4>(out.template ref<1, scale - 1>(), col); + alphaBlend<3, 4>(out.template ref<3, scale - 2>(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + out.template ref<4, scale - 1>() = col; + out.template ref<4, scale - 2>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref<0, scale - 1>(), col); + alphaBlend<1, 4>(out.template ref<2, scale - 2>(), col); + alphaBlend<3, 4>(out.template ref<1, scale - 1>(), col); + + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<3, 4>(out.template ref(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + + out.template ref() = col; + out.template ref() = col; + + out.template ref<4, scale - 1>() = col; + + alphaBlend<2, 3>(out.template ref<3, 3>(), col); + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 8>(out.template ref(), col); + alphaBlend<1, 8>(out.template ref(), col); + alphaBlend<1, 8>(out.template ref(), col); + + alphaBlend<7, 8>(out.template ref<4, 3>(), col); + alphaBlend<7, 8>(out.template ref<3, 4>(), col); + + out.template ref<4, 4>() = col; + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaBlend<86, 100>(out.template ref<4, 4>(), col); //exact: 0.8631434088 + alphaBlend<23, 100>(out.template ref<4, 3>(), col); //0.2306749731 + alphaBlend<23, 100>(out.template ref<3, 4>(), col); //0.2306749731 + //alphaBlend<8, 1000>(out.template ref<4, 2>(), col); //0.008384061834 -> negligable + //alphaBlend<8, 1000>(out.template ref<2, 4>(), col); //0.008384061834 + } +}; +} + + +struct Scaler6x +{ + static const int scale = 6; + + template + static void blendLineShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<1, 4>(out.template ref(), col); + + alphaBlend<3, 4>(out.template ref(), col); + alphaBlend<3, 4>(out.template ref(), col); + alphaBlend<3, 4>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineSteep(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref<0, scale - 1>(), col); + alphaBlend<1, 4>(out.template ref<2, scale - 2>(), col); + alphaBlend<1, 4>(out.template ref<4, scale - 3>(), col); + + alphaBlend<3, 4>(out.template ref<1, scale - 1>(), col); + alphaBlend<3, 4>(out.template ref<3, scale - 2>(), col); + alphaBlend<3, 4>(out.template ref<5, scale - 3>(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + out.template ref<4, scale - 1>() = col; + out.template ref<5, scale - 1>() = col; + + out.template ref<4, scale - 2>() = col; + out.template ref<5, scale - 2>() = col; + } + + template + static void blendLineSteepAndShallow(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 4>(out.template ref<0, scale - 1>(), col); + alphaBlend<1, 4>(out.template ref<2, scale - 2>(), col); + alphaBlend<3, 4>(out.template ref<1, scale - 1>(), col); + alphaBlend<3, 4>(out.template ref<3, scale - 2>(), col); + + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<1, 4>(out.template ref(), col); + alphaBlend<3, 4>(out.template ref(), col); + alphaBlend<3, 4>(out.template ref(), col); + + out.template ref<2, scale - 1>() = col; + out.template ref<3, scale - 1>() = col; + out.template ref<4, scale - 1>() = col; + out.template ref<5, scale - 1>() = col; + + out.template ref<4, scale - 2>() = col; + out.template ref<5, scale - 2>() = col; + + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendLineDiagonal(uint32_t col, OutputMatrix& out) + { + alphaBlend<1, 2>(out.template ref(), col); + alphaBlend<1, 2>(out.template ref(), col); + alphaBlend<1, 2>(out.template ref(), col); + + out.template ref() = col; + out.template ref() = col; + out.template ref() = col; + } + + template + static void blendCorner(uint32_t col, OutputMatrix& out) + { + //model a round corner + alphaBlend<97, 100>(out.template ref<5, 5>(), col); //exact: 0.9711013910 + alphaBlend<42, 100>(out.template ref<4, 5>(), col); //0.4236372243 + alphaBlend<42, 100>(out.template ref<5, 4>(), col); //0.4236372243 + alphaBlend< 6, 100>(out.template ref<5, 3>(), col); //0.05652034508 + alphaBlend< 6, 100>(out.template ref<3, 5>(), col); //0.05652034508 + } +}; + + +void xbrz_old::scale(size_t factor, const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, const xbrz_old::ScalerCfg& cfg, int yFirst, int yLast) +{ + switch (factor) + { + case 2: + return scaleImage(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 3: + return scaleImage(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 4: + return scaleImage(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 5: + return scaleImage(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + case 6: + return scaleImage(src, trg, srcWidth, srcHeight, cfg, yFirst, yLast); + } + assert(false); +} + + +bool xbrz_old::equalColor(uint32_t col1, uint32_t col2, double luminanceWeight, double equalColorTolerance) +{ + return colorDist(col1, col2, luminanceWeight) < equalColorTolerance; +} + + +void xbrz_old::nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, int srcPitch, + uint32_t* trg, int trgWidth, int trgHeight, int trgPitch, + SliceType st, int yFirst, int yLast) +{ + if (srcPitch < srcWidth * static_cast(sizeof(uint32_t)) || + trgPitch < trgWidth * static_cast(sizeof(uint32_t))) + { + assert(false); + return; + } + + switch (st) + { + case NN_SCALE_SLICE_SOURCE: + //nearest-neighbor (going over source image - fast for upscaling, since source is read only once + yFirst = std::max(yFirst, 0); + yLast = std::min(yLast, srcHeight); + if (yFirst >= yLast || trgWidth <= 0 || trgHeight <= 0) return; + + for (int y = yFirst; y < yLast; ++y) + { + //mathematically: ySrc = floor(srcHeight * yTrg / trgHeight) + // => search for integers in: [ySrc, ySrc + 1) * trgHeight / srcHeight + + //keep within for loop to support MT input slices! + const int yTrg_first = ( y * trgHeight + srcHeight - 1) / srcHeight; //=ceil(y * trgHeight / srcHeight) + const int yTrg_last = ((y + 1) * trgHeight + srcHeight - 1) / srcHeight; //=ceil(((y + 1) * trgHeight) / srcHeight) + const int blockHeight = yTrg_last - yTrg_first; + + if (blockHeight > 0) + { + const uint32_t* srcLine = byteAdvance(src, y * srcPitch); + uint32_t* trgLine = byteAdvance(trg, yTrg_first * trgPitch); + int xTrg_first = 0; + + for (int x = 0; x < srcWidth; ++x) + { + int xTrg_last = ((x + 1) * trgWidth + srcWidth - 1) / srcWidth; + const int blockWidth = xTrg_last - xTrg_first; + if (blockWidth > 0) + { + xTrg_first = xTrg_last; + fillBlock(trgLine, trgPitch, srcLine[x], blockWidth, blockHeight); + trgLine += blockWidth; + } + } + } + } + break; + + case NN_SCALE_SLICE_TARGET: + //nearest-neighbor (going over target image - slow for upscaling, since source is read multiple times missing out on cache! Fast for similar image sizes!) + yFirst = std::max(yFirst, 0); + yLast = std::min(yLast, trgHeight); + if (yFirst >= yLast || srcHeight <= 0 || srcWidth <= 0) return; + + for (int y = yFirst; y < yLast; ++y) + { + uint32_t* trgLine = byteAdvance(trg, y * trgPitch); + const int ySrc = srcHeight * y / trgHeight; + const uint32_t* srcLine = byteAdvance(src, ySrc * srcPitch); + for (int x = 0; x < trgWidth; ++x) + { + const int xSrc = srcWidth * x / trgWidth; + trgLine[x] = srcLine[xSrc]; + } + } + break; + } +} diff --git a/source/common/textures/hires/xbr/xbrz_old.h b/source/common/textures/hires/xbr/xbrz_old.h new file mode 100644 index 000000000..9a46f2a97 --- /dev/null +++ b/source/common/textures/hires/xbr/xbrz_old.h @@ -0,0 +1,87 @@ +// **************************************************************************** +// * This file is part of the HqMAME project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// * * +// * Additionally and as a special exception, the author gives permission * +// * to link the code of this program with the MAME library (or with modified * +// * versions of MAME that use the same license as MAME), and distribute * +// * linked combinations including the two. You must obey the GNU General * +// * Public License in all respects for all of the code used other than MAME. * +// * If you modify this file, you may extend this exception to your version * +// * of the file, but you are not obligated to do so. If you do not wish to * +// * do so, delete this exception statement from your version. * +// **************************************************************************** + +#ifndef __XBRZ_OLD_HEADER_INCLUDED__ +#define __XBRZ_OLD_HEADER_INCLUDED__ + +#include //size_t +#include //uint32_t +#include +#include "xbrz_config_old.h" + +namespace xbrz_old +{ +/* +------------------------------------------------------------------------- +| xBRZ: "Scale by rules" - high quality image upscaling filter by Zenju | +------------------------------------------------------------------------- +using a modified approach of xBR: +http://board.byuu.org/viewtopic.php?f=10&t=2248 +- new rule set preserving small image features +- support multithreading +- support 64 bit architectures +- support processing image slices +*/ + +/* +-> map source (srcWidth * srcHeight) to target (scale * width x scale * height) image, optionally processing a half-open slice of rows [yFirst, yLast) only +-> color format: ARGB (BGRA byte order), alpha channel unused +-> support for source/target pitch in bytes! +-> if your emulator changes only a few image slices during each cycle (e.g. Dosbox) then there's no need to run xBRZ on the complete image: + Just make sure you enlarge the source image slice by 2 rows on top and 2 on bottom (this is the additional range the xBRZ algorithm is using during analysis) + Caveat: If there are multiple changed slices, make sure they do not overlap after adding these additional rows in order to avoid a memory race condition + if you are using multiple threads for processing each enlarged slice! + +THREAD-SAFETY: - parts of the same image may be scaled by multiple threads as long as the [yFirst, yLast) ranges do not overlap! + - there is a minor inefficiency for the first row of a slice, so avoid processing single rows only + + +*/ +void scale(size_t factor, //valid range: 2 - 5 + const uint32_t* src, uint32_t* trg, int srcWidth, int srcHeight, + const ScalerCfg& cfg = ScalerCfg(), + int yFirst = 0, int yLast = std::numeric_limits::max()); //slice of source image + +void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, + uint32_t* trg, int trgWidth, int trgHeight); + +enum SliceType +{ + NN_SCALE_SLICE_SOURCE, + NN_SCALE_SLICE_TARGET, +}; +void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, int srcPitch, //pitch in bytes! + uint32_t* trg, int trgWidth, int trgHeight, int trgPitch, + SliceType st, int yFirst, int yLast); + +//parameter tuning +bool equalColor(uint32_t col1, uint32_t col2, double luminanceWeight, double equalColorTolerance); + + + + + +//########################### implementation ########################### +inline +void nearestNeighborScale(const uint32_t* src, int srcWidth, int srcHeight, + uint32_t* trg, int trgWidth, int trgHeight) +{ + nearestNeighborScale(src, srcWidth, srcHeight, srcWidth * sizeof(uint32_t), + trg, trgWidth, trgHeight, trgWidth * sizeof(uint32_t), + NN_SCALE_SLICE_TARGET, 0, trgHeight); +} +} + +#endif diff --git a/source/common/textures/hires/xbr/xbrz_tools.h b/source/common/textures/hires/xbr/xbrz_tools.h new file mode 100644 index 000000000..844201c52 --- /dev/null +++ b/source/common/textures/hires/xbr/xbrz_tools.h @@ -0,0 +1,270 @@ +// **************************************************************************** +// * This file is part of the xBRZ project. It is distributed under * +// * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// * * +// * Additionally and as a special exception, the author gives permission * +// * to link the code of this program with the following libraries * +// * (or with modified versions that use the same licenses), and distribute * +// * linked combinations including the two: MAME, FreeFileSync, Snes9x, ePSXe * +// * You must obey the GNU General Public License in all respects for all of * +// * the code used other than MAME, FreeFileSync, Snes9x, ePSXe. * +// * If you modify this file, you may extend this exception to your version * +// * of the file, but you are not obligated to do so. If you do not wish to * +// * do so, delete this exception statement from your version. * +// **************************************************************************** + +#ifndef XBRZ_TOOLS_H_825480175091875 +#define XBRZ_TOOLS_H_825480175091875 + +#include +#include +#include + + +namespace xbrz +{ +template inline +unsigned char getByte(uint32_t val) { return static_cast((val >> (8 * N)) & 0xff); } + +inline unsigned char getAlpha(uint32_t pix) { return getByte<3>(pix); } +inline unsigned char getRed (uint32_t pix) { return getByte<2>(pix); } +inline unsigned char getGreen(uint32_t pix) { return getByte<1>(pix); } +inline unsigned char getBlue (uint32_t pix) { return getByte<0>(pix); } + +inline uint32_t makePixel(unsigned char a, unsigned char r, unsigned char g, unsigned char b) { return (a << 24) | (r << 16) | (g << 8) | b; } +inline uint32_t makePixel( unsigned char r, unsigned char g, unsigned char b) { return (r << 16) | (g << 8) | b; } + +inline uint32_t rgb555to888(uint16_t pix) { return ((pix & 0x7C00) << 9) | ((pix & 0x03E0) << 6) | ((pix & 0x001F) << 3); } +inline uint32_t rgb565to888(uint16_t pix) { return ((pix & 0xF800) << 8) | ((pix & 0x07E0) << 5) | ((pix & 0x001F) << 3); } + +inline uint16_t rgb888to555(uint32_t pix) { return static_cast(((pix & 0xF80000) >> 9) | ((pix & 0x00F800) >> 6) | ((pix & 0x0000F8) >> 3)); } +inline uint16_t rgb888to565(uint32_t pix) { return static_cast(((pix & 0xF80000) >> 8) | ((pix & 0x00FC00) >> 5) | ((pix & 0x0000F8) >> 3)); } + + +template inline +Pix* byteAdvance(Pix* ptr, int bytes) +{ + using PixNonConst = typename std::remove_cv::type; + using PixByte = typename std::conditional::value, char, const char>::type; + + static_assert(std::is_integral::value, "Pix* is expected to be cast-able to char*"); + + return reinterpret_cast(reinterpret_cast(ptr) + bytes); +} + + +//fill block with the given color +template inline +void fillBlock(Pix* trg, int pitch /*[bytes]*/, Pix col, int blockWidth, int blockHeight) +{ + //for (int y = 0; y < blockHeight; ++y, trg = byteAdvance(trg, pitch)) + // std::fill(trg, trg + blockWidth, col); + + for (int y = 0; y < blockHeight; ++y, trg = byteAdvance(trg, pitch)) + for (int x = 0; x < blockWidth; ++x) + trg[x] = col; +} + + +//nearest-neighbor (going over target image - slow for upscaling, since source is read multiple times missing out on cache! Fast for similar image sizes!) +template +void nearestNeighborScale(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch /*[bytes]*/, + /**/ PixTrg* trg, int trgWidth, int trgHeight, int trgPitch /*[bytes]*/, + int yFirst, int yLast, PixConverter pixCvrt /*convert PixSrc to PixTrg*/) +{ + static_assert(std::is_integral::value, "PixSrc* is expected to be cast-able to char*"); + static_assert(std::is_integral::value, "PixTrg* is expected to be cast-able to char*"); + + static_assert(std::is_same::value, "PixConverter returning wrong pixel format"); + + if (srcPitch < srcWidth * static_cast(sizeof(PixSrc)) || + trgPitch < trgWidth * static_cast(sizeof(PixTrg))) + { + assert(false); + return; + } + + yFirst = std::max(yFirst, 0); + yLast = std::min(yLast, trgHeight); + if (yFirst >= yLast || srcHeight <= 0 || srcWidth <= 0) return; + + for (int y = yFirst; y < yLast; ++y) + { + const int ySrc = srcHeight * y / trgHeight; + const PixSrc* const srcLine = byteAdvance(src, ySrc * srcPitch); + PixTrg* const trgLine = byteAdvance(trg, y * trgPitch); + + for (int x = 0; x < trgWidth; ++x) + { + const int xSrc = srcWidth * x / trgWidth; + trgLine[x] = pixCvrt(srcLine[xSrc]); + } + } +} + + +//nearest-neighbor (going over source image - fast for upscaling, since source is read only once +template +void nearestNeighborScaleOverSource(const PixSrc* src, int srcWidth, int srcHeight, int srcPitch /*[bytes]*/, + /**/ PixTrg* trg, int trgWidth, int trgHeight, int trgPitch /*[bytes]*/, + int yFirst, int yLast, PixConverter pixCvrt /*convert PixSrc to PixTrg*/) +{ + static_assert(std::is_integral::value, "PixSrc* is expected to be cast-able to char*"); + static_assert(std::is_integral::value, "PixTrg* is expected to be cast-able to char*"); + + static_assert(std::is_same::value, "PixConverter returning wrong pixel format"); + + if (srcPitch < srcWidth * static_cast(sizeof(PixSrc)) || + trgPitch < trgWidth * static_cast(sizeof(PixTrg))) + { + assert(false); + return; + } + + yFirst = std::max(yFirst, 0); + yLast = std::min(yLast, srcHeight); + if (yFirst >= yLast || trgWidth <= 0 || trgHeight <= 0) return; + + for (int y = yFirst; y < yLast; ++y) + { + //mathematically: ySrc = floor(srcHeight * yTrg / trgHeight) + // => search for integers in: [ySrc, ySrc + 1) * trgHeight / srcHeight + + //keep within for loop to support MT input slices! + const int yTrgFirst = ( y * trgHeight + srcHeight - 1) / srcHeight; //=ceil(y * trgHeight / srcHeight) + const int yTrgLast = ((y + 1) * trgHeight + srcHeight - 1) / srcHeight; //=ceil(((y + 1) * trgHeight) / srcHeight) + const int blockHeight = yTrgLast - yTrgFirst; + + if (blockHeight > 0) + { + const PixSrc* srcLine = byteAdvance(src, y * srcPitch); + /**/ PixTrg* trgLine = byteAdvance(trg, yTrgFirst * trgPitch); + int xTrgFirst = 0; + + for (int x = 0; x < srcWidth; ++x) + { + const int xTrgLast = ((x + 1) * trgWidth + srcWidth - 1) / srcWidth; + const int blockWidth = xTrgLast - xTrgFirst; + if (blockWidth > 0) + { + xTrgFirst = xTrgLast; + + const auto trgPix = pixCvrt(srcLine[x]); + fillBlock(trgLine, trgPitch, trgPix, blockWidth, blockHeight); + trgLine += blockWidth; + } + } + } + } +} + + +template +void bilinearScale(const uint32_t* src, int srcWidth, int srcHeight, int srcPitch, + /**/ PixTrg* trg, int trgWidth, int trgHeight, int trgPitch, + int yFirst, int yLast, PixConverter pixCvrt /*convert uint32_t to PixTrg*/) +{ + static_assert(std::is_integral::value, "PixTrg* is expected to be cast-able to char*"); + static_assert(std::is_same::value, "PixConverter returning wrong pixel format"); + + if (srcPitch < srcWidth * static_cast(sizeof(uint32_t)) || + trgPitch < trgWidth * static_cast(sizeof(PixTrg))) + { + assert(false); + return; + } + + yFirst = std::max(yFirst, 0); + yLast = std::min(yLast, trgHeight); + if (yFirst >= yLast || srcHeight <= 0 || srcWidth <= 0) return; + + const double scaleX = static_cast(trgWidth ) / srcWidth; + const double scaleY = static_cast(trgHeight) / srcHeight; + + //perf notes: + // -> double-based calculation is (slightly) faster than float + // -> pre-calculation gives significant boost; std::vector<> memory allocation is negligible! + struct CoeffsX + { + int x1 = 0; + int x2 = 0; + double xx1 = 0; + double x2x = 0; + }; + std::vector buf(trgWidth); + for (int x = 0; x < trgWidth; ++x) + { + const int x1 = srcWidth * x / trgWidth; + int x2 = x1 + 1; + if (x2 == srcWidth) --x2; + + const double xx1 = x / scaleX - x1; + const double x2x = 1 - xx1; + + CoeffsX& bx = buf[x]; + bx.x1 = x1; + bx.x2 = x2; + bx.xx1 = xx1; + bx.x2x = x2x; + } + + for (int y = yFirst; y < yLast; ++y) + { + const int y1 = srcHeight * y / trgHeight; + int y2 = y1 + 1; + if (y2 == srcHeight) --y2; + + const double yy1 = y / scaleY - y1; + const double y2y = 1 - yy1; + + const uint32_t* const srcLine = byteAdvance(src, y1 * srcPitch); + const uint32_t* const srcLineNext = byteAdvance(src, y2 * srcPitch); + PixTrg* const trgLine = byteAdvance(trg, y * trgPitch); + + for (int x = 0; x < trgWidth; ++x) + { + //perf: do NOT "simplify" the variable layout without measurement! + const int x1 = buf[x].x1; + const int x2 = buf[x].x2; + const double xx1 = buf[x].xx1; + const double x2x = buf[x].x2x; + + const double x2xy2y = x2x * y2y; + const double xx1y2y = xx1 * y2y; + const double x2xyy1 = x2x * yy1; + const double xx1yy1 = xx1 * yy1; + + auto interpolate = [=](int offset) -> double + { + /* https://en.wikipedia.org/wiki/Bilinear_interpolation + (c11(x2 - x) + c21(x - x1)) * (y2 - y ) + + (c12(x2 - x) + c22(x - x1)) * (y - y1) */ + const auto c11 = (srcLine [x1] >> (8 * offset)) & 0xff; + const auto c21 = (srcLine [x2] >> (8 * offset)) & 0xff; + const auto c12 = (srcLineNext[x1] >> (8 * offset)) & 0xff; + const auto c22 = (srcLineNext[x2] >> (8 * offset)) & 0xff; + + return c11 * x2xy2y + c21 * xx1y2y + + c12 * x2xyy1 + c22 * xx1yy1; + }; + + const double bi = interpolate(0); + const double gi = interpolate(1); + const double ri = interpolate(2); + const double ai = interpolate(3); + + const auto b = static_cast(bi + 0.5); + const auto g = static_cast(gi + 0.5); + const auto r = static_cast(ri + 0.5); + const auto a = static_cast(ai + 0.5); + + const uint32_t trgPix = (a << 24) | (r << 16) | (g << 8) | b; + + trgLine[x] = pixCvrt(trgPix); + } + } +} +} + +#endif //XBRZ_TOOLS_H_825480175091875 diff --git a/source/common/textures/hw_material.cpp b/source/common/textures/hw_material.cpp new file mode 100644 index 000000000..33406ccd9 --- /dev/null +++ b/source/common/textures/hw_material.cpp @@ -0,0 +1,410 @@ +// +//--------------------------------------------------------------------------- +// +// Copyright(C) 2004-2016 Christoph Oelckers +// All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see http://www.gnu.org/licenses/ +// +//-------------------------------------------------------------------------- +// + +#include "filesystem.h" +#include "m_png.h" +#include "c_dispatch.h" +#include "hw_ihwtexture.h" +#include "hw_material.h" +#include "texturemanager.h" +#include "c_cvars.h" + +EXTERN_CVAR(Bool, gl_texture_usehires) +IHardwareTexture* CreateHardwareTexture(); + +//=========================================================================== +// +// Constructor +// +//=========================================================================== + +FMaterial::FMaterial(FTexture * tx, bool expanded) +{ + mShaderIndex = SHADER_Default; + sourcetex = tex = tx; + + if (tx->UseType == ETextureType::SWCanvas && static_cast(tx)->GetColorFormat() == 0) + { + mShaderIndex = SHADER_Paletted; + } + else if (tx->isWarped()) + { + mShaderIndex = tx->isWarped(); // This picks SHADER_Warp1 or SHADER_Warp2 + } + else if (tx->isHardwareCanvas()) + { + if (tx->shaderindex >= FIRST_USER_SHADER) + { + mShaderIndex = tx->shaderindex; + } + // no brightmap for cameratexture + } + else + { + if (tx->Normal && tx->Specular) + { + for (auto &texture : { tx->Normal, tx->Specular }) + { + mTextureLayers.Push(texture); + } + mShaderIndex = SHADER_Specular; + } + else if (tx->Normal && tx->Metallic && tx->Roughness && tx->AmbientOcclusion) + { + for (auto &texture : { tx->Normal, tx->Metallic, tx->Roughness, tx->AmbientOcclusion }) + { + mTextureLayers.Push(texture); + } + mShaderIndex = SHADER_PBR; + } + + tx->CreateDefaultBrightmap(); + if (tx->Brightmap) + { + mTextureLayers.Push(tx->Brightmap); + if (mShaderIndex == SHADER_Specular) + mShaderIndex = SHADER_SpecularBrightmap; + else if (mShaderIndex == SHADER_PBR) + mShaderIndex = SHADER_PBRBrightmap; + else + mShaderIndex = SHADER_Brightmap; + } + + if (tx->shaderindex >= FIRST_USER_SHADER) + { + const UserShaderDesc &usershader = usershaders[tx->shaderindex - FIRST_USER_SHADER]; + if (usershader.shaderType == mShaderIndex) // Only apply user shader if it matches the expected material + { + for (auto &texture : tx->CustomShaderTextures) + { + if (texture == nullptr) continue; + mTextureLayers.Push(texture); + } + mShaderIndex = tx->shaderindex; + } + } + } + mWidth = tx->GetTexelWidth(); + mHeight = tx->GetTexelHeight(); + mLeftOffset = tx->GetLeftOffset(0); // These only get used by decals and decals should not use renderer-specific offsets. + mTopOffset = tx->GetTopOffset(0); + mRenderWidth = tx->GetScaledWidth(); + mRenderHeight = tx->GetScaledHeight(); + mSpriteU[0] = mSpriteV[0] = 0.f; + mSpriteU[1] = mSpriteV[1] = 1.f; + + mExpanded = expanded; + if (expanded) + { + int oldwidth = mWidth; + int oldheight = mHeight; + + mTrimResult = TrimBorders(trim); // get the trim size before adding the empty frame + mWidth += 2; + mHeight += 2; + mRenderWidth = mRenderWidth * mWidth / oldwidth; + mRenderHeight = mRenderHeight * mHeight / oldheight; + + } + SetSpriteRect(); + + mTextureLayers.ShrinkToFit(); + tx->Material[expanded] = this; + if (tx->isHardwareCanvas()) tx->bTranslucent = 0; +} + +//=========================================================================== +// +// Destructor +// +//=========================================================================== + +FMaterial::~FMaterial() +{ +} + +//=========================================================================== +// +// Set the sprite rectangle +// +//=========================================================================== + +void FMaterial::SetSpriteRect() +{ + auto leftOffset = tex->GetLeftOffsetHW(); + auto topOffset = tex->GetTopOffsetHW(); + + float fxScale = (float)tex->Scale.X; + float fyScale = (float)tex->Scale.Y; + + // mSpriteRect is for positioning the sprite in the scene. + mSpriteRect.left = -leftOffset / fxScale; + mSpriteRect.top = -topOffset / fyScale; + mSpriteRect.width = mWidth / fxScale; + mSpriteRect.height = mHeight / fyScale; + + if (mExpanded) + { + // a little adjustment to make sprites look better with texture filtering: + // create a 1 pixel wide empty frame around them. + + int oldwidth = mWidth - 2; + int oldheight = mHeight - 2; + + leftOffset += 1; + topOffset += 1; + + // Reposition the sprite with the frame considered + mSpriteRect.left = -leftOffset / fxScale; + mSpriteRect.top = -topOffset / fyScale; + mSpriteRect.width = mWidth / fxScale; + mSpriteRect.height = mHeight / fyScale; + + if (mTrimResult) + { + mSpriteRect.left += trim[0] / fxScale; + mSpriteRect.top += trim[1] / fyScale; + + mSpriteRect.width -= (oldwidth - trim[2]) / fxScale; + mSpriteRect.height -= (oldheight - trim[3]) / fyScale; + + mSpriteU[0] = trim[0] / (float)mWidth; + mSpriteV[0] = trim[1] / (float)mHeight; + mSpriteU[1] -= (oldwidth - trim[0] - trim[2]) / (float)mWidth; + mSpriteV[1] -= (oldheight - trim[1] - trim[3]) / (float)mHeight; + } + } +} + + +//=========================================================================== +// +// Finds empty space around the texture. +// Used for sprites that got placed into a huge empty frame. +// +//=========================================================================== + +bool FMaterial::TrimBorders(uint16_t *rect) +{ + + auto texbuffer = sourcetex->CreateTexBuffer(0); + int w = texbuffer.mWidth; + int h = texbuffer.mHeight; + auto Buffer = texbuffer.mBuffer; + + if (texbuffer.mBuffer == nullptr) + { + return false; + } + if (w != mWidth || h != mHeight) + { + // external Hires replacements cannot be trimmed. + return false; + } + + int size = w*h; + if (size == 1) + { + // nothing to be done here. + rect[0] = 0; + rect[1] = 0; + rect[2] = 1; + rect[3] = 1; + return true; + } + int first, last; + + for(first = 0; first < size; first++) + { + if (Buffer[first*4+3] != 0) break; + } + if (first >= size) + { + // completely empty + rect[0] = 0; + rect[1] = 0; + rect[2] = 1; + rect[3] = 1; + return true; + } + + for(last = size-1; last >= first; last--) + { + if (Buffer[last*4+3] != 0) break; + } + + rect[1] = first / w; + rect[3] = 1 + last/w - rect[1]; + + rect[0] = 0; + rect[2] = w; + + unsigned char *bufferoff = Buffer + (rect[1] * w * 4); + h = rect[3]; + + for(int x = 0; x < w; x++) + { + for(int y = 0; y < h; y++) + { + if (bufferoff[(x+y*w)*4+3] != 0) goto outl; + } + rect[0]++; + } +outl: + rect[2] -= rect[0]; + + for(int x = w-1; rect[2] > 1; x--) + { + for(int y = 0; y < h; y++) + { + if (bufferoff[(x+y*w)*4+3] != 0) + { + return true; + } + } + rect[2]--; + } + return true; +} + +//=========================================================================== +// +// +// +//=========================================================================== + +IHardwareTexture *FMaterial::GetLayer(int i, int translation, FTexture **pLayer) +{ + FTexture *layer = i == 0 ? tex : mTextureLayers[i - 1]; + if (pLayer) *pLayer = layer; + + if (layer && layer->UseType!=ETextureType::Null) + { + IHardwareTexture *hwtex = layer->SystemTextures.GetHardwareTexture(translation, mExpanded); + if (hwtex == nullptr) + { + hwtex = CreateHardwareTexture(); + layer->SystemTextures.AddHardwareTexture(translation, mExpanded, hwtex); + } + return hwtex; + } + return nullptr; +} + +//=========================================================================== +// +// +// +//=========================================================================== + +int FMaterial::GetAreas(FloatRect **pAreas) const +{ + if (mShaderIndex == SHADER_Default) // texture splitting can only be done if there's no attached effects + { + *pAreas = sourcetex->areas; + return sourcetex->areacount; + } + else + { + return 0; + } +} + +//========================================================================== +// +// Gets a texture from the texture manager and checks its validity for +// GL rendering. +// +//========================================================================== + +FMaterial * FMaterial::ValidateTexture(FTexture * tex, bool expand, bool create) +{ +again: + if (tex && tex->isValid()) + { + if (tex->bNoExpand) expand = false; + + FMaterial *hwtex = tex->Material[expand]; + if (hwtex == NULL && create) + { + if (expand) + { + if (tex->isWarped() || tex->isHardwareCanvas() || tex->shaderindex >= FIRST_USER_SHADER || (tex->shaderindex >= SHADER_Specular && tex->shaderindex <= SHADER_PBRBrightmap)) + { + tex->bNoExpand = true; + goto again; + } + if (tex->Brightmap != NULL && + (tex->GetTexelWidth() != tex->Brightmap->GetTexelWidth() || + tex->GetTexelHeight() != tex->Brightmap->GetTexelHeight()) + ) + { + // do not expand if the brightmap's size differs. + tex->bNoExpand = true; + goto again; + } + } + hwtex = new FMaterial(tex, expand); + } + return hwtex; + } + return NULL; +} + +FMaterial * FMaterial::ValidateTexture(FTextureID no, bool expand, bool translate, bool create) +{ + return ValidateTexture(TexMan.GetTexture(no, translate), expand, create); +} + + +void DeleteMaterial(FMaterial* mat) +{ + delete mat; +} + + +//----------------------------------------------------------------------------- +// +// Make sprite offset adjustment user-configurable per renderer. +// +//----------------------------------------------------------------------------- + +extern int r_spriteadjustSW, r_spriteadjustHW; + +CUSTOM_CVAR(Int, r_spriteadjust, 2, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + r_spriteadjustHW = !!(self & 2); + r_spriteadjustSW = !!(self & 1); + for (int i = 0; i < TexMan.NumTextures(); i++) + { + auto tex = TexMan.GetTexture(FSetTextureID(i)); + if (tex->GetTexelLeftOffset(0) != tex->GetTexelLeftOffset(1) || tex->GetTexelTopOffset(0) != tex->GetTexelTopOffset(1)) + { + for (int i = 0; i < 2; i++) + { + auto mat = tex->GetMaterial(i); + if (mat != nullptr) mat->SetSpriteRect(); + } + } + + } +} diff --git a/source/common/textures/hw_material.h b/source/common/textures/hw_material.h new file mode 100644 index 000000000..f6311c962 --- /dev/null +++ b/source/common/textures/hw_material.h @@ -0,0 +1,140 @@ + +#ifndef __GL_MATERIAL_H +#define __GL_MATERIAL_H + +#include "m_fixed.h" +#include "textures.h" + +struct FRemapTable; +class IHardwareTexture; + +enum +{ + CLAMP_NONE = 0, + CLAMP_X = 1, + CLAMP_Y = 2, + CLAMP_XY = 3, + CLAMP_XY_NOMIP = 4, + CLAMP_NOFILTER = 5, + CLAMP_CAMTEX = 6, +}; + + +//=========================================================================== +// +// this is the material class for OpenGL. +// +//=========================================================================== + +class FMaterial +{ + TArray mTextureLayers; + int mShaderIndex; + + short mLeftOffset; + short mTopOffset; + short mWidth; + short mHeight; + short mRenderWidth; + short mRenderHeight; + bool mExpanded; + bool mTrimResult; + uint16_t trim[4]; + + float mSpriteU[2], mSpriteV[2]; + FloatRect mSpriteRect; + + bool TrimBorders(uint16_t *rect); + +public: + FTexture *tex; + FTexture *sourcetex; // in case of redirection this is different from tex. + + FMaterial(FTexture *tex, bool forceexpand); + ~FMaterial(); + void SetSpriteRect(); + int GetShaderIndex() const { return mShaderIndex; } + void AddTextureLayer(FTexture *tex) + { + ValidateTexture(tex, false); + mTextureLayers.Push(tex); + } + bool isMasked() const + { + return sourcetex->bMasked; + } + bool isExpanded() const + { + return mExpanded; + } + + int GetLayers() const + { + return mTextureLayers.Size() + 1; + } + + bool hasCanvas() + { + return tex->isHardwareCanvas(); + } + + IHardwareTexture *GetLayer(int i, int translation, FTexture **pLayer = nullptr); + + // Patch drawing utilities + + void GetSpriteRect(FloatRect * r) const + { + *r = mSpriteRect; + } + + // This is scaled size in integer units as needed by walls and flats + int TextureHeight() const { return mRenderHeight; } + int TextureWidth() const { return mRenderWidth; } + + int GetAreas(FloatRect **pAreas) const; + + int GetWidth() const + { + return mWidth; + } + + int GetHeight() const + { + return mHeight; + } + + int GetLeftOffset() const + { + return mLeftOffset; + } + + int GetTopOffset() const + { + return mTopOffset; + } + + // Get right/bottom UV coordinates for patch drawing + float GetUL() const { return 0; } + float GetVT() const { return 0; } + float GetUR() const { return 1; } + float GetVB() const { return 1; } + float GetU(float upix) const { return upix/(float)mWidth; } + float GetV(float vpix) const { return vpix/(float)mHeight; } + + float GetSpriteUL() const { return mSpriteU[0]; } + float GetSpriteVT() const { return mSpriteV[0]; } + float GetSpriteUR() const { return mSpriteU[1]; } + float GetSpriteVB() const { return mSpriteV[1]; } + + + static FMaterial *ValidateTexture(FTexture * tex, bool expand, bool create = true); + static FMaterial *ValidateTexture(FTextureID no, bool expand, bool trans, bool create = true); + const TArray &GetLayerArray() const + { + return mTextureLayers; + } +}; + +#endif + + diff --git a/source/common/textures/image.h b/source/common/textures/image.h index c6563dd1d..1f6fcb5fa 100644 --- a/source/common/textures/image.h +++ b/source/common/textures/image.h @@ -1,9 +1,7 @@ #pragma once #include -#include "zstring.h" #include "tarray.h" -#include "textures.h" #include "bitmap.h" #include "memarena.h" @@ -169,4 +167,6 @@ protected: }; -FTexture* CreateImageTexture(FImageSource* img, const char *name = nullptr) noexcept; +class FTexture; + +FTexture* CreateImageTexture(FImageSource* img, const char *name = nullptr) noexcept; diff --git a/source/core/textures/imagehelpers.h b/source/common/textures/imagehelpers.h similarity index 100% rename from source/core/textures/imagehelpers.h rename to source/common/textures/imagehelpers.h diff --git a/source/core/textures/imagetexture.cpp b/source/common/textures/imagetexture.cpp similarity index 100% rename from source/core/textures/imagetexture.cpp rename to source/common/textures/imagetexture.cpp diff --git a/source/common/textures/multipatchtexturebuilder.cpp b/source/common/textures/multipatchtexturebuilder.cpp new file mode 100644 index 000000000..3380968b0 --- /dev/null +++ b/source/common/textures/multipatchtexturebuilder.cpp @@ -0,0 +1,927 @@ +/* +** multipatchtexturebuilder.cpp +** Texture class for standard Doom multipatch textures +** +**--------------------------------------------------------------------------- +** Copyright 2004-2006 Randy Heit +** Copyright 2006-2018 Christoph Oelckers +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** 1. Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** 2. Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** 3. The name of the author may not be used to endorse or promote products +** derived from this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +**--------------------------------------------------------------------------- +** +** +*/ + +#include +#include "files.h" +#include "printf.h" +#include "filesystem.h" +#include "engineerrors.h" +#include "bitmap.h" +#include "textures.h" +#include "image.h" +#include "formats/multipatchtexture.h" +#include "texturemanager.h" + + +// On the Alpha, accessing the shorts directly if they aren't aligned on a +// 4-byte boundary causes unaligned access warnings. Why it does this at +// all and only while initing the textures is beyond me. + +#ifdef ALPHA +#define SAFESHORT(s) ((short)(((uint8_t *)&(s))[0] + ((uint8_t *)&(s))[1] * 256)) +#else +#define SAFESHORT(s) LittleShort(s) +#endif + + + +//-------------------------------------------------------------------------- +// +// Data structures for the TEXTUREx lumps +// +//-------------------------------------------------------------------------- + +// +// Each texture is composed of one or more patches, with patches being lumps +// stored in the WAD. The lumps are referenced by number, and patched into +// the rectangular texture space using origin and possibly other attributes. +// +struct mappatch_t +{ + int16_t originx; + int16_t originy; + int16_t patch; + int16_t stepdir; + int16_t colormap; +}; + +// +// A wall texture is a list of patches which are to be combined in a +// predefined order. +// +struct maptexture_t +{ + uint8_t name[8]; + uint16_t Flags; // [RH] Was unused + uint8_t ScaleX; // [RH] Scaling (8 is normal) + uint8_t ScaleY; // [RH] Same as above + int16_t width; + int16_t height; + uint8_t columndirectory[4]; // OBSOLETE + int16_t patchcount; + mappatch_t patches[1]; +}; + +#define MAPTEXF_WORLDPANNING 0x8000 + +// Strife uses versions of the above structures that remove all unused fields + +struct strifemappatch_t +{ + int16_t originx; + int16_t originy; + int16_t patch; +}; + +// +// A wall texture is a list of patches which are to be combined in a +// predefined order. +// +struct strifemaptexture_t +{ + uint8_t name[8]; + uint16_t Flags; // [RH] Was unused + uint8_t ScaleX; // [RH] Scaling (8 is normal) + uint8_t ScaleY; // [RH] Same as above + int16_t width; + int16_t height; + int16_t patchcount; + strifemappatch_t patches[1]; +}; + + +//========================================================================== +// +// In-memory representation of a single PNAMES lump entry +// +//========================================================================== + +struct FPatchLookup +{ + FString Name; +}; + + +void FMultipatchTextureBuilder::MakeTexture(BuildInfo &buildinfo, ETextureType usetype) +{ + FImageTexture *tex = new FImageTexture(nullptr, buildinfo.Name); + tex->SetUseType(usetype); + tex->bMultiPatch = true; + tex->Width = buildinfo.Width; + tex->Height = buildinfo.Height; + tex->_LeftOffset[0] = buildinfo.LeftOffset[0]; + tex->_LeftOffset[1] = buildinfo.LeftOffset[1]; + tex->_TopOffset[0] = buildinfo.TopOffset[0]; + tex->_TopOffset[1] = buildinfo.TopOffset[1]; + tex->Scale = buildinfo.Scale; + tex->bMasked = true; // we do not really know yet. + tex->bTranslucent = -1; + tex->bWorldPanning = buildinfo.bWorldPanning; + tex->bNoDecals = buildinfo.bNoDecals; + tex->SourceLump = buildinfo.DefinitionLump; + buildinfo.tex = tex; + TexMan.AddTexture(tex); +} + +//========================================================================== +// +// The reader for TEXTUREx +// +//========================================================================== + + +//========================================================================== +// +// FMultiPatchTexture :: FMultiPatchTexture +// +//========================================================================== + +void FMultipatchTextureBuilder::BuildTexture(const void *texdef, FPatchLookup *patchlookup, int maxpatchnum, bool strife, int deflumpnum, ETextureType usetype) +{ + BuildInfo &buildinfo = BuiltTextures[BuiltTextures.Reserve(1)]; + + union + { + const maptexture_t *d; + const strifemaptexture_t *s; + } + mtexture; + + union + { + const mappatch_t *d; + const strifemappatch_t *s; + } + mpatch; + + int i; + + mtexture.d = (const maptexture_t *)texdef; + int NumParts; + + if (strife) + { + NumParts = SAFESHORT(mtexture.s->patchcount); + } + else + { + NumParts = SAFESHORT(mtexture.d->patchcount); + } + + if (NumParts < 0) + { + I_Error("Bad texture directory"); + } + + buildinfo.Parts.Resize(NumParts); + buildinfo.Inits.Resize(NumParts); + buildinfo.Width = SAFESHORT(mtexture.d->width); + buildinfo.Height = SAFESHORT(mtexture.d->height); + buildinfo.Name = (char *)mtexture.d->name; + + buildinfo.Scale.X = mtexture.d->ScaleX ? mtexture.d->ScaleX / 8. : 1.; + buildinfo.Scale.Y = mtexture.d->ScaleY ? mtexture.d->ScaleY / 8. : 1.; + + if (mtexture.d->Flags & MAPTEXF_WORLDPANNING) + { + buildinfo.bWorldPanning = true; + } + + if (strife) + { + mpatch.s = &mtexture.s->patches[0]; + } + else + { + mpatch.d = &mtexture.d->patches[0]; + } + + for (i = 0; i < NumParts; ++i) + { + if (unsigned(LittleShort(mpatch.d->patch)) >= unsigned(maxpatchnum)) + { + I_Error("Bad PNAMES and/or texture directory:\n\nPNAMES has %d entries, but\n%s wants to use entry %d.", + maxpatchnum, buildinfo.Name.GetChars(), LittleShort(mpatch.d->patch) + 1); + } + buildinfo.Parts[i].OriginX = LittleShort(mpatch.d->originx); + buildinfo.Parts[i].OriginY = LittleShort(mpatch.d->originy); + buildinfo.Parts[i].Image = nullptr; + buildinfo.Inits[i].TexName = patchlookup[LittleShort(mpatch.d->patch)].Name; + buildinfo.Inits[i].UseType = ETextureType::WallPatch; + if (strife) + mpatch.s++; + else + mpatch.d++; + } + if (NumParts == 0) + { + Printf("Texture %s is left without any patches\n", buildinfo.Name.GetChars()); + } + + // Insert the incomplete texture right here so that it's in the correct place. + buildinfo.DefinitionLump = deflumpnum; + MakeTexture(buildinfo, usetype); +} + +//========================================================================== +// +// FTextureManager :: AddTexturesLump +// +//========================================================================== + +void FMultipatchTextureBuilder::AddTexturesLump(const void *lumpdata, int lumpsize, int deflumpnum, int patcheslump, int firstdup, bool texture1) +{ + TArray patchlookup; + int i; + uint32_t numpatches; + + if (firstdup == 0) + { + firstdup = (int)TexMan.NumTextures(); + } + + { + auto pnames = fileSystem.OpenFileReader(patcheslump); + numpatches = pnames.ReadUInt32(); + + // Check whether the amount of names reported is correct. + if ((signed)numpatches < 0) + { + Printf("Corrupt PNAMES lump found (negative amount of entries reported)\n"); + return; + } + + // Check whether the amount of names reported is correct. + int lumplength = fileSystem.FileLength(patcheslump); + if (numpatches > uint32_t((lumplength - 4) / 8)) + { + Printf("PNAMES lump is shorter than required (%u entries reported but only %d bytes (%d entries) long\n", + numpatches, lumplength, (lumplength - 4) / 8); + // Truncate but continue reading. Who knows how many such lumps exist? + numpatches = (lumplength - 4) / 8; + } + + // Catalog the patches these textures use so we know which + // textures they represent. + patchlookup.Resize(numpatches); + for (uint32_t i = 0; i < numpatches; ++i) + { + char pname[9]; + pnames.Read(pname, 8); + pname[8] = '\0'; + patchlookup[i].Name = pname; + } + } + + bool isStrife = false; + const uint32_t *maptex, *directory; + uint32_t maxoff; + int numtextures; + uint32_t offset = 0; // Shut up, GCC! + + maptex = (const uint32_t *)lumpdata; + numtextures = LittleLong(*maptex); + maxoff = lumpsize; + + if (maxoff < uint32_t(numtextures + 1) * 4) + { + Printf("Texture directory is too short\n"); + return; + } + + // Scan the texture lump to decide if it contains Doom or Strife textures + for (i = 0, directory = maptex + 1; i < numtextures; ++i) + { + offset = LittleLong(directory[i]); + if (offset > maxoff) + { + Printf("Bad texture directory\n"); + return; + } + + maptexture_t *tex = (maptexture_t *)((uint8_t *)maptex + offset); + + // There is bizzarely a Doom editing tool that writes to the + // first two elements of columndirectory, so I can't check those. + if (SAFESHORT(tex->patchcount) < 0 || + tex->columndirectory[2] != 0 || + tex->columndirectory[3] != 0) + { + isStrife = true; + break; + } + } + + + // Textures defined earlier in the lump take precedence over those defined later, + // but later TEXTUREx lumps take precedence over earlier ones. + for (i = 1, directory = maptex; i <= numtextures; ++i) + { + if (i == 1 && texture1) + { + // The very first texture is just a dummy. Copy its dimensions to texture 0. + // It still needs to be created in case someone uses it by name. + offset = LittleLong(directory[1]); + const maptexture_t *tex = (const maptexture_t *)((const uint8_t *)maptex + offset); + FTexture *tex0 = TexMan.ByIndex(0); + tex0->SetSize(SAFESHORT(tex->width), SAFESHORT(tex->height)); + } + + offset = LittleLong(directory[i]); + if (offset > maxoff) + { + Printf("Bad texture directory\n"); + return; + } + + // If this texture was defined already in this lump, skip it + // This could cause problems with animations that use the same name for intermediate + // textures. Should I be worried? + int j; + for (j = (int)TexMan.NumTextures() - 1; j >= firstdup; --j) + { + if (strnicmp(TexMan.ByIndex(j)->GetName(), (const char *)maptex + offset, 8) == 0) + break; + } + if (j + 1 == firstdup) + { + BuildTexture((const uint8_t *)maptex + offset, patchlookup.Data(), numpatches, isStrife, deflumpnum, (i == 1 && texture1) ? ETextureType::FirstDefined : ETextureType::Wall); + progressFunc(); + } + } +} + + +//========================================================================== +// +// FTextureManager :: AddTexturesLumps +// +//========================================================================== + +void FMultipatchTextureBuilder::AddTexturesLumps(int lump1, int lump2, int patcheslump) +{ + int firstdup = (int)TexMan.NumTextures(); + + if (lump1 >= 0) + { + FileData texdir = fileSystem.ReadFile(lump1); + AddTexturesLump(texdir.GetMem(), fileSystem.FileLength(lump1), lump1, patcheslump, firstdup, true); + } + if (lump2 >= 0) + { + FileData texdir = fileSystem.ReadFile(lump2); + AddTexturesLump(texdir.GetMem(), fileSystem.FileLength(lump2), lump2, patcheslump, firstdup, false); + } +} + +//========================================================================== +// +// THe reader for the textual format +// +//========================================================================== + + +//========================================================================== +// +// +// +//========================================================================== + +void FMultipatchTextureBuilder::ParsePatch(FScanner &sc, BuildInfo &info, TexPart & part, TexInit &init) +{ + FString patchname; + int Mirror = 0; + sc.MustGetString(); + + init.TexName = sc.String; + sc.MustGetStringName(","); + sc.MustGetNumber(); + part.OriginX = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + part.OriginY = sc.Number; + + if (sc.CheckString("{")) + { + while (!sc.CheckString("}")) + { + sc.MustGetString(); + if (sc.Compare("flipx")) + { + Mirror |= 1; + } + else if (sc.Compare("flipy")) + { + Mirror |= 2; + } + else if (sc.Compare("rotate")) + { + sc.MustGetNumber(); + sc.Number = (((sc.Number + 90) % 360) - 90); + if (sc.Number != 0 && sc.Number != 90 && sc.Number != 180 && sc.Number != -90) + { + sc.ScriptError("Rotation must be a multiple of 90 degrees."); + } + part.Rotate = (sc.Number / 90) & 3; + } + else if (sc.Compare("Translation")) + { + int match; + + info.bComplex = true; + if (part.Translation != NULL) delete part.Translation; + part.Translation = NULL; + part.Blend = 0; + static const char *maps[] = { "gold", "red", "green", "blue", "inverse", NULL }; + sc.MustGetString(); + + match = sc.MatchString(maps); + if (match >= 0) + { + part.Blend = BLEND_SPECIALCOLORMAP1 + match + 1; + } + else if (sc.Compare("ICE")) + { + part.Blend = BLEND_ICEMAP; + } + else if (sc.Compare("DESATURATE")) + { + sc.MustGetStringName(","); + sc.MustGetNumber(); + part.Blend = BLEND_DESATURATE1 + clamp(sc.Number - 1, 0, 30); + } + else + { + sc.UnGet(); + part.Translation = new FRemapTable; + part.Translation->MakeIdentity(); + do + { + sc.MustGetString(); + + try + { + part.Translation->AddToTranslation(sc.String); + } + catch (CRecoverableError &err) + { + sc.ScriptMessage("Error in translation '%s':\n" TEXTCOLOR_YELLOW "%s\n", sc.String, err.GetMessage()); + } + } while (sc.CheckString(",")); + } + + } + else if (sc.Compare("Colormap")) + { + float r1, g1, b1; + float r2, g2, b2; + + sc.MustGetFloat(); + r1 = (float)sc.Float; + sc.MustGetStringName(","); + sc.MustGetFloat(); + g1 = (float)sc.Float; + sc.MustGetStringName(","); + sc.MustGetFloat(); + b1 = (float)sc.Float; + if (!sc.CheckString(",")) + { + part.Blend = AddSpecialColormap(GPalette.BaseColors, 0, 0, 0, r1, g1, b1); + } + else + { + sc.MustGetFloat(); + r2 = (float)sc.Float; + sc.MustGetStringName(","); + sc.MustGetFloat(); + g2 = (float)sc.Float; + sc.MustGetStringName(","); + sc.MustGetFloat(); + b2 = (float)sc.Float; + part.Blend = AddSpecialColormap(GPalette.BaseColors, r1, g1, b1, r2, g2, b2); + } + } + else if (sc.Compare("Blend")) + { + info.bComplex = true; + if (part.Translation != NULL) delete part.Translation; + part.Translation = NULL; + part.Blend = 0; + + if (!sc.CheckNumber()) + { + sc.MustGetString(); + part.Blend = V_GetColor(NULL, sc); + } + else + { + int r, g, b; + + r = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + g = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + b = sc.Number; + //sc.MustGetStringName(","); This was never supposed to be here. + part.Blend = MAKERGB(r, g, b); + } + // Blend.a may never be 0 here. + if (sc.CheckString(",")) + { + sc.MustGetFloat(); + if (sc.Float > 0.f) + part.Blend.a = clamp(int(sc.Float * 255), 1, 254); + else + part.Blend = 0; + } + else part.Blend.a = 255; + } + else if (sc.Compare("alpha")) + { + sc.MustGetFloat(); + part.Alpha = clamp(int(sc.Float * BLENDUNIT), 0, BLENDUNIT); + // bComplex is not set because it is only needed when the style is not OP_COPY. + } + else if (sc.Compare("style")) + { + static const char *styles[] = { "copy", "translucent", "add", "subtract", "reversesubtract", "modulate", "copyalpha", "copynewalpha", "overlay", NULL }; + sc.MustGetString(); + part.op = sc.MustMatchString(styles); + info.bComplex |= (part.op != OP_COPY); + } + else if (sc.Compare("useoffsets")) + { + init.UseOffsets = true; + } + } + } + if (Mirror & 2) + { + part.Rotate = (part.Rotate + 2) & 3; + Mirror ^= 1; + } + if (Mirror & 1) + { + part.Rotate |= 4; + } +} + + +//========================================================================== +// +// Constructor for text based multipatch definitions +// +//========================================================================== + +void FMultipatchTextureBuilder::ParseTexture(FScanner &sc, ETextureType UseType) +{ + BuildInfo &buildinfo = BuiltTextures[BuiltTextures.Reserve(1)]; + + bool bSilent = false; + + buildinfo.textual = true; + sc.SetCMode(true); + sc.MustGetString(); + + const char *textureName = nullptr; + if (sc.Compare("optional")) + { + bSilent = true; + sc.MustGetString(); + if (sc.Compare(",")) + { + // this is not right. Apparently a texture named 'optional' is being defined right now... + sc.UnGet(); + textureName = "optional"; + bSilent = false; + } + } + buildinfo.Name = !textureName ? sc.String : textureName; + buildinfo.Name.ToUpper(); + sc.MustGetStringName(","); + sc.MustGetNumber(); + buildinfo.Width = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + buildinfo.Height = sc.Number; + + bool offset2set = false; + if (sc.CheckString("{")) + { + while (!sc.CheckString("}")) + { + sc.MustGetString(); + if (sc.Compare("XScale")) + { + sc.MustGetFloat(); + buildinfo.Scale.X = sc.Float; + if (buildinfo.Scale.X == 0) sc.ScriptError("Texture %s is defined with null x-scale\n", buildinfo.Name.GetChars()); + } + else if (sc.Compare("YScale")) + { + sc.MustGetFloat(); + buildinfo.Scale.Y = sc.Float; + if (buildinfo.Scale.Y == 0) sc.ScriptError("Texture %s is defined with null y-scale\n", buildinfo.Name.GetChars()); + } + else if (sc.Compare("WorldPanning")) + { + buildinfo.bWorldPanning = true; + } + else if (sc.Compare("NullTexture")) + { + UseType = ETextureType::Null; + } + else if (sc.Compare("NoDecals")) + { + buildinfo.bNoDecals = true; + } + else if (sc.Compare("Patch")) + { + TexPart part; + TexInit init; + ParsePatch(sc, buildinfo, part, init); + if (init.TexName.IsNotEmpty()) + { + buildinfo.Parts.Push(part); + init.UseType = ETextureType::WallPatch; + init.Silent = bSilent; + init.HasLine = true; + init.sc = sc; + buildinfo.Inits.Push(init); + } + part.Image = nullptr; + part.Translation = nullptr; + } + else if (sc.Compare("Sprite")) + { + TexPart part; + TexInit init; + ParsePatch(sc, buildinfo, part, init); + if (init.TexName.IsNotEmpty()) + { + buildinfo.Parts.Push(part); + init.UseType = ETextureType::Sprite; + init.Silent = bSilent; + init.HasLine = true; + init.sc = sc; + buildinfo.Inits.Push(init); + } + part.Image = nullptr; + part.Translation = nullptr; + } + else if (sc.Compare("Graphic")) + { + TexPart part; + TexInit init; + ParsePatch(sc, buildinfo, part, init); + if (init.TexName.IsNotEmpty()) + { + buildinfo.Parts.Push(part); + init.UseType = ETextureType::MiscPatch; + init.Silent = bSilent; + init.HasLine = true; + init.sc = sc; + buildinfo.Inits.Push(init); + } + part.Image = nullptr; + part.Translation = nullptr; + } + else if (sc.Compare("Offset")) + { + sc.MustGetNumber(); + buildinfo.LeftOffset[0] = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + buildinfo.TopOffset[0] = sc.Number; + if (!offset2set) + { + buildinfo.LeftOffset[1] = buildinfo.LeftOffset[0]; + buildinfo.TopOffset[1] = buildinfo.TopOffset[0]; + } + } + else if (sc.Compare("Offset2")) + { + sc.MustGetNumber(); + buildinfo.LeftOffset[1] = sc.Number; + sc.MustGetStringName(","); + sc.MustGetNumber(); + buildinfo.TopOffset[1] = sc.Number; + offset2set = true; + } + else + { + sc.ScriptError("Unknown texture property '%s'", sc.String); + } + } + } + + if (buildinfo.Width <= 0 || buildinfo.Height <= 0) + { + UseType = ETextureType::Null; + Printf("Texture %s has invalid dimensions (%d, %d)\n", buildinfo.Name.GetChars(), buildinfo.Width, buildinfo.Height); + buildinfo.Width = buildinfo.Height = 1; + } + + MakeTexture(buildinfo, UseType); + sc.SetCMode(false); +} + + + +//========================================================================== +// +// +// +//========================================================================== + +void FMultipatchTextureBuilder::ResolvePatches(BuildInfo &buildinfo) +{ + for (unsigned i = 0; i < buildinfo.Inits.Size(); i++) + { + FTextureID texno = TexMan.CheckForTexture(buildinfo.Inits[i].TexName, buildinfo.Inits[i].UseType); + if (texno == buildinfo.tex->id) // we found ourselves. Try looking for another one with the same name which is not a multipatch texture itself. + { + TArray list; + TexMan.ListTextures(buildinfo.Inits[i].TexName, list, true); + for (int i = list.Size() - 1; i >= 0; i--) + { + if (list[i] != buildinfo.tex->id && !TexMan.GetTexture(list[i])->bMultiPatch) + { + texno = list[i]; + break; + } + } + if (texno == buildinfo.tex->id) + { + if (buildinfo.Inits[i].HasLine) buildinfo.Inits[i].sc.Message(MSG_WARNING, "Texture '%s' references itself as patch\n", buildinfo.Inits[i].TexName.GetChars()); + else Printf(TEXTCOLOR_YELLOW "Texture '%s' references itself as patch\n", buildinfo.Inits[i].TexName.GetChars()); + continue; + } + else + { + // If it could be resolved, just print a developer warning. + DPrintf(DMSG_WARNING, "Resolved self-referencing texture by picking an older entry for %s\n", buildinfo.Inits[i].TexName.GetChars()); + } + } + + if (!texno.isValid()) + { + if (!buildinfo.Inits[i].Silent) + { + if (buildinfo.Inits[i].HasLine) buildinfo.Inits[i].sc.Message(MSG_WARNING, "Unknown patch '%s' in texture '%s'\n", buildinfo.Inits[i].TexName.GetChars(), buildinfo.Name.GetChars()); + else Printf(TEXTCOLOR_YELLOW "Unknown patch '%s' in texture '%s'\n", buildinfo.Inits[i].TexName.GetChars(), buildinfo.Name.GetChars()); + } + } + else + { + FTexture *tex = TexMan.GetTexture(texno); + + if (tex != nullptr && tex->isValid()) + { + //We cannot set the image source yet. First all textures need to be resolved. + buildinfo.Inits[i].Texture = tex; + buildinfo.tex->bComplex |= tex->bComplex; + buildinfo.bComplex |= tex->bComplex; + if (buildinfo.Inits[i].UseOffsets) + { + buildinfo.Parts[i].OriginX -= tex->GetLeftOffset(0); + buildinfo.Parts[i].OriginY -= tex->GetTopOffset(0); + } + } + else + { + // The patch is bogus. Remove it. + if (buildinfo.Inits[i].HasLine) buildinfo.Inits[i].sc.Message(MSG_WARNING, "Invalid patch '%s' in texture '%s'\n", buildinfo.Inits[i].TexName.GetChars(), buildinfo.Name.GetChars()); + else Printf(TEXTCOLOR_YELLOW "Invalid patch '%s' in texture '%s'\n", buildinfo.Inits[i].TexName.GetChars(), buildinfo.Name.GetChars()); + i--; + } + } + } + for (unsigned i = 0; i < buildinfo.Inits.Size(); i++) + { + if (buildinfo.Inits[i].Texture == nullptr) + { + buildinfo.Inits.Delete(i); + buildinfo.Parts.Delete(i); + i--; + } + } + + checkForHacks(buildinfo); +} + +void FMultipatchTextureBuilder::ResolveAllPatches() +{ + for (auto &bi : BuiltTextures) + { + ResolvePatches(bi); + } + // Now try to resolve the images. We only can do this at the end when all multipatch textures are set up. + int i = 0; + + // reverse the list so that the Delete operation in the loop below deletes at the end. + // For normal sized lists this is of no real concern, but Total Chaos has over 250000 textures where this becomes a performance issue. + for (unsigned i = 0; i < BuiltTextures.Size() / 2; i++) + { + // std::swap is VERY inefficient here... + BuiltTextures[i].swap(BuiltTextures[BuiltTextures.Size() - 1 - i]); + } + + + while (BuiltTextures.Size() > 0) + { + bool donesomething = false; + + for (int i = BuiltTextures.Size()-1; i>= 0; i--) + { + auto &buildinfo = BuiltTextures[i]; + bool hasEmpty = false; + + for (unsigned j = 0; j < buildinfo.Inits.Size(); j++) + { + if (buildinfo.Parts[j].Image == nullptr) + { + auto image = buildinfo.Inits[j].Texture->GetImage(); + if (image != nullptr) + { + buildinfo.Parts[j].Image = image; + donesomething = true; + } + else hasEmpty = true; + } + } + if (!hasEmpty) + { + // If this texture is just a wrapper around a single patch, we can simply + // use that patch's image directly here. + + bool done = false; + if (buildinfo.Parts.Size() == 1) + { + if (buildinfo.Parts[0].OriginX == 0 && buildinfo.Parts[0].OriginY == 0 && + buildinfo.Parts[0].Image->GetWidth() == buildinfo.Width && + buildinfo.Parts[0].Image->GetHeight() == buildinfo.Height && + buildinfo.Parts[0].Rotate == 0 && + !buildinfo.bComplex) + { + buildinfo.tex->SetImage(buildinfo.Parts[0].Image); + done = true; + } + } + if (!done) + { + auto img = new FMultiPatchTexture(buildinfo.Width, buildinfo.Height, buildinfo.Parts, buildinfo.bComplex, buildinfo.textual); + buildinfo.tex->SetImage(img); + } + + BuiltTextures.Delete(i); + donesomething = true; + } + } + if (!donesomething) + { + Printf("%d Unresolved textures remain\n", BuiltTextures.Size()); + for (auto &b : BuiltTextures) + { + Printf("%s\n", b.Name.GetChars()); + b.tex->SetUseType(ETextureType::Null); + } + break; + } + } +} diff --git a/source/core/textures/skyboxtexture.cpp b/source/common/textures/skyboxtexture.cpp similarity index 77% rename from source/core/textures/skyboxtexture.cpp rename to source/common/textures/skyboxtexture.cpp index 186d15107..a1df3a9b1 100644 --- a/source/core/textures/skyboxtexture.cpp +++ b/source/common/textures/skyboxtexture.cpp @@ -36,6 +36,7 @@ #include "textures.h" #include "skyboxtexture.h" #include "bitmap.h" +#include "texturemanager.h" @@ -48,6 +49,15 @@ FSkyBox::FSkyBox(const char *name) : FTexture(name) { + FTextureID texid = TexMan.CheckForTexture(name, ETextureType::Wall); + previous = nullptr; + if (texid.isValid()) + { + previous = TexMan.GetTexture(texid); + CopySize(previous); + } + faces[0]=faces[1]=faces[2]=faces[3]=faces[4]=faces[5] = nullptr; + UseType = ETextureType::Override; bSkybox = true; fliptop = false; } @@ -58,9 +68,20 @@ FSkyBox::FSkyBox(const char *name) // //----------------------------------------------------------------------------- -FBitmap FSkyBox::GetBgraBitmap(PalEntry *p, int *trans) +TArray FSkyBox::Get8BitPixels(bool alphatex) { - return faces[0]->GetBgraBitmap(p, trans); + return previous->Get8BitPixels(alphatex); +} + +//----------------------------------------------------------------------------- +// +// +// +//----------------------------------------------------------------------------- + +FBitmap FSkyBox::GetBgraBitmap(const PalEntry *p, int *trans) +{ + return previous->GetBgraBitmap(p, trans); } //----------------------------------------------------------------------------- @@ -71,5 +92,5 @@ FBitmap FSkyBox::GetBgraBitmap(PalEntry *p, int *trans) FImageSource *FSkyBox::GetImage() const { - return faces[0]->GetImage(); + return previous->GetImage(); } diff --git a/source/core/textures/skyboxtexture.h b/source/common/textures/skyboxtexture.h similarity index 77% rename from source/core/textures/skyboxtexture.h rename to source/common/textures/skyboxtexture.h index 7651dc0c8..07a75f1e8 100644 --- a/source/core/textures/skyboxtexture.h +++ b/source/common/textures/skyboxtexture.h @@ -12,17 +12,23 @@ class FSkyBox : public FTexture { public: - FTexture* faces[6] = {}; + FTexture *previous; + FTexture * faces[6]; bool fliptop; FSkyBox(const char *name); + TArray Get8BitPixels(bool alphatex); FBitmap GetBgraBitmap(const PalEntry *, int *trans) override; FImageSource *GetImage() const override; void SetSize() { - CopySize(faces[0]); + if (!previous && faces[0]) previous = faces[0]; + if (previous) + { + CopySize(previous); + } } bool Is3Face() const diff --git a/source/common/textures/texture.cpp b/source/common/textures/texture.cpp index dc321d04b..26494fdb1 100644 --- a/source/common/textures/texture.cpp +++ b/source/common/textures/texture.cpp @@ -49,8 +49,8 @@ #include "texturemanager.h" // Wrappers to keep the definitions of these classes out of here. -void DeleteMaterial(FMaterial* mat) {} -void DeleteSoftwareTexture(FSoftwareTexture* swtex) {} +void DeleteMaterial(FMaterial* mat); +void DeleteSoftwareTexture(FSoftwareTexture* swtex); IHardwareTexture* CreateHardwareTexture(); diff --git a/source/common/textures/texturemanager.cpp b/source/common/textures/texturemanager.cpp index 686a9c098..66a196a31 100644 --- a/source/common/textures/texturemanager.cpp +++ b/source/common/textures/texturemanager.cpp @@ -119,12 +119,9 @@ void FTextureManager::FlushAll() { for (int j = 0; j < 2; j++) { - Textures[i].Texture->DeleteHardwareTextures(); -#if 0 Textures[i].Texture->SystemTextures.Clean(true, true); DeleteSoftwareTexture(Textures[i].Texture->SoftwareTexture); Textures[i].Texture->SoftwareTexture = nullptr; -#endif } } } @@ -217,6 +214,7 @@ FTextureID FTextureManager::CheckForTexture (const char *name, ETextureType uset } } + if (!(flags & TEXMAN_ShortNameOnly)) { // We intentionally only look for textures in subdirectories. @@ -234,7 +232,7 @@ FTextureID FTextureManager::CheckForTexture (const char *name, ETextureType uset tex = FTexture::CreateTexture("", lump, ETextureType::Override); if (tex != NULL) { - //tex->AddAutoMaterials(); + tex->AddAutoMaterials(); fileSystem.SetLinkedTexture(lump, tex); return AddTexture(tex); } @@ -575,7 +573,6 @@ void FTextureManager::AddHiresTextures (int wadnum) FTexture * oldtex = Textures[tlist[i].GetIndex()].Texture; // Replace the entire texture and adjust the scaling and offset factors. -#if 0 newtex->bWorldPanning = true; newtex->SetDisplaySize(oldtex->GetDisplayWidth(), oldtex->GetDisplayHeight()); newtex->_LeftOffset[0] = int(oldtex->GetScaledLeftOffset(0) * newtex->Scale.X); @@ -583,7 +580,6 @@ void FTextureManager::AddHiresTextures (int wadnum) newtex->_TopOffset[0] = int(oldtex->GetScaledTopOffset(0) * newtex->Scale.Y); newtex->_TopOffset[1] = int(oldtex->GetScaledTopOffset(1) * newtex->Scale.Y); ReplaceTexture(tlist[i], newtex, true); -#endif } } } @@ -616,7 +612,6 @@ void FTextureManager::LoadTextureDefs(int wadnum, const char *lumpname, FMultipa void FTextureManager::ParseTextureDef(int lump, FMultipatchTextureBuilder &build) { -#if 0 TArray tlist; FScanner sc(lump); @@ -770,7 +765,6 @@ void FTextureManager::ParseTextureDef(int lump, FMultipatchTextureBuilder &build sc.ScriptError("Texture definition expected, found '%s'", sc.String); } } -#endif } //========================================================================== @@ -811,7 +805,6 @@ void FTextureManager::AddPatches (int lumpnum) void FTextureManager::LoadTextureX(int wadnum, FMultipatchTextureBuilder &build) { -#if 0 // Use the most recent PNAMES for this WAD. // Multiple PNAMES in a WAD will be ignored. int pnames = fileSystem.CheckNumForName("PNAMES", ns_global, wadnum, false); @@ -829,7 +822,6 @@ void FTextureManager::LoadTextureX(int wadnum, FMultipatchTextureBuilder &build) int texlump1 = fileSystem.CheckNumForName ("TEXTURE1", ns_global, wadnum); int texlump2 = fileSystem.CheckNumForName ("TEXTURE2", ns_global, wadnum); build.AddTexturesLumps (texlump1, texlump2, pnames); -#endif } //========================================================================== @@ -1102,7 +1094,6 @@ void FTextureManager::Init(void (*progressFunc_)(), void (*checkForHacks)(BuildI auto nulltex = new FImageTexture(nullptr, ""); nulltex->SetUseType(ETextureType::Null); AddTexture (nulltex); -#if 0 // stuff for later. Not needed to get things going. // some special textures used in the game. AddTexture(CreateShaderTexture(false, false)); AddTexture(CreateShaderTexture(false, true)); @@ -1142,7 +1133,6 @@ void FTextureManager::Init(void (*progressFunc_)(), void (*checkForHacks)(BuildI glPart = TexMan.CheckForTexture("glstuff/glpart.png", ETextureType::MiscPatch); mirrorTexture = TexMan.CheckForTexture("glstuff/mirror.png", ETextureType::MiscPatch); AddLocalizedVariants(); -#endif } //========================================================================== @@ -1153,7 +1143,6 @@ void FTextureManager::Init(void (*progressFunc_)(), void (*checkForHacks)(BuildI void FTextureManager::InitPalettedVersions() { -#if 0 int lump, lastlump = 0; while ((lump = fileSystem.FindLump("PALVERS", &lastlump)) != -1) @@ -1182,7 +1171,6 @@ void FTextureManager::InitPalettedVersions() } } } -#endif } //========================================================================== @@ -1311,7 +1299,7 @@ int FTextureManager::CountLumpTextures (int lumpnum) void FTextureManager::AdjustSpriteOffsets() { - int /*lump,*/ lastlump = 0; + int lump, lastlump = 0; int sprid; TMap donotprocess; @@ -1335,7 +1323,6 @@ void FTextureManager::AdjustSpriteOffsets() } } -#if 0 while ((lump = fileSystem.FindLump("SPROFS", &lastlump, false)) != -1) { FScanner sc; @@ -1383,7 +1370,6 @@ void FTextureManager::AdjustSpriteOffsets() } } } -#endif } diff --git a/source/common/textures/texturemanager.h b/source/common/textures/texturemanager.h index 3f7b3312d..ef56ded53 100644 --- a/source/common/textures/texturemanager.h +++ b/source/common/textures/texturemanager.h @@ -30,9 +30,7 @@ private: if ((unsigned)texnum >= Textures.Size()) return nullptr; if (animate) texnum = Translation[texnum]; if (localize && Textures[texnum].HasLocalization) texnum = ResolveLocalizedTexture(texnum); -#if 0 if (palettesubst) texnum = PalCheck(texnum); -#endif return Textures[texnum].Texture; } public: diff --git a/source/common/textures/textures.h b/source/common/textures/textures.h index 20c9c497f..a4cb09e82 100644 --- a/source/common/textures/textures.h +++ b/source/common/textures/textures.h @@ -406,7 +406,6 @@ protected: double GetScaledHeightDouble () { return Height / Scale.Y; } double GetScaleY() const { return Scale.Y; } - // Now with improved offset adjustment. int GetLeftOffset(int adjusted) { return _LeftOffset[adjusted]; } int GetTopOffset(int adjusted) { return _TopOffset[adjusted]; } diff --git a/source/core/fonts/font.cpp b/source/core/fonts/font.cpp index 2370b0210..f2215c7cb 100644 --- a/source/core/fonts/font.cpp +++ b/source/core/fonts/font.cpp @@ -604,8 +604,8 @@ double GetBottomAlignOffset(FFont *font, int c) FTexture *tex_zero = font->GetChar('0', CR_UNDEFINED, &w); FTexture *texc = font->GetChar(c, CR_UNDEFINED, &w); double offset = 0; - if (texc) offset += texc->GetDisplayTopOffset(); - if (tex_zero) offset += -tex_zero->GetDisplayTopOffset() + tex_zero->GetDisplayHeight(); + if (texc) offset += texc->GetDisplayTopOffsetDouble(); + if (tex_zero) offset += -tex_zero->GetDisplayTopOffsetDouble() + tex_zero->GetDisplayHeightDouble(); return offset; } diff --git a/source/core/menu/menu.cpp b/source/core/menu/menu.cpp index 8a955bf01..284999171 100644 --- a/source/core/menu/menu.cpp +++ b/source/core/menu/menu.cpp @@ -54,6 +54,7 @@ #include "statistics.h" #include "input/m_joy.h" #include "raze_sound.h" +#include "texturemanager.h" void RegisterDukeMenus(); void RegisterRedneckMenus(); @@ -265,9 +266,10 @@ bool DMenu::MouseEventBack(int type, int x, int y) { if (m_show_backbutton >= 0) { - FTexture* tex = TileFiles.GetTexture("engine/graphics/m_back.png"); - if (tex != NULL) + auto texid = TexMan.CheckForTexture("engine/graphics/m_back.png", ETextureType::Any); + if (texid.isValid()) { + auto tex = TexMan.GetTexture(texid); if (m_show_backbutton&1) x -= screen->GetWidth() - tex->GetDisplayWidth() * CleanXfac; if (m_show_backbutton&2) y -= screen->GetHeight() - tex->GetDisplayHeight() * CleanYfac; mBackbuttonSelected = ( x >= 0 && x < tex->GetDisplayWidth() * CleanXfac && @@ -321,9 +323,11 @@ void DMenu::Drawer () { if (this == DMenu::CurrentMenu && BackbuttonAlpha > 0 && m_show_backbutton >= 0 && m_use_mouse) { - FTexture* tex = TileFiles.GetTexture("engine/graphics/m_back.png"); + auto texid = TexMan.CheckForTexture("engine/graphics/m_back.png", ETextureType::Any); + if (texid.isValid()) if (tex) { + auto tex = TexMan.GetTexture(texid); int w = tex->GetDisplayWidth() * CleanXfac; int h = tex->GetDisplayHeight() * CleanYfac; int x = (!(m_show_backbutton & 1)) ? 0 : screen->GetWidth() - w; diff --git a/source/core/menu/menudef.cpp b/source/core/menu/menudef.cpp index 01161ef93..426558f8a 100644 --- a/source/core/menu/menudef.cpp +++ b/source/core/menu/menudef.cpp @@ -48,7 +48,7 @@ #include "optionmenuitems.h" #include "i_soundfont.h" #include "zstring.h" -#include "buildtiles.h" +#include "texturemanager.h" #include // Menu-relevant content that gets filled in by scripts. This will get processed after the game has loaded. @@ -105,14 +105,13 @@ void M_DeinitMenus() static FTexture* GetMenuTexture(const char* const name) { - auto tex = TileFiles.GetTexture(name); - - if (!tex) + auto texid = TexMan.CheckForTexture(name, ETextureType::Any); + if (!texid.isValid()) { Printf("Missing menu texture: \"%s\"\n", name); } - return tex; + return TexMan.GetTexture(texid); } //============================================================================= diff --git a/source/core/rendering/gl/renderer/gl_renderer.cpp b/source/core/rendering/gl/renderer/gl_renderer.cpp index 40778380f..a8b6d4e76 100644 --- a/source/core/rendering/gl/renderer/gl_renderer.cpp +++ b/source/core/rendering/gl/renderer/gl_renderer.cpp @@ -177,7 +177,7 @@ void FGLRenderer::BindToFrameBuffer(FTexture *mat) if (BaseLayer == nullptr) { // must create the hardware texture first - BaseLayer = new ::FHardwareTexture(); + BaseLayer = new ::FHardwareTexture; BaseLayer->CreateTexture(mat->GetTexelWidth()*4, mat->GetTexelHeight()*4, ::FHardwareTexture::TrueColor, false); mat->SystemTextures.AddHardwareTexture(0, false, BaseLayer); } diff --git a/source/core/textures/buildtiles.cpp b/source/core/textures/buildtiles.cpp index f808e7690..e9f4c40c0 100644 --- a/source/core/textures/buildtiles.cpp +++ b/source/core/textures/buildtiles.cpp @@ -63,7 +63,7 @@ BuildTiles TileFiles; picanm_t tileConvertAnimFormat(int32_t const picanimraw, int* lo, int* to) { - // Unpack a 4 byte packed anim descriptor into the internal 5 byte format. + // Unpack a 4 byte packed anim descriptor into something more accessible picanm_t anm; anm.num = picanimraw & 63; *lo = (int8_t)((picanimraw >> 8) & 255); @@ -111,6 +111,14 @@ TArray FTileTexture::CreatePalettedPixels(int conversion) static FTexture* GetTileTexture(const char* name, const TArray& backingstore, uint32_t offset, int width, int height) { auto tex = new FArtTile(backingstore, offset, width, height); + auto p = &backingstore[offset]; + auto siz = width * height; + for (int i = 0; i < siz; i++, p++) + { + // move transparent color to index 0 to get in line with the rest of the texture management. + if (*p == 0) *p = 255; + else if (*p == 255) *p = 0; + } if (tex) { @@ -295,11 +303,11 @@ void BuildTiles::ClearTextureCache(bool artonly) { for (auto tex : AllTiles) { - tex->DeleteHardwareTextures(); + tex->SystemTextures.Clean(true, true); } for (auto tex : AllMapTiles) { - tex->DeleteHardwareTextures(); + tex->SystemTextures.Clean(true, true); } if (!artonly) { @@ -307,7 +315,7 @@ void BuildTiles::ClearTextureCache(bool artonly) decltype(textures)::Pair* pair; while (it.NextPair(pair)) { - pair->Value->DeleteHardwareTextures(); + pair->Value->SystemTextures.Clean(true, true); } } } @@ -323,14 +331,15 @@ void BuildTiles::InvalidateTile(int num) if ((unsigned) num < MAXTILES) { auto tex = tiledata[num].texture; - tex->DeleteHardwareTextures(); + tex->SystemTextures.Clean(true, true); for (auto &rep : tiledata[num].Hightiles) { for (auto &reptex : rep.faces) { - if (reptex) reptex->DeleteHardwareTextures(); + if (reptex) reptex->SystemTextures.Clean(true, true); } } + tiledata[num].rawCache.data.Clear(); } } @@ -534,7 +543,27 @@ int32_t tileGetCRC32(int tileNum) auto size = tile->GetWidth() * tile->GetHeight(); if (size == 0) return 0; - return crc32(0, (const Bytef*)pixels, size); + + // Temporarily revert the data to its original form with 255 being transparent. Otherwise the CRC won't match. + auto p = pixels; + for (int i = 0; i < size; i++, p++) + { + // move transparent color to index 0 to get in line with the rest of the texture management. + if (*p == 0) *p = 255; + else if (*p == 255) *p = 0; + } + + auto crc = crc32(0, (const Bytef*)pixels, size); + + // ... and back again. + p = pixels; + for (int i = 0; i < size; i++, p++) + { + // move transparent color to index 0 to get in line with the rest of the texture management. + if (*p == 0) *p = 255; + else if (*p == 255) *p = 0; + } + return crc; } @@ -692,6 +721,7 @@ void tileDelete(int tile) void tileRemoveReplacement(int tile) { + if ((unsigned)tile >= MAXTILES) return; TileFiles.DeleteReplacements(tile); } @@ -781,23 +811,6 @@ void tileSetAnim(int tile, const picanm_t& anm) // //========================================================================== -FTexture* BuildTiles::GetTexture(const char* path) -{ - // let this go away. - auto res = textures.CheckKey(path); - if (res) return *res; - auto lump = fileSystem.FindFile(path); - auto tex = FTexture::CreateTexture(path, lump, ETextureType::Override); - if (tex) textures.Insert(path, tex); - return tex; -} - -//========================================================================== -// -// -// -//========================================================================== - void BuildTiles::CloseAll() { decltype(textures)::Iterator it(textures); @@ -831,7 +844,7 @@ int tileSetHightileReplacement(int picnum, int palnum, const char* filename, flo HightileReplacement replace = {}; FTextureID texid = TexMan.CheckForTexture(filename, ETextureType::Any); - if (!texid.isValid()) + if (!texid.isValid()) { Printf("%s: Replacement for tile %d does not exist or is invalid\n", filename, picnum); return -1; @@ -839,7 +852,7 @@ int tileSetHightileReplacement(int picnum, int palnum, const char* filename, flo replace.faces[0] = TexMan.GetTexture(texid); if (replace.faces[0] == nullptr) - replace.alphacut = min(alphacut, 1.f); + replace.alphacut = min(alphacut,1.f); replace.scale = { xscale, yscale }; replace.specpower = specpower; // currently unused replace.specfactor = specfactor; // currently unused @@ -871,12 +884,13 @@ int tileSetSkybox(int picnum, int palnum, const char **facenames, int flags ) for (auto &face : replace.faces) { - face = TileFiles.GetTexture(*facenames); - if (face == nullptr) + FTextureID texid = TexMan.CheckForTexture(*facenames, ETextureType::Any); + if (!texid.isValid()) { Printf("%s: Skybox image for tile %d does not exist or is invalid\n", *facenames, picnum); return -1; } + face = TexMan.GetTexture(texid); } replace.flags = flags; replace.palnum = (uint16_t)palnum; diff --git a/source/core/textures/imagehelpers.cpp b/source/core/textures/imagehelpers.cpp deleted file mode 100644 index fb70316be..000000000 --- a/source/core/textures/imagehelpers.cpp +++ /dev/null @@ -1,46 +0,0 @@ - -/* - ** imagehelpers.cpp - ** Utilities for image conversion - mostly 8 bit paletted baggage - ** - **--------------------------------------------------------------------------- - ** Copyright 2004-2007 Randy Heit - ** Copyright 2006-2018 Christoph Oelckers - ** All rights reserved. - ** - ** Redistribution and use in source and binary forms, with or without - ** modification, are permitted provided that the following conditions - ** are met: - ** - ** 1. Redistributions of source code must retain the above copyright - ** notice, this list of conditions and the following disclaimer. - ** 2. Redistributions in binary form must reproduce the above copyright - ** notice, this list of conditions and the following disclaimer in the - ** documentation and/or other materials provided with the distribution. - ** 3. The name of the author may not be used to endorse or promote products - ** derived from this software without specific prior written permission. - ** - ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR - ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, - ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT - ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - **--------------------------------------------------------------------------- - ** - ** - */ - - #include "imagehelpers.h" -#include "palettecontainer.h" -#include "colormatcher.h" - -namespace ImageHelpers -{ - int alphaThreshold; - -} \ No newline at end of file diff --git a/source/glbackend/gl_texture.cpp b/source/glbackend/gl_texture.cpp index 187a2755e..7598cdb0c 100644 --- a/source/glbackend/gl_texture.cpp +++ b/source/glbackend/gl_texture.cpp @@ -412,4 +412,21 @@ bool GLInstance::SetNamedTexture(FTexture* tex, int palette, int sampler) return true; } +// stand-ins for the texture system. Nothing of this is used right now, but needs to be present to satisfy the linker +int PalCheck(int tex) +{ + return tex; +} + +void DeleteSoftwareTexture(FSoftwareTexture *) +{ + +} + +void InitBuildTiles() +{ + +} + +TArray usershaders; diff --git a/source/platform/win32/i_system.cpp b/source/platform/win32/i_system.cpp index b6b360817..44b883df0 100644 --- a/source/platform/win32/i_system.cpp +++ b/source/platform/win32/i_system.cpp @@ -659,8 +659,8 @@ bool I_SetCursor(FTexture *cursorpic) return false; } // Fixme: This should get a raw image, not a texture. (Once raw images get implemented.) - int lo = cursorpic->GetDisplayLeftOffset(); - int to = cursorpic->GetDisplayTopOffset(); + int lo = cursorpic->GetTexelLeftOffset(0); + int to = cursorpic->GetTexelTopOffset(0); cursor = CreateAlphaCursor(image, lo, to); if (cursor == NULL)