Browse Source

Merge with upstream e2d2f8e388a01cc3c2d2694c808d0d3b465d3763

# Conflicts:
#	.appveyor.yml
#	.bash_helpers.sh
#	.travis.yml
#	CMake/Modules/CheckHost.cmake
#	CMake/Modules/GenerateExportHeader.cmake
#	CMake/Modules/UrhoCommon.cmake
#	CMake/Modules/exportheader.cmake.in
#	CMakeLists.txt
#	Docs/AngelScriptAPI.h
#	Docs/GettingStarted.dox
#	Docs/LuaScriptAPI.dox
#	Docs/Reference.dox
#	Docs/ScriptAPI.dox
#	Docs/Urho3D.dox
#	README.md
#	Rakefile
#	Resources/EditorData/AtomicEditor/eulas/atomic_thirdparty_eula.txt
#	Source/Atomic/Atomic2D/TileMap2D.cpp
#	Source/Atomic/Atomic2D/TileMap2D.h
#	Source/Atomic/Atomic2D/TmxFile2D.cpp
#	Source/Atomic/Atomic2D/TmxFile2D.h
#	Source/Atomic/Core/StringUtils.cpp
#	Source/Atomic/Core/StringUtils.h
#	Source/Atomic/Graphics/Text3D/Text3DFreeType.cpp
#	Source/Atomic/IK/IKEffector.cpp
#	Source/Atomic/IK/IKSolver.cpp
#	Source/Atomic/IK/IKSolver.h
#	Source/Atomic/Navigation/DynamicNavigationMesh.cpp
#	Source/Atomic/Navigation/NavigationMesh.cpp
#	Source/Atomic/Navigation/Obstacle.h
#	Source/Atomic/Resource/Image.cpp
#	Source/Atomic/Resource/Image.h
#	Source/CMakeLists.txt
#	Source/Clang-Tools/CMakeLists.txt
#	Source/Samples/15_Navigation/Navigation.cpp
#	Source/Samples/15_Navigation/Navigation.h
#	Source/Samples/39_CrowdNavigation/CrowdNavigation.cpp
#	Source/Samples/39_CrowdNavigation/CrowdNavigation.h
#	Source/Samples/45_InverseKinematics/InverseKinematics.cpp
#	Source/Samples/47_Typography/Typography.cpp
#	Source/Samples/47_Typography/Typography.h
#	Source/ThirdParty/Lua/CMakeLists.txt
#	Source/ThirdParty/Lua/src/loslib.c
#	Source/ThirdParty/LuaJIT/CMakeLists.txt
#	Source/ThirdParty/LuaJIT/src/host/CMakeLists.txt
#	Source/ThirdParty/toluapp/src/bin/CMakeLists.txt
#	Source/Tools/CMakeLists.txt
#	Source/Tools/PackageTool/CMakeLists.txt
#	Source/Urho3D/.soversion
#	Source/Urho3D/AngelScript/APITemplates.h
#	Source/Urho3D/AngelScript/CoreAPI.cpp
#	Source/Urho3D/AngelScript/GraphicsAPI.cpp
#	Source/Urho3D/AngelScript/IKAPI.cpp
#	Source/Urho3D/AngelScript/MathAPI.cpp
#	Source/Urho3D/AngelScript/NavigationAPI.cpp
#	Source/Urho3D/AngelScript/ResourceAPI.cpp
#	Source/Urho3D/AngelScript/UIAPI.cpp
#	Source/Urho3D/AngelScript/Urho2DAPI.cpp
#	Source/Urho3D/CMakeLists.txt
#	Source/Urho3D/LuaScript/pkgs/Graphics/DebugRenderer.pkg
#	Source/Urho3D/LuaScript/pkgs/Graphics/Terrain.pkg
#	Source/Urho3D/LuaScript/pkgs/IK/IKEffector.pkg
#	Source/Urho3D/LuaScript/pkgs/IK/IKSolver.pkg
#	Source/Urho3D/LuaScript/pkgs/IO/Deserializer.pkg
#	Source/Urho3D/LuaScript/pkgs/IO/File.pkg
#	Source/Urho3D/LuaScript/pkgs/IO/VectorBuffer.pkg
#	Source/Urho3D/LuaScript/pkgs/Math/BoundingBox.pkg
#	Source/Urho3D/LuaScript/pkgs/Math/MathDefs.pkg
#	Source/Urho3D/LuaScript/pkgs/Math/Sphere.pkg
#	Source/Urho3D/LuaScript/pkgs/Navigation/NavigationMesh.pkg
#	Source/Urho3D/LuaScript/pkgs/Resource/Image.pkg
#	Source/Urho3D/LuaScript/pkgs/Scene/Scene.pkg
#	Source/Urho3D/LuaScript/pkgs/UI/UI.pkg
#	Source/Urho3D/Navigation/NavigationEvents.h
#	Source/Urho3D/UI/UI.cpp
#	Source/Urho3D/UI/UI.h
#	SourceAssets/Cylinder.blend
#	bin/ConvertModels.bat
#	bin/Data/LuaScripts/15_Navigation.lua
#	bin/Data/LuaScripts/39_CrowdNavigation.lua
#	bin/Data/LuaScripts/45_InverseKinematics.lua
#	bin/Data/LuaScripts/47_Typography.lua
#	bin/Data/Models/Cylinder.mdl
#	bin/Data/Scripts/15_Navigation.as
#	bin/Data/Scripts/39_CrowdNavigation.as
#	bin/Data/Scripts/45_InverseKinematics.as
#	bin/Data/Scripts/47_Typography.as
#	bin/Data/Scripts/Editor/EditorTerrain.as
#	bin/Editor.bat
#	bin/NinjaSnowWar.bat
#	bin/PBRDemo.bat
#	bin/PBRDemoDeferred.bat
#	bin/PBRDemoDeferredHWDepth.bat
#	cmake_generic.bat
Rokas Kupstys 8 years ago
parent
commit
4abd2f15b3
100 changed files with 11944 additions and 4021 deletions
  1. 1 1
      Build/Docs/CPlusPlus/Doxyfile
  2. 8 2
      Data/AtomicEditor/Deployment/Android/src/org/libsdl/app/SDLActivity.java
  3. 1 1
      Resources/CoreData/RenderPaths/DeferredHWDepth.xml
  4. 1 1
      Resources/CoreData/RenderPaths/PBRDeferredHWDepth.xml
  5. 1 1
      Resources/CoreData/RenderPaths/PrepassHWDepth.xml
  6. 1 237
      Resources/CoreData/Shaders/GLSL/IBL.glsl
  7. 4 4
      Resources/CoreData/Shaders/GLSL/PBRLitSolid.glsl
  8. 1 229
      Resources/CoreData/Shaders/HLSL/IBL.hlsl
  9. 10 10
      Resources/CoreData/Shaders/HLSL/PBRLitSolid.hlsl
  10. 1 1
      Resources/CoreData/Techniques/PBR/DiffNormalSpecEmissive.xml
  11. 1 1
      Resources/CoreData/Techniques/PBR/DiffNormalSpecEmissiveAlpha.xml
  12. 1 1
      Resources/CoreData/Techniques/PBR/PBRDiff.xml
  13. 1 1
      Resources/CoreData/Techniques/PBR/PBRDiffAlpha.xml
  14. 1 1
      Resources/CoreData/Techniques/PBR/PBRDiffNormal.xml
  15. 1 1
      Resources/CoreData/Techniques/PBR/PBRDiffNormalAlpha.xml
  16. 1 1
      Resources/CoreData/Techniques/PBR/PBRDiffNormalEmissive.xml
  17. 1 1
      Resources/CoreData/Techniques/PBR/PBRDiffNormalEmissiveAlpha.xml
  18. 1 1
      Resources/CoreData/Techniques/PBR/PBRMetallicRoughDiffNormalSpec.xml
  19. 1 1
      Resources/CoreData/Techniques/PBR/PBRMetallicRoughDiffNormalSpecEmissive.xml
  20. 1 1
      Resources/CoreData/Techniques/PBR/PBRMetallicRoughDiffNormalSpecEmissiveAlpha.xml
  21. 1 1
      Resources/CoreData/Techniques/PBR/PBRMetallicRoughDiffSpec.xml
  22. 1 1
      Resources/CoreData/Techniques/PBR/PBRMetallicRoughDiffSpecAlpha.xml
  23. 1 1
      Resources/CoreData/Techniques/PBR/PBRNoTexture.xml
  24. 1 1
      Resources/CoreData/Techniques/PBR/PBRNoTextureAlpha.xml
  25. 24 22
      Source/Atomic/Atomic2D/TileMap2D.cpp
  26. 2 1
      Source/Atomic/Atomic2D/TileMap2D.h
  27. 68 14
      Source/Atomic/Atomic2D/TileMapLayer2D.cpp
  28. 340 165
      Source/Atomic/Atomic2D/TmxFile2D.cpp
  29. 11 5
      Source/Atomic/Atomic2D/TmxFile2D.h
  30. 5 61
      Source/Atomic/Audio/SoundSource3D.cpp
  31. 6 3
      Source/Atomic/Core/Object.cpp
  32. 88 2
      Source/Atomic/Core/StringUtils.cpp
  33. 2 0
      Source/Atomic/Core/StringUtils.h
  34. 63 19
      Source/Atomic/Graphics/DebugRenderer.cpp
  35. 3 0
      Source/Atomic/Graphics/DebugRenderer.h
  36. 1 0
      Source/Atomic/Graphics/Direct3D11/D3D11Graphics.cpp
  37. 1 0
      Source/Atomic/Graphics/Direct3D9/D3D9Graphics.cpp
  38. 9 1
      Source/Atomic/Graphics/OpenGL/OGLGraphics.cpp
  39. 2 12
      Source/Atomic/Graphics/Renderer.cpp
  40. 0 4
      Source/Atomic/Graphics/Renderer.h
  41. 15 0
      Source/Atomic/Graphics/Terrain.cpp
  42. 2 0
      Source/Atomic/Graphics/Terrain.h
  43. 2 2
      Source/Atomic/Graphics/Text3D/Text3DBitmap.cpp
  44. 10 6
      Source/Atomic/Graphics/Text3D/Text3DFontFace.h
  45. 126 17
      Source/Atomic/Graphics/Text3D/Text3DFreeType.cpp
  46. 27 1
      Source/Atomic/Graphics/Text3D/Text3DFreeType.h
  47. 8 3
      Source/Atomic/Graphics/Text3D/Text3DText.cpp
  48. 1 1
      Source/Atomic/IK/IK.cpp
  49. 3 7
      Source/Atomic/IK/IK.h
  50. 8 6
      Source/Atomic/IK/IKConstraint.cpp
  51. 2 2
      Source/Atomic/IK/IKConstraint.h
  52. 88 56
      Source/Atomic/IK/IKEffector.cpp
  53. 41 23
      Source/Atomic/IK/IKEffector.h
  54. 447 200
      Source/Atomic/IK/IKSolver.cpp
  55. 214 87
      Source/Atomic/IK/IKSolver.h
  56. 5 0
      Source/Atomic/IO/Deserializer.cpp
  57. 5 1
      Source/Atomic/IO/Deserializer.h
  58. 1 1
      Source/Atomic/IO/FileSystem.cpp
  59. 1 1
      Source/Atomic/IO/MemoryBuffer.h
  60. 1 1
      Source/Atomic/IO/VectorBuffer.h
  61. 7 0
      Source/Atomic/Math/BoundingBox.cpp
  62. 2 0
      Source/Atomic/Math/BoundingBox.h
  63. 10 0
      Source/Atomic/Math/MathDefs.h
  64. 9 0
      Source/Atomic/Math/Sphere.cpp
  65. 4 0
      Source/Atomic/Math/Sphere.h
  66. 23 11
      Source/Atomic/Navigation/CrowdAgent.cpp
  67. 2 0
      Source/Atomic/Navigation/CrowdAgent.h
  68. 297 109
      Source/Atomic/Navigation/DynamicNavigationMesh.cpp
  69. 23 1
      Source/Atomic/Navigation/DynamicNavigationMesh.h
  70. 22 0
      Source/Atomic/Navigation/NavigationEvents.h
  71. 252 90
      Source/Atomic/Navigation/NavigationMesh.cpp
  72. 27 2
      Source/Atomic/Navigation/NavigationMesh.h
  73. 13 1
      Source/Atomic/Navigation/Obstacle.cpp
  74. 4 2
      Source/Atomic/Navigation/Obstacle.h
  75. 38 14
      Source/Atomic/Physics/CollisionShape.cpp
  76. 10 0
      Source/Atomic/Physics/RigidBody.cpp
  77. 1 1
      Source/Atomic/Physics/RigidBody.h
  78. 4 4
      Source/Atomic/Resource/BackgroundLoader.cpp
  79. 2475 2308
      Source/Atomic/Resource/Image.cpp
  80. 252 250
      Source/Atomic/Resource/Image.h
  81. 2 2
      Source/ThirdParty/FreeType/src/gzip/zutil.c
  82. 4 1
      Source/ThirdParty/FreeType/src/gzip/zutil.h
  83. 38 0
      Source/ThirdParty/WebP/AUTHORS
  84. 36 0
      Source/ThirdParty/WebP/CMakeLists.txt
  85. 30 0
      Source/ThirdParty/WebP/COPYING
  86. 23 0
      Source/ThirdParty/WebP/PATENTS
  87. 232 0
      Source/ThirdParty/WebP/src/dec/alpha_dec.c
  88. 54 0
      Source/ThirdParty/WebP/src/dec/alphai_dec.h
  89. 300 0
      Source/ThirdParty/WebP/src/dec/buffer_dec.c
  90. 54 0
      Source/ThirdParty/WebP/src/dec/common_dec.h
  91. 812 0
      Source/ThirdParty/WebP/src/dec/frame_dec.c
  92. 892 0
      Source/ThirdParty/WebP/src/dec/idec_dec.c
  93. 645 0
      Source/ThirdParty/WebP/src/dec/io_dec.c
  94. 110 0
      Source/ThirdParty/WebP/src/dec/quant_dec.c
  95. 528 0
      Source/ThirdParty/WebP/src/dec/tree_dec.c
  96. 721 0
      Source/ThirdParty/WebP/src/dec/vp8_dec.c
  97. 185 0
      Source/ThirdParty/WebP/src/dec/vp8_dec.h
  98. 320 0
      Source/ThirdParty/WebP/src/dec/vp8i_dec.h
  99. 1673 0
      Source/ThirdParty/WebP/src/dec/vp8l_dec.c
  100. 135 0
      Source/ThirdParty/WebP/src/dec/vp8li_dec.h

+ 1 - 1
Build/Docs/CPlusPlus/Doxyfile

@@ -835,7 +835,7 @@ EXAMPLE_RECURSIVE      = NO
 # that contain images that are to be included in the documentation (see the
 # \image command).
 
-IMAGE_PATH             =
+IMAGE_PATH             = @CMAKE_CURRENT_SOURCE_DIR@/images
 
 # The INPUT_FILTER tag can be used to specify a program that doxygen should
 # invoke to filter for each input file. Doxygen will invoke the filter program

+ 8 - 2
Data/AtomicEditor/Deployment/Android/src/org/libsdl/app/SDLActivity.java

@@ -64,6 +64,8 @@ public class SDLActivity extends Activity {
 
     // Urho3D: flag to load the .so and a new method load them
     private static boolean mIsSharedLibraryLoaded = false;
+    // Urho3D: Hash of activity object that owns state, kept around in order to prevent dying activity overwriting state it no longer owns.
+    private static int mStateOwner = 0;
 
     protected boolean onLoadLibrary(ArrayList<String> libraryNames) {
         for (final String name : libraryNames) {
@@ -98,6 +100,7 @@ public class SDLActivity extends Activity {
         mIsPaused = false;
         mIsSurfaceReady = false;
         mHasFocus = true;
+        mStateOwner = 0;
     }
 
     // Setup
@@ -109,6 +112,7 @@ public class SDLActivity extends Activity {
         super.onCreate(savedInstanceState);
 
         SDLActivity.initialize();
+        mStateOwner = hashCode();
         // So we can call stuff from static callbacks
         mSingleton = this;
 
@@ -261,7 +265,8 @@ public class SDLActivity extends Activity {
         if (!SDLActivity.mIsSharedLibraryLoaded) {  // Urho3D
            super.onDestroy();
            // Reset everything in case the user re opens the app
-           SDLActivity.initialize();
+           if (mStateOwner == hashCode())
+               SDLActivity.initialize();
            return;
         }
 
@@ -283,7 +288,8 @@ public class SDLActivity extends Activity {
 
         super.onDestroy();
         // Reset everything in case the user re opens the app
-        SDLActivity.initialize();
+        if (mStateOwner == hashCode())
+            SDLActivity.initialize();
     }
 
     @Override

+ 1 - 1
Resources/CoreData/RenderPaths/DeferredHWDepth.xml

@@ -1,7 +1,7 @@
 <renderpath>
     <rendertarget name="albedo" sizedivisor="1 1" format="rgba" />
     <rendertarget name="normal" sizedivisor="1 1" format="rgba" />
-    <rendertarget name="depth" sizedivisor="1 1" format="readabledepth" />
+    <rendertarget name="depth" sizedivisor="1 1" format="readabledepth" persistent="true" />
     <command type="clear" color="fog" depth="1.0" stencil="0" depthstencil="depth" />
     <command type="clear" color="0 0 0 0" output="albedo" depthstencil="depth" />
     <command type="scenepass" pass="deferred" marktostencil="true" vertexlights="true" metadata="gbuffer" depthstencil="depth">

+ 1 - 1
Resources/CoreData/RenderPaths/PBRDeferredHWDepth.xml

@@ -2,7 +2,7 @@
     <rendertarget name="specular" sizedivisor="1 1" format="rgba16f" />
     <rendertarget name="albedo" sizedivisor="1 1" format="rgba16f" />
     <rendertarget name="normal" sizedivisor="1 1" format="rgba16f" />
-    <rendertarget name="depth" sizedivisor="1 1" format="readabledepth" />
+    <rendertarget name="depth" sizedivisor="1 1" format="readabledepth" persistent="true" />
     <command type="clear" color="0 0 0 0" depth="1.0" stencil="0" depthstencil="depth" />
     <command type="clear" color="0 0 0 0" output="albedo" depthstencil="depth"/>
     <command type="clear" color="0 0 0 0" output="specular" depthstencil="depth" />

+ 1 - 1
Resources/CoreData/RenderPaths/PrepassHWDepth.xml

@@ -1,7 +1,7 @@
 <renderpath>
     <rendertarget name="light" sizedivisor="1 1"  format="rgba" />
     <rendertarget name="normal" sizedivisor="1 1" format="rgba" />
-    <rendertarget name="depth" sizedivisor="1 1"  format="readabledepth" />
+    <rendertarget name="depth" sizedivisor="1 1"  format="readabledepth" persistent="true" />
     <command type="clear" color="fog" depth="1.0" stencil="0" depthstencil="depth" />
     <command type="scenepass" pass="prepass" marktostencil="true" metadata="gbuffer" output="normal" depthstencil="depth" />
     <command type="clear" color="0 0 0 0" output="light" depthstencil="depth" />

+ 1 - 237
Resources/CoreData/Shaders/GLSL/IBL.glsl

@@ -1,241 +1,6 @@
 #line 10001
 #ifdef COMPILEPS
 
-    //
-    // Legacy Importance Sampled IBL
-    //
-
-    // vec3 ImportanceSampleSimple(in vec2 Xi, in float roughness, in vec3 T, in vec3 B, in vec3 N)
-    // {
-    //     float a = roughness * roughness;
-    //     mat3 tbn = mat3(T, B, N);
-    //     #ifdef IBLFAST
-    //         const float blurFactor = 0.0;
-    //     #else
-    //         const float blurFactor = 5.0;
-    //     #endif
-    //     vec2 xx = Xi.xy * blurFactor;
-    //     xx = xx - 1.0 * trunc(xx/1.0); // hlsl style modulo
-    //     vec3 Xi3 = mix(vec3(0,0,1), normalize(vec3(xx, 1.0)), a);
-    //     vec3 XiWS = tbn * Xi3;
-    //     return normalize(N + XiWS);
-    // }
-
-    // // Karis '13
-    // vec3 ImportanceSampleGGX(in vec2 Xi, in float roughness, in vec3 T, in vec3 B, in vec3 N)
-    // {
-    //     float a = roughness * roughness;
-    //     float Phi = 2.0 * M_PI * Xi.x;
-    //     float CosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y));
-    //     float SinTheta = sqrt(1.0 - CosTheta * CosTheta);
-    //     vec3 H = vec3(0,0,0);
-    //     H.x = SinTheta * cos(Phi);
-    //     H.y = SinTheta * sin(Phi);
-    //     H.z = CosTheta;
-
-    //     vec3 UpVector = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0);
-    //     vec3 TangentX = normalize(cross(UpVector, N));
-    //     vec3 TangentY = cross(N, TangentX);
-    //     // Tangent to world space
-    //     return TangentX * H.x + TangentY * H.y + N * H.z;
-    // }
-
-    // #ifdef IBLFAST
-    //     #define IMPORTANCE_SAMPLES 1
-    // #else
-    //     #define IMPORTANCE_SAMPLES 4
-    // #endif
-
-    // #define IMPORTANCE_KERNEL_SIZE 16
-    // vec2 IMPORTANCE_KERNEL[IMPORTANCE_KERNEL_SIZE] = vec2[] (
-    //     vec2(-0.0780436, 0.0558389),
-    //     vec2(0.034318, -0.0635879),
-    //     vec2(0.00230821, 0.0807279),
-    //     vec2(0.0124638, 0.117585),
-    //     vec2(0.093943, -0.0944602),
-    //     vec2(0.139348, -0.109816),
-    //     vec2(-0.181872, -0.129649),
-    //     vec2(0.240066, -0.0494057),
-    //     vec2(0.115965, -0.0374714),
-    //     vec2(-0.294819, -0.100726),
-    //     vec2(-0.149652, 0.37459),
-    //     vec2(0.261695, -0.292813),
-    //     vec2(-0.37944, -0.425145),
-    //     vec2(0.628994, -0.189387),
-    //     vec2(-0.331257, -0.646864),
-    //     vec2(-0.467004, 0.439687)
-    //   );
-
-    //   float GetMipFromRougness(float roughness)
-    //   {
-    //       float smoothness = 1.0 - roughness;
-    //       return (1.0 - smoothness * smoothness) * 10.0;
-    //   }
-
-    // /// Perform importance sampling
-    // ///     reflectVec: calculated vector of reflection
-    // ///     wsNormal: world-space normal of the surface
-    // ///     toCamera: direction from the pixel to the camera
-    // ///     specular: specular color
-    // ///     roughness: surface roughness
-    // ///     reflectionCubeColor: output color for diffuse
-    // // Implementation based on Epics 2013 course notes
-    // vec3 ImportanceSampling(in vec3 reflectVec, in vec3 tangent, in vec3 bitangent, in vec3 wsNormal, in vec3 toCamera,  in vec3 diffColor, in vec3 specColor, in float roughness, inout vec3 reflectionCubeColor)
-    // {
-    //     reflectionCubeColor = vec3(1,1,1);
-
-    //     vec3 reflectSpec = normalize(GetSpecularDominantDir(wsNormal, reflectVec, roughness));
-
-    //     vec3 V = normalize(-toCamera);
-    //     vec3 N = normalize(wsNormal);
-    //     float ndv = clamp(abs(dot(N, V)), 0.0, 1.0);
-
-    //     float specMipLevel = GetMipFromRougness(roughness);
-
-    //     vec3 accumulatedColor = vec3(0,0,0);
-    //     for (int i = 0; i < IMPORTANCE_SAMPLES; ++i)
-    //     {
-    //         vec3 kd = vec3(1,1,1);
-    //         vec3 diffuseFactor = vec3(0,0,0);
-    //         vec3 specularFactor = vec3(0,0,0);
-
-    //         {
-    //             // Diffuse IBL
-    //             const float rough = 1.0;
-    //             const float mipLevel = 9.0;
-
-    //             vec3 H = ImportanceSampleSimple(IMPORTANCE_KERNEL[i], rough, tangent, bitangent, N);
-    //             vec3 L = 2.0 * dot( V, H ) * H - V;
-
-    //             float vdh = clamp(abs(dot(V, H)), 0.0, 1.0);
-    //             float ndh = clamp(abs(dot(N, H)), 0.0, 1.0);
-    //             float ndl = clamp(abs(dot(N, L)), 0.0, 1.0);
-
-    //             vec3 sampledColor = textureLod(sZoneCubeMap, L, mipLevel).rgb;
-
-    //             vec3 diffuseTerm = Diffuse(diffColor, rough, ndv, ndl, vdh);
-    //             vec3 lightTerm = sampledColor;
-
-    //             diffuseFactor = lightTerm * diffuseTerm;
-    //         }
-
-    //         {
-    //             // Specular IBL
-    //             float rough = roughness;
-    //             float mipLevel = specMipLevel;
-
-    //             vec3 H = ImportanceSampleSimple(IMPORTANCE_KERNEL[i], rough, tangent, bitangent, N);
-    //             vec3 L = 2.0 * dot( V, H ) * H - V;
-    //             vec3 sampledColor = textureLod(sZoneCubeMap, L, mipLevel).rgb;
-
-    //             float vdh = clamp(abs(dot(V, H)), 0.0, 1.0);
-    //             float ndh = clamp(abs(dot(N, H)), 0.0, 1.0);
-    //             float ndl = clamp(abs(dot(N, L)), 0.0, 1.0);
-
-    //             vec3 fresnelTerm = Fresnel(specColor, vdh);
-    //             float distTerm = 1.0; // Optimization, this term is mathematically cancelled out  -- Distribution(ndh, roughness);
-    //             float visTerm = Visibility(ndl, ndv, rough);
-
-    //             vec3 lightTerm = sampledColor * ndl;
-
-    //             float pdf = ndl > 0.05 ? ImportanceSamplePDF(distTerm, ndh, vdh) : 4.0; // reduce artifacts at extreme grazing angles
-
-    //             vec3 specularTerm = SpecularBRDF(distTerm, fresnelTerm, visTerm, ndl, ndv);
-
-    //             // energy conservation:
-    //             // Specular conservation:
-    //             specularFactor = lightTerm * specularTerm / pdf;
-    //             specularFactor = max(
-    //               clamp(normalize(specularFactor) * (length(sampledColor * specColor)), 0.0, 1.0),
-    //               specularFactor
-    //             );
-
-    //             // Diffuse conservation:
-    //             //kd = (sampledColor * specColor)/specularFactor; //energy conservation
-    //             kd = 1.0 - specularFactor;
-    //         }
-
-    //         accumulatedColor += specularFactor + diffuseFactor * kd;
-    //     }
-
-    //     return (accumulatedColor / IMPORTANCE_SAMPLES);
-    // }
-
-    // vec3 ImportanceSamplingSimple(in vec3 reflectVec, in vec3 tangent, in vec3 bitangent, in vec3 wsNormal, in vec3 toCamera,  in vec3 diffColor, in vec3 specColor, in float roughness, inout vec3 reflectionCubeColor)
-    // {
-    //     reflectionCubeColor = vec3(1,1,1);
-
-    //     reflectVec = normalize(GetSpecularDominantDir(wsNormal, reflectVec, roughness));
-
-    //     vec3 Hn = normalize(-toCamera + wsNormal);
-    //     float ndv = clamp(dot(-toCamera, wsNormal), 0.0, 1.0);
-    //     float vdh = clamp(dot(-toCamera, Hn), 0.0, 1.0);
-    //     float ndh = clamp(dot(wsNormal, Hn), 0.0, 1.0);
-
-    //     vec3 accumulatedColor = vec3(0,0,0);
-    //     for (int i = 0; i < IMPORTANCE_SAMPLES; ++i)
-    //     {
-    //         vec3 kd = vec3(1,1,1);
-    //         vec3 diffuseFactor = vec3(0,0,0);
-    //         vec3 specularFactor = vec3(0,0,0);
-
-    //         {
-    //             // Diffuse IBL
-    //             const float rough = 1.0;
-    //             const float mipLevel = 9.0;
-
-    //             vec3 perturb = ImportanceSampleGGX(IMPORTANCE_KERNEL[i].xy, rough, tangent, bitangent, wsNormal);
-    //             vec3 sampleVec = wsNormal + perturb; //perturb by the sample vector
-
-    //             vec3 sampledColor = textureLod(sZoneCubeMap, sampleVec, mipLevel).rgb;
-    //             float ndl = clamp(dot(sampleVec, wsNormal), 0.0, 1.0);
-
-    //             vec3 diffuseTerm = Diffuse(diffColor, rough, ndv, ndl, vdh);
-    //             vec3 lightTerm = sampledColor;
-
-    //             diffuseFactor = lightTerm * diffuseTerm;
-    //         }
-
-    //         {
-    //             // Specular IBL
-    //             float rough = roughness;
-    //             float mipLevel =  GetMipFromRougness(rough);
-
-    //             vec3 perturb = ImportanceSampleGGX(IMPORTANCE_KERNEL[i].xy, rough, tangent, bitangent, reflectVec);
-    //             vec3 sampleVec = reflectVec + perturb; //perturb by the sample vector
-
-    //             vec3 sampledColor = textureCube(sZoneCubeMap, sampleVec, mipLevel).rgb;
-    //             float ndl = clamp(dot(sampleVec, wsNormal), 0.0, 1.0);
-
-    //             vec3 fresnelTerm = SchlickFresnel(specColor, ndh) ;
-    //             float distTerm = 1.0; //Optimization, this term is mathematically cancelled out  //Distribution(ndh, roughness);
-    //             float visTerm = SmithGGXVisibility(ndl, ndv, rough);
-    //             vec3 lightTerm = sampledColor * ndl;
-
-    //             float pdf = 1.0; //ImportanceSamplePDF(distTerm, ndh, vdh);
-
-    //             specularFactor = lightTerm * SpecularBRDF(distTerm, fresnelTerm, visTerm, ndl, ndv) / pdf;
-    //             specularFactor *= pdf * ndv * (4.0 * ndl * ndv); // hacks
-    //             kd = (1.0 - clamp(specularFactor, 0.0, 1.0)); //energy conservation
-    //         }
-
-    //         accumulatedColor += specularFactor + diffuseFactor * kd;
-    //     }
-
-    //     return accumulatedColor / IMPORTANCE_SAMPLES;
-    // }
-
-    /// Determine reflection vector based on surface roughness, rougher uses closer to the normal and smoother uses closer to the reflection vector
-    ///     normal: surface normal
-    ///     reflection: vector of reflection off of the surface
-    ///     roughness: surface roughness
-    // vec3 GetSpecularDominantDir(vec3 normal, vec3 reflection, float roughness)
-    // {
-    //     float smoothness = 1.0 - roughness;
-    //     float lerpFactor = smoothness * (sqrt(smoothness) + roughness);
-    //     return mix(normal, reflection, lerpFactor);
-    // }
-
     float GetMipFromRoughness(float roughness)
     {
         return (roughness * 12.0 - pow(roughness, 6.0) * 1.5);
@@ -270,7 +35,7 @@
     ///     toCamera: normalized direction from surface point to camera
     ///     roughness: surface roughness
     ///     ambientOcclusion: ambient occlusion
-    vec3 ImageBasedLighting(vec3 reflectVec, vec3 tangent, vec3 bitangent, vec3 wsNormal, vec3 toCamera, vec3 diffColor, vec3 specColor, float roughness, inout vec3 reflectionCubeColor)
+    vec3 ImageBasedLighting(vec3 reflectVec, vec3 wsNormal, vec3 toCamera, vec3 diffColor, vec3 specColor, float roughness, inout vec3 reflectionCubeColor)
     {
         roughness = max(roughness, 0.08);
         reflectVec = GetSpecularDominantDir(wsNormal, reflectVec, roughness);
@@ -307,6 +72,5 @@
         vec3 environmentDiffuse = EnvBRDFApprox(diffColor, 1.0, ndv);
 
         return (hdrCube * environmentSpecular + hdrCubeD * environmentDiffuse) * brightness;
-        //return ImportanceSampling(reflectVec, tangent, bitangent, wsNormal, toCamera, diffColor, specColor, roughness, reflectionCubeColor);
     }
 #endif

+ 4 - 4
Resources/CoreData/Shaders/GLSL/PBRLitSolid.glsl

@@ -9,7 +9,7 @@
 #include "IBL.glsl"
 #line 30010
 
-#if defined(NORMALMAP) || defined(IBL)
+#if defined(NORMALMAP)
     varying vec4 vTexCoord;
     varying vec4 vTangent;
 #else
@@ -57,7 +57,7 @@ void VS()
         vColor = iColor;
     #endif
 
-    #if defined(NORMALMAP) || defined(DIRBILLBOARD) || defined(IBL)
+    #if defined(NORMALMAP) || defined(DIRBILLBOARD)
         vec4 tangent = GetWorldTangent(modelMatrix);
         vec3 bitangent = cross(tangent.xyz, vNormal) * tangent.w;
         vTexCoord = vec4(GetTexCoord(iTexCoord), bitangent.xy);
@@ -145,7 +145,7 @@ void PS()
     diffColor.rgb = diffColor.rgb - diffColor.rgb * metalness;
 
     // Get normal
-    #if defined(NORMALMAP) || defined(DIRBILLBOARD) || defined(IBL)
+    #if defined(NORMALMAP) || defined(DIRBILLBOARD)
         vec3 tangent = vTangent.xyz;
         vec3 bitangent = vec3(vTexCoord.zw, vTangent.w);
         mat3 tbn = mat3(tangent, bitangent, vNormal);
@@ -239,7 +239,7 @@ void PS()
         vec3 cubeColor = vVertexLight.rgb;
 
         #ifdef IBL
-          vec3 iblColor = ImageBasedLighting(reflection, tangent, bitangent, normal, toCamera, diffColor.rgb, specColor.rgb, roughness, cubeColor);
+          vec3 iblColor = ImageBasedLighting(reflection, normal, toCamera, diffColor.rgb, specColor.rgb, roughness, cubeColor);
           float gamma = 0.0;
           finalColor.rgb += iblColor;
         #endif

+ 1 - 229
Resources/CoreData/Shaders/HLSL/IBL.hlsl

@@ -1,232 +1,5 @@
 #ifdef COMPILEPS
 
-    //  float3 ImportanceSampleSimple(in float2 Xi, in float roughness, in float3 T, in float3 B, in float3 N)
-    // {
-    //     const float a = roughness * roughness;
-    //     const float3x3 tbn = float3x3(T, B, N);
-    //     #ifdef IBLFAST
-    //         const float blurFactor = 0.0;
-    //     #else
-    //         const float blurFactor = 5.0;
-    //     #endif
-    //     const float3 Xi3 = lerp(float3(0,0,1), normalize(float3(Xi.xy * blurFactor % 1.0 , 1.0)), a);
-    //     const float3 XiWS = mul(Xi3, tbn);
-    //     return normalize(N + XiWS);
-    // }
-
-    // // Karis '13
-    // float3 ImportanceSampleGGX(in float2 Xi, in float roughness, in float3 T, in float3 B, in float3 N)
-    // {
-    //     float a = roughness * roughness;
-    //     float Phi = 2.0 * M_PI * Xi.x;
-    //     float CosTheta = (sqrt((1.0 - Xi.y) / (1.0 + (a*a - 1.0) * Xi.y)));
-    //     float SinTheta = sqrt(1.0 - CosTheta * CosTheta);
-    //     float3 H = 0;
-    //     H.x = SinTheta * cos(Phi);
-    //     H.y = SinTheta * sin(Phi);
-    //     H.z = CosTheta;
-
-    //     float3 UpVector = abs(N.z) < 0.999 ? float3(0, 0, 1) : float3(1, 0, 0);
-    //     float3 TangentX = normalize(cross(UpVector, N));
-    //     float3 TangentY = cross(N, TangentX);
-    //     // Tangent to world space
-    //     return TangentX * H.x + TangentY * H.y + N * H.z;
-    // }
-
-    // #ifdef IBLFAST
-    //     #define IMPORTANCE_SAMPLES 1
-    // #else
-    //     #define IMPORTANCE_SAMPLES 16
-    // #endif
-
-    // #define IMPORTANCE_KERNEL_SIZE 16
-    // static const float2 IMPORTANCE_KERNEL[IMPORTANCE_KERNEL_SIZE] =
-    // {
-    //     float2(-0.0780436, 0.0558389),
-    //     float2(0.034318, -0.0635879),
-    //     float2(0.00230821, 0.0807279),
-    //     float2(0.0124638, 0.117585),
-    //     float2(0.093943, -0.0944602),
-    //     float2(0.139348, -0.109816),
-    //     float2(-0.181872, -0.129649),
-    //     float2(0.240066, -0.0494057),
-    //     float2(0.115965, -0.0374714),
-    //     float2(-0.294819, -0.100726),
-    //     float2(-0.149652, 0.37459),
-    //     float2(0.261695, -0.292813),
-    //     float2(-0.37944, -0.425145),
-    //     float2(0.628994, -0.189387),
-    //     float2(-0.331257, -0.646864),
-    //     float2(-0.467004, 0.439687),
-    // };
-
-    // float GetMipFromRougness(float roughness)
-    // {
-    //     const float smoothness = 1.0 - roughness;
-    //     return (1.0 - smoothness * smoothness) * 10.0;
-    // }
-
-    // /// Perform importance sampling
-    // ///     reflectVec: calculated vector of reflection
-    // ///     wsNormal: world-space normal of the surface
-    // ///     toCamera: direction from the pixel to the camera
-    // ///     specular: specular color
-    // ///     roughness: surface roughness
-    // ///     reflectionCubeColor: output color for diffuse
-
-    // // Implementation based on Epics 2013 course notes
-    // float3 ImportanceSampling(in float3 reflectVec, in float3 tangent, in float3 bitangent, in float3 wsNormal, in float3 toCamera,  in float3 diffColor, in float3 specColor, in float roughness, inout float3 reflectionCubeColor)
-    // {
-    //     reflectionCubeColor = 1.0;
-
-    //     const float3 reflectSpec = normalize(GetSpecularDominantDir(wsNormal, reflectVec, roughness));
-
-    //     const float3 V = normalize(-toCamera);
-    //     const float3 N = normalize(wsNormal);
-    //     const float ndv = saturate(abs(dot(N, V)));
-
-    //     const float specMipLevel = GetMipFromRougness(roughness);
-
-    //     float3 accumulatedColor = float3(0,0,0);
-    //     for (int i = 0; i < IMPORTANCE_SAMPLES; ++i)
-    //     {
-    //         float3 kd = 1.0;
-    //         float3 diffuseFactor = 0.0;
-    //         float3 specularFactor = 0.0;
-
-    //         {
-    //             // Diffuse IBL
-    //             const float rough = 1.0;
-    //             const float mipLevel = 9.0;
-
-    //             const float3 H = ImportanceSampleSimple(IMPORTANCE_KERNEL[i], rough, tangent, bitangent, N);
-    //             const float3 L = 2.0 * dot( V, H ) * H - V;
-
-    //             const float vdh = saturate(abs(dot(V, H)));
-    //             const float ndh = saturate(abs(dot(N, H)));
-    //             const float ndl = saturate(abs(dot(N, L)));
-
-    //             const float3 sampledColor = SampleCubeLOD(ZoneCubeMap, float4(L, mipLevel));
-
-    //             const float3 diffuseTerm = Diffuse(diffColor, rough, ndv, ndl, vdh);
-    //             const float3 lightTerm = sampledColor;
-
-    //             diffuseFactor = lightTerm * diffuseTerm;
-    //         }
-
-    //         {
-    //             // Specular IBL
-    //             const float rough = roughness;
-    //             const float mipLevel = specMipLevel;
-
-    //             const float3 H = ImportanceSampleSimple(IMPORTANCE_KERNEL[i], rough, tangent, bitangent, N);
-    //             const float3 L = 2.0 * dot( V, H ) * H - V;
-    //             const float3 sampledColor = SampleCubeLOD(ZoneCubeMap, float4(L, mipLevel));
-
-    //             const float vdh = saturate(abs(dot(V, H)));
-    //             const float ndh = saturate(abs(dot(N, H)));
-    //             const float ndl = saturate(abs(dot(N, L)));
-
-    //             const float3 fresnelTerm = Fresnel(specColor, vdh);
-    //             const float distTerm = 1.0;//Distribution(ndh_, roughness);
-    //             const float visTerm = Visibility(ndl, ndv, rough);
-    //             const float3 lightTerm = sampledColor * ndl;
-
-    //             const float pdf = ndl > 0.05 ? ImportanceSamplePDF(distTerm, ndh, vdh) : 4.0; // reduce artifacts at extreme grazing angles
-
-    //             const float3 specularTerm = SpecularBRDF(distTerm, fresnelTerm, visTerm, ndl, ndv);
-
-    //             // Energy conservation:
-    //             // Specular conservation:
-    //             specularFactor = lightTerm * specularTerm / pdf;
-    //             specularFactor = max(saturate(normalize(specularFactor) * (length(sampledColor * specColor))), specularFactor);
-
-    //             // Diffuse conservation:
-    //             kd = 1.0 - specularFactor;
-    //         }
-
-    //         accumulatedColor += specularFactor + diffuseFactor * kd;
-    //     }
-
-    //     return (accumulatedColor / IMPORTANCE_SAMPLES);
-    // }
-
-
-    // float3 ImportanceSamplingSimple(in float3 reflectVec, in float3 tangent, in float3 bitangent, in float3 wsNormal, in float3 toCamera,  in float3 diffColor, in float3 specColor, in float roughness, inout float3 reflectionCubeColor)
-    // {
-    //     reflectionCubeColor = 1.0;
-
-    //     reflectVec = normalize(GetSpecularDominantDir(wsNormal, reflectVec, roughness));
-
-    //     const float3 Hn = normalize(-toCamera + wsNormal);
-    //     const float ndv = saturate(dot(-toCamera, wsNormal));
-    //     const float vdh = saturate(dot(-toCamera, Hn));
-    //     const float ndh = saturate(dot(wsNormal, Hn));
-
-    //     float3 accumulatedColor = float3(0,0,0);
-    //     for (int i = 0; i < IMPORTANCE_SAMPLES; ++i)
-    //     {
-    //         float3 kd = 1.0;
-    //         float3 diffuseFactor = 0.0;
-    //         float3 specularFactor = 0.0;
-
-    //         {
-    //             // Diffuse IBL
-    //             const float rough = 1.0;
-    //             const float mipLevel = 9.0;
-
-    //             const float3 perturb = ImportanceSampleGGX(IMPORTANCE_KERNEL[i].xy, rough, tangent, bitangent, wsNormal);
-    //             const float3 sampleVec = wsNormal + perturb; //perturb by the sample vector
-
-    //             const float3 sampledColor = SampleCubeLOD(ZoneCubeMap, float4(sampleVec, mipLevel));
-    //             const float ndl = saturate(dot(sampleVec, wsNormal));
-
-    //             const float3 diffuseTerm = Diffuse(diffColor, rough, ndv, ndl, vdh);
-    //             const float3 lightTerm = sampledColor;
-
-    //             diffuseFactor = lightTerm * diffuseTerm;
-    //         }
-
-    //         {
-    //             // Specular IBL
-    //             const float rough = roughness;
-    //             const float mipLevel =  GetMipFromRougness(rough);
-
-    //             const float3 perturb = ImportanceSampleGGX(IMPORTANCE_KERNEL[i].xy, rough, tangent, bitangent, reflectVec);
-    //             const float3 sampleVec = reflectVec + perturb; //perturb by the sample vector
-
-    //             const float3 sampledColor = SampleCubeLOD(ZoneCubeMap, float4(sampleVec, mipLevel));
-    //             const float ndl = saturate(dot(sampleVec, wsNormal));
-
-    //             const float3 fresnelTerm = SchlickFresnel(specColor, ndh) ;
-    //             const float distTerm = 1.0; //Optimization, this term is mathematically cancelled out  //Distribution(ndh, roughness);
-    //             const float visTerm = SmithGGXVisibility(ndl, ndv, rough);
-    //             const float3 lightTerm = sampledColor * ndl;
-
-    //             const float pdf = 1.0;//ImportanceSamplePDF(distTerm, ndh, vdh);
-
-    //             specularFactor = lightTerm * SpecularBRDF(distTerm, fresnelTerm, visTerm, ndl, ndv) / pdf;
-    //             specularFactor *= pdf * ndv * (4.0 * ndl * ndv); // hacks
-    //             kd = (1.0 - saturate(specularFactor)); //energy conservation
-    //         }
-
-    //         accumulatedColor += specularFactor + diffuseFactor * kd;
-    //     }
-
-    //     return accumulatedColor / IMPORTANCE_SAMPLES;
-    // }
-
-    /// Determine reflection vector based on surface roughness, rougher uses closer to the normal and smoother uses closer to the reflection vector
-    ///     normal: surface normal
-    ///     reflection: vector of reflection off of the surface
-    ///     roughness: surface roughness
-    // float3 GetSpecularDominantDir(float3 normal, float3 reflection, float roughness)
-    // {
-    //     const float smoothness = 1.0 - roughness;
-    //     const float lerpFactor = smoothness * (sqrt(smoothness) + roughness);
-    //     return lerp(normal, reflection, lerpFactor);
-    // }
-
     float GetMipFromRoughness(float roughness)
     {
         return (roughness * 12.0 - pow(roughness, 6.0) * 1.5);
@@ -261,7 +34,7 @@
     ///     toCamera: normalized direction from surface point to camera
     ///     roughness: surface roughness
     ///     ambientOcclusion: ambient occlusion
-    float3 ImageBasedLighting(in float3 reflectVec, in float3 tangent, in float3 bitangent, in float3 wsNormal, in float3 toCamera, in float3 diffColor, in float3 specColor, in float roughness, inout float3 reflectionCubeColor)
+    float3 ImageBasedLighting(in float3 reflectVec, in float3 wsNormal, in float3 toCamera, in float3 diffColor, in float3 specColor, in float roughness, inout float3 reflectionCubeColor)
     { 
         roughness = max(roughness, 0.08);
         reflectVec = GetSpecularDominantDir(wsNormal, reflectVec, roughness);
@@ -300,6 +73,5 @@
         const float3 environmentDiffuse = EnvBRDFApprox(diffColor, 1.0, ndv);
 
         return (hdrCube * environmentSpecular + hdrCubeD * environmentDiffuse) * brightness;
-        //return ImportanceSampling(reflectVec, tangent, bitangent, wsNormal, toCamera, diffColor, specColor, roughness, reflectionCubeColor);
     }
 #endif

+ 10 - 10
Resources/CoreData/Shaders/HLSL/PBRLitSolid.hlsl

@@ -21,7 +21,7 @@ void VS(float4 iPos : POSITION,
     #if defined(LIGHTMAP) || defined(AO)
         float2 iTexCoord2 : TEXCOORD1,
     #endif
-    #if (defined(NORMALMAP)|| defined(IBL) || defined(TRAILFACECAM) || defined(TRAILBONE)) && !defined(BILLBOARD) && !defined(DIRBILLBOARD)
+    #if (defined(NORMALMAP) || defined(TRAILFACECAM) || defined(TRAILBONE)) && !defined(BILLBOARD) && !defined(DIRBILLBOARD)
         float4 iTangent : TANGENT,
     #endif
     #ifdef SKINNED
@@ -34,11 +34,11 @@ void VS(float4 iPos : POSITION,
     #if defined(BILLBOARD) || defined(DIRBILLBOARD)
         float2 iSize : TEXCOORD1,
     #endif
-    #if defined(NORMALMAP) || defined(IBL)
+    #ifndef NORMALMAP
+        out float2 oTexCoord : TEXCOORD0,
+    #else
         out float4 oTexCoord : TEXCOORD0,
         out float4 oTangent : TEXCOORD3,
-    #else
-        out float2 oTexCoord : TEXCOORD0,
     #endif
     out float3 oNormal : TEXCOORD1,
     out float4 oWorldPos : TEXCOORD2,
@@ -89,7 +89,7 @@ void VS(float4 iPos : POSITION,
         oColor = iColor;
     #endif
 
-    #if defined(NORMALMAP) || defined(IBL)
+    #if defined(NORMALMAP)
         const float4 tangent = GetWorldTangent(modelMatrix);
         const float3 bitangent = cross(tangent.xyz, oNormal) * tangent.w;
         oTexCoord = float4(GetTexCoord(iTexCoord), bitangent.xy);
@@ -140,11 +140,11 @@ void VS(float4 iPos : POSITION,
 }
 
 void PS(
-    #if defined(NORMALMAP) || defined(IBL)
+    #ifndef NORMALMAP
+        float2 iTexCoord : TEXCOORD0,
+    #else
         float4 iTexCoord : TEXCOORD0,
         float4 iTangent : TEXCOORD3,
-    #else
-        float2 iTexCoord : TEXCOORD0,
     #endif
     float3 iNormal : TEXCOORD1,
     float4 iWorldPos : TEXCOORD2,
@@ -226,7 +226,7 @@ void PS(
     diffColor.rgb = diffColor.rgb - diffColor.rgb * metalness; // Modulate down the diffuse
 
     // Get normal
-    #if defined(NORMALMAP) || defined(IBL)
+    #if defined(NORMALMAP)
         const float3 tangent = normalize(iTangent.xyz);
         const float3 bitangent = normalize(float3(iTexCoord.zw, iTangent.w));
         const float3x3 tbn = float3x3(tangent, bitangent, iNormal);
@@ -322,7 +322,7 @@ void PS(
         float3 cubeColor = iVertexLight.rgb;
 
         #ifdef IBL
-            const float3 iblColor = ImageBasedLighting(reflection, tangent, bitangent, normal, toCamera, diffColor, specColor, roughness, cubeColor);
+            const float3 iblColor = ImageBasedLighting(reflection, normal, toCamera, diffColor, specColor, roughness, cubeColor);
             const float gamma = 0;
             finalColor += iblColor;
         #endif

+ 1 - 1
Resources/CoreData/Techniques/PBR/DiffNormalSpecEmissive.xml

@@ -1,4 +1,4 @@
-<technique vs="PBRLitSolid" ps="PBRLitSolid" psdefines="DIFFMAP NORMALMAP SPECMAP EMISSIVEMAP">
+<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="NORMALMAP" psdefines="DIFFMAP NORMALMAP SPECMAP EMISSIVEMAP">
     <pass name="base" />
     <pass name="light" depthtest="equal" depthwrite="false" blend="add" />>
     <pass name="material" psdefines="MATERIAL" depthtest="equal" depthwrite="false" />

+ 1 - 1
Resources/CoreData/Techniques/PBR/DiffNormalSpecEmissiveAlpha.xml

@@ -1,4 +1,4 @@
-<technique vs="PBRLitSolid" ps="PBRLitSolid" psdefines="DIFFMAP SPECMAP EMISSIVEMAP NORMALMAP">
+<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="NORMALMAP" psdefines="DIFFMAP SPECMAP EMISSIVEMAP NORMALMAP">
     <pass name="alpha" depthwrite="false" blend="alpha" />
     <pass name="litalpha" depthwrite="false" blend="addalpha" />
     <pass name="shadow" vs="Shadow" ps="Shadow" psexcludes="PACKEDNORMAL" />

+ 1 - 1
Resources/CoreData/Techniques/PBR/PBRDiff.xml

@@ -1,4 +1,4 @@
-<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="IBL" psdefines="DIFFMAP PBR IBL">
+<technique vs="PBRLitSolid" ps="PBRLitSolid" psdefines="DIFFMAP PBR IBL">
     <pass name="base" />
     <pass name="light" depthtest="equal" depthwrite="false" blend="add" />
     <pass name="material" psdefines="MATERIAL" depthtest="equal" depthwrite="false" />

+ 1 - 1
Resources/CoreData/Techniques/PBR/PBRDiffAlpha.xml

@@ -1,4 +1,4 @@
-<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="IBL" psdefines="DIFFMAP PBR IBL">
+<technique vs="PBRLitSolid" ps="PBRLitSolid" psdefines="DIFFMAP PBR IBL">
     <pass name="alpha" depthwrite="false" blend="alpha" />
     <pass name="litalpha" depthwrite="false" blend="addalpha" />
     <pass name="shadow" vs="Shadow" ps="Shadow" />

+ 1 - 1
Resources/CoreData/Techniques/PBR/PBRDiffNormal.xml

@@ -1,4 +1,4 @@
-<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="IBL" psdefines="DIFFMAP NORMALMAP PBR IBL">
+<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="NORMALMAP" psdefines="DIFFMAP NORMALMAP PBR IBL">
     <pass name="base" />
     <pass name="light" depthtest="equal" depthwrite="false" blend="add" />
     <pass name="material" psdefines="MATERIAL" depthtest="equal" depthwrite="false" />

+ 1 - 1
Resources/CoreData/Techniques/PBR/PBRDiffNormalAlpha.xml

@@ -1,4 +1,4 @@
-<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="IBL" psdefines="DIFFMAP NORMALMAP PBR IBL">
+<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="NORMALMAP" psdefines="DIFFMAP NORMALMAP PBR IBL">
     <pass name="alpha" depthwrite="false" blend="alpha" />
     <pass name="litalpha" depthwrite="false" blend="addalpha" />
     <pass name="shadow" vs="Shadow" ps="Shadow" psexcludes="PACKEDNORMAL" />

+ 1 - 1
Resources/CoreData/Techniques/PBR/PBRDiffNormalEmissive.xml

@@ -1,4 +1,4 @@
-<technique vs="PBRLitSolid" ps="PBRLitSolid"  vsdefines="IBL" psdefines="DIFFMAP NORMALMAP EMISSIVEMAP PBR IBL">
+<technique vs="PBRLitSolid" ps="PBRLitSolid"  vsdefines="NORMALMAP" psdefines="DIFFMAP NORMALMAP EMISSIVEMAP PBR IBL">
     <pass name="light" depthtest="equal" depthwrite="false" blend="add" />
     <pass name="material" psdefines="MATERIAL" depthtest="equal" depthwrite="false" />
     <pass name="deferred" psdefines="DEFERRED" blend="add" />

+ 1 - 1
Resources/CoreData/Techniques/PBR/PBRDiffNormalEmissiveAlpha.xml

@@ -1,4 +1,4 @@
-<technique vs="PBRLitSolid" ps="PBRLitSolid"  vsdefines="IBL" psdefines="DIFFMAP NORMALMAP EMISSIVEMAP PBR IBL">
+<technique vs="PBRLitSolid" ps="PBRLitSolid"  vsdefines="NORMALMAP" psdefines="DIFFMAP NORMALMAP EMISSIVEMAP PBR IBL">
     <pass name="alpha" depthwrite="false" blend="alpha" />
     <pass name="litalpha" depthwrite="false" blend="addalpha" />
     <pass name="shadow" vs="Shadow" ps="Shadow" psexcludes="PACKEDNORMAL" />

+ 1 - 1
Resources/CoreData/Techniques/PBR/PBRMetallicRoughDiffNormalSpec.xml

@@ -1,4 +1,4 @@
-<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="IBL" psdefines="NORMALMAP DIFFMAP METALLIC ROUGHNESS PBR IBL">
+<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="NORMALMAP" psdefines="NORMALMAP DIFFMAP METALLIC ROUGHNESS PBR IBL">
     <pass name="base" />
     <pass name="light" depthtest="equal" depthwrite="false" blend="add" />
     <pass name="material" psdefines="MATERIAL" depthtest="equal" depthwrite="false" />

+ 1 - 1
Resources/CoreData/Techniques/PBR/PBRMetallicRoughDiffNormalSpecEmissive.xml

@@ -1,4 +1,4 @@
-<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="IBL" psdefines="DIFFMAP NORMALMAP EMISSIVEMAP METALLIC ROUGHNESS PBR IBL">
+<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="NORMALMAP" psdefines="DIFFMAP NORMALMAP EMISSIVEMAP METALLIC ROUGHNESS PBR IBL">
     <pass name="base" />
     <pass name="light" depthtest="equal" depthwrite="false" blend="add" />
     <pass name="material" psdefines="MATERIAL" depthtest="equal" depthwrite="false" />

+ 1 - 1
Resources/CoreData/Techniques/PBR/PBRMetallicRoughDiffNormalSpecEmissiveAlpha.xml

@@ -1,4 +1,4 @@
-<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="IBL" psdefines="DIFFMAP NORMALMAP EMISSIVEMAP PBR IBL METALLIC ROUGHNESS">
+<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="NORMALMAP" psdefines="DIFFMAP NORMALMAP EMISSIVEMAP PBR IBL METALLIC ROUGHNESS">
     <pass name="alpha" depthwrite="false" blend="alpha" />
     <pass name="litalpha" depthwrite="false" blend="addalpha" />
     <pass name="shadow" vs="Shadow" ps="Shadow" psexcludes="PACKEDNORMAL" />

+ 1 - 1
Resources/CoreData/Techniques/PBR/PBRMetallicRoughDiffSpec.xml

@@ -1,4 +1,4 @@
-<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="IBL" psdefines="DIFFMAP PBR IBL METALLIC ROUGHNESS">
+<technique vs="PBRLitSolid" ps="PBRLitSolid" psdefines="DIFFMAP PBR IBL METALLIC ROUGHNESS">
     <pass name="base"/>
     <pass name="light" depthtest="equal" depthwrite="false" blend="add" />
     <pass name="material" psdefines="MATERIAL" depthtest="equal" depthwrite="false" />

+ 1 - 1
Resources/CoreData/Techniques/PBR/PBRMetallicRoughDiffSpecAlpha.xml

@@ -1,4 +1,4 @@
-<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="IBL" psdefines="DIFFMAP PBR IBL METALLIC ROUGHNESS">
+<technique vs="PBRLitSolid" ps="PBRLitSolid" psdefines="DIFFMAP PBR IBL METALLIC ROUGHNESS">
     <pass name="alpha" depthwrite="false" blend="alpha" />
     <pass name="litalpha" depthwrite="false" blend="addalpha" />
     <pass name="shadow" vs="Shadow" ps="Shadow" />

+ 1 - 1
Resources/CoreData/Techniques/PBR/PBRNoTexture.xml

@@ -1,4 +1,4 @@
-<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="NOUV PBR IBL" psdefines="PBR IBL">
+<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="NOUV" psdefines="PBR IBL">
     <pass name="base" />
     <pass name="light" depthtest="equal" depthwrite="false" blend="add" />
     <pass name="prepass" psdefines="PREPASS" />

+ 1 - 1
Resources/CoreData/Techniques/PBR/PBRNoTextureAlpha.xml

@@ -1,4 +1,4 @@
-<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="NOUV IBL" psdefines="PBR IBL">
+<technique vs="PBRLitSolid" ps="PBRLitSolid" vsdefines="NOUV" psdefines="PBR IBL">
     <pass name="alpha"  depthwrite="false" blend="alpha" />
     <pass name="litalpha" depthwrite="false" blend="addalpha" />
     <pass name="shadow" vs="Shadow" ps="Shadow" />

+ 24 - 22
Source/Atomic/Atomic2D/TileMap2D.cpp

@@ -60,40 +60,36 @@ void TileMap2D::RegisterObject(Context* context)
         AM_DEFAULT);
 }
 
+// Transform vector from node-local space to global space
+static Vector2 TransformNode2D(Matrix3x4 transform, Vector2 local)
+{
+    Vector3 transformed = transform * Vector4(local.x_, local.y_, 0.f, 1.f);
+    return Vector2(transformed.x_, transformed.y_);
+}
+
 void TileMap2D::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 {
     const Color& color = Color::RED;
     float mapW = info_.GetMapWidth();
     float mapH = info_.GetMapHeight();
+    const Matrix3x4 transform = GetNode()->GetTransform();
 
     switch (info_.orientation_)
     {
     case O_ORTHOGONAL:
-        debug->AddLine(Vector2(0.0f, 0.0f), Vector2(mapW, 0.0f), color);
-        debug->AddLine(Vector2(mapW, 0.0f), Vector2(mapW, mapH), color);
-        debug->AddLine(Vector2(mapW, mapH), Vector2(0.0f, mapH), color);
-        debug->AddLine(Vector2(0.0f, mapH), Vector2(0.0f, 0.0f), color);
-        break;
-
-    case O_ISOMETRIC:
-        debug->AddLine(Vector2(0.0f, mapH * 0.5f), Vector2(mapW * 0.5f, 0.0f), color);
-        debug->AddLine(Vector2(mapW * 0.5f, 0.0f), Vector2(mapW, mapH * 0.5f), color);
-        debug->AddLine(Vector2(mapW, mapH * 0.5f), Vector2(mapW * 0.5f, mapH), color);
-        debug->AddLine(Vector2(mapW * 0.5f, mapH), Vector2(0.0f, mapH * 0.5f), color);
-        break;
-
     case O_STAGGERED:
-        debug->AddLine(Vector2(0.0f, 0.0f), Vector2(mapW, 0.0f), color);
-        debug->AddLine(Vector2(mapW, 0.0f), Vector2(mapW, mapH), color);
-        debug->AddLine(Vector2(mapW, mapH), Vector2(0.0f, mapH), color);
-        debug->AddLine(Vector2(0.0f, mapH), Vector2(0.0f, 0.0f), color);
+    case O_HEXAGONAL:
+        debug->AddLine(TransformNode2D(transform, Vector2(0.0f, 0.0f)), TransformNode2D(transform, Vector2(mapW, 0.0f)), color);
+        debug->AddLine(TransformNode2D(transform, Vector2(mapW, 0.0f)), TransformNode2D(transform, Vector2(mapW, mapH)), color);
+        debug->AddLine(TransformNode2D(transform, Vector2(mapW, mapH)), TransformNode2D(transform, Vector2(0.0f, mapH)), color);
+        debug->AddLine(TransformNode2D(transform, Vector2(0.0f, mapH)), TransformNode2D(transform, Vector2(0.0f, 0.0f)), color);
         break;
 
-    case O_HEXAGONAL:
-        debug->AddLine(Vector2(0.0f, 0.0f), Vector2(mapW, 0.0f), color);
-        debug->AddLine(Vector2(mapW, 0.0f), Vector2(mapW, mapH), color);
-        debug->AddLine(Vector2(mapW, mapH), Vector2(0.0f, mapH), color);
-        debug->AddLine(Vector2(0.0f, mapH), Vector2(0.0f, 0.0f), color);
+    case O_ISOMETRIC:
+        debug->AddLine(TransformNode2D(transform, Vector2(0.0f, mapH * 0.5f)), TransformNode2D(transform, Vector2(mapW * 0.5f, 0.0f)), color);
+        debug->AddLine(TransformNode2D(transform, Vector2(mapW * 0.5f, 0.0f)), TransformNode2D(transform, Vector2(mapW, mapH * 0.5f)), color);
+        debug->AddLine(TransformNode2D(transform, Vector2(mapW, mapH * 0.5f)), TransformNode2D(transform, Vector2(mapW * 0.5f, mapH)), color);
+        debug->AddLine(TransformNode2D(transform, Vector2(mapW * 0.5f, mapH)), TransformNode2D(transform, Vector2(0.0f, mapH * 0.5f)), color);
         break;
     }
 
@@ -186,6 +182,12 @@ ResourceRef TileMap2D::GetTmxFileAttr() const
     return GetResourceRef(tmxFile_, TmxFile2D::GetTypeStatic());
 }
 
+Vector<SharedPtr<TileMapObject2D> > TileMap2D::GetTileCollisionShapes(int gid) const
+{
+    Vector<SharedPtr<TileMapObject2D> > shapes;
+    return tmxFile_ ? tmxFile_->GetTileCollisionShapes(gid) : shapes;
+}
+
 // ATOMIC BEGIN
 
 TileMapLayer2D* TileMap2D::GetLayerByName(const String& name) const

+ 2 - 1
Source/Atomic/Atomic2D/TileMap2D.h

@@ -72,7 +72,8 @@ public:
     void SetTmxFileAttr(const ResourceRef& value);
     /// Return tile map file attribute.
     ResourceRef GetTmxFileAttr() const;
-
+    ///
+    Vector<SharedPtr<TileMapObject2D> > GetTileCollisionShapes(int gid) const;
     // ATOMIC BEGIN
 
     TileMapLayer2D* GetLayerByName(const String& name) const;

+ 68 - 14
Source/Atomic/Atomic2D/TileMapLayer2D.cpp

@@ -57,6 +57,13 @@ void TileMapLayer2D::RegisterObject(Context* context)
     context->RegisterFactory<TileMapLayer2D>();
 }
 
+// Transform vector from node-local space to global space
+static Vector2 TransformNode2D(Matrix3x4 transform, Vector2 local)
+{
+    Vector3 transformed = transform * Vector4(local.x_, local.y_, 0.f, 1.f);
+    return Vector2(transformed.x_, transformed.y_);
+}
+
 void TileMapLayer2D::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 {
     if (!debug)
@@ -64,29 +71,65 @@ void TileMapLayer2D::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 
     if (objectGroup_)
     {
+        const Matrix3x4 transform = GetTileMap()->GetNode()->GetTransform();
         for (unsigned i = 0; i < objectGroup_->GetNumObjects(); ++i)
         {
             TileMapObject2D* object = objectGroup_->GetObject(i);
             const Color& color = Color::YELLOW;
+            const Vector2& size = object->GetSize();
+            const TileMapInfo2D& info = tileMap_->GetInfo();
+
 
             switch (object->GetObjectType())
             {
             case OT_RECTANGLE:
                 {
-                    const Vector2& lb = object->GetPosition();
-                    const Vector2& rt = lb + object->GetSize();
+                    Vector<Vector2> points;
+
+                    switch (info.orientation_)
+                    {
+                    case O_ORTHOGONAL:
+                    case O_HEXAGONAL:
+                    case O_STAGGERED:
+                        {
+                            points.Push(Vector2::ZERO);
+                            points.Push(Vector2(size.x_, 0.0f));
+                            points.Push(Vector2(size.x_, -size.y_));
+                            points.Push(Vector2(0.0f, -size.y_));
+                            break;
+                        }
+                    case O_ISOMETRIC:
+                        {
+                            float ratio = (info.tileWidth_ / info.tileHeight_) * 0.5f;
+                            points.Push(Vector2::ZERO);
+                            points.Push(Vector2(size.y_ * ratio, size.y_ * 0.5f));
+                            points.Push(Vector2((size.x_ + size.y_) * ratio, (-size.x_ + size.y_) * 0.5f));
+                            points.Push(Vector2(size.x_ * ratio, -size.x_ * 0.5f));
+                            break;
+                        }
+                    }
 
-                    debug->AddLine(Vector2(lb.x_, lb.y_), Vector2(rt.x_, lb.y_), color, depthTest);
-                    debug->AddLine(Vector2(rt.x_, lb.y_), Vector2(rt.x_, rt.y_), color, depthTest);
-                    debug->AddLine(Vector2(rt.x_, rt.y_), Vector2(lb.x_, rt.y_), color, depthTest);
-                    debug->AddLine(Vector2(lb.x_, rt.y_), Vector2(lb.x_, lb.y_), color, depthTest);
+                    for (unsigned j = 0; j < points.Size(); ++j)
+                        debug->AddLine(TransformNode2D(transform, points[j] + object->GetPosition()),
+                                       TransformNode2D(transform, points[(j + 1) % points.Size()] + object->GetPosition()), color, depthTest);
                 }
                 break;
 
             case OT_ELLIPSE:
                 {
                     const Vector2 halfSize = object->GetSize() * 0.5f;
-                    const Vector2 center = object->GetPosition() + halfSize;
+                    float ratio = (info.tileWidth_ / info.tileHeight_) * 0.5f; // For isometric only
+
+                    Vector2 pivot = object->GetPosition();
+                    if (info.orientation_ == O_ISOMETRIC)
+                    {
+                        pivot += Vector2((halfSize.x_ + halfSize.y_) * ratio, (-halfSize.x_ + halfSize.y_) * 0.5f);
+                    }
+                    else
+                    {
+                        pivot += halfSize;
+                    }
+
                     for (unsigned i = 0; i < 360; i += 30)
                     {
                         unsigned j = i + 30;
@@ -94,7 +137,16 @@ void TileMapLayer2D::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
                         float y1 = halfSize.y_ * Sin((float)i);
                         float x2 = halfSize.x_ * Cos((float)j);
                         float y2 = halfSize.y_ * Sin((float)j);
-                        debug->AddLine(center + Vector2(x1, y1), center + Vector2(x2, y2), color, depthTest);
+                        Vector2 point1 = Vector2(x1, - y1);
+                        Vector2 point2 = Vector2(x2, - y2);
+
+                        if (info.orientation_ == O_ISOMETRIC)
+                        {
+                            point1 = Vector2((point1.x_ + point1.y_) * ratio, (point1.y_ - point1.x_) * 0.5f);
+                            point2 = Vector2((point2.x_ + point2.y_) * ratio, (point2.y_ - point2.x_) * 0.5f);
+                        }
+
+                        debug->AddLine(TransformNode2D(transform, pivot + point1), TransformNode2D(transform, pivot + point2), color, depthTest);
                     }
                 }
                 break;
@@ -103,10 +155,15 @@ void TileMapLayer2D::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
             case OT_POLYLINE:
                 {
                     for (unsigned j = 0; j < object->GetNumPoints() - 1; ++j)
-                        debug->AddLine(object->GetPoint(j), object->GetPoint(j + 1), color, depthTest);
+                        debug->AddLine(TransformNode2D(transform, object->GetPoint(j)),
+                                       TransformNode2D(transform, object->GetPoint(j + 1)), color, depthTest);
 
                     if (object->GetObjectType() == OT_POLYGON)
-                        debug->AddLine(object->GetPoint(0), object->GetPoint(object->GetNumPoints() - 1), color, depthTest);
+                        debug->AddLine(TransformNode2D(transform, object->GetPoint(0)),
+                                       TransformNode2D(transform, object->GetPoint(object->GetNumPoints() - 1)), color, depthTest);
+                    // Also draw a circle at origin to indicate direction
+                    else
+                        debug->AddCircle(TransformNode2D(transform, object->GetPoint(0)), Vector3::FORWARD, 0.05f, color, 64, depthTest);
                 }
                 break;
 
@@ -278,10 +335,7 @@ Node* TileMapLayer2D::GetObjectNode(unsigned index) const
 
 Node* TileMapLayer2D::GetImageNode() const
 {
-    if (!imageLayer_)
-        return 0;
-
-    if (nodes_.Empty())
+    if (!imageLayer_ || nodes_.Empty())
         return 0;
 
     return nodes_[0];

+ 340 - 165
Source/Atomic/Atomic2D/TmxFile2D.cpp

@@ -20,23 +20,24 @@
 // THE SOFTWARE.
 //
 
-#include "../Precompiled.h"
-
 #include "../Core/Context.h"
 #include "../Graphics/Texture2D.h"
+#include "../Graphics/Graphics.h"
 #include "../IO/FileSystem.h"
 #include "../IO/Log.h"
 #include "../Resource/ResourceCache.h"
 #include "../Resource/XMLFile.h"
+#include "../Resource/Image.h"
 #include "../Atomic2D/Sprite2D.h"
 #include "../Atomic2D/TmxFile2D.h"
-
+#include "../Math/AreaAllocator.h"
 // ATOMIC BEGIN
 #include "../Atomic2D/Drawable2D.h"
 // ATOMIC END
 
 #include "../DebugNew.h"
 
+
 namespace Atomic
 {
 
@@ -96,6 +97,12 @@ TmxTileLayer2D::TmxTileLayer2D(TmxFile2D* tmxFile) :
 {
 }
 
+enum LayerEncoding {
+    XML,
+    CSV,
+    Base64,
+};
+
 bool TmxTileLayer2D::Load(const XMLElement& element, const TileMapInfo2D& info)
 {
     LoadInfo(element);
@@ -107,38 +114,111 @@ bool TmxTileLayer2D::Load(const XMLElement& element, const TileMapInfo2D& info)
         return false;
     }
 
-    if (dataElem.HasAttribute("encoding") && dataElem.GetAttribute("encoding") != "xml")
+    LayerEncoding encoding;
+    if (dataElem.HasAttribute("compression"))
     {
-        ATOMIC_LOGERROR("Encoding not support now");
+        ATOMIC_LOGERROR("Compression not supported now");
         return false;
     }
 
-    XMLElement tileElem = dataElem.GetChild("tile");
-    tiles_.Resize((unsigned)(width_ * height_));
-
-    for (int y = 0; y < height_; ++y)
+    if (dataElem.HasAttribute("encoding"))
     {
-        for (int x = 0; x < width_; ++x)
+        String encodingAttribute = dataElem.GetAttribute("encoding");
+        if (encodingAttribute == "xml")
+            encoding = XML;
+        else if (encodingAttribute == "csv")
+            encoding = CSV;
+        else if (encodingAttribute == "base64")
+            encoding = Base64;
+        else
         {
-            if (!tileElem)
-                return false;
+            ATOMIC_LOGERROR("Invalid encoding: " + encodingAttribute);
+            return false;
+        }
+    }
+    else
+        encoding = XML;
+
+    tiles_.Resize((unsigned)(width_ * height_));
+    if (encoding == XML)
+    {
+        XMLElement tileElem = dataElem.GetChild("tile");
 
-            int gid = tileElem.GetInt("gid");
-            if (gid > 0)
+        for (int y = 0; y < height_; ++y)
+        {
+            for (int x = 0; x < width_; ++x)
             {
-                SharedPtr<Tile2D> tile(new Tile2D());
-                tile->gid_ = gid;
-                tile->sprite_ = tmxFile_->GetTileSprite(gid);
-                tile->propertySet_ = tmxFile_->GetTilePropertySet(gid);
+                if (!tileElem)
+                    return false;
+
+                int gid = tileElem.GetInt("gid");
+                if (gid > 0)
+                {
+                    SharedPtr<Tile2D> tile(new Tile2D());
+                    tile->gid_ = gid;
+                    tile->sprite_ = tmxFile_->GetTileSprite(gid);
+                    tile->propertySet_ = tmxFile_->GetTilePropertySet(gid);
 
 // ATOMIC BEGIN
-                tile->objectGroup_ = tmxFile_->GetTileObjectGroup(gid);
+                    tile->objectGroup_ = tmxFile_->GetTileObjectGroup(gid);
 // ATOMIC END
 
-                tiles_[y * width_ + x] = tile;
-            }
+                    tiles_[y * width_ + x] = tile;
+                }
 
-            tileElem = tileElem.GetNext("tile");
+                tileElem = tileElem.GetNext("tile");
+            }
+        }
+    }
+    else if (encoding == CSV)
+    {
+        String dataValue = dataElem.GetValue();
+        Vector<String> gidVector = dataValue.Split(',');
+        int currentIndex = 0;
+        for (int y = 0; y < height_; ++y)
+        {
+            for (int x = 0; x < width_; ++x)
+            {
+                gidVector[currentIndex].Replace("\n", "");
+                int gid = ToInt(gidVector[currentIndex]);
+                if (gid > 0)
+                {
+                    SharedPtr<Tile2D> tile(new Tile2D());
+                    tile->gid_ = gid;
+                    tile->sprite_ = tmxFile_->GetTileSprite(gid);
+                    tile->propertySet_ = tmxFile_->GetTilePropertySet(gid);
+                    tiles_[y * width_ + x] = tile;
+                }
+                ++currentIndex;
+            }
+        }
+    }
+    else if (encoding == Base64)
+    {
+        String dataValue = dataElem.GetValue();
+        int startPosition = 0;
+        while (!IsAlpha(dataValue[startPosition]) && !IsDigit(dataValue[startPosition])
+              && dataValue[startPosition] != '+' && dataValue[startPosition] != '/') ++startPosition;
+        dataValue = dataValue.Substring(startPosition);
+        PODVector<unsigned char> buffer = DecodeBase64(dataValue);
+        int currentIndex = 0;
+        for (int y = 0; y < height_; ++y)
+        {
+            for (int x = 0; x < width_; ++x)
+            {
+                // buffer contains 32-bit integers in little-endian format
+                int gid = (buffer[currentIndex+3] << 24) | (buffer[currentIndex+2] << 16)
+                        | (buffer[currentIndex+1] << 8) | buffer[currentIndex];
+                if (gid > 0)
+                {
+                    SharedPtr<Tile2D> tile(new Tile2D());
+                    tile->gid_ = gid;
+                    tile->sprite_ = tmxFile_->GetTileSprite(gid);
+                    tile->propertySet_ = tmxFile_->GetTilePropertySet(gid);
+                    tiles_[y * width_ + x] = tile;
+                }
+                currentIndex += 4;
+            }
         }
     }
 
@@ -168,118 +248,115 @@ bool TmxObjectGroup2D::Load(const XMLElement& element, const TileMapInfo2D& info
     for (XMLElement objectElem = element.GetChild("object"); objectElem; objectElem = objectElem.GetNext("object"))
     {
         SharedPtr<TileMapObject2D> object(new TileMapObject2D());
+        StoreObject(objectElem, object, info, false, local);
+        objects_.Push(object);
+    }
 
-        // ATOMIC BEGIN
+    if (element.HasChild("properties"))
+        LoadPropertySet(element.GetChild("properties"));
+    return true;
+}
 
-        if (objectElem.HasAttribute("name"))
-            object->name_ = objectElem.GetAttribute("name");
-        else
-            object->name_ = "Object";
+void TmxObjectGroup2D::StoreObject(XMLElement objectElem, SharedPtr<TileMapObject2D> object, const TileMapInfo2D& info, bool isTile, bool local)
+{
+    if (objectElem.HasAttribute("name"))
+        object->name_ = objectElem.GetAttribute("name");
+// ATOMIC BEGIN
+    else
+        object->name_ = "Object";
+// ATOMIC END
+    if (objectElem.HasAttribute("type"))
+        object->type_ = objectElem.GetAttribute("type");
+
+    if (objectElem.HasAttribute("gid"))
+        object->objectType_ = OT_TILE;
+    else if (objectElem.HasChild("polygon"))
+        object->objectType_ = OT_POLYGON;
+    else if (objectElem.HasChild("polyline"))
+        object->objectType_ = OT_POLYLINE;
+    else if (objectElem.HasChild("ellipse"))
+        object->objectType_ = OT_ELLIPSE;
+    else
+        object->objectType_ = OT_RECTANGLE;
 
-        // ATOMIC END
+    const Vector2 position(objectElem.GetFloat("x"), objectElem.GetFloat("y"));
+    const Vector2 size(objectElem.GetFloat("width"), objectElem.GetFloat("height"));
 
-        if (objectElem.HasAttribute("type"))
-            object->type_ = objectElem.GetAttribute("type");
-
-        if (objectElem.HasAttribute("gid"))
-            object->objectType_ = OT_TILE;
-        else if (objectElem.HasChild("polygon"))
-            object->objectType_ = OT_POLYGON;
-        else if (objectElem.HasChild("polyline"))
-            object->objectType_ = OT_POLYLINE;
-        else if (objectElem.HasChild("ellipse"))
-            object->objectType_ = OT_ELLIPSE;
+    switch (object->objectType_)
+    {
+    case OT_RECTANGLE:
+    case OT_ELLIPSE:
+        // ATOMIC BEGIN
+        object->size_ = Vector2(size.x_ * PIXEL_SIZE, size.y_ * PIXEL_SIZE);
+        // ATOMIC BEGIN
+        if (!local)
+        {
+            object->position_ = info.ConvertPosition(Vector2(position.x_, position.y_ + size.y_));
+        }
         else
-            object->objectType_ = OT_RECTANGLE;
-
-        const Vector2 position(objectElem.GetFloat("x"), objectElem.GetFloat("y"));
-        const Vector2 size(objectElem.GetFloat("width"), objectElem.GetFloat("height"));
-
-        switch (object->objectType_)
         {
-        case OT_RECTANGLE:
-        case OT_ELLIPSE:
-
-            // ATOMIC BEGIN
-            object->size_ = Vector2(size.x_ * PIXEL_SIZE, size.y_ * PIXEL_SIZE);
-
-
-            if (!local)
-            {
-                object->position_ = info.ConvertPosition(Vector2(position.x_, position.y_ + size.y_));
-            }
-            else
-            {
-                Vector2 nposition = position;
-
-                nposition.x_ *= PIXEL_SIZE;
-                nposition.y_ *= PIXEL_SIZE;
+            Vector2 nposition = position;
 
-                nposition.x_ = nposition.x_ + object->size_.x_ / 2.0f;
-                nposition.y_ = nposition.y_ + object->size_.y_ / 2.0f;
+            nposition.x_ *= PIXEL_SIZE;
+            nposition.y_ *= PIXEL_SIZE;
 
-                nposition.y_ = info.tileHeight_  - nposition.y_;
+            nposition.x_ = nposition.x_ + object->size_.x_ / 2.0f;
+            nposition.y_ = nposition.y_ + object->size_.y_ / 2.0f;
 
-                object->position_ = nposition;
-            }
-            // ATOMIC END
-            break;
+            nposition.y_ = info.tileHeight_  - nposition.y_;
 
-        case OT_TILE:
-            object->position_ = info.ConvertPosition(position);
-            object->gid_ = objectElem.GetInt("gid");
-            object->sprite_ = tmxFile_->GetTileSprite(object->gid_);
+            object->position_ = nposition;
+        }
+        // ATOMIC END
+        break;
 
-            if (objectElem.HasAttribute("width") || objectElem.HasAttribute("height"))
-            {
-                object->size_ = Vector2(size.x_ * PIXEL_SIZE, size.y_ * PIXEL_SIZE);
-            }
-            else if (object->sprite_)
-            {
-                IntVector2 spriteSize = object->sprite_->GetRectangle().Size();
-                object->size_ = Vector2(spriteSize.x_, spriteSize.y_);
-            }
-            break;
+    case OT_TILE:
+        object->position_ = info.ConvertPosition(position);
+        object->gid_ = objectElem.GetInt("gid");
+        object->sprite_ = tmxFile_->GetTileSprite(object->gid_);
 
-        case OT_POLYGON:
-        case OT_POLYLINE:
-            {
-                Vector<String> points;
+        if (objectElem.HasAttribute("width") || objectElem.HasAttribute("height"))
+        {
+            object->size_ = Vector2(size.x_ * PIXEL_SIZE, size.y_ * PIXEL_SIZE);
+        }
+        else if (object->sprite_)
+        {
+            IntVector2 spriteSize = object->sprite_->GetRectangle().Size();
+            object->size_ = Vector2(spriteSize.x_, spriteSize.y_);
+        }
+        break;
 
-                const char* name = object->objectType_ == OT_POLYGON ? "polygon" : "polyline";
-                XMLElement polygonElem = objectElem.GetChild(name);
-                points = polygonElem.GetAttribute("points").Split(' ');
+    case OT_POLYGON:
+    case OT_POLYLINE:
+    {
+        Vector<String> points;
 
-                if (points.Size() <= 1)
-                    continue;
+        const char* name = object->objectType_ == OT_POLYGON ? "polygon" : "polyline";
+        XMLElement polygonElem = objectElem.GetChild(name);
+        points = polygonElem.GetAttribute("points").Split(' ');
 
-                object->points_.Resize(points.Size());
+        if (points.Size() <= 1)
+            return;
 
-                for (unsigned i = 0; i < points.Size(); ++i)
-                {
-                    points[i].Replace(',', ' ');
-                    Vector2 point = position + ToVector2(points[i]);
-                    object->points_[i] = info.ConvertPosition(point);
-                }
-            }
-            break;
+        object->points_.Resize(points.Size());
 
-        default: break;
-        }
-
-        if (objectElem.HasChild("properties"))
+        for (unsigned i = 0; i < points.Size(); ++i)
         {
-            object->propertySet_ = new PropertySet2D();
-            object->propertySet_->Load(objectElem.GetChild("properties"));
+            points[i].Replace(',', ' ');
+            Vector2 point = position + ToVector2(points[i]);
+            object->points_[i] = info.ConvertPosition(point);
         }
-
-        objects_.Push(object);
     }
+        break;
 
-    if (element.HasChild("properties"))
-        LoadPropertySet(element.GetChild("properties"));
+    default: break;
+    }
 
-    return true;
+    if (objectElem.HasChild("properties"))
+    {
+        object->propertySet_ = new PropertySet2D();
+        object->propertySet_->Load(objectElem.GetChild("properties"));
+    }
 }
 
 TileMapObject2D* TmxObjectGroup2D::GetObject(unsigned index) const
@@ -397,7 +474,6 @@ bool TmxFile2D::BeginLoad(Deserializer& source)
                     return false;
                 }
                 // ATOMIC END
-
                 tsxXMLFiles_[source] = tsxXMLFile;
                 
                 String textureFilePath =
@@ -477,7 +553,7 @@ bool TmxFile2D::EndLoad()
         }
         else if (name == "objectgroup")
         {
-            SharedPtr<TmxObjectGroup2D> objectGroup (new TmxObjectGroup2D(this));\
+            SharedPtr<TmxObjectGroup2D> objectGroup (new TmxObjectGroup2D(this));
             ret = objectGroup->Load(childElement, info_);
 
             layers_.Push(objectGroup);
@@ -543,6 +619,16 @@ Sprite2D* TmxFile2D::GetTileSprite(int gid) const
     return i->second_;
 }
 
+Vector<SharedPtr<TileMapObject2D> > TmxFile2D::GetTileCollisionShapes(int gid) const
+{
+    Vector<SharedPtr<TileMapObject2D> > tileShapes;
+    HashMap<int, Vector<SharedPtr<TileMapObject2D> > >::ConstIterator i = gidToCollisionShapeMapping_.Find(gid);
+    if (i == gidToCollisionShapeMapping_.End())
+        return tileShapes;
+
+    return i->second_;
+}
+
 PropertySet2D* TmxFile2D::GetTilePropertySet(int gid) const
 {
     HashMap<int, SharedPtr<PropertySet2D> >::ConstIterator i = gidToPropertySetMapping_.Find(gid);
@@ -573,6 +659,15 @@ SharedPtr<XMLFile> TmxFile2D::LoadTSXFile(const String& source)
     return tsxXMLFile;
 }
 
+struct TileImageInfo {
+    Image* image;
+    int tileGid;
+    int imageWidth;
+    int imageHeight;
+    int x;
+    int y;
+};
+
 bool TmxFile2D::LoadTileSet(const XMLElement& element)
 {
     int firstgid = element.GetInt("firstgid");
@@ -598,7 +693,7 @@ bool TmxFile2D::LoadTileSet(const XMLElement& element)
             }
             // ATOMIC END
 
-            // Add to napping to avoid release
+            // Add to mapping to avoid release
             tsxXMLFiles_[source] = tsxXMLFile;
 
             tileSetElem = tsxXMLFile->GetRoot("tileset");
@@ -609,75 +704,109 @@ bool TmxFile2D::LoadTileSet(const XMLElement& element)
     else
         tileSetElem = element;
 
-    XMLElement imageElem = tileSetElem.GetChild("image");
-    String textureFilePath = GetParentPath(GetName()) + imageElem.GetAttribute("source");
+    int tileWidth = tileSetElem.GetInt("tilewidth");
+    int tileHeight = tileSetElem.GetInt("tileheight");
+    int spacing = tileSetElem.GetInt("spacing");
+    int margin = tileSetElem.GetInt("margin");
+    int imageWidth;
+    int imageHeight;
+    bool isSingleTileSet = false;
+
     ResourceCache* cache = GetSubsystem<ResourceCache>();
-    SharedPtr<Texture2D> texture(cache->GetResource<Texture2D>(textureFilePath));
-    if (!texture)
     {
-        ATOMIC_LOGERROR("Could not load texture " + textureFilePath);
-        return false;
-    }
-
-    // ATOMIC BEGIN
+        XMLElement imageElem = tileSetElem.GetChild("image");
+        // Tileset based on single tileset image
+        if (imageElem.NotNull()) {
+            isSingleTileSet = true;
+            String textureFilePath = GetParentPath(GetName()) + imageElem.GetAttribute("source");
+            SharedPtr<Texture2D> texture(cache->GetResource<Texture2D>(textureFilePath));
+            if (!texture)
+            {
+                ATOMIC_LOGERROR("Could not load texture " + textureFilePath);
+                return false;
+            }
 
-    // reduces border tile sample errors
-    texture->SetFilterMode(FILTER_NEAREST);
+            // ATOMIC BEGIN
 
-    // ATOMIC END
+            // reduces border tile sample errors
+            texture->SetFilterMode(FILTER_NEAREST);
 
-    tileSetTextures_.Push(texture);
+            // ATOMIC END
 
-    int tileWidth = tileSetElem.GetInt("tilewidth");
-    int tileHeight = tileSetElem.GetInt("tileheight");
-    int spacing = tileSetElem.GetInt("spacing");
-    int margin = tileSetElem.GetInt("margin");
-    int imageWidth = imageElem.GetInt("width");
-    int imageHeight = imageElem.GetInt("height");
+            // Set hot spot at left bottom
+            Vector2 hotSpot(0.0f, 0.0f);
+            if (tileSetElem.HasChild("tileoffset"))
+            {
+                XMLElement offsetElem = tileSetElem.GetChild("tileoffset");
+                hotSpot.x_ += offsetElem.GetFloat("x") / (float)tileWidth;
+                hotSpot.y_ += offsetElem.GetFloat("y") / (float)tileHeight;
+            }
 
-    // Set hot spot at left bottom
-    Vector2 hotSpot(0.0f, 0.0f);
-    if (tileSetElem.HasChild("tileoffset"))
-    {
-        XMLElement offsetElem = tileSetElem.GetChild("tileoffset");
-        hotSpot.x_ += offsetElem.GetFloat("x") / (float)tileWidth;
-        hotSpot.y_ += offsetElem.GetFloat("y") / (float)tileHeight;
-    }
+            imageWidth = imageElem.GetInt("width");
+            imageHeight = imageElem.GetInt("height");
 
-    int gid = firstgid;
-    for (int y = margin; y + tileHeight <= imageHeight - margin; y += tileHeight + spacing)
-    {
-        for (int x = margin; x + tileWidth <= imageWidth - margin; x += tileWidth + spacing)
-        {
-            SharedPtr<Sprite2D> sprite(new Sprite2D(context_));
-            sprite->SetTexture(texture);
-            sprite->SetRectangle(IntRect(x, y, x + tileWidth, y + tileHeight));
-            sprite->SetHotSpot(hotSpot);
+            int gid = firstgid;
+            for (int y = margin; y + tileHeight <= imageHeight - margin; y += tileHeight + spacing)
+            {
+                for (int x = margin; x + tileWidth <= imageWidth - margin; x += tileWidth + spacing)
+                {
+                    SharedPtr<Sprite2D> sprite(new Sprite2D(context_));
+                    sprite->SetTexture(texture);
+                    sprite->SetRectangle(IntRect(x, y, x + tileWidth, y + tileHeight));
+                    sprite->SetHotSpot(hotSpot);
 
-            gidToSpriteMapping_[gid++] = sprite;
+                    gidToSpriteMapping_[gid++] = sprite;
+                }
+            }
         }
     }
 
+    Vector<TileImageInfo> tileImageInfos;
     for (XMLElement tileElem = tileSetElem.GetChild("tile"); tileElem; tileElem = tileElem.GetNext("tile"))
     {
-        if (tileElem.HasChild("properties"))
+        int gid = firstgid + tileElem.GetInt("id");
+        // Tileset based on collection of images
+        if (!isSingleTileSet)
         {
-            SharedPtr<PropertySet2D> propertySet(new PropertySet2D());
-            propertySet->Load(tileElem.GetChild("properties"));
-            gidToPropertySetMapping_[firstgid + tileElem.GetInt("id")] = propertySet;
+            XMLElement imageElem = tileElem.GetChild("image");
+            if (imageElem.NotNull()) {
+                String textureFilePath = GetParentPath(GetName()) + imageElem.GetAttribute("source");
+                SharedPtr<Image> image(cache->GetResource<Image>(textureFilePath));
+                if (!image)
+                {
+                    ATOMIC_LOGERROR("Could not load image " + textureFilePath);
+                    return false;
+                }
+                tileWidth = imageWidth = imageElem.GetInt("width");
+                tileHeight = imageHeight = imageElem.GetInt("height");
+                TileImageInfo info = {image, gid, imageWidth, imageHeight, 0, 0};
+                tileImageInfos.Push(info);
+            }
         }
-		else if (tileElem.HasChild("objectgroup"))
+        // Tile collision shape(s)
+        TmxObjectGroup2D objectGroup(this);
+        for (XMLElement collisionElem = tileElem.GetChild("objectgroup"); collisionElem; collisionElem = collisionElem.GetNext("objectgroup"))
         {
-            XMLElement objectGroup = tileElem.GetChild("objectgroup");
-
-            if (objectGroup.HasChild("properties"))
+            Vector<SharedPtr<TileMapObject2D> > objects;
+            for (XMLElement objectElem = collisionElem.GetChild("object"); objectElem; objectElem = objectElem.GetNext("object"))
             {
-                SharedPtr<PropertySet2D> propertySet(new PropertySet2D());
-                propertySet->Load(objectGroup.GetChild("properties"));
-                gidToPropertySetMapping_[firstgid + tileElem.GetInt("id")] = propertySet;
+                SharedPtr<TileMapObject2D> object(new TileMapObject2D());
+
+                // Convert Tiled local position (left top) to Urho3D local position (left bottom)
+                objectElem.SetAttribute("y", String(info_.GetMapHeight() / PIXEL_SIZE - (tileHeight - objectElem.GetFloat("y"))));
+
+                objectGroup.StoreObject(objectElem, object, info_, true, false);
+                objects.Push(object);
             }
+            gidToCollisionShapeMapping_[gid] = objects;
         }
-
+        if (tileElem.HasChild("properties"))
+        {
+            SharedPtr<PropertySet2D> propertySet(new PropertySet2D());
+            propertySet->Load(tileElem.GetChild("properties"));
+            gidToPropertySetMapping_[gid] = propertySet;
+        }
+        
         // ATOMIC BEGIN
 
         // collision information
@@ -703,7 +832,7 @@ bool TmxFile2D::LoadTileSet(const XMLElement& element)
             }
             else
             {
-                gidToObjectGroupMapping_[firstgid + tileElem.GetInt("id")] = objectGroup;
+                gidToObjectGroupMapping_[gid] = objectGroup;
             }
 
             info_.tileWidth_ = _tileWidth;
@@ -711,7 +840,53 @@ bool TmxFile2D::LoadTileSet(const XMLElement& element)
         }
 
         // ATOMIC END
+        
+    }
+
+    if (!isSingleTileSet)
+    {
+        if (tileImageInfos.Empty())
+            return false;
+
+        AreaAllocator allocator(128, 128, 2048, 2048);
+
+        for (int i = 0; i < tileImageInfos.Size(); ++i)
+        {
+            TileImageInfo& info = tileImageInfos[i];
+            if (!allocator.Allocate(info.imageWidth + 1, info.imageHeight + 1, info.x, info.y))
+            {
+                ATOMIC_LOGERROR("Could not allocate area");
+                return false;
+            }
+        }
 
+        SharedPtr<Texture2D> texture(new Texture2D(context_));
+        texture->SetMipsToSkip(QUALITY_LOW, 0);
+        texture->SetNumLevels(1);
+        texture->SetSize(allocator.GetWidth(), allocator.GetHeight(), Graphics::GetRGBAFormat());
+
+        unsigned textureDataSize = allocator.GetWidth() * allocator.GetHeight() * 4;
+        SharedArrayPtr<unsigned char> textureData(new unsigned char[textureDataSize]);
+        memset(textureData.Get(), 0, textureDataSize);
+
+        for (int i = 0; i < tileImageInfos.Size(); ++i)
+        {
+            TileImageInfo& info = tileImageInfos[i];
+            Image* image = info.image;
+
+            for (int y = 0; y < image->GetHeight(); ++y)
+            {
+                memcpy(textureData.Get() + ((info.y + y) * allocator.GetWidth() + info.x) * 4,
+                    image->GetData() + y * image->GetWidth() * 4, image->GetWidth() * 4);
+            }
+
+            SharedPtr<Sprite2D> sprite(new Sprite2D(context_));
+            sprite->SetTexture(texture);
+            sprite->SetRectangle(IntRect(info.x, info.y, info.x + info.imageWidth, info.y +  + info.imageHeight));
+            sprite->SetHotSpot(Vector2::ZERO);
+            gidToSpriteMapping_[info.tileGid] = sprite;
+        }
+        texture->SetData(0, 0, 0, allocator.GetWidth(), allocator.GetHeight(), textureData.Get());
     }
 
     return true;

+ 11 - 5
Source/Atomic/Atomic2D/TmxFile2D.h

@@ -108,11 +108,11 @@ public:
     Tile2D* GetTile(int x, int y) const;
 
 protected:
-    /// Tile.
+    /// Tiles.
     Vector<SharedPtr<Tile2D> > tiles_;
 };
 
-/// Tmx image layer.
+/// Tmx objects layer.
 class TmxObjectGroup2D : public TmxLayer2D
 {
     ATOMIC_REFCOUNTED(TmxObjectGroup2D)
@@ -125,6 +125,10 @@ public:
     bool Load(const XMLElement& element, const TileMapInfo2D& info, bool local = false);
 // ATOMIC END
 
+    /// Store object.
+    void StoreObject(XMLElement objectElem, SharedPtr<TileMapObject2D> object, const TileMapInfo2D& info,
+                     bool isTile = false, bool local = false);
+
     /// Return number of objects.
     unsigned GetNumObjects() const { return objects_.Size(); }
 
@@ -203,6 +207,9 @@ public:
     /// Return tile sprite by gid, if not exist return 0.
     Sprite2D* GetTileSprite(int gid) const;
 
+    /// Return tile collision shapes for a given gid.
+    Vector<SharedPtr<TileMapObject2D> > GetTileCollisionShapes(int gid) const;
+
     /// Return tile property set by gid, if not exist return 0.
     PropertySet2D* GetTilePropertySet(int gid) const;
 
@@ -230,12 +237,12 @@ private:
     HashMap<String, SharedPtr<XMLFile> > tsxXMLFiles_;
     /// Tile map information.
     TileMapInfo2D info_;
-    /// Tile set textures.
-    Vector<SharedPtr<Texture2D> > tileSetTextures_;
     /// Gid to tile sprite mapping.
     HashMap<int, SharedPtr<Sprite2D> > gidToSpriteMapping_;
     /// Gid to tile property set mapping.
     HashMap<int, SharedPtr<PropertySet2D> > gidToPropertySetMapping_;
+    /// Gid to tile collision shape mapping.
+    HashMap<int, Vector<SharedPtr<TileMapObject2D> > > gidToCollisionShapeMapping_;
 
     // ATOMIC BEGIN
 
@@ -249,4 +256,3 @@ private:
 };
 
 }
-

+ 5 - 61
Source/Atomic/Audio/SoundSource3D.cpp

@@ -43,63 +43,6 @@ static const Color OUTER_COLOR(1.0f, 0.0f, 1.0f);
 
 extern const char* AUDIO_CATEGORY;
 
-static Vector3 PointOnSphere(float radius, float theta, float phi)
-{
-    // Zero angles point toward positive Z axis
-    phi += 90.0f;
-
-    return Vector3(
-        radius * Sin(theta) * Sin(phi),
-        radius * Cos(phi),
-        radius * Cos(theta) * Sin(phi)
-    );
-}
-
-static void DrawDebugArc(const Vector3& worldPosition, const Quaternion& worldRotation, float angle, float distance, bool drawLines,
-    const Color& color, DebugRenderer* debug, bool depthTest)
-{
-    if (angle <= 0.f)
-        return;
-    else if (angle >= 360.0f)
-    {
-        debug->AddSphere(Sphere(worldPosition, distance), color, depthTest);
-        return;
-    }
-
-    unsigned uintColor = color.ToUInt();
-    float halfAngle = 0.5f * angle;
-
-    if (drawLines)
-    {
-        debug->AddLine(worldPosition, worldPosition + worldRotation * PointOnSphere(distance, halfAngle, halfAngle),
-            uintColor);
-        debug->AddLine(worldPosition, worldPosition + worldRotation * PointOnSphere(distance, -halfAngle, halfAngle),
-            uintColor);
-        debug->AddLine(worldPosition, worldPosition + worldRotation * PointOnSphere(distance, halfAngle, -halfAngle),
-            uintColor);
-        debug->AddLine(worldPosition, worldPosition + worldRotation * PointOnSphere(distance, -halfAngle, -halfAngle),
-            uintColor);
-    }
-
-    const float step = 0.5f;
-
-    for (float x = -1.0f; x < 1.0f; x += step)
-    {
-        debug->AddLine(worldPosition + worldRotation * PointOnSphere(distance, x * halfAngle, halfAngle),
-            worldPosition + worldRotation * PointOnSphere(distance, (x + step) * halfAngle, halfAngle),
-            uintColor);
-        debug->AddLine(worldPosition + worldRotation * PointOnSphere(distance, x * halfAngle, -halfAngle),
-            worldPosition + worldRotation * PointOnSphere(distance, (x + step) * halfAngle, -halfAngle),
-            uintColor);
-        debug->AddLine(worldPosition + worldRotation * PointOnSphere(distance, halfAngle, x * halfAngle),
-            worldPosition + worldRotation * PointOnSphere(distance, halfAngle, (x + step) * halfAngle),
-            uintColor);
-        debug->AddLine(worldPosition + worldRotation * PointOnSphere(distance, -halfAngle, x * halfAngle),
-            worldPosition + worldRotation * PointOnSphere(distance, -halfAngle, (x + step) * halfAngle),
-            uintColor);
-    }
-}
-
 SoundSource3D::SoundSource3D(Context* context) :
     SoundSource(context),
     nearDistance_(DEFAULT_NEARDISTANCE),
@@ -139,10 +82,11 @@ void SoundSource3D::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
     // Draw cones for directional sounds, or spheres for non-directional
     if (innerAngle_ < DEFAULT_ANGLE && outerAngle_ > 0.0f)
     {
-        DrawDebugArc(worldPosition, worldRotation, innerAngle_, nearDistance_, false, INNER_COLOR, debug, depthTest);
-        DrawDebugArc(worldPosition, worldRotation, outerAngle_, nearDistance_, false, OUTER_COLOR, debug, depthTest);
-        DrawDebugArc(worldPosition, worldRotation, innerAngle_, farDistance_, true, INNER_COLOR, debug, depthTest);
-        DrawDebugArc(worldPosition, worldRotation, outerAngle_, farDistance_, true, OUTER_COLOR, debug, depthTest);
+        const Quaternion rotation = worldRotation * Quaternion(Vector3::UP, Vector3::FORWARD);
+        debug->AddSphereSector(Sphere(worldPosition, nearDistance_), rotation, innerAngle_, false, INNER_COLOR, depthTest);
+        debug->AddSphereSector(Sphere(worldPosition, nearDistance_), rotation, outerAngle_, false, OUTER_COLOR, depthTest);
+        debug->AddSphereSector(Sphere(worldPosition, farDistance_), rotation, innerAngle_, true, INNER_COLOR, depthTest);
+        debug->AddSphereSector(Sphere(worldPosition, farDistance_), rotation, outerAngle_, true, OUTER_COLOR, depthTest);
     }
     else
     {

+ 6 - 3
Source/Atomic/Core/Object.cpp

@@ -371,7 +371,8 @@ void Object::SendEventNonProfiled(StringHash eventType, VariantMap& eventData)
     {
         group->BeginSendEvent();
 
-        for (unsigned i = 0; i < group->receivers_.Size(); ++i)
+        const unsigned numReceivers = group->receivers_.Size();
+        for (unsigned i = 0; i < numReceivers; ++i)
         {
             Object* receiver = group->receivers_[i];
             // Holes may exist if receivers removed during send
@@ -402,7 +403,8 @@ void Object::SendEventNonProfiled(StringHash eventType, VariantMap& eventData)
 
         if (processed.Empty())
         {
-            for (unsigned i = 0; i < group->receivers_.Size(); ++i)
+            const unsigned numReceivers = group->receivers_.Size();
+            for (unsigned i = 0; i < numReceivers; ++i)
             {
                 Object* receiver = group->receivers_[i];
                 if (!receiver)
@@ -421,7 +423,8 @@ void Object::SendEventNonProfiled(StringHash eventType, VariantMap& eventData)
         else
         {
             // If there were specific receivers, check that the event is not sent doubly to them
-            for (unsigned i = 0; i < group->receivers_.Size(); ++i)
+            const unsigned numReceivers = group->receivers_.Size();
+            for (unsigned i = 0; i < numReceivers; ++i)
             {
                 Object* receiver = group->receivers_[i];
                 if (!receiver || processed.Contains(receiver))

+ 88 - 2
Source/Atomic/Core/StringUtils.cpp

@@ -31,6 +31,11 @@
 namespace Atomic
 {
 
+static const String base64_chars =
+        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+        "abcdefghijklmnopqrstuvwxyz"
+        "0123456789+/";
+
 unsigned CountElements(const char* buffer, char separator)
 {
     if (!buffer)
@@ -753,6 +758,89 @@ String GetFileSizeString(unsigned long long memorySize)
     return output;
 }
 
+// Implementation of base64 decoding originally by René Nyffenegger.
+// Modified by Konstantin Guschin and Lasse Oorni
+
+/*
+base64.cpp and base64.h
+
+Copyright (C) 2004-2017 René Nyffenegger
+
+This source code is provided 'as-is', without any express or implied
+warranty. In no event will the author be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+1. The origin of this source code must not be misrepresented; you must not
+claim that you wrote the original source code. If you use this source code
+in a product, an acknowledgment in the product documentation would be
+appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and must not be
+misrepresented as being the original source code.
+
+3. This notice may not be removed or altered from any source distribution.
+
+René Nyffenegger [email protected]
+
+*/
+
+static inline bool IsBase64(char c) {
+    return (isalnum(c) || (c == '+') || (c == '/'));
+}
+
+PODVector<unsigned char> DecodeBase64(String encodedString) 
+{
+    int inLen = encodedString.Length();
+    int i = 0;
+    int j = 0;
+    int in_ = 0;
+    unsigned char charArray4[4], charArray3[3];
+    PODVector<unsigned char> ret;
+
+    while (inLen-- && (encodedString[in_] != '=') && IsBase64(encodedString[in_])) 
+    {
+        charArray4[i++] = encodedString[in_]; 
+        in_++;
+
+        if (i == 4)
+        {
+            for (i = 0; i < 4; i++)
+                charArray4[i] = base64_chars.Find(charArray4[i]);
+
+            charArray3[0] = (charArray4[0] << 2) + ((charArray4[1] & 0x30) >> 4);
+            charArray3[1] = ((charArray4[1] & 0xf) << 4) + ((charArray4[2] & 0x3c) >> 2);
+            charArray3[2] = ((charArray4[2] & 0x3) << 6) + charArray4[3];
+
+            for (i = 0; (i < 3); i++)
+                ret.Push(charArray3[i]);
+
+            i = 0;
+        }
+    }
+
+    if (i)
+    {
+        for (j = i; j <4; j++)
+            charArray4[j] = 0;
+
+        for (j = 0; j <4; j++)
+            charArray4[j] = base64_chars.Find(charArray4[j]);
+
+        charArray3[0] = (charArray4[0] << 2) + ((charArray4[1] & 0x30) >> 4);
+        charArray3[1] = ((charArray4[1] & 0xf) << 4) + ((charArray4[2] & 0x3c) >> 2);
+        charArray3[2] = ((charArray4[2] & 0x3) << 6) + charArray4[3];
+
+        for (j = 0; (j < i - 1); j++) 
+            ret.Push(charArray3[j]);
+    }
+
+    return ret;
+}
+
 // ATOMIC BEGIN
 
 String ToStringVariadic(const char* formatString, va_list args)
@@ -762,6 +850,4 @@ String ToStringVariadic(const char* formatString, va_list args)
     return ret;
 }
 
-// ATOMIC END
-
 }

+ 2 - 0
Source/Atomic/Core/StringUtils.h

@@ -135,6 +135,8 @@ ATOMIC_API unsigned ToUpper(unsigned ch);
 ATOMIC_API unsigned ToLower(unsigned ch);
 /// Convert a memory size into a formatted size string, of the style "1.5 Mb".
 ATOMIC_API String GetFileSizeString(unsigned long long memorySize);
+/// Decode a base64-encoded string into buffer.
+ATOMIC_API PODVector<unsigned char> DecodeBase64(String encoded_string);
 /// Parse type from a C string.
 template <class T> T FromString(const char* source);
 

+ 63 - 19
Source/Atomic/Graphics/DebugRenderer.cpp

@@ -234,7 +234,6 @@ void DebugRenderer::AddBoundingBox(const BoundingBox& box, const Matrix3x4& tran
     }
 }
 
-
 void DebugRenderer::AddFrustum(const Frustum& frustum, const Color& color, bool depthTest)
 {
     const Vector3* vertices = frustum.vertices_;
@@ -269,27 +268,18 @@ void DebugRenderer::AddPolyhedron(const Polyhedron& poly, const Color& color, bo
     }
 }
 
-static Vector3 PointOnSphere(const Sphere& sphere, unsigned theta, unsigned phi)
-{
-    return Vector3(
-        sphere.center_.x_ + sphere.radius_ * Sin((float)theta) * Sin((float)phi),
-        sphere.center_.y_ + sphere.radius_ * Cos((float)phi),
-        sphere.center_.z_ + sphere.radius_ * Cos((float)theta) * Sin((float)phi)
-    );
-}
-
 void DebugRenderer::AddSphere(const Sphere& sphere, const Color& color, bool depthTest)
 {
     unsigned uintColor = color.ToUInt();
 
-    for (unsigned j = 0; j < 180; j += 45)
+    for (float j = 0; j < 180; j += 45)
     {
-        for (unsigned i = 0; i < 360; i += 45)
+        for (float i = 0; i < 360; i += 45)
         {
-            Vector3 p1 = PointOnSphere(sphere, i, j);
-            Vector3 p2 = PointOnSphere(sphere, i + 45, j);
-            Vector3 p3 = PointOnSphere(sphere, i, j + 45);
-            Vector3 p4 = PointOnSphere(sphere, i + 45, j + 45);
+            Vector3 p1 = sphere.GetPoint(i, j);
+            Vector3 p2 = sphere.GetPoint(i + 45, j);
+            Vector3 p3 = sphere.GetPoint(i, j + 45);
+            Vector3 p4 = sphere.GetPoint(i + 45, j + 45);
 
             AddLine(p1, p2, uintColor, depthTest);
             AddLine(p3, p4, uintColor, depthTest);
@@ -299,16 +289,70 @@ void DebugRenderer::AddSphere(const Sphere& sphere, const Color& color, bool dep
     }
 }
 
+void DebugRenderer::AddSphereSector(const Sphere& sphere, const Quaternion& rotation, float angle,
+    bool drawLines, const Color& color, bool depthTest)
+{
+    if (angle <= 0.0f)
+        return;
+    else if (angle >= 360.0f)
+    {
+        AddSphere(sphere, color, depthTest);
+        return;
+    }
+
+    static const unsigned numCircleSegments = 8;
+    static const unsigned numLines = 4;
+    static const float arcStep = 45.0f;
+
+    const unsigned uintColor = color.ToUInt();
+    const float halfAngle = 0.5f * angle;
+    const unsigned numArcSegments = static_cast<unsigned>(Ceil(halfAngle / arcStep)) + 1;
+
+    // Draw circle
+    for (unsigned j = 0; j < numCircleSegments; ++j)
+    {
+        AddLine(
+            sphere.center_ + rotation * sphere.GetLocalPoint(j * 360.0f / numCircleSegments, halfAngle),
+            sphere.center_ + rotation * sphere.GetLocalPoint((j + 1) * 360.0f / numCircleSegments, halfAngle),
+            uintColor);
+    }
+
+    // Draw arcs
+    const unsigned step = numCircleSegments / numLines;
+    for (unsigned i = 0; i < numArcSegments - 1; ++i)
+    {
+        for (unsigned j = 0; j < numCircleSegments; j += step)
+        {
+            const float nextPhi = i + 1 == numArcSegments - 1 ? halfAngle : (i + 1) * arcStep;
+            AddLine(
+                sphere.center_ + rotation * sphere.GetLocalPoint(j * 360.0f / numCircleSegments, i * arcStep),
+                sphere.center_ + rotation * sphere.GetLocalPoint(j * 360.0f / numCircleSegments, nextPhi),
+                uintColor);
+        }
+    }
+
+    // Draw lines
+    if (drawLines)
+    {
+        for (unsigned j = 0; j < numCircleSegments; j += step)
+        {
+            AddLine(sphere.center_,
+                sphere.center_ + rotation * sphere.GetLocalPoint(j * 360.0f / numCircleSegments, halfAngle),
+                uintColor);
+        }
+    }
+}
+
 void DebugRenderer::AddCylinder(const Vector3& position, float radius, float height, const Color& color, bool depthTest)
 {
     Sphere sphere(position, radius);
     Vector3 heightVec(0, height, 0);
     Vector3 offsetXVec(radius, 0, 0);
     Vector3 offsetZVec(0, 0, radius);
-    for (unsigned i = 0; i < 360; i += 45)
+    for (float i = 0; i < 360; i += 45)
     {
-        Vector3 p1 = PointOnSphere(sphere, i, 90);
-        Vector3 p2 = PointOnSphere(sphere, i + 45, 90);
+        Vector3 p1 = sphere.GetPoint(i, 90);
+        Vector3 p2 = sphere.GetPoint(i + 45, 90);
         AddLine(p1, p2, color, depthTest);
         AddLine(p1 + heightVec, p2 + heightVec, color, depthTest);
     }

+ 3 - 0
Source/Atomic/Graphics/DebugRenderer.h

@@ -132,6 +132,9 @@ public:
     void AddPolyhedron(const Polyhedron& poly, const Color& color, bool depthTest = true);
     /// Add a sphere.
     void AddSphere(const Sphere& sphere, const Color& color, bool depthTest = true);
+    /// Add a sphere sector.
+    void AddSphereSector(const Sphere& sphere, const Quaternion& rotation, float angle,
+        bool drawLines, const Color& color, bool depthTest = true);
     /// Add a cylinder
     void AddCylinder(const Vector3& position, float radius, float height, const Color& color, bool depthTest = true);
     /// Add a skeleton.

+ 1 - 0
Source/Atomic/Graphics/Direct3D11/D3D11Graphics.cpp

@@ -1880,6 +1880,7 @@ void Graphics::OnWindowResized()
     eventData[P_FULLSCREEN] = fullscreen_;
     eventData[P_RESIZABLE] = resizable_;
     eventData[P_BORDERLESS] = borderless_;
+    eventData[P_HIGHDPI] = highDPI_;
     SendEvent(E_SCREENMODE, eventData);
 }
 

+ 1 - 0
Source/Atomic/Graphics/Direct3D9/D3D9Graphics.cpp

@@ -2163,6 +2163,7 @@ void Graphics::OnWindowResized()
     eventData[P_FULLSCREEN] = fullscreen_;
     eventData[P_RESIZABLE] = resizable_;
     eventData[P_BORDERLESS] = borderless_;
+    eventData[P_HIGHDPI] = highDPI_;
     SendEvent(E_SCREENMODE, eventData);
 }
 

+ 9 - 1
Source/Atomic/Graphics/OpenGL/OGLGraphics.cpp

@@ -507,7 +507,6 @@ bool Graphics::SetMode(int width, int height, bool fullscreen, bool borderless,
     fullscreen_ = fullscreen;
     borderless_ = borderless;
     resizable_ = resizable;
-    highDPI_ = highDPI;
     vsync_ = vsync;
     tripleBuffer_ = tripleBuffer;
     multiSample_ = multiSample;
@@ -518,6 +517,10 @@ bool Graphics::SetMode(int width, int height, bool fullscreen, bool borderless,
     if (!fullscreen)
         SDL_GetWindowPosition(window_, &position_.x_, &position_.y_);
 
+    int logicalWidth, logicalHeight;
+    SDL_GetWindowSize(window_, &logicalWidth, &logicalHeight);
+    highDPI_ = (width_ != logicalWidth) || (height_ != logicalHeight);
+
     // Reset rendertargets and viewport for the new screen mode
     ResetRenderTargets();
 
@@ -2270,6 +2273,10 @@ void Graphics::OnWindowResized()
     width_ = newWidth;
     height_ = newHeight;
 
+    int logicalWidth, logicalHeight;
+    SDL_GetWindowSize(window_, &logicalWidth, &logicalHeight);
+    highDPI_ = (width_ != logicalWidth) || (height_ != logicalHeight);
+
     // Reset rendertargets and viewport for the new screen size. Also clean up any FBO's, as they may be screen size dependent
     CleanupFramebuffers();
     ResetRenderTargets();
@@ -2284,6 +2291,7 @@ void Graphics::OnWindowResized()
     eventData[P_FULLSCREEN] = fullscreen_;
     eventData[P_RESIZABLE] = resizable_;
     eventData[P_BORDERLESS] = borderless_;
+    eventData[P_HIGHDPI] = highDPI_;
     SendEvent(E_SCREENMODE, eventData);
 }
 

+ 2 - 12
Source/Atomic/Graphics/Renderer.cpp

@@ -1047,8 +1047,9 @@ Texture* Renderer::GetScreenBuffer(int width, int height, unsigned format, int m
         screenBufferAllocations_[searchKey] = 0;
 
     // Reuse depth-stencil buffers whenever the size matches, instead of allocating new
+    // Unless persistency specified
     unsigned allocations = screenBufferAllocations_[searchKey];
-    if (!depthStencil)
+    if (!depthStencil || persistentKey)
         ++screenBufferAllocations_[searchKey];
 
     if (allocations >= screenBuffers_[searchKey].Size())
@@ -1386,17 +1387,6 @@ bool Renderer::ResizeInstancingBuffer(unsigned numInstances)
     return true;
 }
 
-void Renderer::SaveScreenBufferAllocations()
-{
-    savedScreenBufferAllocations_ = screenBufferAllocations_;
-}
-
-void Renderer::RestoreScreenBufferAllocations()
-{
-    screenBufferAllocations_ = savedScreenBufferAllocations_;
-}
-
-
 void Renderer::OptimizeLightByScissor(Light* light, Camera* camera)
 {
     if (light && light->GetLightType() != LIGHT_DIRECTIONAL)

+ 0 - 4
Source/Atomic/Graphics/Renderer.h

@@ -415,10 +415,6 @@ public:
     void SetCullMode(CullMode mode, Camera* camera);
     /// Ensure sufficient size of the instancing vertex buffer. Return true if successful.
     bool ResizeInstancingBuffer(unsigned numInstances);
-    /// Save the screen buffer allocation status. Called by View.
-    void SaveScreenBufferAllocations();
-    /// Restore the screen buffer allocation status. Called by View.
-    void RestoreScreenBufferAllocations();
     /// Optimize a light by scissor rectangle.
     void OptimizeLightByScissor(Light* light, Camera* camera);
     /// Optimize a light by marking it to the stencil buffer and setting a stencil test.

+ 15 - 0
Source/Atomic/Graphics/Terrain.cpp

@@ -649,6 +649,21 @@ IntVector2 Terrain::WorldToHeightMap(const Vector3& worldPosition) const
     return IntVector2(xPos, numVertices_.y_ - 1 - zPos);
 }
 
+Vector3 Terrain::HeightMapToWorld(const IntVector2& pixelPosition) const
+{
+    if (!node_)
+        return Vector3::ZERO;
+
+    IntVector2 pos(pixelPosition.x_, numVertices_.y_ - 1 - pixelPosition.y_);
+    float xPos = (float)(pos.x_ * spacing_.x_ + patchWorldOrigin_.x_);
+    float zPos = (float)(pos.y_ * spacing_.z_ + patchWorldOrigin_.y_);
+    Vector3 lPos(xPos, 0.0f, zPos);
+    Vector3 wPos = node_->GetWorldTransform() * lPos;
+    wPos.y_ = GetHeight(wPos);
+
+    return wPos;
+}
+
 void Terrain::CreatePatchGeometry(TerrainPatch* patch)
 {
     ATOMIC_PROFILE(CreatePatchGeometry);

+ 2 - 0
Source/Atomic/Graphics/Terrain.h

@@ -140,6 +140,8 @@ public:
     Vector3 GetNormal(const Vector3& worldPosition) const;
     /// Convert world position to heightmap pixel position. Note that the internal height data representation is reversed vertically, but in the heightmap image north is at the top.
     IntVector2 WorldToHeightMap(const Vector3& worldPosition) const;
+    /// Convert heightmap pixel position to world position.
+    Vector3 HeightMapToWorld(const IntVector2& pixelPosition) const;
 
     /// Return north neighbor terrain.
     Terrain* GetNorthNeighbor() const { return north_; }

+ 2 - 2
Source/Atomic/Graphics/Text3D/Text3DBitmap.cpp

@@ -133,8 +133,8 @@ bool Text3DBitmap::Load(const unsigned char* fontData, unsigned fontDataSize, fl
         Text3DFontGlyph glyph;
         glyph.x_ = (short)charElem.GetInt("x");
         glyph.y_ = (short)charElem.GetInt("y");
-        glyph.width_ = (short)charElem.GetInt("width");
-        glyph.height_ = (short)charElem.GetInt("height");
+        glyph.width_ = glyph.texWidth_ = (short)charElem.GetInt("width");
+        glyph.height_ = glyph.texHeight_ = (short)charElem.GetInt("height");
         glyph.offsetX_ = (short)charElem.GetInt("xoffset");
         glyph.offsetY_ = (short)charElem.GetInt("yoffset");
         glyph.advanceX_ = (short)charElem.GetInt("xadvance");

+ 10 - 6
Source/Atomic/Graphics/Text3D/Text3DFontFace.h

@@ -43,14 +43,18 @@ struct ATOMIC_API Text3DFontGlyph
     short x_;
     /// Y position in texture.
     short y_;
-    /// Width.
-    short width_;
-    /// Height.
-    short height_;
+    /// Width in texture.
+    short texWidth_;
+    /// Height in texture.
+    short texHeight_;
+    /// Width on screen.
+    float width_;
+    /// Height on screen.
+    float height_;
     /// Glyph X offset from origin.
-    short offsetX_;
+    float offsetX_;
     /// Glyph Y offset from origin.
-    short offsetY_;
+    float offsetY_;
     /// Horizontal advance.
     float advanceX_;
     /// Texture page. M_MAX_UNSIGNED if not yet resident on any texture.

+ 126 - 17
Source/Atomic/Graphics/Text3D/Text3DFreeType.cpp

@@ -28,9 +28,12 @@
 #include "../../IO/FileSystem.h"
 #include "../../IO/Log.h"
 #include "../../IO/MemoryBuffer.h"
+#include "../../Resource/ResourceCache.h"
 #include "Text3DFont.h"
 #include "Text3DFreeType.h"
 
+#include <assert.h>
+
 #include <ft2build.h>
 #include FT_FREETYPE_H
 #include FT_TRUETYPE_TABLES_H
@@ -106,6 +109,11 @@ bool Text3DFreeType::Load(const unsigned char* fontData, unsigned fontDataSize,
     freeType_ = freeType;
 
     int maxTextureSize = TEXT3D_DEFAULT_FONT_TEXTURE_MAX_SIZE;
+    const FontHintLevel hintLevel = GetFontHintLevel();
+    const float subpixelThreshold = GetFontSubpixelThreshold();
+
+    subpixel_ = (hintLevel <= FONT_HINT_LEVEL_LIGHT) && (pointSize <= subpixelThreshold);
+    oversampling_ = subpixel_ ? GetFontOversampling() : 1;
 
     FT_Face face;
     FT_Error error;
@@ -129,7 +137,7 @@ bool Text3DFreeType::Load(const unsigned char* fontData, unsigned fontDataSize,
         ATOMIC_LOGERROR("Could not create font face");
         return false;
     }
-    error = FT_Set_Char_Size(face, 0, pointSize * 64, TEXT3D_FONT_DPI, TEXT3D_FONT_DPI);
+    error = FT_Set_Char_Size(face, 0, pointSize * 64, oversampling_ * TEXT3D_FONT_DPI, TEXT3D_FONT_DPI);
     if (error)
     {
         FT_Done_Face(face);
@@ -263,11 +271,14 @@ bool Text3DFreeType::Load(const unsigned char* fontData, unsigned fontDataSize,
                     // Skip searchRange, entrySelector and rangeShift
                     deserializer.Seek((unsigned)(deserializer.GetPosition() + 3 * sizeof(unsigned short)));
 
+                    // x_scale is a 16.16 fixed-point value that converts font units -> 26.6 pixels (oversampled!)
+                    float xScale = face->size->metrics.x_scale / float(1 << 22) / oversampling_;
+
                     for (unsigned j = 0; j < numKerningPairs; ++j)
                     {
                         unsigned leftIndex = deserializer.ReadUShort();
                         unsigned rightIndex = deserializer.ReadUShort();
-                        short amount = FixedToFloat(deserializer.ReadShort());
+                        float amount = deserializer.ReadShort() * xScale;
 
                         unsigned leftCharCode = leftIndex < numGlyphs ? charCodes[leftIndex + 1] : 0;
                         unsigned rightCharCode = rightIndex < numGlyphs ? charCodes[rightIndex + 1] : 0;
@@ -341,6 +352,65 @@ bool Text3DFreeType::SetupNextTexture(int textureWidth, int textureHeight)
     return true;
 }
 
+void Text3DFreeType::BoxFilter(unsigned char* dest, size_t destSize, const unsigned char* src, size_t srcSize)
+{
+    const int filterSize = oversampling_;
+
+    assert(filterSize > 0);
+    assert(destSize == srcSize + filterSize - 1);
+
+    if (filterSize == 1)
+    {
+        memcpy(dest, src, srcSize);
+        return;
+    }
+
+    // "accumulator" holds the total value of filterSize samples. We add one sample
+    // and remove one sample per step (with special cases for left and right edges).
+    int accumulator = 0;
+
+    // The divide might make these inner loops slow. If so, some possible optimizations:
+    // a) Turn it into a fixed-point multiply-and-shift rather than an integer divide;
+    // b) Make this function a template, with the filter size a compile-time constant.
+
+    int i = 0;
+
+    if (srcSize < filterSize)
+    {
+        for (; i < srcSize; ++i)
+        {
+            accumulator += src[i];
+            dest[i] = accumulator / filterSize;
+        }
+
+        for (; i < filterSize; ++i)
+        {
+            dest[i] = accumulator / filterSize;
+        }
+    }
+    else
+    {
+        for ( ; i < filterSize; ++i)
+        {
+            accumulator += src[i];
+            dest[i] = accumulator / filterSize;
+        }
+
+        for (; i < srcSize; ++i)
+        {
+            accumulator += src[i];
+            accumulator -= src[i - filterSize];
+            dest[i] = accumulator / filterSize;
+        }
+    }
+
+    for (; i < srcSize + filterSize - 1; ++i)
+    {
+        accumulator -= src[i - filterSize];
+        dest[i] = accumulator / filterSize;
+    }
+}
+
 bool Text3DFreeType::LoadCharGlyph(unsigned charCode, Image* image)
 {
     if (!face_)
@@ -355,6 +425,8 @@ bool Text3DFreeType::LoadCharGlyph(unsigned charCode, Image* image)
     {
         const char* family = face->family_name ? face->family_name : "NULL";
         ATOMIC_LOGERRORF("FT_Load_Char failed (family: %s, char code: %u)", family, charCode);
+        fontGlyph.texWidth_ = 0;
+        fontGlyph.texHeight_ = 0;
         fontGlyph.width_ = 0;
         fontGlyph.height_ = 0;
         fontGlyph.offsetX_ = 0;
@@ -365,14 +437,16 @@ bool Text3DFreeType::LoadCharGlyph(unsigned charCode, Image* image)
     else
     {
         // Note: position within texture will be filled later
-        fontGlyph.width_ = slot->bitmap.width;
+        fontGlyph.texWidth_ = slot->bitmap.width + oversampling_ - 1;
+        fontGlyph.texHeight_ = slot->bitmap.rows;
+        fontGlyph.width_ = slot->bitmap.width + oversampling_ - 1;
         fontGlyph.height_ = slot->bitmap.rows;
-        fontGlyph.offsetX_ = slot->bitmap_left;
-        fontGlyph.offsetY_ = ascender_ - slot->bitmap_top;
+        fontGlyph.offsetX_ = slot->bitmap_left - (oversampling_ - 1) / 2.0f;
+        fontGlyph.offsetY_ = floorf(ascender_ + 0.5f) - slot->bitmap_top;
 
         FontHintLevel level = GetFontHintLevel();
         bool subpixel = GetSubpixelGlyphPositions();
-        if (level <= FONT_HINT_LEVEL_LIGHT && subpixel && slot->linearHoriAdvance)
+        if (subpixel_ && slot->linearHoriAdvance)
         {
             // linearHoriAdvance is stored in 16.16 fixed point, not the usual 26.6
             fontGlyph.advanceX_ = slot->linearHoriAdvance / 65536.0;
@@ -380,14 +454,18 @@ bool Text3DFreeType::LoadCharGlyph(unsigned charCode, Image* image)
         else
         {
             // Round to nearest pixel (only necessary when hinting is disabled)
-            fontGlyph.advanceX_ = floor(FixedToFloat(slot->metrics.horiAdvance) + 0.5f);
+            fontGlyph.advanceX_ = floorf(FixedToFloat(slot->metrics.horiAdvance) + 0.5f);
         }
+
+        fontGlyph.width_ /= oversampling_;
+        fontGlyph.offsetX_ /= oversampling_;
+        fontGlyph.advanceX_ /= oversampling_;
     }
 
     int x = 0, y = 0;
-    if (fontGlyph.width_ > 0 && fontGlyph.height_ > 0)
+    if (fontGlyph.texWidth_ > 0 && fontGlyph.texHeight_ > 0)
     {
-        if (!allocator_.Allocate(fontGlyph.width_ + 1, fontGlyph.height_ + 1, x, y))
+        if (!allocator_.Allocate(fontGlyph.texWidth_ + 1, fontGlyph.texHeight_ + 1, x, y))
         {
             if (image)
             {
@@ -402,7 +480,7 @@ bool Text3DFreeType::LoadCharGlyph(unsigned charCode, Image* image)
                 return false;
             }
 
-            if (!allocator_.Allocate(fontGlyph.width_ + 1, fontGlyph.height_ + 1, x, y))
+            if (!allocator_.Allocate(fontGlyph.texWidth_ + 1, fontGlyph.texHeight_ + 1, x, y))
             {
                 return false;
             }
@@ -422,8 +500,8 @@ bool Text3DFreeType::LoadCharGlyph(unsigned charCode, Image* image)
         else
         {
             fontGlyph.page_ = textures_.Size() - 1;
-            dest = new unsigned char[fontGlyph.width_ * fontGlyph.height_];
-            pitch = (unsigned)fontGlyph.width_;
+            dest = new unsigned char[fontGlyph.texWidth_ * fontGlyph.texHeight_];
+            pitch = (unsigned)fontGlyph.texWidth_;
         }
 
         if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO)
@@ -431,8 +509,9 @@ bool Text3DFreeType::LoadCharGlyph(unsigned charCode, Image* image)
             for (unsigned y = 0; y < (unsigned)slot->bitmap.rows; ++y)
             {
                 unsigned char* src = slot->bitmap.buffer + slot->bitmap.pitch * y;
-                unsigned char* rowDest = dest + y * pitch;
+                unsigned char* rowDest = dest + (oversampling_ - 1)/2 + y * pitch;
 
+                // Don't do any oversampling, just unpack the bits directly.
                 for (unsigned x = 0; x < (unsigned)slot->bitmap.width; ++x)
                     rowDest[x] = (unsigned char)((src[x >> 3] & (0x80 >> (x & 7))) ? 255 : 0);
             }
@@ -443,15 +522,13 @@ bool Text3DFreeType::LoadCharGlyph(unsigned charCode, Image* image)
             {
                 unsigned char* src = slot->bitmap.buffer + slot->bitmap.pitch * y;
                 unsigned char* rowDest = dest + y * pitch;
-
-                for (unsigned x = 0; x < (unsigned)slot->bitmap.width; ++x)
-                    rowDest[x] = src[x];
+                BoxFilter(rowDest, fontGlyph.texWidth_, src, slot->bitmap.width);
             }
         }
 
         if (!image)
         {
-            textures_.Back()->SetData(0, fontGlyph.x_, fontGlyph.y_, fontGlyph.width_, fontGlyph.height_, dest);
+            textures_.Back()->SetData(0, fontGlyph.x_, fontGlyph.y_, fontGlyph.texWidth_, fontGlyph.texHeight_, dest);
             delete[] dest;
         }
     }
@@ -467,4 +544,36 @@ bool Text3DFreeType::LoadCharGlyph(unsigned charCode, Image* image)
     return true;
 }
 
+void Text3DFreeType::ReleaseFontFaces()
+{
+    ATOMIC_LOGDEBUG("Reloading font faces");
+
+    PODVector<Text3DFont*> fonts;
+    font_->GetSubsystem<ResourceCache>()->GetResources<Text3DFont>(fonts);
+
+    for (unsigned i = 0; i < fonts.Size(); ++i)
+        fonts[i]->ReleaseFaces();
+}
+
+void Text3DFreeType::SetFontSubpixelThreshold(float threshold)
+{
+    assert(threshold >= 0);
+    if (threshold != fontSubpixelThreshold_)
+    {
+        fontSubpixelThreshold_ = threshold;
+        ReleaseFontFaces();
+    }
+}
+
+void Text3DFreeType::SetFontOversampling(int oversampling)
+{
+    assert(oversampling >= 1);
+    oversampling = Clamp(oversampling, 1, 8);
+    if (oversampling != fontOversampling_)
+    {
+        fontOversampling_ = oversampling;
+        ReleaseFontFaces();
+    }
+}
+
 }

+ 27 - 1
Source/Atomic/Graphics/Text3D/Text3DFreeType.h

@@ -76,11 +76,27 @@ public:
     /// Set whether text glyphs can have fractional positions. Default is false (pixel-aligned).
     void SetSubpixelGlyphPositions(bool enable) { subpixelGlyphPositions_ = enable; }
 
+    /// Set the font subpixel threshold. Below this size, if the hint level is LIGHT or NONE, fonts will use subpixel positioning plus oversampling for higher-quality rendering. Has no effect at hint level NORMAL.
+    void SetFontSubpixelThreshold(float threshold);
+
+    /// Set the oversampling (horizonal stretching) used to improve subpixel font rendering. Only affects fonts smaller than the subpixel limit.
+    void SetFontOversampling(int oversampling);
+
+    /// Get the font subpixel threshold. Below this size, if the hint level is LIGHT or NONE, fonts will use subpixel positioning plus oversampling for higher-quality rendering. Has no effect at hint level NORMAL.
+    float GetFontSubpixelThreshold() const { return fontSubpixelThreshold_; }
+
+    /// Get the oversampling (horizonal stretching) used to improve subpixel font rendering. Only affects fonts smaller than the subpixel limit.
+    int GetFontOversampling() const { return fontOversampling_; }
+
 private:
     /// Setup next texture.
     bool SetupNextTexture(int textureWidth, int textureHeight);
     /// Load char glyph.
     bool LoadCharGlyph(unsigned charCode, Image* image = 0);
+    /// Smooth one row of a horizontally oversampled glyph image.
+    void BoxFilter(unsigned char* dest, size_t destSize, const unsigned char* src, size_t srcSize);
+    /// Force release of font faces when global font properties change.
+    void ReleaseFontFaces();
 
     /// FreeType library.
     SharedPtr<FreeTypeLibrary> freeType_;
@@ -88,6 +104,10 @@ private:
     void* face_;
     /// Load mode.
     int loadMode_;
+    /// Use subpixel glyph positioning?
+    bool subpixel_;
+    /// Oversampling level.
+    int oversampling_;
     /// Ascender.
     float ascender_;
     /// Has mutable glyph.
@@ -95,9 +115,15 @@ private:
     /// Glyph area allocator.
     AreaAllocator allocator_;
 
-    bool forceAutoHint_;
     bool subpixelGlyphPositions_;
+    /// Flag for forcing FreeType auto hinting.
+    bool forceAutoHint_;
+    /// FreeType hinting level (default is FONT_HINT_LEVEL_NORMAL).
     FontHintLevel fontHintLevel_;
+    /// Maxmimum font size for subpixel glyph positioning and oversampling (default is 12).
+    float fontSubpixelThreshold_;
+    /// Horizontal oversampling for subpixel fonts (default is 2).
+    int fontOversampling_;
 };
 
 }

+ 8 - 3
Source/Atomic/Graphics/Text3D/Text3DText.cpp

@@ -731,8 +731,13 @@ void Text3DText::UpdateText(bool onResize)
         // Set minimum and current size according to the text size, but respect fixed width if set
         if (!IsFixedWidth())
         {
-            SetMinWidth(wordWrap_ ? 0 : width);
-            SetWidth(width);
+            if (wordWrap_)
+                SetMinWidth(0);
+            else
+            {
+                SetMinWidth(width);
+                SetWidth(width);
+            }
         }
 
         SetFixedHeight(height);
@@ -881,7 +886,7 @@ void Text3DText::ConstructBatch(Text3DBatch& pageBatch, const PODVector<Text3DGl
         const Text3DGlyphLocation& glyphLocation = pageGlyphLocation[i];
         const Text3DFontGlyph& glyph = *glyphLocation.glyph_;
         pageBatch.AddQuad(dx + glyphLocation.x_ + glyph.offsetX_, dy + glyphLocation.y_ + glyph.offsetY_, glyph.width_,
-            glyph.height_, glyph.x_, glyph.y_);
+            glyph.height_, glyph.x_, glyph.y_, glyph.texWidth_, glyph.texHeight_);
     }
 
     if (depthBias != 0.0f)

+ 1 - 1
Source/Atomic/IK/IK.cpp

@@ -33,7 +33,7 @@ const char* IK_CATEGORY = "Inverse Kinematics";
 // ----------------------------------------------------------------------------
 void RegisterIKLibrary(Context* context)
 {
-    IKConstraint::RegisterObject(context);
+    //IKConstraint::RegisterObject(context);
     IKEffector::RegisterObject(context);
     IKSolver::RegisterObject(context);
 }

+ 3 - 7
Source/Atomic/IK/IK.h

@@ -22,20 +22,16 @@
 
 /*
  * TODO
- *  - Add support for manually updating initial pose.
- *  - Lua script bindings crash.
- *  - Implement inherit parent rotations in IKEffector.
  *  - Optimise.
  *  - Profile.
  *  - Documentation.
- *  - Move log callback into context init function.
- *  - Bug when enabling continuous mode and IKSolver is placed somewhere
- *    on part of the model's bones.
  *
  * FUTURE
  *  - Support for "stretchiness" with min/max lengths.
  *  - Support for "stiffness" factor, describes how well a bone rotates.
- *  - Apply bullet constraints to joints.
+ *  - Implement constraints.
+ *  - Skip bones when building the tree.
+ *  - Mass/Spring/Damper solver.
  */
 
 namespace Atomic

+ 8 - 6
Source/Atomic/IK/IKConstraint.cpp

@@ -26,6 +26,7 @@
 #include "../Scene/Node.h"
 #include "../Scene/SceneEvents.h"
 
+#include <ik/constraint.h>
 #include <ik/node.h>
 
 namespace Atomic
@@ -36,6 +37,7 @@ extern const char* IK_CATEGORY;
 // ----------------------------------------------------------------------------
 IKConstraint::IKConstraint(Context* context) :
     Component(context),
+    ikConstraintNode_(NULL),
     stiffness_(0.0f),
     stretchiness_(0.0f)
 {
@@ -66,7 +68,7 @@ float IKConstraint::GetStiffness() const
 void IKConstraint::SetStiffness(float stiffness)
 {
     stiffness_ = Clamp(stiffness, 0.0f, 1.0f);
-    if (ikNode_ != NULL)
+    if (ikConstraintNode_ != NULL)
         /* TODO ikNode_->stiffness = stiffness_; */
         ;
 }
@@ -81,7 +83,7 @@ float IKConstraint::GetStretchiness() const
 void IKConstraint::SetStretchiness(float stretchiness)
 {
     stretchiness_ = Clamp(stretchiness, 0.0f, 1.0f);
-    if (ikNode_)
+    if (ikConstraintNode_)
         /* TODO ikNode_->stretchiness = stretchiness_;*/
         ;
 }
@@ -96,7 +98,7 @@ const Vector2& IKConstraint::GetLengthConstraints() const
 void IKConstraint::SetLengthConstraints(const Vector2& lengthConstraints)
 {
     lengthConstraints_ = lengthConstraints;
-    if (ikNode_ != NULL)
+    if (ikConstraintNode_ != NULL)
     {
         /* TODO
         ikNode_->min_length = lengthConstraints_.x_;
@@ -105,10 +107,10 @@ void IKConstraint::SetLengthConstraints(const Vector2& lengthConstraints)
 }
 
 // ----------------------------------------------------------------------------
-void IKConstraint::SetIKNode(ik_node_t* node)
+void IKConstraint::SetIKConstraintNode(ik_node_t* constraintNode)
 {
-    ikNode_ = node;
-    if (node)
+    ikConstraintNode_ = constraintNode;
+    if (constraintNode != NULL)
     {
         /* TODO
         node->stiffness = stiffness_;

+ 2 - 2
Source/Atomic/IK/IKConstraint.h

@@ -60,9 +60,9 @@ private:
     friend class IKSolver;
 
     /// Intended to be used only by IKSolver
-    void SetIKNode(ik_node_t* effector);
+    void SetIKConstraintNode(ik_node_t* constraintNode);
 
-    ik_node_t* ikNode_;
+    ik_node_t* ikConstraintNode_;
 
     float stiffness_;
     float stretchiness_;

+ 88 - 56
Source/Atomic/IK/IKEffector.cpp

@@ -27,8 +27,12 @@
 #include "../IK/IKEvents.h"
 #include "../IK/IKSolver.h"
 #include "../Scene/SceneEvents.h"
+#include "../IO/Log.h"
 
+#include <ik/node.h>
 #include <ik/effector.h>
+#include <ik/solver.h>
+#include <ik/util.h>
 
 namespace Atomic
 {
@@ -38,19 +42,20 @@ extern const char* IK_CATEGORY;
 // ----------------------------------------------------------------------------
 IKEffector::IKEffector(Context* context) :
     Component(context),
-    ikEffector_(NULL),
+    ikEffectorNode_(NULL),
     chainLength_(0),
     weight_(1.0f),
     rotationWeight_(1.0),
     rotationDecay_(0.25),
-    weightedNlerp_(false),
-    inheritParentRotation_(false)
+    features_(0)
 {
+    ATOMIC_LOGDEBUG("IKEffector created");
 }
 
 // ----------------------------------------------------------------------------
 IKEffector::~IKEffector()
 {
+    ATOMIC_LOGDEBUG("IKEffector destroyed");
 }
 
 // ----------------------------------------------------------------------------
@@ -113,8 +118,8 @@ const Vector3& IKEffector::GetTargetPosition() const
 void IKEffector::SetTargetPosition(const Vector3& targetPosition)
 {
     targetPosition_ = targetPosition;
-    if (ikEffector_ != NULL)
-        ikEffector_->target_position = Vec3Urho2IK(targetPosition);
+    if (ikEffectorNode_ != NULL)
+        ikEffectorNode_->effector->target_position = Vec3Urho2IK(targetPosition);
 }
 
 // ----------------------------------------------------------------------------
@@ -127,8 +132,8 @@ const Quaternion& IKEffector::GetTargetRotation() const
 void IKEffector::SetTargetRotation(const Quaternion& targetRotation)
 {
     targetRotation_ = targetRotation;
-    if (ikEffector_)
-        ikEffector_->target_rotation = QuatUrho2IK(targetRotation);
+    if (ikEffectorNode_)
+        ikEffectorNode_->effector->target_rotation = QuatUrho2IK(targetRotation);
 }
 
 // ----------------------------------------------------------------------------
@@ -153,10 +158,10 @@ unsigned int IKEffector::GetChainLength() const
 void IKEffector::SetChainLength(unsigned chainLength)
 {
     chainLength_ = chainLength;
-    if (ikEffector_ != NULL)
+    if (ikEffectorNode_ != NULL)
     {
-        ikEffector_->chain_length = chainLength;
-        solver_->MarkSolverTreeDirty();
+        ikEffectorNode_->effector->chain_length = chainLength;
+        solver_->MarkChainsNeedUpdating();
     }
 }
 
@@ -170,8 +175,8 @@ float IKEffector::GetWeight() const
 void IKEffector::SetWeight(float weight)
 {
     weight_ = Clamp(weight, 0.0f, 1.0f);
-    if (ikEffector_ != NULL)
-        ikEffector_->weight = weight_;
+    if (ikEffectorNode_ != NULL)
+        ikEffectorNode_->effector->weight = weight_;
 }
 
 // ----------------------------------------------------------------------------
@@ -184,8 +189,11 @@ float IKEffector::GetRotationWeight() const
 void IKEffector::SetRotationWeight(float weight)
 {
     rotationWeight_ = Clamp(weight, 0.0f, 1.0f);
-    if (ikEffector_ != NULL)
-        ikEffector_->rotation_weight = rotationWeight_;
+    if (ikEffectorNode_ != NULL)
+    {
+        ikEffectorNode_->rotation_weight = rotationWeight_;
+        ik_calculate_rotation_weight_decays(&solver_->solver_->chain_tree);
+    }
 }
 
 // ----------------------------------------------------------------------------
@@ -198,55 +206,53 @@ float IKEffector::GetRotationDecay() const
 void IKEffector::SetRotationDecay(float decay)
 {
     rotationDecay_ = Clamp(decay, 0.0f, 1.0f);
-    if (ikEffector_ != NULL)
-        ikEffector_->rotation_decay = rotationDecay_;
-}
-
-// ----------------------------------------------------------------------------
-bool IKEffector::WeightedNlerpEnabled() const
-{
-    return weightedNlerp_;
+    if (ikEffectorNode_ != NULL)
+    {
+        ikEffectorNode_->effector->rotation_decay = rotationDecay_;
+        ik_calculate_rotation_weight_decays(&solver_->solver_->chain_tree);
+    }
 }
 
 // ----------------------------------------------------------------------------
-void IKEffector::EnableWeightedNlerp(bool enable)
+void IKEffector::SetFeature(Feature feature, bool enable)
 {
-    weightedNlerp_ = enable;
-    if (ikEffector_ != NULL)
+    switch (feature)
     {
-        ikEffector_->flags &= ~EFFECTOR_WEIGHT_NLERP;
-        if (enable)
-            ikEffector_->flags |= EFFECTOR_WEIGHT_NLERP;
+        case WEIGHT_NLERP:
+        {
+            if (ikEffectorNode_ != NULL)
+            {
+                ikEffectorNode_->effector->flags &= ~EFFECTOR_WEIGHT_NLERP;
+                if (enable)
+                    ikEffectorNode_->effector->flags |= EFFECTOR_WEIGHT_NLERP;
+            }
+        } break;
+
+        case INHERIT_PARENT_ROTATION:
+            break;
     }
-}
 
-// ----------------------------------------------------------------------------
-bool IKEffector::InheritParentRotationEnabled() const
-{
-    return inheritParentRotation_;
+    features_ &= ~feature;
+    if (enable)
+        features_ |= feature;
 }
 
 // ----------------------------------------------------------------------------
-void IKEffector::EnableInheritParentRotation(bool enable)
+bool IKEffector::GetFeature(Feature feature) const
 {
-    inheritParentRotation_ = enable;
-    if(ikEffector_ != NULL)
-    {
-        ikEffector_->flags &= ~EFFECTOR_INHERIT_PARENT_ROTATION;
-        if (enable)
-            ikEffector_->flags |= EFFECTOR_INHERIT_PARENT_ROTATION;
-    }
+    return (features_ & feature) != 0;
 }
 
 // ----------------------------------------------------------------------------
 void IKEffector::UpdateTargetNodePosition()
 {
+    if (targetNode_ != NULL || targetName_.Empty())
+        return;
+
+    // Try to find a node with the same name as our target name
+    SetTargetNode(node_->GetScene()->GetChild(targetName_, true));
     if (targetNode_ == NULL)
-    {
-        SetTargetNode(node_->GetScene()->GetChild(targetName_, true));
-        if (targetNode_ == NULL)
-            return;
-    }
+        return;
 
     SetTargetPosition(targetNode_->GetWorldPosition());
     SetTargetRotation(targetNode_->GetWorldRotation());
@@ -316,18 +322,20 @@ void IKEffector::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 }
 
 // ----------------------------------------------------------------------------
-void IKEffector::SetIKEffector(ik_effector_t* effector)
+void IKEffector::SetIKEffectorNode(ik_node_t* effectorNode)
 {
-    ikEffector_ = effector;
-    if (effector)
+    ikEffectorNode_ = effectorNode;
+    if (effectorNode)
     {
-        effector->target_position = Vec3Urho2IK(targetPosition_);
-        effector->target_rotation = QuatUrho2IK(targetRotation_);
-        effector->weight = weight_;
-        effector->rotation_weight = rotationWeight_;
-        effector->rotation_decay = rotationDecay_;
-        effector->chain_length = chainLength_;
-        EnableWeightedNlerp(weightedNlerp_);
+        ikEffectorNode_->effector->target_position = Vec3Urho2IK(targetPosition_);
+        ikEffectorNode_->effector->target_rotation = QuatUrho2IK(targetRotation_);
+        ikEffectorNode_->effector->weight = weight_;
+        ikEffectorNode_->effector->rotation_weight = rotationWeight_;
+        ikEffectorNode_->effector->rotation_decay = rotationDecay_;
+        ikEffectorNode_->effector->chain_length = chainLength_;
+
+        if (features_ & WEIGHT_NLERP)
+            ikEffectorNode_->effector->flags |= EFFECTOR_WEIGHT_NLERP;
     }
 }
 
@@ -337,4 +345,28 @@ void IKEffector::SetIKSolver(IKSolver* solver)
     solver_ = solver;
 }
 
+// ----------------------------------------------------------------------------
+// Need these wrapper functions flags of GetFeature/SetFeature can be correctly
+// exposed to the editor
+// ----------------------------------------------------------------------------
+
+#define DEF_FEATURE_GETTER(feature_name)   \
+bool IKEffector::Get##feature_name() const \
+{                                          \
+    return GetFeature(feature_name);       \
+}
+
+#define DEF_FEATURE_SETTER(feature_name)        \
+void IKEffector::Set##feature_name(bool enable) \
+{                                               \
+    SetFeature(feature_name, enable);           \
+}
+
+DEF_FEATURE_GETTER(WEIGHT_NLERP)
+DEF_FEATURE_GETTER(INHERIT_PARENT_ROTATION)
+
+DEF_FEATURE_SETTER(WEIGHT_NLERP)
+DEF_FEATURE_SETTER(INHERIT_PARENT_ROTATION)
+
+
 } // namespace Atomic

+ 41 - 23
Source/Atomic/IK/IKEffector.h

@@ -25,7 +25,7 @@
 #include "../Scene/Component.h"
 #include "../Scene/Scene.h"
 
-struct ik_effector_t;
+typedef struct ik_node_t ik_node_t;
 
 namespace Atomic
 {
@@ -39,6 +39,28 @@ class ATOMIC_API IKEffector : public Component
 
 public:
 
+    enum Feature
+    {
+
+        /*!
+         * If you set the effector weight (see SetWeight()) to a value in
+         * between 0 and 1, the default behaviour is to linearly interpolate
+         * the effector's target position. If the solved tree and the initial
+         * tree are far apart, this can look very strange, especially if you
+         * are controlling limbs on a character that are designed to rotation.
+         * Enabling this causes a rotational based interpolation (nlerp) around
+         * the chain's base node and makes transitions look much more natural.
+         */
+        WEIGHT_NLERP = 0x01,
+
+        /*!
+         * By default the end effector node will retain its global orientation,
+         * even after solving. By enabling this feature, the node will instead
+         * "rotate with" its parent node.
+         */
+        INHERIT_PARENT_ROTATION = 0x02
+    };
+
     /// Constructs a new IK effector.
     IKEffector(Context* context);
 
@@ -48,6 +70,11 @@ public:
     /// Registers this class as an object factory.
     static void RegisterObject(Context* context);
 
+    /// Test if a certain feature is enabled (see IKEffector::Feature)
+    bool GetFeature(Feature feature) const;
+    /// Enable or disable a certain feature (see IKEffector::Feature)
+    void SetFeature(Feature feature, bool enable);
+
     /// Retrieves the node that is being used as a target. Can be NULL.
     Node* GetTargetNode() const;
 
@@ -113,12 +140,13 @@ public:
 
     /// How strongly the target node's rotation influences the solution
     float GetRotationWeight() const;
+
     /*!
      * @brief Sets how much influence the target rotation should have on the
      * solution. A value of 1 means to match the target rotation exactly, if
      * possible. A value of 0 means to not match it at all.
      * @note The solver must have target rotation enabled for this to have
-     * any effect. See IKSolver::EnableTargetRotation().
+     * any effect. See IKSolver::Feature::TARGET_ROTATIONS.
      */
     void SetRotationWeight(float weight);
 
@@ -137,23 +165,6 @@ public:
      */
     void SetRotationDecay(float decay);
 
-    /// Whether or not to nlerp instead of lerp when transitioning with the weight parameter
-    bool WeightedNlerpEnabled() const;
-
-    /*!
-     * @brief If you set the effector weight (see SetWeight()) to a value in
-     * between 0 and 1, the default behaviour is to linearly interpolate the
-     * effector's target position. If the solved tree and the initial tree
-     * are far apart, this can look very strange, especially if you are
-     * controlling limbs on a character that are designed to rotation. Enabling
-     * this causes a rotational based interpolation (nlerp) around the chain's
-     * base node and makes transitions look much more natural.
-     */
-    void EnableWeightedNlerp(bool enable);
-
-    bool InheritParentRotationEnabled() const;
-    void EnableInheritParentRotation(bool enable);
-
     void DrawDebugGeometry(bool depthTest);
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
 
@@ -163,13 +174,21 @@ private:
     /// Intended to be used only by IKSolver
     void SetIKSolver(IKSolver* solver);
     /// Intended to be used only by IKSolver
-    void SetIKEffector(ik_effector_t* effector);
+    void SetIKEffectorNode(ik_node_t* effector);
     /// Intended to be used by IKSolver. Copies the positions/rotations of the target node into the effector
     void UpdateTargetNodePosition();
 
+public:
+    /// Need these wrapper functions flags of GetFeature/SetFeature can be correctly exposed to the editor and to AngelScript and lua
+    bool GetWEIGHT_NLERP() const;
+    bool GetINHERIT_PARENT_ROTATION() const;
+    void SetWEIGHT_NLERP(bool enable);
+    void SetINHERIT_PARENT_ROTATION(bool enable);
+
+private:
     WeakPtr<Node> targetNode_;
     WeakPtr<IKSolver> solver_;
-    ik_effector_t* ikEffector_;
+    ik_node_t* ikEffectorNode_;
 
     String targetName_;
     Vector3 targetPosition_;
@@ -178,8 +197,7 @@ private:
     float weight_;
     float rotationWeight_;
     float rotationDecay_;
-    bool weightedNlerp_;
-    bool inheritParentRotation_;
+    unsigned features_;
 };
 
 } // namespace Atomic

+ 447 - 200
Source/Atomic/IK/IKSolver.cpp

@@ -37,34 +37,22 @@
 #include <ik/effector.h>
 #include <ik/node.h>
 #include <ik/solver.h>
+#include <ik/util.h>
 
 namespace Atomic
 {
 
 extern const char* IK_CATEGORY;
 
-static bool ChildrenHaveEffector(const Node* node)
-{
-    if (node->HasComponent<IKEffector>())
-        return true;
-
-    const Vector<SharedPtr<Node> >& children = node->GetChildren();
-    for (Vector<SharedPtr<Node> >::ConstIterator it = children.Begin(); it != children.End(); ++it)
-    {
-        if (ChildrenHaveEffector(it->Get()))
-            return true;
-    }
-
-    return false;
-}
-
 // ----------------------------------------------------------------------------
 IKSolver::IKSolver(Context* context) :
     Component(context),
     solver_(NULL),
-    solverTreeNeedsRebuild_(false),
-    updateInitialPose_(false),
-    autoSolveEnabled_(true)
+    algorithm_(FABRIK),
+    features_(AUTO_SOLVE | JOINT_ROTATIONS | UPDATE_ACTIVE_POSE),
+    chainTreesNeedUpdating_(false),
+    treeNeedsRebuild(true),
+    solverTreeValid_(false)
 {
     context_->RequireIK();
 
@@ -82,7 +70,7 @@ IKSolver::~IKSolver()
     // Destroying the solver tree will destroy the effector objects, so remove
     // any references any of the IKEffector objects could be holding
     for (PODVector<IKEffector*>::ConstIterator it = effectorList_.Begin(); it != effectorList_.End(); ++it)
-        (*it)->SetIKEffector(NULL);
+        (*it)->SetIKEffectorNode(NULL);
 
     ik_solver_destroy(solver_);
     context_->ReleaseIK();
@@ -94,8 +82,11 @@ void IKSolver::RegisterObject(Context* context)
     context->RegisterFactory<IKSolver>(IK_CATEGORY);
 
     static const char* algorithmNames[] = {
+        "1 Bone",
+        "2 Bone",
         "FABRIK",
-        /* not implemented
+        /* not implemented,
+        "MSD (Mass/Spring/Damper)",
         "Jacobian Inverse",
         "Jacobian Transpose",*/
         NULL
@@ -107,7 +98,9 @@ void IKSolver::RegisterObject(Context* context)
     ATOMIC_ACCESSOR_ATTRIBUTE("Bone Rotations", BoneRotationsEnabled, EnableBoneRotations, bool, true, AM_DEFAULT);
     ATOMIC_ACCESSOR_ATTRIBUTE("Target Rotation", TargetRotationEnabled, EnableTargetRotation, bool, false, AM_DEFAULT);
     ATOMIC_ACCESSOR_ATTRIBUTE("Continuous Solving", ContinuousSolvingEnabled, EnableContinuousSolving, bool, false, AM_DEFAULT);
+    ATOMIC_ACCESSOR_ATTRIBUTE("Update Active Pose", GetUPDATE_ACTIVE_POSE, SetUPDATE_ACTIVE_POSE, bool, true, AM_DEFAULT);
     ATOMIC_ACCESSOR_ATTRIBUTE("Update Pose", UpdatePoseEnabled, EnableUpdatePose, bool, false, AM_DEFAULT);
+    ATOMIC_ACCESSOR_ATTRIBUTE("Enable Constraints", GetCONSTRAINTS, SetCONSTRAINTS, bool, false, AM_DEFAULT);
     ATOMIC_ACCESSOR_ATTRIBUTE("Auto Solve", AutoSolveEnabled, EnableAutoSolve, bool, true, AM_DEFAULT);
 }
 
@@ -122,15 +115,78 @@ void IKSolver::SetAlgorithm(IKSolver::Algorithm algorithm)
 {
     algorithm_ = algorithm;
 
+    /* We need to rebuild the tree so make sure that the scene is in the
+     * initial pose when this occurs.*/
+    if (node_ != NULL)
+        ApplyOriginalPoseToScene();
+
+    // Initial flags for when there is no solver to destroy
+    uint8_t initialFlags = 0;
+
+    // Destroys the tree and the solver
     if (solver_ != NULL)
+    {
+        initialFlags = solver_->flags;
+        DestroyTree();
         ik_solver_destroy(solver_);
+    }
 
     switch (algorithm_)
     {
-        case FABRIK: solver_ = ik_solver_create(SOLVER_FABRIK); break;
+        case ONE_BONE : solver_ = ik_solver_create(SOLVER_ONE_BONE); break;
+        case TWO_BONE : solver_ = ik_solver_create(SOLVER_TWO_BONE); break;
+        case FABRIK   : solver_ = ik_solver_create(SOLVER_FABRIK);   break;
+        /*case MSD      : solver_ = ik_solver_create(SOLVER_MSD);      break;*/
+    }
+
+    solver_->flags = initialFlags;
+
+    if (node_ != NULL)
+        RebuildTree();
+}
+
+// ----------------------------------------------------------------------------
+bool IKSolver::GetFeature(Feature feature) const
+{
+    return (features_ & feature) != 0;
+}
+
+// ----------------------------------------------------------------------------
+void IKSolver::SetFeature(Feature feature, bool enable)
+{
+    switch (feature)
+    {
+        case CONSTRAINTS:
+        {
+            solver_->flags &= ~SOLVER_ENABLE_CONSTRAINTS;
+            if (enable)
+                solver_->flags |= SOLVER_ENABLE_CONSTRAINTS;
+        } break;
+
+        case TARGET_ROTATIONS:
+        {
+            solver_->flags &= ~SOLVER_CALCULATE_TARGET_ROTATIONS;
+            if (enable)
+                solver_->flags |= SOLVER_CALCULATE_TARGET_ROTATIONS;
+        } break;
+
+        case AUTO_SOLVE:
+        {
+            if (((features_ & AUTO_SOLVE) != 0) == enable)
+                break;
+
+            if (enable)
+                SubscribeToEvent(GetScene(), E_SCENEDRAWABLEUPDATEFINISHED, ATOMIC_HANDLER(IKSolver, HandleSceneDrawableUpdateFinished));
+            else
+                UnsubscribeFromEvent(GetScene(), E_SCENEDRAWABLEUPDATEFINISHED);
+        } break;
+
+        default: break;
     }
 
-    solver_->flags = SOLVER_CALCULATE_FINAL_ROTATIONS;
+    features_ &= ~feature;
+    if (enable)
+        features_ |= feature;
 }
 
 // ----------------------------------------------------------------------------
@@ -160,138 +216,292 @@ void IKSolver::SetTolerance(float tolerance)
 }
 
 // ----------------------------------------------------------------------------
-bool IKSolver::BoneRotationsEnabled() const
+ik_node_t* IKSolver::CreateIKNodeFromUrhoNode(const Node* node)
 {
-    return (solver_->flags & SOLVER_CALCULATE_FINAL_ROTATIONS) != 0;
-}
+    ik_node_t* ikNode = ik_node_create(node->GetID());
 
-// ----------------------------------------------------------------------------
-void IKSolver::EnableBoneRotations(bool enable)
-{
-    solver_->flags &= ~SOLVER_CALCULATE_FINAL_ROTATIONS;
-    if (enable)
-        solver_->flags |= SOLVER_CALCULATE_FINAL_ROTATIONS;
-}
+    // Set initial position/rotation and pass in Node* as user data for later
+    ikNode->original_position = Vec3Urho2IK(node->GetWorldPosition());
+    ikNode->original_rotation = QuatUrho2IK(node->GetWorldRotation());
+    ikNode->user_data = (void*)node;
 
-// ----------------------------------------------------------------------------
-bool IKSolver::TargetRotationEnabled() const
-{
-    return (solver_->flags & SOLVER_CALCULATE_TARGET_ROTATIONS) != 0;
-}
+    /*
+     * If Urho's node has an effector, also create and attach one to the
+     * library's node. At this point, the IKEffector component shouldn't be
+     * holding a reference to any internal effector. Check this for debugging
+     * purposes and log if it does.
+     */
+    IKEffector* effector = node->GetComponent<IKEffector>();
+    if (effector != NULL)
+    {
+#ifdef DEBUG
+        if (effector->ikEffectorNode_ != NULL)
+            ATOMIC_LOGWARNINGF("[ik] IKEffector (attached to node \"%s\") has a reference to a possibly invalid internal effector. Should be NULL.", effector->GetNode()->GetName().CString());
+#endif
+        ik_effector_t* ikEffector = ik_effector_create();
+        ik_node_attach_effector(ikNode, ikEffector); // ownership of effector
+
+        effector->SetIKSolver(this);
+        effector->SetIKEffectorNode(ikNode);
+    }
 
-// ----------------------------------------------------------------------------
-void IKSolver::EnableTargetRotation(bool enable)
-{
-    solver_->flags &= ~SOLVER_CALCULATE_TARGET_ROTATIONS;
-    if (enable)
-        solver_->flags |= SOLVER_CALCULATE_TARGET_ROTATIONS;
-}
+    // Exact same deal with the constraint
+    IKConstraint* constraint = node->GetComponent<IKConstraint>();
+    if (constraint != NULL)
+    {
+#ifdef DEBUG
+        if (constraint->ikConstraintNode_ != NULL)
+            ATOMIC_LOGWARNINGF("[ik] IKConstraint (attached to node \"%s\") has a reference to a possibly invalid internal constraint. Should be NULL.", constraint->GetNode()->GetName().CString());
+#endif
 
-// ----------------------------------------------------------------------------
-bool IKSolver::ContinuousSolvingEnabled() const
-{
-    return (solver_->flags & SOLVER_SKIP_RESET) != 0;
+        constraint->SetIKConstraintNode(ikNode);
+    }
+
+    return ikNode;
 }
 
 // ----------------------------------------------------------------------------
-void IKSolver::EnableContinuousSolving(bool enable)
+void IKSolver::DestroyTree()
 {
-    solver_->flags &= ~SOLVER_SKIP_RESET;
-    if (enable)
-        solver_->flags |= SOLVER_SKIP_RESET;
+    ik_solver_destroy_tree(solver_);
+    effectorList_.Clear();
+    constraintList_.Clear();
 }
 
 // ----------------------------------------------------------------------------
-bool IKSolver::UpdatePoseEnabled() const
+void IKSolver::RebuildTree()
 {
-    return updateInitialPose_;
+    assert (node_ != NULL);
+
+    // Destroy current tree and set a new root node
+    DestroyTree();
+    ik_node_t* ikRoot = CreateIKNodeFromUrhoNode(node_);
+    ik_solver_set_tree(solver_, ikRoot);
+
+    /*
+     * Collect all effectors and constraints from children, and filter them to
+     * make sure they are in our subtree.
+     */
+    node_->GetComponents<IKEffector>(effectorList_, true);
+    node_->GetComponents<IKConstraint>(constraintList_, true);
+    for (PODVector<IKEffector*>::Iterator it = effectorList_.Begin(); it != effectorList_.End();)
+    {
+        if (ComponentIsInOurSubtree(*it))
+        {
+            BuildTreeToEffector((*it));
+            ++it;
+        }
+        else
+        {
+            it = effectorList_.Erase(it);
+        }
+    }
+    for (PODVector<IKConstraint*>::Iterator it = constraintList_.Begin(); it != constraintList_.End();)
+    {
+        if (ComponentIsInOurSubtree(*it))
+            ++it;
+        else
+            it = constraintList_.Erase(it);
+    }
+
+    treeNeedsRebuild = false;
+    MarkChainsNeedUpdating();
 }
 
 // ----------------------------------------------------------------------------
-void IKSolver::EnableUpdatePose(bool enable)
+bool IKSolver::BuildTreeToEffector(IKEffector* effector)
 {
-    updateInitialPose_ = enable;
+    /*
+     * NOTE: This function makes the assumption that the node the effector is
+     * attached to is -- without a doubt -- in our subtree (by using
+     * ComponentIsInOurSubtree() first). If this is not the case, the program
+     * will abort.
+     */
+
+    /*
+     * we need to build tree up to the node where this effector was added. Do
+     * this by following the chain of parent nodes until we hit a node that
+     * exists in the solver's subtree. Then iterate backwards again and add each
+     * missing node to the solver's tree.
+     */
+    const Node* iterNode = effector->GetNode();
+    ik_node_t* ikNode;
+    PODVector<const Node*> missingNodes;
+    while ((ikNode = ik_node_find_child(solver_->tree, iterNode->GetID())) == NULL)
+    {
+        missingNodes.Push(iterNode);
+        iterNode = iterNode->GetParent();
+
+        // Assert the assumptions made (described in the beginning of this function)
+        assert(iterNode != NULL);
+        assert (iterNode->HasComponent<IKSolver>() == false || iterNode == node_);
+    }
+
+    while (missingNodes.Size() > 0)
+    {
+        iterNode = missingNodes.Back();
+        missingNodes.Pop();
+
+        ik_node_t* ikChildNode = CreateIKNodeFromUrhoNode(iterNode);
+        ik_node_add_child(ikNode, ikChildNode);
+
+        ikNode = ikChildNode;
+    }
+
+    return true;
 }
 
 // ----------------------------------------------------------------------------
-void IKSolver::MarkSolverTreeDirty()
+bool IKSolver::ComponentIsInOurSubtree(Component* component) const
 {
-    solverTreeNeedsRebuild_ = true;
+    const Node* iterNode = component->GetNode();
+    while (true)
+    {
+        // Note part of our subtree
+        if (iterNode == NULL)
+            return false;
+        // Reached the root node, it's part of our subtree!
+        if (iterNode == node_)
+            return true;
+        // Path to us is being blocked by another solver
+        Component* otherSolver = iterNode->GetComponent<IKSolver>();
+        if (otherSolver != NULL && otherSolver != component)
+            return false;
+
+        iterNode = iterNode->GetParent();
+    }
+
+    return true;
 }
 
 // ----------------------------------------------------------------------------
-bool IKSolver::AutoSolveEnabled() const
+void IKSolver::RebuildChainTrees()
 {
-    return autoSolveEnabled_;
+    solverTreeValid_ = (ik_solver_rebuild_chain_trees(solver_) == 0);
+    ik_calculate_rotation_weight_decays(&solver_->chain_tree);
+
+    chainTreesNeedUpdating_ = false;
 }
 
 // ----------------------------------------------------------------------------
-void IKSolver::EnableAutoSolve(bool enable)
+void IKSolver::RecalculateSegmentLengths()
 {
-    if (autoSolveEnabled_ == enable)
-        return;
-
-    if (enable)
+    ik_solver_recalculate_segment_lengths(solver_);
         SubscribeToEvent(GetScene(), E_SCENEDRAWABLEUPDATEFINISHED, ATOMIC_HANDLER(IKSolver, HandleSceneDrawableUpdateFinished));
-    else
-        UnsubscribeFromEvent(GetScene(), E_SCENEDRAWABLEUPDATEFINISHED);
-
-    autoSolveEnabled_ = enable;
 }
 
 // ----------------------------------------------------------------------------
-static void ApplySolvedDataCallback(ik_node_t* ikNode)
+void IKSolver::CalculateJointRotations()
 {
-    Node* node = (Node*)ikNode->user_data;
-    node->SetWorldRotation(QuatIK2Urho(&ikNode->solved_rotation));
-    node->SetWorldPosition(Vec3IK2Urho(&ikNode->solved_position));
+    ik_solver_calculate_joint_rotations(solver_);
 }
+
+// ----------------------------------------------------------------------------
 void IKSolver::Solve()
 {
     ATOMIC_PROFILE(IKSolve);
 
-    if (solverTreeNeedsRebuild_)
-    {
-        ik_solver_rebuild_data(solver_);
-        solverTreeNeedsRebuild_ = false;
-    }
+    if (treeNeedsRebuild)
+        RebuildTree();
+
+    if (chainTreesNeedUpdating_)
+        RebuildChainTrees();
+
+    if (IsSolverTreeValid() == false)
+        return;
 
-    if (updateInitialPose_)
-        UpdateInitialPose();
+    if (features_ & UPDATE_ORIGINAL_POSE)
+        ApplySceneToOriginalPose();
+
+    if (features_ & UPDATE_ACTIVE_POSE)
+        ApplySceneToActivePose();
+
+    if (features_ & USE_ORIGINAL_POSE)
+        ApplyOriginalPoseToActivePose();
 
     for (PODVector<IKEffector*>::ConstIterator it = effectorList_.Begin(); it != effectorList_.End(); ++it)
     {
         (*it)->UpdateTargetNodePosition();
     }
 
-    solver_->apply_result = ApplySolvedDataCallback;
     ik_solver_solve(solver_);
+
+    if (features_ & JOINT_ROTATIONS)
+        ik_solver_calculate_joint_rotations(solver_);
+
+    ApplyActivePoseToScene();
+}
+
+// ----------------------------------------------------------------------------
+static void ApplyInitialPoseToSceneCallback(ik_node_t* ikNode)
+{
+    Node* node = (Node*)ikNode->user_data;
+    node->SetWorldRotation(QuatIK2Urho(&ikNode->original_rotation));
+    node->SetWorldPosition(Vec3IK2Urho(&ikNode->original_position));
+}
+void IKSolver::ApplyOriginalPoseToScene()
+{
+    ik_solver_iterate_tree(solver_, ApplyInitialPoseToSceneCallback);
 }
 
 // ----------------------------------------------------------------------------
-static void ApplyInitialDataCallback(ik_node_t* ikNode)
+static void ApplySceneToInitialPoseCallback(ik_node_t* ikNode)
+{
+    Node* node = (Node*)ikNode->user_data;
+    ikNode->original_rotation = QuatUrho2IK(node->GetWorldRotation());
+    ikNode->original_position = Vec3Urho2IK(node->GetWorldPosition());
+}
+void IKSolver::ApplySceneToOriginalPose()
+{
+    ik_solver_iterate_tree(solver_, ApplySceneToInitialPoseCallback);
+}
+
+// ----------------------------------------------------------------------------
+static void ApplyActivePoseToSceneCallback(ik_node_t* ikNode)
 {
     Node* node = (Node*)ikNode->user_data;
     node->SetWorldRotation(QuatIK2Urho(&ikNode->rotation));
     node->SetWorldPosition(Vec3IK2Urho(&ikNode->position));
 }
-void IKSolver::ResetToInitialPose()
+void IKSolver::ApplyActivePoseToScene()
 {
-    solver_->apply_result = ApplyInitialDataCallback;
-    ik_solver_iterate_tree(solver_);
+    ik_solver_iterate_tree(solver_, ApplyActivePoseToSceneCallback);
 }
 
 // ----------------------------------------------------------------------------
-static void UpdateInitialPoseCallback(ik_node_t* ikNode)
+static void ApplySceneToActivePoseCallback(ik_node_t* ikNode)
 {
     Node* node = (Node*)ikNode->user_data;
     ikNode->rotation = QuatUrho2IK(node->GetWorldRotation());
     ikNode->position = Vec3Urho2IK(node->GetWorldPosition());
 }
-void IKSolver::UpdateInitialPose()
+void IKSolver::ApplySceneToActivePose()
+{
+    ik_solver_iterate_tree(solver_, ApplySceneToActivePoseCallback);
+}
+
+// ----------------------------------------------------------------------------
+void IKSolver::ApplyOriginalPoseToActivePose()
+{
+    ik_solver_reset_to_original_pose(solver_);
+}
+
+// ----------------------------------------------------------------------------
+void IKSolver::MarkChainsNeedUpdating()
+{
+    chainTreesNeedUpdating_ = true;
+}
+
+// ----------------------------------------------------------------------------
+void IKSolver::MarkTreeNeedsRebuild()
+{
+    treeNeedsRebuild = true;
+}
+
+// ----------------------------------------------------------------------------
+bool IKSolver::IsSolverTreeValid() const
 {
-    solver_->apply_result = UpdateInitialPoseCallback;
-    ik_solver_iterate_tree(solver_);
+    return solverTreeValid_;
 }
 
 // ----------------------------------------------------------------------------
@@ -309,14 +519,14 @@ void IKSolver::UpdateInitialPose()
 // ----------------------------------------------------------------------------
 void IKSolver::OnSceneSet(Scene* scene)
 {
-    if (autoSolveEnabled_)
+    if (features_ & AUTO_SOLVE)
         SubscribeToEvent(scene, E_SCENEDRAWABLEUPDATEFINISHED, ATOMIC_HANDLER(IKSolver, HandleSceneDrawableUpdateFinished));
 }
 
 // ----------------------------------------------------------------------------
 void IKSolver::OnNodeSet(Node* node)
 {
-    ResetToInitialPose();
+    ApplyOriginalPoseToScene();
     DestroyTree();
 
     if (node != NULL)
@@ -324,131 +534,116 @@ void IKSolver::OnNodeSet(Node* node)
 }
 
 // ----------------------------------------------------------------------------
-ik_node_t* IKSolver::CreateIKNode(const Node* node)
+void IKSolver::HandleComponentAdded(StringHash eventType, VariantMap& eventData)
 {
-    ik_node_t* ikNode = ik_node_create(node->GetID());
-
-    // Set initial position/rotation and pass in Node* as user data for later
-    ikNode->position = Vec3Urho2IK(node->GetWorldPosition());
-    ikNode->rotation = QuatUrho2IK(node->GetWorldRotation());
-    ikNode->user_data = (void*)node;
-
-    // If the node has a constraint, it needs access to the ikNode
-    IKConstraint* constraint = node->GetComponent<IKConstraint>();
-    if (constraint != NULL)
-        constraint->SetIKNode(ikNode);
-
-    return ikNode;
-}
+    using namespace ComponentAdded;
+    (void)eventType;
 
-// ----------------------------------------------------------------------------
-void IKSolver::DestroyTree()
-{
-    ik_solver_destroy_tree(solver_);
-    effectorList_.Clear();
-}
+    Node* node = static_cast<Node*>(eventData[P_NODE].GetPtr());
+    Component* component = static_cast<Component*>(eventData[P_COMPONENT].GetPtr());
 
-// ----------------------------------------------------------------------------
-void IKSolver::RebuildTree()
-{
-    assert(node_ != NULL);
+    /*
+     * When a solver gets added into the scene, any parent solver's tree will
+     * be invalidated. We need to find all parent solvers (by iterating up the
+     * tree) and mark them as such.
+     */
+    if (component->GetType() == IKSolver::GetTypeStatic())
+    {
+        for (Node* iterNode = node; iterNode != NULL; iterNode = iterNode->GetParent())
+        {
+            IKSolver* parentSolver = iterNode->GetComponent<IKSolver>();
+            if (parentSolver != NULL)
+                parentSolver->MarkTreeNeedsRebuild();
 
-    ik_node_t* ikRoot = CreateIKNode(node_);
-    ik_solver_set_tree(solver_, ikRoot);
+        }
 
-    PODVector<Node*> effectorNodes;
-    node_->GetChildrenWithComponent<IKEffector>(effectorNodes, true);
-    for (PODVector<Node*>::ConstIterator it = effectorNodes.Begin(); it != effectorNodes.End(); ++it)
-    {
-        BuildTreeToEffector(*it);
+        return; // No need to continue processing effectors or constraints
     }
-}
 
-// ----------------------------------------------------------------------------
-void IKSolver::BuildTreeToEffector(const Node* node)
-{
-    // Check if the component that was added is an IK effector. If not, then it
-    // does not concern us.
-    IKEffector* effector = static_cast<IKEffector*>(node->GetComponent<IKEffector>());
-    if (effector == NULL || effector->GetType() != IKEffector::GetTypeStatic())
+    if (solver_->tree == NULL)
         return;
 
-    // May need to build tree up to the node where this effector was added. Do
-    // this by following the chain of parent nodes until we hit a node that
-    // exists in the solver's tree. Then iterate backwards again and add each
-    // missing node to the solver's tree.
-    PODVector<const Node*> missingNodes;
-    const Node* iterNode = node;
-    ik_node_t* ikNode = ik_node_find_child(solver_->tree, node->GetID());
-    while (ikNode == NULL)
-    {
-        missingNodes.Push(iterNode);
-        iterNode = iterNode->GetParent();
-        ikNode = ik_node_find_child(solver_->tree, iterNode->GetID());
-    }
-    while (missingNodes.Size() > 0)
+    /*
+     * Update tree if component is an effector and is part of our subtree.
+     */
+    if (component->GetType() == IKEffector::GetTypeStatic())
     {
-        iterNode = missingNodes.Back();
-        missingNodes.Pop();
-        ik_node_t* ikChildNode = CreateIKNode(iterNode);
-        ik_node_add_child(ikNode, ikChildNode);
-        ikNode = ikChildNode;
+        // Not interested in components that won't be part of our
+        if (ComponentIsInOurSubtree(component) == false)
+            return;
+
+        BuildTreeToEffector(static_cast<IKEffector*>(component));
+        effectorList_.Push(static_cast<IKEffector*>(component));
+        return;
     }
 
-    // The tip of the tree is the effector. The solver library has ownership of
-    // the effector object, but our IKEffector object also needs to know about
-    // it.
-    ik_effector_t* ikEffector = ik_effector_create();
-    ik_node_attach_effector(ikNode, ikEffector); // ownership of effector
-    effector->SetIKEffector(ikEffector);           // "weak" reference to effector
-    effector->SetIKSolver(this);
-    effectorList_.Push(effector);
+    if (component->GetType() == IKConstraint::GetTypeStatic())
+    {
+        if (ComponentIsInOurSubtree(component) == false)
+            return;
 
-    MarkSolverTreeDirty();
+        constraintList_.Push(static_cast<IKConstraint*>(component));
+    }
 }
 
 // ----------------------------------------------------------------------------
-void IKSolver::HandleComponentAdded(StringHash eventType, VariantMap& eventData)
+void IKSolver::HandleComponentRemoved(StringHash eventType, VariantMap& eventData)
 {
-    using namespace ComponentAdded;
-    (void)eventType;
+    using namespace ComponentRemoved;
 
     if (solver_->tree == NULL)
         return;
 
     Node* node = static_cast<Node*>(eventData[P_NODE].GetPtr());
-    BuildTreeToEffector(node);
-}
+    Component* component = static_cast<Component*>(eventData[P_COMPONENT].GetPtr());
 
-// ----------------------------------------------------------------------------
-void IKSolver::HandleComponentRemoved(StringHash eventType, VariantMap& eventData)
-{
-    using namespace ComponentRemoved;
+    /*
+     * When a solver gets added into the scene, any parent solver's tree will
+     * be invalidated. We need to find all parent solvers (by iterating up the
+     * tree) and mark them as such.
+     */
+    if (component->GetType() == IKSolver::GetTypeStatic())
+    {
+        for (Node* iterNode = node; iterNode != NULL; iterNode = iterNode->GetParent())
+        {
+            IKSolver* parentSolver = iterNode->GetComponent<IKSolver>();
+            if (parentSolver != NULL)
+                parentSolver->MarkTreeNeedsRebuild();
 
-    if (solver_->tree == NULL)
-        return;
+        }
+
+        return; // No need to continue processing effectors or constraints
+    }
 
     // If an effector was removed, the tree will have to be rebuilt.
-    Component* component = static_cast<Component*>(eventData[P_COMPONENT].GetPtr());
     if (component->GetType() == IKEffector::GetTypeStatic())
     {
-        IKEffector* effector = static_cast<IKEffector*>(component);
-        Node* node = static_cast<Node*>(eventData[P_NODE].GetPtr());
+        if (ComponentIsInOurSubtree(component) == false)
+            return;
+
         ik_node_t* ikNode = ik_node_find_child(solver_->tree, node->GetID());
         assert(ikNode != NULL);
+
         ik_node_destroy_effector(ikNode);
-        effector->SetIKEffector(NULL);
-        effectorList_.RemoveSwap(effector);
+        static_cast<IKEffector*>(component)->SetIKEffectorNode(NULL);
+        effectorList_.RemoveSwap(static_cast<IKEffector*>(component));
 
-        ResetToInitialPose();
-        MarkSolverTreeDirty();
+        ApplyOriginalPoseToScene();
+        MarkTreeNeedsRebuild();
+        return;
     }
 
     // Remove the ikNode* reference the IKConstraint was holding
     if (component->GetType() == IKConstraint::GetTypeStatic())
     {
-        IKConstraint* constraint = static_cast<IKConstraint*>(component);
-        constraint->SetIKNode(NULL);  // NOTE: Should restore default settings to the node
+        if (ComponentIsInOurSubtree(component) == false)
+            return;
+
+        ik_node_t* ikNode = ik_node_find_child(solver_->tree, node->GetID());
+        assert(ikNode != NULL);
+
+        static_cast<IKConstraint*>(component)->SetIKConstraintNode(NULL);
+        constraintList_.RemoveSwap(static_cast<IKConstraint*>(component));
     }
 }
 
@@ -462,12 +657,25 @@ void IKSolver::HandleNodeAdded(StringHash eventType, VariantMap& eventData)
 
     Node* node = static_cast<Node*>(eventData[P_NODE].GetPtr());
 
-    PODVector<Node*> nodes;
-    node->GetChildrenWithComponent<IKEffector>(nodes, true);
-    for (PODVector<Node*>::ConstIterator it = nodes.Begin(); it != nodes.End(); ++it)
+    PODVector<IKEffector*> effectors;
+    node->GetComponents<IKEffector>(effectors, true);
+    for (PODVector<IKEffector*>::ConstIterator it = effectors.Begin(); it != effectors.End(); ++it)
     {
+        if (ComponentIsInOurSubtree(*it) == false)
+            continue;
+
         BuildTreeToEffector(*it);
-        effectorList_.Push((*it)->GetComponent<IKEffector>());
+        effectorList_.Push(*it);
+    }
+
+    PODVector<IKConstraint*> constraints;
+    node->GetComponents<IKConstraint>(constraints, true);
+    for (PODVector<IKConstraint*>::ConstIterator it = constraints.Begin(); it != constraints.End(); ++it)
+    {
+        if (ComponentIsInOurSubtree(*it) == false)
+            continue;
+
+        constraintList_.Push(*it);
     }
 }
 
@@ -482,13 +690,19 @@ void IKSolver::HandleNodeRemoved(StringHash eventType, VariantMap& eventData)
     Node* node = static_cast<Node*>(eventData[P_NODE].GetPtr());
 
     // Remove cached IKEffectors from our list
-    PODVector<Node*> nodes;
-    node->GetChildrenWithComponent<IKEffector>(nodes, true);
-    for (PODVector<Node*>::ConstIterator it = nodes.Begin(); it != nodes.End(); ++it)
+    PODVector<IKEffector*> effectors;
+    node->GetComponents<IKEffector>(effectors, true);
+    for (PODVector<IKEffector*>::ConstIterator it = effectors.Begin(); it != effectors.End(); ++it)
+    {
+        (*it)->SetIKEffectorNode(NULL);
+        effectorList_.RemoveSwap(*it);
+    }
+
+    PODVector<IKConstraint*> constraints;
+    node->GetComponents<IKConstraint>(constraints, true);
+    for (PODVector<IKConstraint*>::ConstIterator it = constraints.Begin(); it != constraints.End(); ++it)
     {
-        IKEffector* effector = (*it)->GetComponent<IKEffector>();
-        effector->SetIKEffector(NULL);
-        effectorList_.RemoveSwap(effector);
+        constraintList_.RemoveSwap(*it);
     }
 
     // Special case, if the node being destroyed is the root node, destroy the
@@ -502,7 +716,7 @@ void IKSolver::HandleNodeRemoved(StringHash eventType, VariantMap& eventData)
         else
             ik_node_destroy(ikNode);
 
-        MarkSolverTreeDirty();
+        MarkChainsNeedUpdating();
     }
 }
 
@@ -539,8 +753,8 @@ void IKSolver::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
         unsigned numberOfSegments = 0;
         while (b && chainLength-- != 0)
         {
-            vec3_t v = a->position;
-            vec3_sub_vec3(v.f, b->position.f);
+            vec3_t v = a->original_position;
+            vec3_sub_vec3(v.f, b->original_position.f);
             averageLength += vec3_length(v.f);
             ++numberOfSegments;
             a = b;
@@ -553,36 +767,36 @@ void IKSolver::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
         a = *pnode;
         b = a->parent;
         debug->AddSphere(
-            Sphere(Vec3IK2Urho(&a->position), averageLength * 0.1f),
+            Sphere(Vec3IK2Urho(&a->original_position), averageLength * 0.1f),
             Color(0, 0, 255),
             depthTest
         );
         debug->AddSphere(
-            Sphere(Vec3IK2Urho(&a->solved_position), averageLength * 0.1f),
+            Sphere(Vec3IK2Urho(&a->position), averageLength * 0.1f),
             Color(255, 128, 0),
             depthTest
         );
         while (b && chainLength-- != 0)
         {
             debug->AddLine(
-                Vec3IK2Urho(&a->position),
-                Vec3IK2Urho(&b->position),
+                Vec3IK2Urho(&a->original_position),
+                Vec3IK2Urho(&b->original_position),
                 Color(0, 255, 255),
                 depthTest
             );
             debug->AddSphere(
-                Sphere(Vec3IK2Urho(&b->position), averageLength * 0.1f),
+                Sphere(Vec3IK2Urho(&b->original_position), averageLength * 0.1f),
                 Color(0, 0, 255),
                 depthTest
             );
             debug->AddLine(
-                Vec3IK2Urho(&a->solved_position),
-                Vec3IK2Urho(&b->solved_position),
+                Vec3IK2Urho(&a->position),
+                Vec3IK2Urho(&b->position),
                 Color(255, 0, 0),
                 depthTest
             );
             debug->AddSphere(
-                Sphere(Vec3IK2Urho(&b->solved_position), averageLength * 0.1f),
+                Sphere(Vec3IK2Urho(&b->position), averageLength * 0.1f),
                 Color(255, 128, 0),
                 depthTest
             );
@@ -592,4 +806,37 @@ void IKSolver::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
     ORDERED_VECTOR_END_EACH
 }
 
+// ----------------------------------------------------------------------------
+// Need these wrapper functions flags of GetFeature/SetFeature can be correctly
+// exposed to the editor
+// ----------------------------------------------------------------------------
+
+#define DEF_FEATURE_GETTER(feature_name) \
+bool IKSolver::Get##feature_name() const \
+{                                        \
+    return GetFeature(feature_name);     \
+}
+
+#define DEF_FEATURE_SETTER(feature_name)      \
+void IKSolver::Set##feature_name(bool enable) \
+{                                             \
+    SetFeature(feature_name, enable);         \
+}
+
+DEF_FEATURE_GETTER(JOINT_ROTATIONS)
+DEF_FEATURE_GETTER(TARGET_ROTATIONS)
+DEF_FEATURE_GETTER(UPDATE_ORIGINAL_POSE)
+DEF_FEATURE_GETTER(UPDATE_ACTIVE_POSE)
+DEF_FEATURE_GETTER(USE_ORIGINAL_POSE)
+DEF_FEATURE_GETTER(CONSTRAINTS)
+DEF_FEATURE_GETTER(AUTO_SOLVE)
+
+DEF_FEATURE_SETTER(JOINT_ROTATIONS)
+DEF_FEATURE_SETTER(TARGET_ROTATIONS)
+DEF_FEATURE_SETTER(UPDATE_ORIGINAL_POSE)
+DEF_FEATURE_SETTER(UPDATE_ACTIVE_POSE)
+DEF_FEATURE_SETTER(USE_ORIGINAL_POSE)
+DEF_FEATURE_SETTER(CONSTRAINTS)
+DEF_FEATURE_SETTER(AUTO_SOLVE)
+
 } // namespace Atomic

+ 214 - 87
Source/Atomic/IK/IKSolver.h

@@ -47,12 +47,124 @@ public:
 
     enum Algorithm
     {
+        ONE_BONE = 0,
+        TWO_BONE,
         FABRIK
         /* not implemented yet
+        MSD,
         JACOBIAN_INVERSE,
         JACOBIAN_TRANSPOSE*/
     };
 
+    enum Feature
+    {
+        /*!
+         * @brief Should be enabled if your model uses skinning or if you are
+         * generally interested in correct joint rotations. Has a minor
+         * performance impact.
+         *
+         * When enabled, final joint rotations are calculated as a post
+         * processing step. If you are using IK on a model with skinning, you will
+         * want to enable this or it will look wrong. If you disable this, then
+         * you will get a slight performance boost (less calculations are required)
+         * but only the node positions are updated. This can be useful for scene
+         * IK (perhaps a chain of platforms, where each platform should retain its
+         * initial world rotation?)
+         */
+        JOINT_ROTATIONS = 0x01,
+
+        /*!
+         * @brief When enabled, the effector will try to match the target's
+         * rotation as well as the effectors position. When disabled, the target
+         * node will reach the effector with any rotation necessary.
+         *
+         * If the target position goes out of range of the effector then the
+         * rotation will no longer be matched. The chain will try to reach out to
+         * reach the target position, even if it means rotating towards it.
+         */
+        TARGET_ROTATIONS = 0x02,
+
+        /*!
+         * When the solver is first initialized, it will copy the positions
+         * and rotations of the current Atomic scene graph into an internal
+         * structure. This is referred to as the "original pose" and will by
+         * default never change for the duration of the solver's life cycle.
+         * When the solver is destroyed, the original pose is applied back to
+         * Atomic's scene graph so the nodes are restored to whatever they were
+         * before the solver was created.
+         *
+         * By enabling UPDATE_ORIGINAL_POSE, the original pose will be updated
+         * right before solving to reflect the current Atomic scene graph. As
+         * a consequence, there will no longer be an original pose to restore
+         * when the solver is destroyed.
+         *
+         * When disabled, the original pose will remain unmodified. The original
+         * pose is set when the solver is first created. You can manually update the
+         * original pose at any time by calling UpdateInitialPose().
+         */
+        UPDATE_ORIGINAL_POSE = 0x04,
+
+        /*!
+         * @brief Should be enabled if you are using IK on an animated model,
+         * along with disabling USE_ORIGINAL_POSE.
+         *
+         * The "active pose" has two purposes: The solver uses it as the
+         * initial tree to derive a solution from, and at the same time uses it
+         * to store the solution into. Thus, the typical solving process is:
+         *   1) The active pose needs to be updated to reflect a preferred
+         *      initial condition (such as the current frame of animation)
+         *   2) Call Solve()
+         *   3) The active pose now holds the solution, so it must be applied
+         *      back to the Atomic scene graph.
+         *
+         * When enabled, the active pose is updated right before solving to
+         * reflect the current state of the Atomic scene graph.
+         *
+         * When disabled, the active pose will simply remain as it was since
+         * the last time Solve() was called.
+         *
+         * @note This option conflicts with USE_ORIGINAL_POSE. Make sure to
+         * disable USE_ORIGINAL_POSE if you enable this feature.
+         */
+        UPDATE_ACTIVE_POSE = 0x08,
+
+        /*!
+         * @brief Choose between using the original pose or the active pose as
+         * a basis for a solution.
+         *
+         * When enabled, the solver will copy the original pose
+         * (see UPDATE_ORIGINAL_POSE) into the active pose before solving (and
+         * thus use the original pose as a basis for a solution).
+         *
+         * @note This option conflicts with UPDATE_ACTIVE_POSE. If you enable
+         * this feature, make sure to disable UPDATE_ACTIVE_POSE.
+         *
+         * If both UPDATE_ACTIVE_POSE and USE_ORIGINAL_POSE are disabled, then
+         * the solver will use the previously solved tree as a basis for the new
+         * calculation. The result is a more "continuous" solution that unfolds
+         * over time. This can be useful if you want to simulate chains or
+         * something similar.
+         */
+        USE_ORIGINAL_POSE = 0x10,
+
+        /*!
+         * Due to the somewhat unfortunate performance impacts, the solver
+         * does not enable constraints by default. Enabling constraints causes
+         * the solver's tree to be written to and from Atomic's scene graph every
+         * iteration, while calling ApplyConstraints(). Disabling constraints means
+         * ApplyConstraints() is never called.
+         */
+        CONSTRAINTS = 0x20,
+
+        /*!
+         * Mostly exists because of the editor. When enabled, the solver
+         * will be invoked automatically for you. If you need to do additional
+         * calculations before being able to set the effector target data, you will
+         * want to disable this and call Solve() manually.
+         */
+        AUTO_SOLVE = 0x40
+    };
+
     /// Construct an IK root component.
     IKSolver(Context* context);
     /// Default destructor.
@@ -64,16 +176,27 @@ public:
     Algorithm GetAlgorithm() const;
 
     /*!
-     * @brief Selects the solver algorithm. Default is FABRIK.
+     * @brief Selects the solver algorithm. Default is FABRIK. Note that this
+     * may not be the most efficient algorithm available. The specialized
+     * solvers will be a lot faster.
      *
      * The currently supported solvers are listed below.
      *   + **FABRIK**: This is a fairly new and highly efficient inverse
      *     kinematic solving algorithm. It requires the least iterations to
      *     reach its goal, it does not suffer from singularities (nearly no
      *     violent snapping about), and it always converges.
+     *   + **2 Bone**: A specialized solver optimized for 2 bone problems (such
+     *     as a human leg)
+     *   + **1 Bone**: A specialized solver optimized for 1 bone problems (such
+     *     as a look-at target, e.g. eyes or a head)
      */
     void SetAlgorithm(Algorithm algorithm);
 
+    /// Test if a certain feature is enabled (see IKSolver::Feature)
+    bool GetFeature(Feature feature) const;
+    /// Enable or disable a certain feature (see IKSolver::Feature)
+    void SetFeature(Feature feature, bool enable);
+
     /// Returns the configured maximum number of iterations.
     unsigned GetMaximumIterations() const;
 
@@ -111,130 +234,113 @@ public:
      */
     void SetTolerance(float tolerance);
 
-    /// Whether or not rotations should be calculated.
-    bool BoneRotationsEnabled() const;
-
     /*!
-     * @brief When enabled, final joint rotations are calculated as a post
-     * processing step. If you are using IK on a model with skinning, you will
-     * want to enable this or it will look wrong. If you disable this, then
-     * you will get a slight performance boost (less calculations are required)
-     * but only the node positions are updated. This can be useful for scene
-     * IK (perhaps a chain of platforms, where each platform should retain its
-     * initial world rotation?)
+     * @brief Updates the solver's internal data structures, which is required
+     * whenever the tree is modified in any way (e.g. adding or removing nodes,
+     * adding or removing effectors, etc.).
+     * @note This gets called  automatically for you in Solve().
      */
-    void EnableBoneRotations(bool enable);
-
-    /// Whether or not target rotation is enabled
-    bool TargetRotationEnabled() const;
+    void RebuildChainTrees();
 
     /*!
-     * @brief When enabled, the effector will try to match the target's
-     * rotation as well as the effectors position. When disabled, the target
-     * node will reach the effector with any rotation necessary.
-     *
-     * If the target position goes out of range of the effector then the
-     * rotation will no longer be matched. The chain will try to reach out to
-     * reach the target position, even if it means rotating towards it.
+     * @brief Unusual, but if you have a tree with translational motions such
+     * that the distances between nodes changes (perhaps a slider?), you can
+     * call this to recalculate the segment lengths after assigning new
+     * positions to the nodes.
+     * @note This function gets called by RebuildData() and by extension in
+     * Solve().
      */
-    void EnableTargetRotation(bool enable);
-
-    /// Whether or not continuous solving is enabled or not.
-    bool ContinuousSolvingEnabled() const;
+    void RecalculateSegmentLengths();
 
     /*!
-     * @brief When enabled, the solver will refrain from applying the initial
-     * pose before solving. The result is that it will use the previously
-     * solved tree as a basis for the new calculation instead of using the
-     * initial tree. This can be useful if you want to simulate chains or
-     * something similar. When disabled, the solver will use the initial
-     * positions/rotations which where set when the solver was first created.
-     *
-     * If you call UpdateInitialPose() then the initial tree will be matched to
-     * the current nodes in the scene graph.
-     *
-     * If you call ResetToInitialPose() then you will do the opposite of
-     * UpdateInitialPose() -- the initial pose is applied back to the scene
-     * graph.
-     *
-     * If you enable pose updating with EnableUpdatePose(), then the initial
-     * tree will automatically be matched to the current nodes in the scene
-     * graph.
+     * @brief Skinned models require joint rotations to be calculated so
+     * skinning works correctly. This is automatically enabled by default with
+     * the feature flag JOINT_ROTATIONS.
      */
-    void EnableContinuousSolving(bool enable);
+    void CalculateJointRotations();
 
-    /// Whether or not the initial pose is updated for every solution
-    bool UpdatePoseEnabled() const;
+    /*!
+     * @brief Invokes the solver. The solution is applied back to the scene
+     * graph automatically.
+     * @note By default this is called automatically for you if the feature
+     * flag AUTO_SOLVE is set. For more complex IK problems you can disable
+     * that flag and call Solve() in response to E_SCENEDRAWABLEUPDATEFINISHED.
+     * This is right after the animations have been applied.
+     */
+    void Solve();
 
     /*!
      * @brief When enabled, the current Atomic node positions and rotations in
-     * the scene graph will be copied into the solver's initial tree right
-     * before solving. This should generally be enabled for animated models
-     * so the solver refers to the current frame of animation rather than to
-     * the animation's initial pose.
-     *
-     * When disabled, the initial pose will remain unmodified. The initial pose
-     * is set when the solver is first created. You can manually update the
-     * initial pose at any time by calling UpdateInitialPose().
+     * Copies the original pose into the scene graph. This will reset the pose
+     * to whatever state it had when the IKSolver component was first created,
+     * or, if the original pose was updated since then (for example if
+     * Feature::UPDATE_ORIGINAL_POSE is set), will reset it to that state.
      */
-    void EnableUpdatePose(bool enable);
-
-    /// Whether or not the solver should be invoked automatically
-    bool AutoSolveEnabled() const;
+    void ApplyOriginalPoseToScene();
 
     /*!
-     * @brief Mostly exists because of the editor. When enabled, the solver
-     * will be invoked automatically for you. If you need to do additional
-     * calculations before being able to set the effector target data, you will
-     * want to disable this and call Solve() manually.
+     * Copies the current scene graph data into the solvers original pose. You
+     * generally won't need to call this, because it gets called for you
+     * automatically if Feature::UPDATE_ORIGINAL_POSE is set.
      */
-    void EnableAutoSolve(bool enable);
+    void ApplySceneToOriginalPose();
 
     /*!
-     * @brief Invokes the solver. The solution is applied back to the scene
-     * graph automatically.
-     * @note You will want to register to E_SCENEDRAWABLEUPDATEFINISHED and
-     * call this method there. This is right after the animations have been
-     * applied.
+     * Copies the solvers current active pose into the scene graph. You
+     * generally won't need to call this because it gets called for you
+     * automatically in Solve(). This is used to apply the solution back to the
+     * scene graph.
      */
-    void Solve();
+    void ApplyActivePoseToScene();
 
     /*!
-     * @brief Causes the initial tree to be applied back to Atomic's scene
-     * graph. This is what gets called when continuous solving is disabled.
+     * Copies the current scene graph data into the solvers active pose. You
+     * generally won't need to call this because it gets called for you
+     * automatically if Feature::UPDATE_ACTIVE_POSE is set.
      */
-    void ResetToInitialPose();
+    void ApplySceneToActivePose();
 
     /*!
-     * @brief Causes the current scene graph data to be copied into the solvers
-     * initial pose. This should generally be called before solving if you
-     * are using IK on an animated model. If you don't update the initial pose,
-     * then the result will be a "continuous solution", where the solver will
-     * use the previously calculated tree as a basis for the new solution.
+     * Copies the solvers original pose into the solvers active pose. This is
+     * used in Solve() automatically if Feature::USE_ORIGINAL_POSE is set.
      */
-    void UpdateInitialPose();
-
-    /// Causes the solver tree to be rebuilt before solving the next time.
-    void MarkSolverTreeDirty();
+    void ApplyOriginalPoseToActivePose();
 
     void DrawDebugGeometry(bool depthTest);
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
 
 private:
+    friend class IKEffector;
+
+    /// Indicates that the internal structures of the IK library need to be updated. See the documentation of ik_solver_rebuild_chain_trees() for more info on when this happens.
+    void MarkChainsNeedUpdating();
+    /// Indicates that the tree structure has changed in some way and needs updating (nodes added or removed, components added or removed)
+    void MarkTreeNeedsRebuild();
+    /// Returns false if calling Solve() would cause the IK library to abort. Atomic's error handling philosophy is to log an error and continue, not crash.
+    bool IsSolverTreeValid() const;
+
     /// Subscribe to drawable update finished event here
     virtual void OnSceneSet(Scene* scene);
     /// Destroys and creates the tree
     virtual void OnNodeSet(Node* scene);
 
     /// Creates the ik library node and sets the current rotation/position and user data correctly.
-    ik_node_t* CreateIKNode(const Node* node);
+    ik_node_t* CreateIKNodeFromUrhoNode(const Node* node);
 
     /// Destroys the solver's tree
     void DestroyTree();
     /// Builds the solver's tree to match the scene graph's tree. If a tree already exists, it is first destroyed
     void RebuildTree();
-    /// Builds a chain of nodes up to the specified node and adds an effector. Thus, the specified node must have an IKEffector attached.
-    void BuildTreeToEffector(const Node* node);
+    /// Builds a chain of nodes up to the node of the specified effector component.
+    bool BuildTreeToEffector(IKEffector* effector);
+    /*!
+     * Checks if the specified component is 1) attached to a node that is below
+     * the one we are attached to and 2) isn't in the subtree of a child solver.
+     * @note This will return false if the component is attached to our root
+     * node, because in that case the solver can't do anything to it, it's in
+     * the hands of a parent solver (if it exists).
+     */
+    bool ComponentIsInOurSubtree(Component* component) const;
 
     void HandleComponentAdded(StringHash eventType, VariantMap& eventData);
     void HandleComponentRemoved(StringHash eventType, VariantMap& eventData);
@@ -243,12 +349,33 @@ private:
     /// Invokes the IK solver
     void HandleSceneDrawableUpdateFinished(StringHash eventType, VariantMap& eventData);
 
+    /// Need these wrapper functions flags of GetFeature/SetFeature can be correctly exposed to the editor and to AngelScript and lua
+public:
+    bool GetJOINT_ROTATIONS() const;
+    bool GetTARGET_ROTATIONS() const;
+    bool GetUPDATE_ORIGINAL_POSE() const;
+    bool GetUPDATE_ACTIVE_POSE() const;
+    bool GetUSE_ORIGINAL_POSE() const;
+    bool GetCONSTRAINTS() const;
+    bool GetAUTO_SOLVE() const;
+
+    void SetJOINT_ROTATIONS(bool enable);
+    void SetTARGET_ROTATIONS(bool enable);
+    void SetUPDATE_ORIGINAL_POSE(bool enable);
+    void SetUPDATE_ACTIVE_POSE(bool enable);
+    void SetUSE_ORIGINAL_POSE(bool enable);
+    void SetCONSTRAINTS(bool enable);
+    void SetAUTO_SOLVE(bool enable);
+
+private:
     PODVector<IKEffector*> effectorList_;
+    PODVector<IKConstraint*> constraintList_;
     ik_solver_t* solver_;
     Algorithm algorithm_;
-    bool solverTreeNeedsRebuild_;
-    bool updateInitialPose_;
-    bool autoSolveEnabled_;
+    unsigned features_;
+    bool chainTreesNeedUpdating_;
+    bool treeNeedsRebuild;
+    bool solverTreeValid_;
 };
 
 } // namespace Atomic

+ 5 - 0
Source/Atomic/IO/Deserializer.cpp

@@ -47,6 +47,11 @@ Deserializer::~Deserializer()
 {
 }
 
+unsigned Deserializer::SeekRelative(int delta)
+{
+    return Seek(GetPosition() + delta);
+}
+
 const String& Deserializer::GetName() const
 {
     return String::EMPTY;

+ 5 - 1
Source/Atomic/IO/Deserializer.h

@@ -42,7 +42,7 @@ public:
 
     /// Read bytes from the stream. Return number of bytes actually read.
     virtual unsigned Read(void* dest, unsigned size) = 0;
-    /// Set position from the beginning of the stream.
+    /// Set position from the beginning of the stream. Return actual new position.
     virtual unsigned Seek(unsigned position) = 0;
     /// Return name of the stream.
     virtual const String& GetName() const;
@@ -51,8 +51,12 @@ public:
     /// Return whether the end of stream has been reached.
     virtual bool IsEof() const { return position_ >= size_; }
 
+    /// Set position relative to current position. Return actual new position.
+    unsigned SeekRelative(int delta);
     /// Return current position.
     unsigned GetPosition() const { return position_; }
+    /// Return current position.
+    unsigned Tell() const { return position_; }
 
     /// Return size.
     unsigned GetSize() const { return size_; }

+ 1 - 1
Source/Atomic/IO/FileSystem.cpp

@@ -89,7 +89,7 @@ namespace Atomic
 
 int DoSystemCommand(const String& commandLine, bool redirectToLog, Context* context)
 {
-#ifdef TVOS
+#if defined(TVOS) || defined(IOS)
     return -1;
 #else
 #if !defined(__EMSCRIPTEN__) && !defined(MINI_URHO)

+ 1 - 1
Source/Atomic/IO/MemoryBuffer.h

@@ -42,7 +42,7 @@ public:
 
     /// Read bytes from the memory area. Return number of bytes actually read.
     virtual unsigned Read(void* dest, unsigned size);
-    /// Set position from the beginning of the memory area.
+    /// Set position from the beginning of the memory area. Return actual new position.
     virtual unsigned Seek(unsigned position);
     /// Write bytes to the memory area.
     virtual unsigned Write(const void* data, unsigned size);

+ 1 - 1
Source/Atomic/IO/VectorBuffer.h

@@ -42,7 +42,7 @@ public:
 
     /// Read bytes from the buffer. Return number of bytes actually read.
     virtual unsigned Read(void* dest, unsigned size);
-    /// Set position from the beginning of the buffer.
+    /// Set position from the beginning of the buffer. Return actual new position.
     virtual unsigned Seek(unsigned position);
     /// Write bytes to the buffer. Return number of bytes actually written.
     virtual unsigned Write(const void* data, unsigned size);

+ 7 - 0
Source/Atomic/Math/BoundingBox.cpp

@@ -196,6 +196,13 @@ Rect BoundingBox::Projected(const Matrix4& projection) const
     return rect;
 }
 
+float BoundingBox::DistanceToPoint(const Vector3& point) const
+{
+    const Vector3 offset = Center() - point;
+    const Vector3 absOffset(Abs(offset.x_), Abs(offset.y_), Abs(offset.z_));
+    return VectorMax(Vector3::ZERO, absOffset - HalfSize()).Length();
+}
+
 Intersection BoundingBox::IsInside(const Sphere& sphere) const
 {
     float distSquared = 0;

+ 2 - 0
Source/Atomic/Math/BoundingBox.h

@@ -273,6 +273,8 @@ public:
     BoundingBox Transformed(const Matrix3x4& transform) const;
     /// Return projected by a 4x4 projection matrix.
     Rect Projected(const Matrix4& projection) const;
+    /// Return distance to point.
+    float DistanceToPoint(const Vector3& point) const;
 
     /// Test if a point is inside.
     Intersection IsInside(const Vector3& point) const

+ 10 - 0
Source/Atomic/Math/MathDefs.h

@@ -205,6 +205,16 @@ inline unsigned NextPowerOfTwo(unsigned value)
     return ++value;
 }
 
+/// Return log base two or the MSB position of the given value.
+inline unsigned LogBaseTwo(unsigned value)
+{
+    // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
+    unsigned ret = 0;
+    while (value >>= 1)     // Unroll for more speed...
+        ++ret;
+    return ret;
+}
+
 /// Count the number of set bits in a mask.
 inline unsigned CountSetBits(unsigned value)
 {

+ 9 - 0
Source/Atomic/Math/Sphere.cpp

@@ -254,4 +254,13 @@ Intersection Sphere::IsInsideFast(const BoundingBox& box) const
         return INSIDE;
 }
 
+Vector3 Sphere::GetLocalPoint(float theta, float phi) const
+{
+    return Vector3(
+        radius_ * Sin(theta) * Sin(phi),
+        radius_ * Cos(phi),
+        radius_ * Cos(theta) * Sin(phi)
+    );
+}
+
 }

+ 4 - 0
Source/Atomic/Math/Sphere.h

@@ -202,6 +202,10 @@ public:
 
     /// Return distance of a point to the surface, or 0 if inside.
     float Distance(const Vector3& point) const { return Max((point - center_).Length() - radius_, 0.0f); }
+    /// Return point on the sphere relative to sphere position.
+    Vector3 GetLocalPoint(float theta, float phi) const;
+    /// Return point on the sphere.
+    Vector3 GetPoint(float theta, float phi) const { return center_ + GetLocalPoint(theta, phi); }
 
     /// Sphere center.
     Vector3 center_;

+ 23 - 11
Source/Atomic/Navigation/CrowdAgent.cpp

@@ -28,6 +28,7 @@
 #include "../IO/Log.h"
 #include "../IO/MemoryBuffer.h"
 #include "../Navigation/NavigationEvents.h"
+#include "../Navigation/NavigationMesh.h"
 #include "../Navigation/CrowdAgent.h"
 #include "../Scene/Node.h"
 #include "../Scene/Scene.h"
@@ -95,6 +96,7 @@ CrowdAgent::CrowdAgent(Context* context) :
     previousAgentState_(CA_STATE_WALKING),
     ignoreTransformChanges_(false)
 {
+    SubscribeToEvent(E_NAVIGATION_TILE_ADDED, ATOMIC_HANDLER(CrowdAgent, HandleNavigationTileAdded));
 }
 
 CrowdAgent::~CrowdAgent()
@@ -628,20 +630,11 @@ void CrowdAgent::OnMarkedDirty(Node* node)
         {
             Vector3& agentPos = reinterpret_cast<Vector3&>(agent->npos);
             Vector3 nodePos = node->GetWorldPosition();
-            
+
             // Only reset position / state if actually changed
             if (nodePos != agentPos)
             {
-                // If position difference is significant, readd to crowd (issue 1695)
-                /// \todo Somewhat arbitrary
-                float diff = (agentPos - nodePos).LengthSquared();
-                if (diff >= 1.0f)
-                {
-                    RemoveAgentFromCrowd();
-                    AddAgentToCrowd();
-                }
-                else
-                    agentPos = nodePos;
+                agentPos = nodePos;
 
                 // If the node has been externally altered, provide the opportunity for DetourCrowd to reevaluate the crowd agent
                 if (agent->state == CA_STATE_INVALID)
@@ -656,4 +649,23 @@ const dtCrowdAgent* CrowdAgent::GetDetourCrowdAgent() const
     return IsInCrowd() ? crowdManager_->GetDetourCrowdAgent(agentCrowdId_) : 0;
 }
 
+void CrowdAgent::HandleNavigationTileAdded(StringHash eventType, VariantMap& eventData)
+{
+    if (!crowdManager_)
+        return;
+
+    NavigationMesh* mesh = static_cast<NavigationMesh*>(eventData[NavigationTileAdded::P_MESH].GetPtr());
+    if (crowdManager_->GetNavigationMesh() != mesh)
+        return;
+
+    const IntVector2 tile = eventData[NavigationTileRemoved::P_TILE].GetIntVector2();
+    const IntVector2 agentTile = mesh->GetTileIndex(node_->GetWorldPosition());
+    const BoundingBox boundingBox = mesh->GetTileBoudningBox(agentTile);
+    if (tile == agentTile && IsInCrowd())
+    {
+        RemoveAgentFromCrowd();
+        AddAgentToCrowd();
+    }
+}
+
 }

+ 2 - 0
Source/Atomic/Navigation/CrowdAgent.h

@@ -188,6 +188,8 @@ protected:
     virtual void OnMarkedDirty(Node* node);
     /// Get internal Detour crowd agent.
     const dtCrowdAgent* GetDetourCrowdAgent() const;
+    /// Handle navigation mesh tile added.
+    void HandleNavigationTileAdded(StringHash eventType, VariantMap& eventData);
 
 private:
     /// Update Detour crowd agent parameter.

+ 297 - 109
Source/Atomic/Navigation/DynamicNavigationMesh.cpp

@@ -240,6 +240,105 @@ void DynamicNavigationMesh::RegisterObject(Context* context)
     ATOMIC_ACCESSOR_ATTRIBUTE("Draw Obstacles", GetDrawObstacles, SetDrawObstacles, bool, false, AM_DEFAULT);
 }
 
+bool DynamicNavigationMesh::Allocate(const BoundingBox& boundingBox, unsigned maxTiles)
+{
+    // Release existing navigation data and zero the bounding box
+    ReleaseNavigationMesh();
+
+    if (!node_)
+        return false;
+
+    if (!node_->GetWorldScale().Equals(Vector3::ONE))
+        ATOMIC_LOGWARNING("Navigation mesh root node has scaling. Agent parameters may not work as intended");
+
+    boundingBox_ = boundingBox.Transformed(node_->GetWorldTransform().Inverse());
+    maxTiles = NextPowerOfTwo(maxTiles);
+
+    // Calculate number of tiles
+    int gridW = 0, gridH = 0;
+    float tileEdgeLength = (float)tileSize_ * cellSize_;
+    rcCalcGridSize(&boundingBox_.min_.x_, &boundingBox_.max_.x_, cellSize_, &gridW, &gridH);
+    numTilesX_ = (gridW + tileSize_ - 1) / tileSize_;
+    numTilesZ_ = (gridH + tileSize_ - 1) / tileSize_;
+
+    // Calculate max number of polygons, 22 bits available to identify both tile & polygon within tile
+    unsigned tileBits = LogBaseTwo(maxTiles);
+    unsigned maxPolys = (unsigned)(1 << (22 - tileBits));
+
+    dtNavMeshParams params;
+    rcVcopy(params.orig, &boundingBox_.min_.x_);
+    params.tileWidth = tileEdgeLength;
+    params.tileHeight = tileEdgeLength;
+    params.maxTiles = maxTiles;
+    params.maxPolys = maxPolys;
+
+    navMesh_ = dtAllocNavMesh();
+    if (!navMesh_)
+    {
+        ATOMIC_LOGERROR("Could not allocate navigation mesh");
+        return false;
+    }
+
+    if (dtStatusFailed(navMesh_->init(&params)))
+    {
+        ATOMIC_LOGERROR("Could not initialize navigation mesh");
+        ReleaseNavigationMesh();
+        return false;
+    }
+
+    dtTileCacheParams tileCacheParams;
+    memset(&tileCacheParams, 0, sizeof(tileCacheParams));
+    rcVcopy(tileCacheParams.orig, &boundingBox_.min_.x_);
+    tileCacheParams.ch = cellHeight_;
+    tileCacheParams.cs = cellSize_;
+    tileCacheParams.width = tileSize_;
+    tileCacheParams.height = tileSize_;
+    tileCacheParams.maxSimplificationError = edgeMaxError_;
+    tileCacheParams.maxTiles = maxTiles * maxLayers_;
+    tileCacheParams.maxObstacles = maxObstacles_;
+    // Settings from NavigationMesh
+    tileCacheParams.walkableClimb = agentMaxClimb_;
+    tileCacheParams.walkableHeight = agentHeight_;
+    tileCacheParams.walkableRadius = agentRadius_;
+
+    tileCache_ = dtAllocTileCache();
+    if (!tileCache_)
+    {
+        ATOMIC_LOGERROR("Could not allocate tile cache");
+        ReleaseNavigationMesh();
+        return false;
+    }
+
+    if (dtStatusFailed(tileCache_->init(&tileCacheParams, allocator_.Get(), compressor_.Get(), meshProcessor_.Get())))
+    {
+        ATOMIC_LOGERROR("Could not initialize tile cache");
+        ReleaseNavigationMesh();
+        return false;
+    }
+
+    ATOMIC_LOGDEBUG("Allocated empty navigation mesh with max " + String(maxTiles) + " tiles");
+
+    // Scan for obstacles to insert into us
+    PODVector<Node*> obstacles;
+    GetScene()->GetChildrenWithComponent<Obstacle>(obstacles, true);
+    for (unsigned i = 0; i < obstacles.Size(); ++i)
+    {
+        Obstacle* obs = obstacles[i]->GetComponent<Obstacle>();
+        if (obs && obs->IsEnabledEffective())
+            AddObstacle(obs);
+    }
+
+    // Send a notification event to concerned parties that we've been fully rebuilt
+    {
+        using namespace NavigationMeshRebuilt;
+        VariantMap& buildEventParams = GetContext()->GetEventDataMap();
+        buildEventParams[P_NODE] = node_;
+        buildEventParams[P_MESH] = this;
+        SendEvent(E_NAVIGATION_MESH_REBUILT, buildEventParams);
+    }
+    return true;
+}
+
 bool DynamicNavigationMesh::Build()
 {
     ATOMIC_PROFILE(BuildNavigationMesh);
@@ -278,14 +377,7 @@ bool DynamicNavigationMesh::Build()
 
         // Calculate max. number of tiles and polygons, 22 bits available to identify both tile & polygon within tile
         unsigned maxTiles = NextPowerOfTwo((unsigned)(numTilesX_ * numTilesZ_)) * maxLayers_;
-        unsigned tileBits = 0;
-        unsigned temp = maxTiles;
-        while (temp > 1)
-        {
-            temp >>= 1;
-            ++tileBits;
-        }
-
+        unsigned tileBits = LogBaseTwo(maxTiles);
         unsigned maxPolys = (unsigned)(1 << (22 - tileBits));
 
         dtNavMeshParams params;
@@ -358,10 +450,9 @@ bool DynamicNavigationMesh::Build()
                         tiles[i].data = 0x0;
                     }
                 }
+                tileCache_->buildNavMeshTilesAt(x, z, navMesh_);
                 ++numTiles;
             }
-            for (int x = 0; x < numTilesX_; ++x)
-                tileCache_->buildNavMeshTilesAt(x, z, navMesh_);
         }
 
         // For a full build it's necessary to update the nav mesh
@@ -421,45 +512,88 @@ bool DynamicNavigationMesh::Build(const BoundingBox& boundingBox)
     int ex = Clamp((int)((localSpaceBox.max_.x_ - boundingBox_.min_.x_) / tileEdgeLength), 0, numTilesX_ - 1);
     int ez = Clamp((int)((localSpaceBox.max_.z_ - boundingBox_.min_.z_) / tileEdgeLength), 0, numTilesZ_ - 1);
 
-    unsigned numTiles = 0;
+    unsigned numTiles = BuildTiles(geometryList, IntVector2(sx, sz), IntVector2(ex, ez));
 
-    for (int z = sz; z <= ez; ++z)
-    {
-        for (int x = sx; x <= ex; ++x)
-        {
-            dtCompressedTileRef existing[TILECACHE_MAXLAYERS];
-            const int existingCt = tileCache_->getTilesAt(x, z, existing, maxLayers_);
-            for (int i = 0; i < existingCt; ++i)
-            {
-                unsigned char* data = 0x0;
-                if (!dtStatusFailed(tileCache_->removeTile(existing[i], &data, 0)) && data != 0x0)
-                    dtFree(data);
-            }
+    ATOMIC_LOGDEBUG("Rebuilt " + String(numTiles) + " tiles of the navigation mesh");
+    return true;
+}
 
-            TileCacheData tiles[TILECACHE_MAXLAYERS];
-            int layerCt = BuildTile(geometryList, x, z, tiles);
-            for (int i = 0; i < layerCt; ++i)
-            {
-                dtCompressedTileRef tileRef;
-                int status = tileCache_->addTile(tiles[i].data, tiles[i].dataSize, DT_COMPRESSEDTILE_FREE_DATA, &tileRef);
-                if (dtStatusFailed((dtStatus)status))
-                {
-                    dtFree(tiles[i].data);
-                    tiles[i].data = 0x0;
-                }
-                else
-                {
-                    tileCache_->buildNavMeshTile(tileRef, navMesh_);
-                    ++numTiles;
-                }
-            }
-        }
+bool DynamicNavigationMesh::Build(const IntVector2& from, const IntVector2& to)
+{
+    ATOMIC_PROFILE(BuildPartialNavigationMesh);
+
+    if (!node_)
+        return false;
+
+    if (!navMesh_)
+    {
+        ATOMIC_LOGERROR("Navigation mesh must first be built fully before it can be partially rebuilt");
+        return false;
     }
 
+    if (!node_->GetWorldScale().Equals(Vector3::ONE))
+        ATOMIC_LOGWARNING("Navigation mesh root node has scaling. Agent parameters may not work as intended");
+
+    Vector<NavigationGeometryInfo> geometryList;
+    CollectGeometries(geometryList);
+
+    unsigned numTiles = BuildTiles(geometryList, from, to);
+
     ATOMIC_LOGDEBUG("Rebuilt " + String(numTiles) + " tiles of the navigation mesh");
     return true;
 }
 
+PODVector<unsigned char> DynamicNavigationMesh::GetTileData(const IntVector2& tile) const
+{
+    VectorBuffer ret;
+    WriteTiles(ret, tile.x_, tile.y_);
+    return ret.GetBuffer();
+}
+
+bool DynamicNavigationMesh::IsObstacleInTile(Obstacle* obstacle, const IntVector2& tile) const
+{
+    const BoundingBox tileBoundingBox = GetTileBoudningBox(tile);
+    const Vector3 obstaclePosition = obstacle->GetNode()->GetWorldPosition();
+    return tileBoundingBox.DistanceToPoint(obstaclePosition) < obstacle->GetRadius();
+}
+
+bool DynamicNavigationMesh::AddTile(const PODVector<unsigned char>& tileData)
+{
+    MemoryBuffer buffer(tileData);
+    return ReadTiles(buffer, false);
+}
+
+void DynamicNavigationMesh::RemoveTile(const IntVector2& tile)
+{
+    if (!navMesh_)
+        return;
+
+    dtCompressedTileRef existing[TILECACHE_MAXLAYERS];
+    const int existingCt = tileCache_->getTilesAt(tile.x_, tile.y_, existing, maxLayers_);
+    for (int i = 0; i < existingCt; ++i)
+    {
+        unsigned char* data = 0x0;
+        if (!dtStatusFailed(tileCache_->removeTile(existing[i], &data, 0)) && data != 0x0)
+            dtFree(data);
+    }
+
+    NavigationMesh::RemoveTile(tile);
+}
+
+void DynamicNavigationMesh::RemoveAllTiles()
+{
+    int numTiles = tileCache_->getTileCount();
+    for (int i = 0; i < numTiles; ++i)
+    {
+        const dtCompressedTile* tile = tileCache_->getTile(i);
+        assert(tile);
+        if (tile->header)
+            tileCache_->removeTile(tileCache_->getTileRef(tile), 0, 0);
+    }
+
+    NavigationMesh::RemoveAllTiles();
+    ATOMIC_LOGDEBUG("Rebuilt " + String(numTiles) + " tiles of the navigation mesh");
+}
 
 void DynamicNavigationMesh::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 {
@@ -470,29 +604,21 @@ void DynamicNavigationMesh::DrawDebugGeometry(DebugRenderer* debug, bool depthTe
 
     const dtNavMesh* navMesh = navMesh_;
 
-    for (int z = 0; z < numTilesZ_; ++z)
+    for (int j = 0; j < navMesh->getMaxTiles(); ++j)
     {
-        for (int x = 0; x < numTilesX_; ++x)
+        const dtMeshTile* tile = navMesh->getTile(j);
+        assert(tile);
+        if (!tile->header)
+            continue;
+
+        for (int i = 0; i < tile->header->polyCount; ++i)
         {
-            // Get the layers from the tile-cache
-            const dtMeshTile* tiles[TILECACHE_MAXLAYERS];
-            int tileCount = navMesh->getTilesAt(x, z, tiles, TILECACHE_MAXLAYERS);
-            for (int i = 0; i < tileCount; ++i)
+            dtPoly* poly = tile->polys + i;
+            for (unsigned j = 0; j < poly->vertCount; ++j)
             {
-                const dtMeshTile* tile = tiles[i];
-                if (!tile)
-                    continue;
-
-                for (int i = 0; i < tile->header->polyCount; ++i)
-                {
-                    dtPoly* poly = tile->polys + i;
-                    for (unsigned j = 0; j < poly->vertCount; ++j)
-                    {
-                        debug->AddLine(worldTransform * *reinterpret_cast<const Vector3*>(&tile->verts[poly->verts[j] * 3]),
-                            worldTransform * *reinterpret_cast<const Vector3*>(&tile->verts[poly->verts[(j + 1) % poly->vertCount] * 3]),
-                            Color::YELLOW, depthTest);
-                    }
-                }
+                debug->AddLine(worldTransform * *reinterpret_cast<const Vector3*>(&tile->verts[poly->verts[j] * 3]),
+                    worldTransform * *reinterpret_cast<const Vector3*>(&tile->verts[poly->verts[(j + 1) % poly->vertCount] * 3]),
+                    Color::YELLOW, depthTest);
             }
         }
     }
@@ -598,29 +724,9 @@ void DynamicNavigationMesh::SetNavigationDataAttr(const PODVector<unsigned char>
         return;
     }
 
-    while (!buffer.IsEof())
-    {
-        dtTileCacheLayerHeader header;
-        buffer.Read(&header, sizeof(dtTileCacheLayerHeader));
-        const int dataSize = buffer.ReadInt();
-        unsigned char* data = (unsigned char*)dtAlloc(dataSize, DT_ALLOC_PERM);
-        buffer.Read(data, (unsigned)dataSize);
-
-        if (dtStatusFailed(tileCache_->addTile(data, dataSize, DT_TILE_FREE_DATA, 0)))
-        {
+    ReadTiles(buffer, true);
+    // \todo Shall we send E_NAVIGATION_MESH_REBUILT here?
             ATOMIC_LOGERROR("Failed to add tile");
-            dtFree(data);
-            return;
-        }
-    }
-
-    for (int x = 0; x < numTilesX_; ++x)
-    {
-        for (int z = 0; z < numTilesZ_; ++z)
-            tileCache_->buildNavMeshTilesAt(x, z, navMesh_);
-    }
-
-    tileCache_->update(0, navMesh_);
 }
 
 PODVector<unsigned char> DynamicNavigationMesh::GetNavigationDataAttr() const
@@ -639,23 +745,8 @@ PODVector<unsigned char> DynamicNavigationMesh::GetNavigationDataAttr() const
         ret.Write(tcParams, sizeof(dtTileCacheParams));
 
         for (int z = 0; z < numTilesZ_; ++z)
-        {
             for (int x = 0; x < numTilesX_; ++x)
-            {
-                dtCompressedTileRef tiles[TILECACHE_MAXLAYERS];
-                const int ct = tileCache_->getTilesAt(x, z, tiles, maxLayers_);
-                for (int i = 0; i < ct; ++i)
-                {
-                    const dtCompressedTile* tile = tileCache_->getTileByRef(tiles[i]);
-                    if (!tile || !tile->header || !tile->dataSize)
-                        continue; // Don't write "void-space" tiles
-                    // The header conveniently has the majority of the information required
-                    ret.Write(tile->header, sizeof(dtTileCacheLayerHeader));
-                    ret.WriteInt(tile->dataSize);
-                    ret.Write(tile->data, (unsigned)tile->dataSize);
-                }
-            }
-        }
+                WriteTiles(ret, x, z);
     }
     return ret.GetBuffer();
 }
@@ -667,22 +758,79 @@ void DynamicNavigationMesh::SetMaxLayers(unsigned maxLayers)
     maxLayers_ = Max(3U, Min(maxLayers, TILECACHE_MAXLAYERS));
 }
 
+void DynamicNavigationMesh::WriteTiles(Serializer& dest, int x, int z) const
+{
+    dtCompressedTileRef tiles[TILECACHE_MAXLAYERS];
+    const int ct = tileCache_->getTilesAt(x, z, tiles, maxLayers_);
+    for (int i = 0; i < ct; ++i)
+    {
+        const dtCompressedTile* tile = tileCache_->getTileByRef(tiles[i]);
+        if (!tile || !tile->header || !tile->dataSize)
+            continue; // Don't write "void-space" tiles
+                      // The header conveniently has the majority of the information required
+        dest.Write(tile->header, sizeof(dtTileCacheLayerHeader));
+        dest.WriteInt(tile->dataSize);
+        dest.Write(tile->data, (unsigned)tile->dataSize);
+    }
+}
+
+bool DynamicNavigationMesh::ReadTiles(Deserializer& source, bool silent)
+{
+    tileQueue_.Clear();
+    while (!source.IsEof())
+    {
+        dtTileCacheLayerHeader header;
+        source.Read(&header, sizeof(dtTileCacheLayerHeader));
+        const int dataSize = source.ReadInt();
+
+        unsigned char* data = (unsigned char*)dtAlloc(dataSize, DT_ALLOC_PERM);
+        if (!data)
+        {
+            ATOMIC_LOGERROR("Could not allocate data for navigation mesh tile");
+            return false;
+        }
+
+        source.Read(data, (unsigned)dataSize);
+        if (dtStatusFailed(tileCache_->addTile(data, dataSize, DT_TILE_FREE_DATA, 0)))
+        {
+            ATOMIC_LOGERROR("Failed to add tile");
+            dtFree(data);
+            return false;
+        }
+
+        const IntVector2 tileIdx = IntVector2(header.tx, header.ty);
+        if (tileQueue_.Empty() || tileQueue_.Back() != tileIdx)
+            tileQueue_.Push(tileIdx);
+    }
+
+    for (unsigned i = 0; i < tileQueue_.Size(); ++i)
+        tileCache_->buildNavMeshTilesAt(tileQueue_[i].x_, tileQueue_[i].y_, navMesh_);
+
+    tileCache_->update(0, navMesh_);
+
+    // Send event
+    if (!silent)
+    {
+        for (unsigned i = 0; i < tileQueue_.Size(); ++i)
+        {
+            using namespace NavigationTileAdded;
+            VariantMap& eventData = GetContext()->GetEventDataMap();
+            eventData[P_NODE] = GetNode();
+            eventData[P_MESH] = this;
+            eventData[P_TILE] = tileQueue_[i];
+            SendEvent(E_NAVIGATION_TILE_ADDED, eventData);
+        }
+    }
+    return true;
+}
+
 int DynamicNavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryList, int x, int z, TileCacheData* tiles)
 {
     ATOMIC_PROFILE(BuildNavigationMeshTile);
 
     tileCache_->removeTile(navMesh_->getTileRefAt(x, z, 0), 0, 0);
 
-    float tileEdgeLength = (float)tileSize_ * cellSize_;
-
-    BoundingBox tileBoundingBox(Vector3(
-            boundingBox_.min_.x_ + tileEdgeLength * (float)x,
-            boundingBox_.min_.y_,
-            boundingBox_.min_.z_ + tileEdgeLength * (float)z),
-        Vector3(
-            boundingBox_.min_.x_ + tileEdgeLength * (float)(x + 1),
-            boundingBox_.max_.y_,
-            boundingBox_.min_.z_ + tileEdgeLength * (float)(z + 1)));
+    const BoundingBox tileBoundingBox = GetTileBoudningBox(IntVector2(x, z));
 
     DynamicNavBuildData build(allocator_.Get());
 
@@ -855,6 +1003,46 @@ int DynamicNavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryLis
     return retCt;
 }
 
+unsigned DynamicNavigationMesh::BuildTiles(Vector<NavigationGeometryInfo>& geometryList, const IntVector2& from, const IntVector2& to)
+{
+    unsigned numTiles = 0;
+
+    for (int z = from.y_; z <= to.y_; ++z)
+    {
+        for (int x = from.x_; x <= to.x_; ++x)
+        {
+            dtCompressedTileRef existing[TILECACHE_MAXLAYERS];
+            const int existingCt = tileCache_->getTilesAt(x, z, existing, maxLayers_);
+            for (int i = 0; i < existingCt; ++i)
+            {
+                unsigned char* data = 0x0;
+                if (!dtStatusFailed(tileCache_->removeTile(existing[i], &data, 0)) && data != 0x0)
+                    dtFree(data);
+            }
+
+            TileCacheData tiles[TILECACHE_MAXLAYERS];
+            int layerCt = BuildTile(geometryList, x, z, tiles);
+            for (int i = 0; i < layerCt; ++i)
+            {
+                dtCompressedTileRef tileRef;
+                int status = tileCache_->addTile(tiles[i].data, tiles[i].dataSize, DT_COMPRESSEDTILE_FREE_DATA, &tileRef);
+                if (dtStatusFailed((dtStatus)status))
+                {
+                    dtFree(tiles[i].data);
+                    tiles[i].data = 0x0;
+                }
+                else
+                {
+                    tileCache_->buildNavMeshTile(tileRef, navMesh_);
+                    ++numTiles;
+                }
+            }
+        }
+    }
+
+    return numTiles;
+}
+
 PODVector<OffMeshConnection*> DynamicNavigationMesh::CollectOffMeshConnections(const BoundingBox& bounds)
 {
     PODVector<OffMeshConnection*> connections;

+ 23 - 1
Source/Atomic/Navigation/DynamicNavigationMesh.h

@@ -54,10 +54,24 @@ public:
     /// Register with engine context.
     static void RegisterObject(Context*);
 
+    /// Allocate the navigation mesh without building any tiles. Bounding box is not padded. Return true if successful.
+    virtual bool Allocate(const BoundingBox& boundingBox, unsigned maxTiles);
     /// Build/rebuild the entire navigation mesh.
     virtual bool Build();
     /// Build/rebuild a portion of the navigation mesh.
     virtual bool Build(const BoundingBox& boundingBox);
+    /// Rebuild part of the navigation mesh in the rectangular area. Return true if successful.
+    virtual bool Build(const IntVector2& from, const IntVector2& to);
+    /// Return tile data.
+    virtual PODVector<unsigned char> GetTileData(const IntVector2& tile) const;
+    /// Return whether the Obstacle is touching the given tile.
+    bool IsObstacleInTile(Obstacle* obstacle, const IntVector2& tile) const;
+    /// Add tile to navigation mesh.
+    virtual bool AddTile(const PODVector<unsigned char>& tileData);
+    /// Remove tile from navigation mesh.
+    virtual void RemoveTile(const IntVector2& tile);
+    /// Remove all tiles from navigation mesh.
+    virtual void RemoveAllTiles();
     /// Visualize the component as debug geometry.
     virtual void DrawDebugGeometry(DebugRenderer* debug, bool depthTest);
     /// Add debug geometry to the debug renderer.
@@ -100,13 +114,19 @@ protected:
     void RemoveObstacle(Obstacle*, bool silent = false);
 
     /// Build one tile of the navigation mesh. Return true if successful.
-    int BuildTile(Vector<NavigationGeometryInfo>& geometryList, int x, int z, TileCacheData*);
+    int BuildTile(Vector<NavigationGeometryInfo>& geometryList, int x, int z, TileCacheData* tiles);
+    /// Build tiles in the rectangular area. Return number of built tiles.
+    unsigned BuildTiles(Vector<NavigationGeometryInfo>& geometryList, const IntVector2& from, const IntVector2& to);
     /// Off-mesh connections to be rebuilt in the mesh processor.
     PODVector<OffMeshConnection*> CollectOffMeshConnections(const BoundingBox& bounds);
     /// Release the navigation mesh, query, and tile cache.
     virtual void ReleaseNavigationMesh();
 
 private:
+    /// Write tiles data.
+    void WriteTiles(Serializer& dest, int x, int z) const;
+    /// Read tiles data to the navigation mesh.
+    bool ReadTiles(Deserializer& source, bool silent);
     /// Free the tile cache.
     void ReleaseTileCache();
 
@@ -124,6 +144,8 @@ private:
     unsigned maxLayers_;
     /// Debug draw Obstacles.
     bool drawObstacles_;
+    /// Queue of tiles to be built.
+    PODVector<IntVector2> tileQueue_;
 };
 
 }

+ 22 - 0
Source/Atomic/Navigation/NavigationEvents.h

@@ -43,6 +43,28 @@ ATOMIC_EVENT(E_NAVIGATION_AREA_REBUILT, NavigationAreaRebuilt)
     ATOMIC_PARAM(P_BOUNDSMAX, BoundsMax); // Vector3
 }
 
+/// Mesh tile is added to navigation mesh.
+ATOMIC_EVENT(E_NAVIGATION_TILE_ADDED, NavigationTileAdded)
+{
+    ATOMIC_PARAM(P_NODE, Node); // Node pointer
+    ATOMIC_PARAM(P_MESH, Mesh); // NavigationMesh pointer
+    ATOMIC_PARAM(P_TILE, Tile); // IntVector2
+}
+
+/// Mesh tile is removed from navigation mesh.
+ATOMIC_EVENT(E_NAVIGATION_TILE_REMOVED, NavigationTileRemoved)
+{
+    ATOMIC_PARAM(P_NODE, Node); // Node pointer
+    ATOMIC_PARAM(P_MESH, Mesh); // NavigationMesh pointer
+    ATOMIC_PARAM(P_TILE, Tile); // IntVector2
+}
+
+/// All mesh tiles are removed from navigation mesh.
+ATOMIC_EVENT(E_NAVIGATION_ALL_TILES_REMOVED, NavigationAllTilesRemoved)
+{
+    ATOMIC_PARAM(P_NODE, Node); // Node pointer
+    ATOMIC_PARAM(P_MESH, Mesh); // NavigationMesh pointer
+}
 /// Crowd agent formation.
 ATOMIC_EVENT(E_CROWD_AGENT_FORMATION, CrowdAgentFormation)
 {

+ 252 - 90
Source/Atomic/Navigation/NavigationMesh.cpp

@@ -171,29 +171,24 @@ void NavigationMesh::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 
     const dtNavMesh* navMesh = navMesh_;
 
-    for (int z = 0; z < numTilesZ_; ++z)
+    for (int j = 0; j < navMesh->getMaxTiles(); ++j)
     {
-        for (int x = 0; x < numTilesX_; ++x)
+        const dtMeshTile* tile = navMesh->getTile(j);
+        assert(tile);
+        if (!tile->header)
+            continue;
+
+        for (int i = 0; i < tile->header->polyCount; ++i)
         {
-            for (int i = 0; i < 128; ++i)
+            dtPoly* poly = tile->polys + i;
+            for (unsigned j = 0; j < poly->vertCount; ++j)
             {
-                const dtMeshTile* tile = navMesh->getTileAt(x, z, i);
-                if (!tile)
-                    continue;
-
-                for (int i = 0; i < tile->header->polyCount; ++i)
-                {
-                    dtPoly* poly = tile->polys + i;
-                    for (unsigned j = 0; j < poly->vertCount; ++j)
-                    {
-                        debug->AddLine(
-                            worldTransform * *reinterpret_cast<const Vector3*>(&tile->verts[poly->verts[j] * 3]),
-                            worldTransform * *reinterpret_cast<const Vector3*>(&tile->verts[poly->verts[(j + 1) % poly->vertCount] * 3]),
-                            Color::YELLOW,
-                            depthTest
-                        );
-                    }
-                }
+                debug->AddLine(
+                    worldTransform * *reinterpret_cast<const Vector3*>(&tile->verts[poly->verts[j] * 3]),
+                    worldTransform * *reinterpret_cast<const Vector3*>(&tile->verts[poly->verts[(j + 1) % poly->vertCount] * 3]),
+                    Color::YELLOW,
+                    depthTest
+                );
             }
         }
     }
@@ -330,6 +325,65 @@ void NavigationMesh::SetPadding(const Vector3& padding)
     MarkNetworkUpdate();
 }
 
+bool NavigationMesh::Allocate(const BoundingBox& boundingBox, unsigned maxTiles)
+{
+    // Release existing navigation data and zero the bounding box
+    ReleaseNavigationMesh();
+
+    if (!node_)
+        return false;
+
+    if (!node_->GetWorldScale().Equals(Vector3::ONE))
+        ATOMIC_LOGWARNING("Navigation mesh root node has scaling. Agent parameters may not work as intended");
+
+    boundingBox_ = boundingBox.Transformed(node_->GetWorldTransform().Inverse());
+    maxTiles = NextPowerOfTwo(maxTiles);
+
+    // Calculate number of tiles
+    int gridW = 0, gridH = 0;
+    float tileEdgeLength = (float)tileSize_ * cellSize_;
+    rcCalcGridSize(&boundingBox_.min_.x_, &boundingBox_.max_.x_, cellSize_, &gridW, &gridH);
+    numTilesX_ = (gridW + tileSize_ - 1) / tileSize_;
+    numTilesZ_ = (gridH + tileSize_ - 1) / tileSize_;
+
+    // Calculate max number of polygons, 22 bits available to identify both tile & polygon within tile
+    unsigned tileBits = LogBaseTwo(maxTiles);
+    unsigned maxPolys = (unsigned)(1 << (22 - tileBits));
+
+    dtNavMeshParams params;
+    rcVcopy(params.orig, &boundingBox_.min_.x_);
+    params.tileWidth = tileEdgeLength;
+    params.tileHeight = tileEdgeLength;
+    params.maxTiles = maxTiles;
+    params.maxPolys = maxPolys;
+
+    navMesh_ = dtAllocNavMesh();
+    if (!navMesh_)
+    {
+        ATOMIC_LOGERROR("Could not allocate navigation mesh");
+        return false;
+    }
+
+    if (dtStatusFailed(navMesh_->init(&params)))
+    {
+        ATOMIC_LOGERROR("Could not initialize navigation mesh");
+        ReleaseNavigationMesh();
+        return false;
+    }
+
+    ATOMIC_LOGDEBUG("Allocated empty navigation mesh with max " + String(maxTiles) + " tiles");
+
+    // Send a notification event to concerned parties that we've been fully rebuilt
+    {
+        using namespace NavigationMeshRebuilt;
+        VariantMap& buildEventParams = GetContext()->GetEventDataMap();
+        buildEventParams[P_NODE] = node_;
+        buildEventParams[P_MESH] = this;
+        SendEvent(E_NAVIGATION_MESH_REBUILT, buildEventParams);
+    }
+    return true;
+}
+
 bool NavigationMesh::Build()
 {
     ATOMIC_PROFILE(BuildNavigationMesh);
@@ -369,14 +423,7 @@ bool NavigationMesh::Build()
 
         // Calculate max. number of tiles and polygons, 22 bits available to identify both tile & polygon within tile
         unsigned maxTiles = NextPowerOfTwo((unsigned)(numTilesX_ * numTilesZ_));
-        unsigned tileBits = 0;
-        unsigned temp = maxTiles;
-        while (temp > 1)
-        {
-            temp >>= 1;
-            ++tileBits;
-        }
-
+        unsigned tileBits = LogBaseTwo(maxTiles);
         unsigned maxPolys = (unsigned)(1 << (22 - tileBits));
 
         dtNavMeshParams params;
@@ -401,16 +448,7 @@ bool NavigationMesh::Build()
         }
 
         // Build each tile
-        unsigned numTiles = 0;
-
-        for (int z = 0; z < numTilesZ_; ++z)
-        {
-            for (int x = 0; x < numTilesX_; ++x)
-            {
-                if (BuildTile(geometryList, x, z))
-                    ++numTiles;
-            }
-        }
+        unsigned numTiles = BuildTiles(geometryList, IntVector2::ZERO, GetNumTiles() - IntVector2::ONE);
 
         ATOMIC_LOGDEBUG("Built navigation mesh with " + String(numTiles) + " tiles");
 
@@ -455,21 +493,120 @@ bool NavigationMesh::Build(const BoundingBox& boundingBox)
     int ex = Clamp((int)((localSpaceBox.max_.x_ - boundingBox_.min_.x_) / tileEdgeLength), 0, numTilesX_ - 1);
     int ez = Clamp((int)((localSpaceBox.max_.z_ - boundingBox_.min_.z_) / tileEdgeLength), 0, numTilesZ_ - 1);
 
-    unsigned numTiles = 0;
+    unsigned numTiles = BuildTiles(geometryList, IntVector2(sx, sz), IntVector2(ex, ez));
 
-    for (int z = sz; z <= ez; ++z)
+    ATOMIC_LOGDEBUG("Rebuilt " + String(numTiles) + " tiles of the navigation mesh");
+    return true;
+}
+
+bool NavigationMesh::Build(const IntVector2& from, const IntVector2& to)
+{
+    ATOMIC_PROFILE(BuildPartialNavigationMesh);
+
+    if (!node_)
+        return false;
+
+    if (!navMesh_)
     {
-        for (int x = sx; x <= ex; ++x)
-        {
-            if (BuildTile(geometryList, x, z))
-                ++numTiles;
-        }
+        ATOMIC_LOGERROR("Navigation mesh must first be built fully before it can be partially rebuilt");
+        return false;
     }
 
+    if (!node_->GetWorldScale().Equals(Vector3::ONE))
+        ATOMIC_LOGWARNING("Navigation mesh root node has scaling. Agent parameters may not work as intended");
+
+    Vector<NavigationGeometryInfo> geometryList;
+    CollectGeometries(geometryList);
+
+    unsigned numTiles = BuildTiles(geometryList, from, to);
+
     ATOMIC_LOGDEBUG("Rebuilt " + String(numTiles) + " tiles of the navigation mesh");
     return true;
 }
 
+PODVector<unsigned char> NavigationMesh::GetTileData(const IntVector2& tile) const
+{
+    VectorBuffer ret;
+    WriteTile(ret, tile.x_, tile.y_);
+    return ret.GetBuffer();
+}
+
+bool NavigationMesh::AddTile(const PODVector<unsigned char>& tileData)
+{
+    MemoryBuffer buffer(tileData);
+    return ReadTile(buffer, false);
+}
+
+bool NavigationMesh::HasTile(const IntVector2& tile) const
+{
+    if (navMesh_)
+        return !!navMesh_->getTileAt(tile.x_, tile.y_, 0);
+    return false;
+}
+
+BoundingBox NavigationMesh::GetTileBoudningBox(const IntVector2& tile) const
+{
+    const float tileEdgeLength = (float)tileSize_ * cellSize_;
+    return BoundingBox(
+        Vector3(
+            boundingBox_.min_.x_ + tileEdgeLength * (float)tile.x_,
+            boundingBox_.min_.y_,
+            boundingBox_.min_.z_ + tileEdgeLength * (float)tile.y_
+        ),
+        Vector3(
+            boundingBox_.min_.x_ + tileEdgeLength * (float)(tile.x_ + 1),
+            boundingBox_.max_.y_,
+            boundingBox_.min_.z_ + tileEdgeLength * (float)(tile.y_ + 1)
+        ));
+}
+
+IntVector2 NavigationMesh::GetTileIndex(const Vector3& position) const
+{
+    const float tileEdgeLength = (float)tileSize_ * cellSize_;
+    const Vector3 localPosition = node_->GetWorldTransform().Inverse() * position - boundingBox_.min_;
+    const Vector2 localPosition2D(localPosition.x_, localPosition.z_);
+    return VectorMin(VectorMax(IntVector2::ZERO, VectorFloorToInt(localPosition2D / tileEdgeLength)), GetNumTiles() - IntVector2::ONE);
+}
+
+void NavigationMesh::RemoveTile(const IntVector2& tile)
+{
+    if (!navMesh_)
+        return;
+
+    const dtTileRef tileRef = navMesh_->getTileRefAt(tile.x_, tile.y_, 0);
+    if (!tileRef)
+        return;
+
+    navMesh_->removeTile(tileRef, 0, 0);
+
+    // Send event
+    using namespace NavigationTileRemoved;
+    VariantMap& eventData = GetContext()->GetEventDataMap();
+    eventData[P_NODE] = GetNode();
+    eventData[P_MESH] = this;
+    eventData[P_TILE] = tile;
+    SendEvent(E_NAVIGATION_TILE_REMOVED, eventData);
+}
+
+void NavigationMesh::RemoveAllTiles()
+{
+    const dtNavMesh* navMesh = navMesh_;
+    for (int i = 0; i < navMesh_->getMaxTiles(); ++i)
+    {
+        const dtMeshTile* tile = navMesh->getTile(i);
+        assert(tile);
+        if (tile->header)
+            navMesh_->removeTile(navMesh_->getTileRef(tile), 0, 0);
+    }
+
+    // Send event
+    using namespace NavigationAllTilesRemoved;
+    VariantMap& eventData = GetContext()->GetEventDataMap();
+    eventData[P_NODE] = GetNode();
+    eventData[P_MESH] = this;
+    SendEvent(E_NAVIGATION_ALL_TILES_REMOVED, eventData);
+}
+
 Vector3 NavigationMesh::FindNearestPoint(const Vector3& point, const Vector3& extents, const dtQueryFilter* filter,
     dtPolyRef* nearestRef)
 {
@@ -780,30 +917,14 @@ void NavigationMesh::SetNavigationDataAttr(const PODVector<unsigned char>& value
 
     while (!buffer.IsEof())
     {
-        /*int x =*/ buffer.ReadInt();
-        /*int z =*/ buffer.ReadInt();
-        /*dtTileRef tileRef =*/ buffer.ReadUInt();
-        unsigned navDataSize = buffer.ReadUInt();
-
-        unsigned char* navData = (unsigned char*)dtAlloc(navDataSize, DT_ALLOC_PERM);
-        if (!navData)
-        {
-            ATOMIC_LOGERROR("Could not allocate data for navigation mesh tile");
-            return;
-        }
-
-        buffer.Read(navData, navDataSize);
-        if (dtStatusFailed(navMesh_->addTile(navData, navDataSize, DT_TILE_FREE_DATA, 0, 0)))
-        {
-            ATOMIC_LOGERROR("Failed to add navigation mesh tile");
-            dtFree(navData);
-            return;
-        }
-        else
+        if (ReadTile(buffer, true))
             ++numTiles;
+        else
+            return;
     }
 
     ATOMIC_LOGDEBUG("Created navigation mesh with " + String(numTiles) + " tiles from serialized data");
+    // \todo Shall we send E_NAVIGATION_MESH_REBUILT here?
 }
 
 PODVector<unsigned char> NavigationMesh::GetNavigationDataAttr() const
@@ -825,20 +946,8 @@ PODVector<unsigned char> NavigationMesh::GetNavigationDataAttr() const
         const dtNavMesh* navMesh = navMesh_;
 
         for (int z = 0; z < numTilesZ_; ++z)
-        {
             for (int x = 0; x < numTilesX_; ++x)
-            {
-                const dtMeshTile* tile = navMesh->getTileAt(x, z, 0);
-                if (!tile)
-                    continue;
-
-                ret.WriteInt(x);
-                ret.WriteInt(z);
-                ret.WriteUInt(navMesh->getTileRef(tile));
-                ret.WriteUInt((unsigned)tile->dataSize);
-                ret.Write(tile->data, (unsigned)tile->dataSize);
-            }
-        }
+                WriteTile(ret, x, z);
     }
 
     return ret.GetBuffer();
@@ -1144,6 +1253,55 @@ void NavigationMesh::AddTriMeshGeometry(NavBuildData* build, Geometry* geometry,
     }
 }
 
+void NavigationMesh::WriteTile(Serializer& dest, int x, int z) const
+{
+    const dtNavMesh* navMesh = navMesh_;
+    const dtMeshTile* tile = navMesh->getTileAt(x, z, 0);
+    if (!tile)
+        return;
+
+    dest.WriteInt(x);
+    dest.WriteInt(z);
+    dest.WriteUInt(navMesh->getTileRef(tile));
+    dest.WriteUInt((unsigned)tile->dataSize);
+    dest.Write(tile->data, (unsigned)tile->dataSize);
+}
+
+bool NavigationMesh::ReadTile(Deserializer& source, bool silent)
+{
+    const int x = source.ReadInt();
+    const int z = source.ReadInt();
+    /*dtTileRef tileRef =*/ source.ReadUInt();
+    unsigned navDataSize = source.ReadUInt();
+
+    unsigned char* navData = (unsigned char*)dtAlloc(navDataSize, DT_ALLOC_PERM);
+    if (!navData)
+    {
+        ATOMIC_LOGERROR("Could not allocate data for navigation mesh tile");
+        return false;
+    }
+
+    source.Read(navData, navDataSize);
+    if (dtStatusFailed(navMesh_->addTile(navData, navDataSize, DT_TILE_FREE_DATA, 0, 0)))
+    {
+        ATOMIC_LOGERROR("Failed to add navigation mesh tile");
+        dtFree(navData);
+        return false;
+    }
+
+    // Send event
+    if (!silent)
+    {
+        using namespace NavigationTileAdded;
+        VariantMap& eventData = GetContext()->GetEventDataMap();
+        eventData[P_NODE] = GetNode();
+        eventData[P_MESH] = this;
+        eventData[P_TILE] = IntVector2(x, z);
+        SendEvent(E_NAVIGATION_TILE_ADDED, eventData);
+    }
+    return true;
+}
+
 bool NavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryList, int x, int z)
 {
     ATOMIC_PROFILE(BuildNavigationMeshTile);
@@ -1151,18 +1309,7 @@ bool NavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryList, int
     // Remove previous tile (if any)
     navMesh_->removeTile(navMesh_->getTileRefAt(x, z, 0), 0, 0);
 
-    float tileEdgeLength = (float)tileSize_ * cellSize_;
-
-    BoundingBox tileBoundingBox(Vector3(
-            boundingBox_.min_.x_ + tileEdgeLength * (float)x,
-            boundingBox_.min_.y_,
-            boundingBox_.min_.z_ + tileEdgeLength * (float)z
-        ),
-        Vector3(
-            boundingBox_.min_.x_ + tileEdgeLength * (float)(x + 1),
-            boundingBox_.max_.y_,
-            boundingBox_.min_.z_ + tileEdgeLength * (float)(z + 1)
-        ));
+    const BoundingBox tileBoundingBox = GetTileBoudningBox(IntVector2(x, z));
 
     SimpleNavBuildData build;
 
@@ -1383,6 +1530,21 @@ bool NavigationMesh::BuildTile(Vector<NavigationGeometryInfo>& geometryList, int
     return true;
 }
 
+unsigned NavigationMesh::BuildTiles(Vector<NavigationGeometryInfo>& geometryList, const IntVector2& from, const IntVector2& to)
+{
+    unsigned numTiles = 0;
+
+    for (int z = from.y_; z <= to.y_; ++z)
+    {
+        for (int x = from.x_; x <= to.x_; ++x)
+        {
+            if (BuildTile(geometryList, x, z))
+                ++numTiles;
+        }
+    }
+    return numTiles;
+}
+
 bool NavigationMesh::InitializeQuery()
 {
     if (!navMesh_ || !node_)

+ 27 - 2
Source/Atomic/Navigation/NavigationMesh.h

@@ -134,10 +134,28 @@ public:
     void SetPadding(const Vector3& padding);
     /// Set the cost of an area.
     void SetAreaCost(unsigned areaID, float cost);
+    /// Allocate the navigation mesh without building any tiles. Bounding box is not padded. Return true if successful.
+    virtual bool Allocate(const BoundingBox& boundingBox, unsigned maxTiles);
     /// Rebuild the navigation mesh. Return true if successful.
     virtual bool Build();
     /// Rebuild part of the navigation mesh contained by the world-space bounding box. Return true if successful.
     virtual bool Build(const BoundingBox& boundingBox);
+    /// Rebuild part of the navigation mesh in the rectangular area. Return true if successful.
+    virtual bool Build(const IntVector2& from, const IntVector2& to);
+    /// Return tile data.
+    virtual PODVector<unsigned char> GetTileData(const IntVector2& tile) const;
+    /// Add tile to navigation mesh.
+    virtual bool AddTile(const PODVector<unsigned char>& tileData);
+    /// Remove tile from navigation mesh.
+    virtual void RemoveTile(const IntVector2& tile);
+    /// Remove all tiles from navigation mesh.
+    virtual void RemoveAllTiles();
+    /// Return whether the navigation mesh has tile.
+    bool HasTile(const IntVector2& tile) const;
+    /// Return bounding box of the tile in the node space.
+    BoundingBox GetTileBoudningBox(const IntVector2& tile) const;
+    /// Return index of the tile at the position.
+    IntVector2 GetTileIndex(const Vector3& position) const;
     /// Find the nearest point on the navigation mesh to a given point. Extents specifies how far out from the specified point to check along each axis.
     Vector3 FindNearestPoint
         (const Vector3& point, const Vector3& extents = Vector3::ONE, const dtQueryFilter* filter = 0, dtPolyRef* nearestRef = 0);
@@ -254,18 +272,25 @@ public:
     /// Return whether to draw NavArea components.
     bool GetDrawNavAreas() const { return drawNavAreas_; }
 
+private:
+    /// Write tile data.
+    void WriteTile(Serializer& dest, int x, int z) const;
+    /// Read tile data to the navigation mesh.
+    bool ReadTile(Deserializer& source, bool silent);
+
 protected:
     /// Collect geometry from under Navigable components.
     void CollectGeometries(Vector<NavigationGeometryInfo>& geometryList);
     /// Visit nodes and collect navigable geometry.
-    void
-        CollectGeometries(Vector<NavigationGeometryInfo>& geometryList, Node* node, HashSet<Node*>& processedNodes, bool recursive);
+    void CollectGeometries(Vector<NavigationGeometryInfo>& geometryList, Node* node, HashSet<Node*>& processedNodes, bool recursive);
     /// Get geometry data within a bounding box.
     void GetTileGeometry(NavBuildData* build, Vector<NavigationGeometryInfo>& geometryList, BoundingBox& box);
     /// Add a triangle mesh to the geometry data.
     void AddTriMeshGeometry(NavBuildData* build, Geometry* geometry, const Matrix3x4& transform);
     /// Build one tile of the navigation mesh. Return true if successful.
     virtual bool BuildTile(Vector<NavigationGeometryInfo>& geometryList, int x, int z);
+    /// Build tiles in the rectangular area. Return number of built tiles.
+    unsigned BuildTiles(Vector<NavigationGeometryInfo>& geometryList, const IntVector2& from, const IntVector2& to);
     /// Ensure that the navigation mesh query is initialized. Return true if successful.
     bool InitializeQuery();
     /// Release the navigation mesh and the query.

+ 13 - 1
Source/Atomic/Navigation/Obstacle.cpp

@@ -104,13 +104,17 @@ void Obstacle::OnSceneSet(Scene* scene)
         if (!ownerMesh_)
             ownerMesh_ = node_->GetParentComponent<DynamicNavigationMesh>(true);
         if (ownerMesh_)
+        {
             ownerMesh_->AddObstacle(this);
+            SubscribeToEvent(ownerMesh_, E_NAVIGATION_TILE_ADDED, ATOMIC_HANDLER(Obstacle, HandleNavigationTileAdded));
+        }
     }
     else
     {
         if (obstacleId_ > 0 && ownerMesh_)
             ownerMesh_->RemoveObstacle(this);
-        
+
+        UnsubscribeFromEvent(E_NAVIGATION_TILE_ADDED);
         ownerMesh_.Reset();
     }
 }
@@ -135,6 +139,14 @@ void Obstacle::OnMarkedDirty(Node* node)
     }
 }
 
+void Obstacle::HandleNavigationTileAdded(StringHash eventType, VariantMap& eventData)
+{
+    // Re-add obstacle if it is intersected with newly added tile
+    const IntVector2 tile = eventData[NavigationTileAdded::P_TILE].GetIntVector2();
+    if (IsEnabledEffective() && ownerMesh_ && ownerMesh_->IsObstacleInTile(this, tile))
+        ownerMesh_->ObstacleChanged(this);
+}
+
 void Obstacle::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 {
     if (debug && IsEnabledEffective())

+ 4 - 2
Source/Atomic/Navigation/Obstacle.h

@@ -53,13 +53,13 @@ public:
     float GetHeight() const { return height_; }
 
     /// Set the height of this obstacle.
-    void SetHeight(float height);
+    void SetHeight(float newHeight);
 
     /// Get the blocking radius of this obstacle.
     float GetRadius() const { return radius_; }
 
     /// Set the blocking radius of this obstacle.
-    void SetRadius(float radius);
+    void SetRadius(float newRadius);
 
     /// Get the internal obstacle ID.
     unsigned GetObstacleID() const { return obstacleId_; }
@@ -76,6 +76,8 @@ protected:
     virtual void OnSceneSet(Scene* scene);
     /// Handle node transform being dirtied.
     virtual void OnMarkedDirty(Node* node);
+    /// Handle navigation mesh tile added.
+    void HandleNavigationTileAdded(StringHash eventType, VariantMap& eventData);
 
 private:
     /// Radius of this obstacle.

+ 38 - 14
Source/Atomic/Physics/CollisionShape.cpp

@@ -461,9 +461,6 @@ void CollisionShape::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
 {
     if (debug && physicsWorld_ && shape_ && node_ && IsEnabledEffective())
     {
-        physicsWorld_->SetDebugRenderer(debug);
-        physicsWorld_->SetDebugDepthTest(depthTest);
-
         // Use the rigid body's world transform if possible, as it may be different from the rendering transform
         Matrix3x4 worldTransform;
         RigidBody* body = GetComponent<RigidBody>();
@@ -476,22 +473,49 @@ void CollisionShape::DrawDebugGeometry(DebugRenderer* debug, bool depthTest)
         else
             worldTransform = node_->GetWorldTransform();
 
-        Vector3 position = position_;
-        // For terrains, undo the height centering performed automatically by Bullet
-        if (shapeType_ == SHAPE_TERRAIN && geometry_)
+        // Special case code for convex hull: bypass Bullet's own rendering to draw triangles correctly, not just edges
+        if (shapeType_ == SHAPE_CONVEXHULL)
         {
-            HeightfieldData* heightfield = static_cast<HeightfieldData*>(geometry_.Get());
-            position.y_ += (heightfield->minHeight_ + heightfield->maxHeight_) * 0.5f;
+            ConvexData *convexData = static_cast<ConvexData*>(GetGeometryData());
+            RigidBody* body = GetComponent<RigidBody>();
+            Color color = bodyActive ? Color::WHITE : Color::GREEN;
+            Matrix3x4 shapeTransform(worldTransform * position_, worldTransform.Rotation() * rotation_, worldTransform.Scale());
+
+            if (convexData)
+            {
+                for (unsigned i = 0; i < convexData->indexCount_; i += 3)
+                {
+                    Vector3 a = shapeTransform * convexData->vertexData_[convexData->indexData_[i + 0]];
+                    Vector3 b = shapeTransform * convexData->vertexData_[convexData->indexData_[i + 1]];
+                    Vector3 c = shapeTransform * convexData->vertexData_[convexData->indexData_[i + 2]];
+                    debug->AddLine(a, b, color, depthTest);
+                    debug->AddLine(b, c, color, depthTest);
+                    debug->AddLine(a, c, color, depthTest);
+                }
+             }
         }
+        else
+        {
+            physicsWorld_->SetDebugRenderer(debug);
+            physicsWorld_->SetDebugDepthTest(depthTest);
 
-        Vector3 worldPosition(worldTransform * position);
-        Quaternion worldRotation(worldTransform.Rotation() * rotation_);
+            Vector3 position = position_;
+            // For terrains, undo the height centering performed automatically by Bullet
+            if (shapeType_ == SHAPE_TERRAIN && geometry_)
+            {
+                HeightfieldData* heightfield = static_cast<HeightfieldData*>(geometry_.Get());
+                position.y_ += (heightfield->minHeight_ + heightfield->maxHeight_) * 0.5f;
+            }
 
-        btDiscreteDynamicsWorld* world = physicsWorld_->GetWorld();
-        world->debugDrawObject(btTransform(ToBtQuaternion(worldRotation), ToBtVector3(worldPosition)), shape_.Get(), bodyActive ?
-            WHITE : GREEN);
+            Vector3 worldPosition(worldTransform * position);
+            Quaternion worldRotation(worldTransform.Rotation() * rotation_);
 
-        physicsWorld_->SetDebugRenderer(0);
+            btDiscreteDynamicsWorld* world = physicsWorld_->GetWorld();
+            world->debugDrawObject(btTransform(ToBtQuaternion(worldRotation), ToBtVector3(worldPosition)), shape_.Get(), bodyActive ?
+                WHITE : GREEN);
+
+            physicsWorld_->SetDebugRenderer(0);
+        }
     }
 }
 

+ 10 - 0
Source/Atomic/Physics/RigidBody.cpp

@@ -788,6 +788,8 @@ void RigidBody::UpdateMass()
             !ToQuaternion(childTransform.getRotation()).Equals(Quaternion::IDENTITY))
             useCompound = true;
     }
+
+    btCollisionShape* oldCollisionShape = body_->getCollisionShape();
     body_->setCollisionShape(useCompound ? shiftedCompoundShape_.Get() : shiftedCompoundShape_->getChildShape(0));
 
     // If we have one shape and this is a triangle mesh, we use a custom material callback in order to adjust internal edges
@@ -815,6 +817,14 @@ void RigidBody::UpdateMass()
         for (PODVector<Constraint*>::Iterator i = constraints_.Begin(); i != constraints_.End(); ++i)
             (*i)->ApplyFrames();
     }
+
+    // Readd body to world to reset Bullet collision cache if collision shape was changed (issue #2064)
+    if (inWorld_ && body_->getCollisionShape() != oldCollisionShape && physicsWorld_)
+    {
+        btDiscreteDynamicsWorld* world = physicsWorld_->GetWorld();
+        world->removeRigidBody(body_.Get());
+        world->addRigidBody(body_.Get(), (short)collisionLayer_, (short)collisionMask_);
+    }
 }
 
 void RigidBody::UpdateGravity()

+ 1 - 1
Source/Atomic/Physics/RigidBody.h

@@ -232,7 +232,7 @@ public:
 
     /// Apply new world transform after a simulation step. Called internally.
     void ApplyWorldTransform(const Vector3& newWorldPosition, const Quaternion& newWorldRotation);
-    /// Update mass and inertia to the Bullet rigid body.
+    /// Update mass and inertia to the Bullet rigid body. Readd body to world if necessary: if was in world and the Bullet collision shape to use changed.
     void UpdateMass();
     /// Update gravity parameters to the Bullet rigid body.
     void UpdateGravity();

+ 4 - 4
Source/Atomic/Resource/BackgroundLoader.cpp

@@ -282,6 +282,10 @@ void BackgroundLoader::FinishBackgroundLoading(BackgroundLoadItem& item)
         owner_->SendEvent(E_LOADFAILED, eventData);
     }
 
+    // Store to the cache just before sending the event; use same mechanism as for manual resources
+    if (success || owner_->GetReturnFailedResources())
+        owner_->AddManualResource(resource);
+
     // Send event, either success or failure
     {
         using namespace ResourceBackgroundLoaded;
@@ -292,10 +296,6 @@ void BackgroundLoader::FinishBackgroundLoading(BackgroundLoadItem& item)
         eventData[P_RESOURCE] = resource;
         owner_->SendEvent(E_RESOURCEBACKGROUNDLOADED, eventData);
     }
-
-    // Store to the cache; use same mechanism as for manual resources
-    if (success || owner_->GetReturnFailedResources())
-        owner_->AddManualResource(resource);
 }
 
 }

+ 2475 - 2308
Source/Atomic/Resource/Image.cpp

@@ -1,2308 +1,2475 @@
-//
-// Copyright (c) 2008-2017 the Urho3D project.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-//
-
-#include "../Precompiled.h"
-
-#include "../Core/Context.h"
-#include "../Core/Profiler.h"
-#include "../IO/File.h"
-#include "../IO/FileSystem.h"
-#include "../IO/Log.h"
-#include "../Resource/Decompress.h"
-
-#include <JO/jo_jpeg.h>
-
-// ATOMIC BEGIN
-#include <SDL/include/SDL_surface.h>
-
-#ifdef ATOMIC_PLATFORM_DESKTOP
-#include <libsquish/squish.h>
-#endif
-
-#include <STB/stb_image.h>
-#include <STB/stb_image_write.h>
-
-// ATOMIC END
-
-
-#include "../DebugNew.h"
-
-#ifndef MAKEFOURCC
-#define MAKEFOURCC(ch0, ch1, ch2, ch3) ((unsigned)(ch0) | ((unsigned)(ch1) << 8) | ((unsigned)(ch2) << 16) | ((unsigned)(ch3) << 24))
-#endif
-
-#define FOURCC_DXT1 (MAKEFOURCC('D','X','T','1'))
-#define FOURCC_DXT2 (MAKEFOURCC('D','X','T','2'))
-#define FOURCC_DXT3 (MAKEFOURCC('D','X','T','3'))
-#define FOURCC_DXT4 (MAKEFOURCC('D','X','T','4'))
-#define FOURCC_DXT5 (MAKEFOURCC('D','X','T','5'))
-#define FOURCC_DX10 (MAKEFOURCC('D','X','1','0'))
-
-static const unsigned DDSCAPS_COMPLEX = 0x00000008U;
-static const unsigned DDSCAPS_TEXTURE = 0x00001000U;
-static const unsigned DDSCAPS_MIPMAP = 0x00400000U;
-static const unsigned DDSCAPS2_VOLUME = 0x00200000U;
-static const unsigned DDSCAPS2_CUBEMAP = 0x00000200U;
-
-static const unsigned DDSCAPS2_CUBEMAP_POSITIVEX = 0x00000400U;
-static const unsigned DDSCAPS2_CUBEMAP_NEGATIVEX = 0x00000800U;
-static const unsigned DDSCAPS2_CUBEMAP_POSITIVEY = 0x00001000U;
-static const unsigned DDSCAPS2_CUBEMAP_NEGATIVEY = 0x00002000U;
-static const unsigned DDSCAPS2_CUBEMAP_POSITIVEZ = 0x00004000U;
-static const unsigned DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x00008000U;
-static const unsigned DDSCAPS2_CUBEMAP_ALL_FACES = 0x0000FC00U;
-
-// DX10 flags
-static const unsigned DDS_DIMENSION_TEXTURE1D = 2;
-static const unsigned DDS_DIMENSION_TEXTURE2D = 3;
-static const unsigned DDS_DIMENSION_TEXTURE3D = 4;
-
-static const unsigned DDS_RESOURCE_MISC_TEXTURECUBE = 0x4;
-
-static const unsigned DDS_DXGI_FORMAT_R8G8B8A8_UNORM = 28;
-static const unsigned DDS_DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 26;
-static const unsigned DDS_DXGI_FORMAT_BC1_UNORM = 71;
-static const unsigned DDS_DXGI_FORMAT_BC1_UNORM_SRGB = 72;
-static const unsigned DDS_DXGI_FORMAT_BC2_UNORM = 74;
-static const unsigned DDS_DXGI_FORMAT_BC2_UNORM_SRGB = 75;
-static const unsigned DDS_DXGI_FORMAT_BC3_UNORM = 77;
-static const unsigned DDS_DXGI_FORMAT_BC3_UNORM_SRGB = 78;
-
-// ATOMIC BEGIN
-static const unsigned DDSD_CAPS = 0x00000001;
-static const unsigned DDSD_HEIGHT = 0x00000002;
-static const unsigned DDSD_WIDTH = 0x00000004;
-static const unsigned DDSD_PITCH = 0x00000008;
-static const unsigned DDSD_PIXELFORMAT = 0x00001000;
-static const unsigned DDSD_MIPMAPCOUNT = 0x00020000;
-static const unsigned DDSD_LINEARSIZE = 0x00080000;
-static const unsigned DDSD_DEPTH = 0x00800000;
-
-static const unsigned DDPF_ALPHAPIXELS = 0x00000001;
-static const unsigned DDPF_ALPHA = 0x00000002;
-static const unsigned DDPF_FOURCC = 0x00000004;
-static const unsigned DDPF_PALETTEINDEXED8 = 0x00000020;
-static const unsigned DDPF_RGB = 0x00000040;
-static const unsigned DDPF_LUMINANCE = 0x00020000;
-static const unsigned DDPF_NORMAL = 0x80000000;  // nvidia specific
-// ATOMIC END
-
-
-namespace Atomic
-{
-
-/// DirectDraw color key definition.
-struct DDColorKey
-{
-    unsigned dwColorSpaceLowValue_;
-    unsigned dwColorSpaceHighValue_;
-};
-
-/// DirectDraw pixel format definition.
-struct DDPixelFormat
-{
-    unsigned dwSize_;
-    unsigned dwFlags_;
-    unsigned dwFourCC_;
-    union
-    {
-        unsigned dwRGBBitCount_;
-        unsigned dwYUVBitCount_;
-        unsigned dwZBufferBitDepth_;
-        unsigned dwAlphaBitDepth_;
-        unsigned dwLuminanceBitCount_;
-        unsigned dwBumpBitCount_;
-        unsigned dwPrivateFormatBitCount_;
-    };
-    union
-    {
-        unsigned dwRBitMask_;
-        unsigned dwYBitMask_;
-        unsigned dwStencilBitDepth_;
-        unsigned dwLuminanceBitMask_;
-        unsigned dwBumpDuBitMask_;
-        unsigned dwOperations_;
-    };
-    union
-    {
-        unsigned dwGBitMask_;
-        unsigned dwUBitMask_;
-        unsigned dwZBitMask_;
-        unsigned dwBumpDvBitMask_;
-        struct
-        {
-            unsigned short wFlipMSTypes_;
-            unsigned short wBltMSTypes_;
-        } multiSampleCaps_;
-    };
-    union
-    {
-        unsigned dwBBitMask_;
-        unsigned dwVBitMask_;
-        unsigned dwStencilBitMask_;
-        unsigned dwBumpLuminanceBitMask_;
-    };
-    union
-    {
-        unsigned dwRGBAlphaBitMask_;
-        unsigned dwYUVAlphaBitMask_;
-        unsigned dwLuminanceAlphaBitMask_;
-        unsigned dwRGBZBitMask_;
-        unsigned dwYUVZBitMask_;
-    };
-};
-
-/// DirectDraw surface capabilities.
-struct DDSCaps2
-{
-    unsigned dwCaps_;
-    unsigned dwCaps2_;
-    unsigned dwCaps3_;
-    union
-    {
-        unsigned dwCaps4_;
-        unsigned dwVolumeDepth_;
-    };
-};
-
-struct DDSHeader10
-{
-    unsigned dxgiFormat;
-    unsigned resourceDimension;
-    unsigned miscFlag;
-    unsigned arraySize;
-    unsigned reserved;
-};
-
-/// DirectDraw surface description.
-struct DDSurfaceDesc2
-{
-    unsigned dwSize_;
-    unsigned dwFlags_;
-    unsigned dwHeight_;
-    unsigned dwWidth_;
-    union
-    {
-        unsigned lPitch_;
-        unsigned dwLinearSize_;
-    };
-    union
-    {
-        unsigned dwBackBufferCount_;
-        unsigned dwDepth_;
-    };
-    union
-    {
-        unsigned dwMipMapCount_;
-        unsigned dwRefreshRate_;
-        unsigned dwSrcVBHandle_;
-    };
-    unsigned dwAlphaBitDepth_;
-    unsigned dwReserved_;
-    unsigned lpSurface_; // Do not define as a void pointer, as it is 8 bytes in a 64bit build
-    union
-    {
-        DDColorKey ddckCKDestOverlay_;
-        unsigned dwEmptyFaceColor_;
-    };
-    DDColorKey ddckCKDestBlt_;
-    DDColorKey ddckCKSrcOverlay_;
-    DDColorKey ddckCKSrcBlt_;
-    union
-    {
-        DDPixelFormat ddpfPixelFormat_;
-        unsigned dwFVF_;
-    };
-    DDSCaps2 ddsCaps_;
-    unsigned dwTextureStage_;
-};
-
-bool CompressedLevel::Decompress(unsigned char* dest)
-{
-    if (!data_)
-        return false;
-
-    switch (format_)
-    {
-    case CF_DXT1:
-    case CF_DXT3:
-    case CF_DXT5:
-        DecompressImageDXT(dest, data_, width_, height_, depth_, format_);
-        return true;
-
-    case CF_ETC1:
-        DecompressImageETC(dest, data_, width_, height_);
-        return true;
-
-    case CF_PVRTC_RGB_2BPP:
-    case CF_PVRTC_RGBA_2BPP:
-    case CF_PVRTC_RGB_4BPP:
-    case CF_PVRTC_RGBA_4BPP:
-        DecompressImagePVRTC(dest, data_, width_, height_, format_);
-        return true;
-
-    default:
-        // Unknown format
-        return false;
-    }
-}
-
-Image::Image(Context* context) :
-    Resource(context),
-    width_(0),
-    height_(0),
-    depth_(0),
-    components_(0),
-    numCompressedLevels_(0),
-    cubemap_(false),
-    array_(false),
-    sRGB_(false),
-    compressedFormat_(CF_NONE)
-{
-}
-
-Image::~Image()
-{
-}
-
-void Image::RegisterObject(Context* context)
-{
-    context->RegisterFactory<Image>();
-}
-
-bool Image::BeginLoad(Deserializer& source)
-{
-    // Check for DDS, KTX or PVR compressed format
-    String fileID = source.ReadFileID();
-
-    if (fileID == "DDS ")
-    {
-        // DDS compressed format
-        DDSurfaceDesc2 ddsd;
-        source.Read(&ddsd, sizeof(ddsd));
-
-        // DDS DX10+
-        const bool hasDXGI = ddsd.ddpfPixelFormat_.dwFourCC_ == FOURCC_DX10;
-        DDSHeader10 dxgiHeader;
-        if (hasDXGI)
-            source.Read(&dxgiHeader, sizeof(dxgiHeader));
-
-        unsigned fourCC = ddsd.ddpfPixelFormat_.dwFourCC_;
-
-        // If the DXGI header is available then remap formats and check sRGB
-        if (hasDXGI)
-        {
-            switch (dxgiHeader.dxgiFormat)
-            {
-            case DDS_DXGI_FORMAT_BC1_UNORM:
-            case DDS_DXGI_FORMAT_BC1_UNORM_SRGB:
-                fourCC = FOURCC_DXT1;
-                break;
-            case DDS_DXGI_FORMAT_BC2_UNORM:
-            case DDS_DXGI_FORMAT_BC2_UNORM_SRGB:
-                fourCC = FOURCC_DXT3;
-                break;
-            case DDS_DXGI_FORMAT_BC3_UNORM:
-            case DDS_DXGI_FORMAT_BC3_UNORM_SRGB:
-                fourCC = FOURCC_DXT5;
-                break;
-            case DDS_DXGI_FORMAT_R8G8B8A8_UNORM:
-            case DDS_DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
-                fourCC = 0;
-                break;
-            default:
-                ATOMIC_LOGERROR("Unrecognized DDS DXGI image format");
-                return false;
-            }
-
-            // Check the internal sRGB formats
-            if (dxgiHeader.dxgiFormat == DDS_DXGI_FORMAT_BC1_UNORM_SRGB ||
-                dxgiHeader.dxgiFormat == DDS_DXGI_FORMAT_BC2_UNORM_SRGB ||
-                dxgiHeader.dxgiFormat == DDS_DXGI_FORMAT_BC3_UNORM_SRGB ||
-                dxgiHeader.dxgiFormat == DDS_DXGI_FORMAT_R8G8B8A8_UNORM_SRGB)
-            {
-                sRGB_ = true;
-            }
-        }
-        switch (fourCC)
-        {
-        case FOURCC_DXT1:
-            compressedFormat_ = CF_DXT1;
-            components_ = 3;
-            break;
-
-        case FOURCC_DXT3:
-            compressedFormat_ = CF_DXT3;
-            components_ = 4;
-            break;
-
-        case FOURCC_DXT5:
-            compressedFormat_ = CF_DXT5;
-            components_ = 4;
-            break;
-
-        case 0:
-            if (ddsd.ddpfPixelFormat_.dwRGBBitCount_ != 32 && ddsd.ddpfPixelFormat_.dwRGBBitCount_ != 24 &&
-                ddsd.ddpfPixelFormat_.dwRGBBitCount_ != 16)
-            {
-                ATOMIC_LOGERROR("Unsupported DDS pixel byte size");
-                return false;
-            }
-            compressedFormat_ = CF_RGBA;
-            components_ = 4;
-            break;
-
-        default:
-            ATOMIC_LOGERROR("Unrecognized DDS image format");
-            return false;
-        }
-
-        // Is it a cube map or texture array? If so determine the size of the image chain.
-        cubemap_ = (ddsd.ddsCaps_.dwCaps2_ & DDSCAPS2_CUBEMAP_ALL_FACES) != 0 || (hasDXGI && (dxgiHeader.miscFlag & DDS_RESOURCE_MISC_TEXTURECUBE) != 0);
-        unsigned imageChainCount = 1;
-        if (cubemap_)
-            imageChainCount = 6;
-        else if (hasDXGI && dxgiHeader.arraySize > 1)
-        {
-            imageChainCount = dxgiHeader.arraySize;
-            array_ = true;
-        }
-
-        // Calculate the size of the data
-        unsigned dataSize = 0;
-        if (compressedFormat_ != CF_RGBA)
-        {
-            const unsigned blockSize = compressedFormat_ == CF_DXT1 ? 8 : 16; //DXT1/BC1 is 8 bytes, DXT3/BC2 and DXT5/BC3 are 16 bytes
-            // Add 3 to ensure valid block: ie 2x2 fits uses a whole 4x4 block
-            unsigned blocksWide = (ddsd.dwWidth_ + 3) / 4;
-            unsigned blocksHeight = (ddsd.dwHeight_ + 3) / 4;
-            dataSize = blocksWide * blocksHeight * blockSize;
-
-            // Calculate mip data size
-            unsigned x = ddsd.dwWidth_ / 2;
-            unsigned y = ddsd.dwHeight_ / 2;
-            unsigned z = ddsd.dwDepth_ / 2;
-            for (unsigned level = ddsd.dwMipMapCount_; level > 1; x /= 2, y /= 2, z /= 2, --level)
-            {
-                blocksWide = (Max(x, 1U) + 3) / 4;
-                blocksHeight = (Max(y, 1U) + 3) / 4;
-                dataSize += blockSize * blocksWide * blocksHeight * Max(z, 1U);
-            }
-        }
-        else
-        {
-            dataSize = (ddsd.ddpfPixelFormat_.dwRGBBitCount_ / 8) * ddsd.dwWidth_ * ddsd.dwHeight_ * Max(ddsd.dwDepth_, 1U);
-            // Calculate mip data size
-            unsigned x = ddsd.dwWidth_ / 2;
-            unsigned y = ddsd.dwHeight_ / 2;
-            unsigned z = ddsd.dwDepth_ / 2;
-            for (unsigned level = ddsd.dwMipMapCount_; level > 1; x /= 2, y /= 2, z /= 2, --level)
-                dataSize += (ddsd.ddpfPixelFormat_.dwRGBBitCount_ / 8) * Max(x, 1U) * Max(y, 1U) * Max(z, 1U);
-        }
-
-        // Do not use a shared ptr here, in case nothing is refcounting the image outside this function.
-        // A raw pointer is fine as the image chain (if needed) uses shared ptr's properly
-        Image* currentImage = this;
-
-        for (unsigned faceIndex = 0; faceIndex < imageChainCount; ++faceIndex)
-        {
-            currentImage->data_ = new unsigned char[dataSize];
-            currentImage->cubemap_ = cubemap_;
-            currentImage->array_ = array_;
-            currentImage->components_ = components_;
-            currentImage->compressedFormat_ = compressedFormat_;
-            currentImage->width_ = ddsd.dwWidth_;
-            currentImage->height_ = ddsd.dwHeight_;
-            currentImage->depth_ = ddsd.dwDepth_;
-
-            currentImage->numCompressedLevels_ = ddsd.dwMipMapCount_;
-            if (!currentImage->numCompressedLevels_)
-                currentImage->numCompressedLevels_ = 1;
-
-            // Memory use needs to be exact per image as it's used for verifying the data size in GetCompressedLevel()
-            // even though it would be more proper for the first image to report the size of all siblings combined
-            currentImage->SetMemoryUse(dataSize);
-
-            source.Read(currentImage->data_.Get(), dataSize);
-
-            if (faceIndex < imageChainCount - 1)
-            {
-                // Build the image chain
-                SharedPtr<Image> nextImage(new Image(context_));
-                currentImage->nextSibling_ = nextImage;
-                currentImage = nextImage;
-            }
-        }
-
-        // If uncompressed DDS, convert the data to 8bit RGBA as the texture classes can not currently use eg. RGB565 format
-        if (compressedFormat_ == CF_RGBA)
-        {
-            ATOMIC_PROFILE(ConvertDDSToRGBA);
-
-            currentImage = this;
-
-            while (currentImage)
-            {
-                unsigned sourcePixelByteSize = ddsd.ddpfPixelFormat_.dwRGBBitCount_ >> 3;
-                unsigned numPixels = dataSize / sourcePixelByteSize;
-
-#define ADJUSTSHIFT(mask, l, r) \
-                if (mask && mask >= 0x100) \
-                { \
-                    while ((mask >> r) >= 0x100) \
-                    ++r; \
-                } \
-                else if (mask && mask < 0x80) \
-                { \
-                    while ((mask << l) < 0x80) \
-                    ++l; \
-                }
-
-                unsigned rShiftL = 0, gShiftL = 0, bShiftL = 0, aShiftL = 0;
-                unsigned rShiftR = 0, gShiftR = 0, bShiftR = 0, aShiftR = 0;
-                unsigned rMask = ddsd.ddpfPixelFormat_.dwRBitMask_;
-                unsigned gMask = ddsd.ddpfPixelFormat_.dwGBitMask_;
-                unsigned bMask = ddsd.ddpfPixelFormat_.dwBBitMask_;
-                unsigned aMask = ddsd.ddpfPixelFormat_.dwRGBAlphaBitMask_;
-                ADJUSTSHIFT(rMask, rShiftL, rShiftR)
-                ADJUSTSHIFT(gMask, gShiftL, gShiftR)
-                ADJUSTSHIFT(bMask, bShiftL, bShiftR)
-                ADJUSTSHIFT(aMask, aShiftL, aShiftR)
-
-                SharedArrayPtr<unsigned char> rgbaData(new unsigned char[numPixels * 4]);
-
-                switch (sourcePixelByteSize)
-                {
-                case 4:
-                {
-                    unsigned* src = (unsigned*)currentImage->data_.Get();
-                    unsigned char* dest = rgbaData.Get();
-
-                    while (numPixels--)
-                    {
-                        unsigned pixels = *src++;
-                        *dest++ = ((pixels & rMask) << rShiftL) >> rShiftR;
-                        *dest++ = ((pixels & gMask) << gShiftL) >> gShiftR;
-                        *dest++ = ((pixels & bMask) << bShiftL) >> bShiftR;
-                        *dest++ = ((pixels & aMask) << aShiftL) >> aShiftR;
-                    }
-                }
-                break;
-
-                case 3:
-                {
-                    unsigned char* src = currentImage->data_.Get();
-                    unsigned char* dest = rgbaData.Get();
-
-                    while (numPixels--)
-                    {
-                        unsigned pixels = src[0] | (src[1] << 8) | (src[2] << 16);
-                        src += 3;
-                        *dest++ = ((pixels & rMask) << rShiftL) >> rShiftR;
-                        *dest++ = ((pixels & gMask) << gShiftL) >> gShiftR;
-                        *dest++ = ((pixels & bMask) << bShiftL) >> bShiftR;
-                        *dest++ = ((pixels & aMask) << aShiftL) >> aShiftR;
-                    }
-                }
-                break;
-
-                default:
-                {
-                    unsigned short* src = (unsigned short*)currentImage->data_.Get();
-                    unsigned char* dest = rgbaData.Get();
-
-                    while (numPixels--)
-                    {
-                        unsigned short pixels = *src++;
-                        *dest++ = ((pixels & rMask) << rShiftL) >> rShiftR;
-                        *dest++ = ((pixels & gMask) << gShiftL) >> gShiftR;
-                        *dest++ = ((pixels & bMask) << bShiftL) >> bShiftR;
-                        *dest++ = ((pixels & aMask) << aShiftL) >> aShiftR;
-                    }
-                }
-                break;
-                }
-
-                // Replace with converted data
-                currentImage->data_ = rgbaData;
-                currentImage->SetMemoryUse(numPixels * 4);
-                currentImage = currentImage->GetNextSibling();
-            }
-        }
-    }
-    else if (fileID == "\253KTX")
-    {
-        source.Seek(12);
-
-        unsigned endianness = source.ReadUInt();
-        unsigned type = source.ReadUInt();
-        /* unsigned typeSize = */ source.ReadUInt();
-        unsigned format = source.ReadUInt();
-        unsigned internalFormat = source.ReadUInt();
-        /* unsigned baseInternalFormat = */ source.ReadUInt();
-        unsigned width = source.ReadUInt();
-        unsigned height = source.ReadUInt();
-        unsigned depth = source.ReadUInt();
-        /* unsigned arrayElements = */ source.ReadUInt();
-        unsigned faces = source.ReadUInt();
-        unsigned mipmaps = source.ReadUInt();
-        unsigned keyValueBytes = source.ReadUInt();
-
-        if (endianness != 0x04030201)
-        {
-            ATOMIC_LOGERROR("Big-endian KTX files not supported");
-            return false;
-        }
-
-        if (type != 0 || format != 0)
-        {
-            ATOMIC_LOGERROR("Uncompressed KTX files not supported");
-            return false;
-        }
-
-        if (faces > 1 || depth > 1)
-        {
-            ATOMIC_LOGERROR("3D or cube KTX files not supported");
-            return false;
-        }
-
-        if (mipmaps == 0)
-        {
-            ATOMIC_LOGERROR("KTX files without explicitly specified mipmap count not supported");
-            return false;
-        }
-
-        switch (internalFormat)
-        {
-        case 0x83f1:
-            compressedFormat_ = CF_DXT1;
-            components_ = 4;
-            break;
-
-        case 0x83f2:
-            compressedFormat_ = CF_DXT3;
-            components_ = 4;
-            break;
-
-        case 0x83f3:
-            compressedFormat_ = CF_DXT5;
-            components_ = 4;
-            break;
-
-        case 0x8d64:
-            compressedFormat_ = CF_ETC1;
-            components_ = 3;
-            break;
-
-        case 0x8c00:
-            compressedFormat_ = CF_PVRTC_RGB_4BPP;
-            components_ = 3;
-            break;
-
-        case 0x8c01:
-            compressedFormat_ = CF_PVRTC_RGB_2BPP;
-            components_ = 3;
-            break;
-
-        case 0x8c02:
-            compressedFormat_ = CF_PVRTC_RGBA_4BPP;
-            components_ = 4;
-            break;
-
-        case 0x8c03:
-            compressedFormat_ = CF_PVRTC_RGBA_2BPP;
-            components_ = 4;
-            break;
-
-        default:
-            compressedFormat_ = CF_NONE;
-            break;
-        }
-
-        if (compressedFormat_ == CF_NONE)
-        {
-            ATOMIC_LOGERROR("Unsupported texture format in KTX file");
-            return false;
-        }
-
-        source.Seek(source.GetPosition() + keyValueBytes);
-        unsigned dataSize = (unsigned)(source.GetSize() - source.GetPosition() - mipmaps * sizeof(unsigned));
-
-        data_ = new unsigned char[dataSize];
-        width_ = width;
-        height_ = height;
-        numCompressedLevels_ = mipmaps;
-
-        unsigned dataOffset = 0;
-        for (unsigned i = 0; i < mipmaps; ++i)
-        {
-            unsigned levelSize = source.ReadUInt();
-            if (levelSize + dataOffset > dataSize)
-            {
-                ATOMIC_LOGERROR("KTX mipmap level data size exceeds file size");
-                return false;
-            }
-
-            source.Read(&data_[dataOffset], levelSize);
-            dataOffset += levelSize;
-            if (source.GetPosition() & 3)
-                source.Seek((source.GetPosition() + 3) & 0xfffffffc);
-        }
-
-        SetMemoryUse(dataSize);
-    }
-    else if (fileID == "PVR\3")
-    {
-        /* unsigned flags = */ source.ReadUInt();
-        unsigned pixelFormatLo = source.ReadUInt();
-        /* unsigned pixelFormatHi = */ source.ReadUInt();
-        /* unsigned colourSpace = */ source.ReadUInt();
-        /* unsigned channelType = */ source.ReadUInt();
-        unsigned height = source.ReadUInt();
-        unsigned width = source.ReadUInt();
-        unsigned depth = source.ReadUInt();
-        /* unsigned numSurfaces = */ source.ReadUInt();
-        unsigned numFaces = source.ReadUInt();
-        unsigned mipmapCount = source.ReadUInt();
-        unsigned metaDataSize = source.ReadUInt();
-
-        if (depth > 1 || numFaces > 1)
-        {
-            ATOMIC_LOGERROR("3D or cube PVR files not supported");
-            return false;
-        }
-
-        if (mipmapCount == 0)
-        {
-            ATOMIC_LOGERROR("PVR files without explicitly specified mipmap count not supported");
-            return false;
-        }
-
-        switch (pixelFormatLo)
-        {
-        case 0:
-            compressedFormat_ = CF_PVRTC_RGB_2BPP;
-            components_ = 3;
-            break;
-
-        case 1:
-            compressedFormat_ = CF_PVRTC_RGBA_2BPP;
-            components_ = 4;
-            break;
-
-        case 2:
-            compressedFormat_ = CF_PVRTC_RGB_4BPP;
-            components_ = 3;
-            break;
-
-        case 3:
-            compressedFormat_ = CF_PVRTC_RGBA_4BPP;
-            components_ = 4;
-            break;
-
-        case 6:
-            compressedFormat_ = CF_ETC1;
-            components_ = 3;
-            break;
-
-        case 7:
-            compressedFormat_ = CF_DXT1;
-            components_ = 4;
-            break;
-
-        case 9:
-            compressedFormat_ = CF_DXT3;
-            components_ = 4;
-            break;
-
-        case 11:
-            compressedFormat_ = CF_DXT5;
-            components_ = 4;
-            break;
-
-        default:
-            compressedFormat_ = CF_NONE;
-            break;
-        }
-
-        if (compressedFormat_ == CF_NONE)
-        {
-            ATOMIC_LOGERROR("Unsupported texture format in PVR file");
-            return false;
-        }
-
-        source.Seek(source.GetPosition() + metaDataSize);
-        unsigned dataSize = source.GetSize() - source.GetPosition();
-
-        data_ = new unsigned char[dataSize];
-        width_ = width;
-        height_ = height;
-        numCompressedLevels_ = mipmapCount;
-
-        source.Read(data_.Get(), dataSize);
-        SetMemoryUse(dataSize);
-    }
-    else
-    {
-        // Not DDS, KTX or PVR, use STBImage to load other image formats as uncompressed
-        source.Seek(0);
-        int width, height;
-        unsigned components;
-        unsigned char* pixelData = GetImageData(source, width, height, components);
-        if (!pixelData)
-        {
-            ATOMIC_LOGERROR("Could not load image " + source.GetName() + ": " + String(stbi_failure_reason()));
-            return false;
-        }
-        SetSize(width, height, components);
-        SetData(pixelData);
-        FreeImageData(pixelData);
-    }
-
-    return true;
-}
-
-bool Image::Save(Serializer& dest) const
-{
-    ATOMIC_PROFILE(SaveImage);
-
-    if (IsCompressed())
-    {
-        ATOMIC_LOGERROR("Can not save compressed image " + GetName());
-        return false;
-    }
-
-    if (!data_)
-    {
-        ATOMIC_LOGERROR("Can not save zero-sized image " + GetName());
-        return false;
-    }
-
-    int len;
-    unsigned char* png = stbi_write_png_to_mem(data_.Get(), 0, width_, height_, components_, &len);
-    bool success = dest.Write(png, (unsigned)len) == (unsigned)len;
-    free(png);
-    return success;
-}
-
-bool Image::SaveFile(const String& fileName) const
-{
-    if (fileName.EndsWith(".dds", false))
-        return SaveDDS(fileName);
-    else if (fileName.EndsWith(".bmp", false))
-        return SaveBMP(fileName);
-    else if (fileName.EndsWith(".jpg", false) || fileName.EndsWith(".jpeg", false))
-        return SaveJPG(fileName, 100);
-    else if (fileName.EndsWith(".tga", false))
-        return SaveTGA(fileName);
-    else
-        return SavePNG(fileName);
-}
-
-bool Image::SetSize(int width, int height, unsigned components)
-{
-    return SetSize(width, height, 1, components);
-}
-
-bool Image::SetSize(int width, int height, int depth, unsigned components)
-{
-    if (width == width_ && height == height_ && depth == depth_ && components == components_)
-        return true;
-
-    if (width <= 0 || height <= 0 || depth <= 0)
-        return false;
-
-    if (components > 4)
-    {
-        ATOMIC_LOGERROR("More than 4 color components are not supported");
-        return false;
-    }
-
-    data_ = new unsigned char[width * height * depth * components];
-    width_ = width;
-    height_ = height;
-    depth_ = depth;
-    components_ = components;
-    compressedFormat_ = CF_NONE;
-    numCompressedLevels_ = 0;
-    nextLevel_.Reset();
-
-    SetMemoryUse(width * height * depth * components);
-    return true;
-}
-
-void Image::SetPixel(int x, int y, const Color& color)
-{
-    SetPixelInt(x, y, 0, color.ToUInt());
-}
-
-void Image::SetPixel(int x, int y, int z, const Color& color)
-{
-    SetPixelInt(x, y, z, color.ToUInt());
-}
-
-void Image::SetPixelInt(int x, int y, unsigned uintColor)
-{
-    SetPixelInt(x, y, 0, uintColor);
-}
-
-void Image::SetPixelInt(int x, int y, int z, unsigned uintColor)
-{
-    if (!data_ || x < 0 || x >= width_ || y < 0 || y >= height_ || z < 0 || z >= depth_ || IsCompressed())
-        return;
-
-    unsigned char* dest = data_ + (z * width_ * height_ + y * width_ + x) * components_;
-    unsigned char* src = (unsigned char*)&uintColor;
-
-    switch (components_)
-    {
-    case 4:
-        dest[3] = src[3];
-        // Fall through
-    case 3:
-        dest[2] = src[2];
-        // Fall through
-    case 2:
-        dest[1] = src[1];
-        // Fall through
-    default:
-        dest[0] = src[0];
-        break;
-    }
-}
-
-void Image::SetData(const unsigned char* pixelData)
-{
-    if (!data_)
-        return;
-
-    if (IsCompressed())
-    {
-        ATOMIC_LOGERROR("Can not set new pixel data for a compressed image");
-        return;
-    }
-
-    memcpy(data_.Get(), pixelData, width_ * height_ * depth_ * components_);
-    nextLevel_.Reset();
-}
-
-bool Image::LoadColorLUT(Deserializer& source)
-{
-    String fileID = source.ReadFileID();
-
-    if (fileID == "DDS " || fileID == "\253KTX" || fileID == "PVR\3")
-    {
-        ATOMIC_LOGERROR("Invalid image format, can not load image");
-        return false;
-    }
-
-    source.Seek(0);
-    int width, height;
-    unsigned components;
-    unsigned char* pixelDataIn = GetImageData(source, width, height, components);
-    if (!pixelDataIn)
-    {
-        ATOMIC_LOGERROR("Could not load image " + source.GetName() + ": " + String(stbi_failure_reason()));
-        return false;
-    }
-    if (components != 3)
-    {
-        ATOMIC_LOGERROR("Invalid image format, can not load image");
-        return false;
-    }
-
-    SetSize(COLOR_LUT_SIZE, COLOR_LUT_SIZE, COLOR_LUT_SIZE, components);
-    SetMemoryUse(width_ * height_ * depth_ * components);
-
-    unsigned char* pixelDataOut = GetData();
-
-    for (int z = 0; z < depth_; ++z)
-    {
-        for (int y = 0; y < height_; ++y)
-        {
-            unsigned char* in = &pixelDataIn[z * width_ * 3 + y * width * 3];
-            unsigned char* out = &pixelDataOut[z * width_ * height_ * 3 + y * width_ * 3];
-
-            for (int x = 0; x < width_ * 3; x += 3)
-            {
-                out[x] = in[x];
-                out[x + 1] = in[x + 1];
-                out[x + 2] = in[x + 2];
-            }
-        }
-    }
-
-    FreeImageData(pixelDataIn);
-
-    return true;
-}
-
-bool Image::FlipHorizontal()
-{
-    if (!data_)
-        return false;
-
-    if (depth_ > 1)
-    {
-        ATOMIC_LOGERROR("FlipHorizontal not supported for 3D images");
-        return false;
-    }
-
-    if (!IsCompressed())
-    {
-        SharedArrayPtr<unsigned char> newData(new unsigned char[width_ * height_ * components_]);
-        unsigned rowSize = width_ * components_;
-
-        for (int y = 0; y < height_; ++y)
-        {
-            for (int x = 0; x < width_; ++x)
-            {
-                for (unsigned c = 0; c < components_; ++c)
-                    newData[y * rowSize + x * components_ + c] = data_[y * rowSize + (width_ - x - 1) * components_ + c];
-            }
-        }
-
-        data_ = newData;
-    }
-    else
-    {
-        if (compressedFormat_ > CF_DXT5)
-        {
-            ATOMIC_LOGERROR("FlipHorizontal not yet implemented for other compressed formats than RGBA & DXT1,3,5");
-            return false;
-        }
-
-        // Memory use = combined size of the compressed mip levels
-        SharedArrayPtr<unsigned char> newData(new unsigned char[GetMemoryUse()]);
-        unsigned dataOffset = 0;
-
-        for (unsigned i = 0; i < numCompressedLevels_; ++i)
-        {
-            CompressedLevel level = GetCompressedLevel(i);
-            if (!level.data_)
-            {
-                ATOMIC_LOGERROR("Got compressed level with no data, aborting horizontal flip");
-                return false;
-            }
-
-            for (unsigned y = 0; y < level.rows_; ++y)
-            {
-                for (unsigned x = 0; x < level.rowSize_; x += level.blockSize_)
-                {
-                    unsigned char* src = level.data_ + y * level.rowSize_ + (level.rowSize_ - level.blockSize_ - x);
-                    unsigned char* dest = newData.Get() + y * level.rowSize_ + x;
-                    FlipBlockHorizontal(dest, src, compressedFormat_);
-                }
-            }
-
-            dataOffset += level.dataSize_;
-        }
-
-        data_ = newData;
-    }
-
-    return true;
-}
-
-bool Image::FlipVertical()
-{
-    if (!data_)
-        return false;
-
-    if (depth_ > 1)
-    {
-        ATOMIC_LOGERROR("FlipVertical not supported for 3D images");
-        return false;
-    }
-
-    if (!IsCompressed())
-    {
-        SharedArrayPtr<unsigned char> newData(new unsigned char[width_ * height_ * components_]);
-        unsigned rowSize = width_ * components_;
-
-        for (int y = 0; y < height_; ++y)
-            memcpy(&newData[(height_ - y - 1) * rowSize], &data_[y * rowSize], rowSize);
-
-        data_ = newData;
-    }
-    else
-    {
-        if (compressedFormat_ > CF_DXT5)
-        {
-            ATOMIC_LOGERROR("FlipVertical not yet implemented for other compressed formats than DXT1,3,5");
-            return false;
-        }
-
-        // Memory use = combined size of the compressed mip levels
-        SharedArrayPtr<unsigned char> newData(new unsigned char[GetMemoryUse()]);
-        unsigned dataOffset = 0;
-
-        for (unsigned i = 0; i < numCompressedLevels_; ++i)
-        {
-            CompressedLevel level = GetCompressedLevel(i);
-            if (!level.data_)
-            {
-                ATOMIC_LOGERROR("Got compressed level with no data, aborting vertical flip");
-                return false;
-            }
-
-            for (unsigned y = 0; y < level.rows_; ++y)
-            {
-                unsigned char* src = level.data_ + y * level.rowSize_;
-                unsigned char* dest = newData.Get() + dataOffset + (level.rows_ - y - 1) * level.rowSize_;
-
-                for (unsigned x = 0; x < level.rowSize_; x += level.blockSize_)
-                    FlipBlockVertical(dest + x, src + x, compressedFormat_);
-            }
-
-            dataOffset += level.dataSize_;
-        }
-
-        data_ = newData;
-    }
-
-    return true;
-}
-
-bool Image::Resize(int width, int height)
-{
-    ATOMIC_PROFILE(ResizeImage);
-
-    if (IsCompressed())
-    {
-        ATOMIC_LOGERROR("Resize not supported for compressed images");
-        return false;
-    }
-
-    if (depth_ > 1)
-    {
-        ATOMIC_LOGERROR("Resize not supported for 3D images");
-        return false;
-    }
-
-    if (!data_ || width <= 0 || height <= 0)
-        return false;
-
-    /// \todo Reducing image size does not sample all needed pixels
-    SharedArrayPtr<unsigned char> newData(new unsigned char[width * height * components_]);
-    for (int y = 0; y < height; ++y)
-    {
-        for (int x = 0; x < width; ++x)
-        {
-            // Calculate float coordinates between 0 - 1 for resampling
-            float xF = (width_ > 1) ? (float)x / (float)(width - 1) : 0.0f;
-            float yF = (height_ > 1) ? (float)y / (float)(height - 1) : 0.0f;
-            unsigned uintColor = GetPixelBilinear(xF, yF).ToUInt();
-            unsigned char* dest = newData + (y * width + x) * components_;
-            unsigned char* src = (unsigned char*)&uintColor;
-
-            switch (components_)
-            {
-            case 4:
-                dest[3] = src[3];
-                // Fall through
-            case 3:
-                dest[2] = src[2];
-                // Fall through
-            case 2:
-                dest[1] = src[1];
-                // Fall through
-            default:
-                dest[0] = src[0];
-                break;
-            }
-        }
-    }
-
-    width_ = width;
-    height_ = height;
-    data_ = newData;
-    SetMemoryUse(width * height * depth_ * components_);
-    return true;
-}
-
-void Image::Clear(const Color& color)
-{
-    ClearInt(color.ToUInt());
-}
-
-void Image::ClearInt(unsigned uintColor)
-{
-    ATOMIC_PROFILE(ClearImage);
-
-    if (!data_)
-        return;
-
-    if (IsCompressed())
-    {
-        ATOMIC_LOGERROR("Clear not supported for compressed images");
-        return;
-    }
-
-    if (components_ == 4)
-    {
-        unsigned color = uintColor;
-        unsigned* data = (unsigned*)GetData();
-        unsigned* data_end = (unsigned*)(GetData() + width_ * height_ * depth_ * components_);
-        for (; data < data_end; ++data)
-            *data = color;
-    }
-    else
-    {
-        unsigned char* src = (unsigned char*)&uintColor;
-        for (unsigned i = 0; i < width_ * height_ * depth_ * components_; ++i)
-            data_[i] = src[i % components_];
-    }
-}
-
-bool Image::SaveBMP(const String& fileName) const
-{
-    ATOMIC_PROFILE(SaveImageBMP);
-
-    FileSystem* fileSystem = GetSubsystem<FileSystem>();
-    if (fileSystem && !fileSystem->CheckAccess(GetPath(fileName)))
-    {
-        ATOMIC_LOGERROR("Access denied to " + fileName);
-        return false;
-    }
-
-    if (IsCompressed())
-    {
-        ATOMIC_LOGERROR("Can not save compressed image to BMP");
-        return false;
-    }
-
-    if (data_)
-        return stbi_write_bmp(fileName.CString(), width_, height_, components_, data_.Get()) != 0;
-    else
-        return false;
-}
-
-bool Image::SavePNG(const String& fileName) const
-{
-    ATOMIC_PROFILE(SaveImagePNG);
-
-    File outFile(context_, fileName, FILE_WRITE);
-    if (outFile.IsOpen())
-        return Image::Save(outFile); // Save uses PNG format
-    else
-        return false;
-}
-
-bool Image::SaveTGA(const String& fileName) const
-{
-    ATOMIC_PROFILE(SaveImageTGA);
-
-    FileSystem* fileSystem = GetSubsystem<FileSystem>();
-    if (fileSystem && !fileSystem->CheckAccess(GetPath(fileName)))
-    {
-        ATOMIC_LOGERROR("Access denied to " + fileName);
-        return false;
-    }
-
-    if (IsCompressed())
-    {
-        ATOMIC_LOGERROR("Can not save compressed image to TGA");
-        return false;
-    }
-
-    if (data_)
-        return stbi_write_tga(GetNativePath(fileName).CString(), width_, height_, components_, data_.Get()) != 0;
-    else
-        return false;
-}
-
-bool Image::SaveJPG(const String& fileName, int quality) const
-{
-    ATOMIC_PROFILE(SaveImageJPG);
-
-    FileSystem* fileSystem = GetSubsystem<FileSystem>();
-    if (fileSystem && !fileSystem->CheckAccess(GetPath(fileName)))
-    {
-        ATOMIC_LOGERROR("Access denied to " + fileName);
-        return false;
-    }
-
-    if (IsCompressed())
-    {
-        ATOMIC_LOGERROR("Can not save compressed image to JPG");
-        return false;
-    }
-
-    if (data_)
-        return jo_write_jpg(GetNativePath(fileName).CString(), data_.Get(), width_, height_, components_, quality) != 0;
-    else
-        return false;
-}
-// ATOMIC BEGIN
-bool Image::SaveDDS(const String& fileName) const
-{
-#if !defined(ATOMIC_PLATFORM_DESKTOP)
-
-    ATOMIC_LOGERRORF("Image::SaveDDS - Unsupported on current platform: %s", fileName.CString());
-    return false;
-
-#else
-    ATOMIC_PROFILE(SaveImageDDS);
-
-    FileSystem* fileSystem = GetSubsystem<FileSystem>();
-    if (fileSystem && !fileSystem->CheckAccess(GetPath(fileName)))
-    {
-        ATOMIC_LOGERROR("Access denied to " + fileName);
-        return false;
-    }
-
-    if (IsCompressed())
-    {
-        ATOMIC_LOGERROR("Can not save compressed image to DDS");
-        return false;
-    }
-
-    if (data_)
-    {
-        // #623 BEGIN TODO: Should have an abstract ImageReader and ImageWriter classes
-        // with subclasses for particular image output types. Also should have image settings in the image meta.
-        // ImageReader/Writers should also support a progress callback so UI can be updated.
-
-        if (!width_ || !height_)
-        {
-            ATOMIC_LOGERRORF("Attempting to save zero width/height DDS to %s", fileName.CString());
-            return false;
-        }
-
-        squish::u8 *inputData = (squish::u8 *) data_.Get();
-
-        SharedPtr<Image> tempImage;
-
-        // libsquish expects 4 channel RGBA, so create a temporary image if necessary
-        if (components_ != 4)
-        {
-            if (components_ != 3)
-            {
-                ATOMIC_LOGERROR("Can only save images with 3 or 4 components to DDS");
-                return false;
-            }
-
-            tempImage = new Image(context_);
-            tempImage->SetSize(width_, height_, 4);
-
-            squish::u8* srcBits = (squish::u8*) data_;
-            squish::u8* dstBits = (squish::u8*) tempImage->data_;
-
-            for (unsigned i = 0; i < (unsigned) width_ * (unsigned) height_; i++)
-            {
-                *dstBits++ = *srcBits++;
-                *dstBits++ = *srcBits++;
-                *dstBits++ = *srcBits++;
-                *dstBits++ = 255;
-            }
-
-            inputData = (squish::u8 *) tempImage->data_.Get();
-        }
-
-        unsigned squishFlags = HasAlphaChannel() ? squish::kDxt5 : squish::kDxt1;
-
-        // Use a slow but high quality colour compressor (the default). (TODO: expose other settings as parameter)
-        squishFlags |= squish::kColourClusterFit;
-
-        unsigned storageRequirements = (unsigned) squish::GetStorageRequirements( width_, height_,  squishFlags);
-
-        SharedArrayPtr<unsigned char> compressedData(new unsigned char[storageRequirements]);
-
-        squish::CompressImage(inputData, width_, height_, compressedData.Get(), squishFlags);
-
-        DDSurfaceDesc2 sdesc;
-
-        if (sizeof(sdesc) != 124)
-        {
-            ATOMIC_LOGERROR("Image::SaveDDS - sizeof(DDSurfaceDesc2) != 124");
-            return false;
-        }
-        memset(&sdesc, 0, sizeof(sdesc));
-        sdesc.dwSize_ = 124;
-        sdesc.dwFlags_ = DDSD_CAPS | DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT;
-        sdesc.dwWidth_ = width_;
-        sdesc.dwHeight_ = height_;
-
-        sdesc.ddpfPixelFormat_.dwSize_ = 32;
-        sdesc.ddpfPixelFormat_.dwFlags_ = DDPF_FOURCC | (HasAlphaChannel() ? DDPF_ALPHAPIXELS : 0);
-        sdesc.ddpfPixelFormat_.dwFourCC_ = HasAlphaChannel() ? FOURCC_DXT5 : FOURCC_DXT1;
-
-        SharedPtr<File> dest(new File(context_, fileName, FILE_WRITE));
-
-        if (!dest->IsOpen())
-        {
-            ATOMIC_LOGERRORF("Failed to open DXT image file for writing %s", fileName.CString());
-            return false;
-        }
-        else
-        {
-
-            dest->Write((void*)"DDS ", 4);
-            dest->Write((void*)&sdesc, sizeof(sdesc));
-
-            bool success = dest->Write(compressedData.Get(), storageRequirements) == storageRequirements;
-
-            if (!success)
-                ATOMIC_LOGERRORF("Failed to write image to DXT, file size mismatch %s", fileName.CString());
-
-            return success;
-        }
-        // #623 END TODO
-    }
-#endif
-
-    return false;
-}
-// ATOMIC END
-Color Image::GetPixel(int x, int y) const
-{
-    return GetPixel(x, y, 0);
-}
-
-Color Image::GetPixel(int x, int y, int z) const
-{
-    if (!data_ || z < 0 || z >= depth_ || IsCompressed())
-        return Color::BLACK;
-    x = Clamp(x, 0, width_ - 1);
-    y = Clamp(y, 0, height_ - 1);
-
-    unsigned char* src = data_ + (z * width_ * height_ + y * width_ + x) * components_;
-    Color ret;
-
-    switch (components_)
-    {
-    case 4:
-        ret.a_ = (float)src[3] / 255.0f;
-        // Fall through
-    case 3:
-        ret.b_ = (float)src[2] / 255.0f;
-        // Fall through
-    case 2:
-        ret.g_ = (float)src[1] / 255.0f;
-        ret.r_ = (float)src[0] / 255.0f;
-        break;
-    default:
-        ret.r_ = ret.g_ = ret.b_ = (float)src[0] / 255.0f;
-        break;
-    }
-
-    return ret;
-}
-
-unsigned Image::GetPixelInt(int x, int y) const
-{
-    return GetPixelInt(x, y, 0);
-}
-
-unsigned Image::GetPixelInt(int x, int y, int z) const
-{
-    if (!data_ || z < 0 || z >= depth_ || IsCompressed())
-        return 0xff000000;
-    x = Clamp(x, 0, width_ - 1);
-    y = Clamp(y, 0, height_ - 1);
-
-    unsigned char* src = data_ + (z * width_ * height_ + y * width_ + x) * components_;
-    unsigned ret = 0;
-    if (components_ < 4)
-        ret |= 0xff000000;
-
-    switch (components_)
-    {
-    case 4:
-        ret |= (unsigned)src[3] << 24;
-        // Fall through
-    case 3:
-        ret |= (unsigned)src[2] << 16;
-        // Fall through
-    case 2:
-        ret |= (unsigned)src[1] << 8;
-        ret |= (unsigned)src[0];
-        break;
-    default:
-        ret |= (unsigned)src[0] << 16;
-        ret |= (unsigned)src[0] << 8;
-        ret |= (unsigned)src[0];
-        break;
-    }
-
-    return ret;
-}
-
-Color Image::GetPixelBilinear(float x, float y) const
-{
-    x = Clamp(x * width_ - 0.5f, 0.0f, (float)(width_ - 1));
-    y = Clamp(y * height_ - 0.5f, 0.0f, (float)(height_ - 1));
-
-    int xI = (int)x;
-    int yI = (int)y;
-    float xF = Fract(x);
-    float yF = Fract(y);
-
-    Color topColor = GetPixel(xI, yI).Lerp(GetPixel(xI + 1, yI), xF);
-    Color bottomColor = GetPixel(xI, yI + 1).Lerp(GetPixel(xI + 1, yI + 1), xF);
-    return topColor.Lerp(bottomColor, yF);
-}
-
-Color Image::GetPixelTrilinear(float x, float y, float z) const
-{
-    if (depth_ < 2)
-        return GetPixelBilinear(x, y);
-
-    x = Clamp(x * width_ - 0.5f, 0.0f, (float)(width_ - 1));
-    y = Clamp(y * height_ - 0.5f, 0.0f, (float)(height_ - 1));
-    z = Clamp(z * depth_ - 0.5f, 0.0f, (float)(depth_ - 1));
-
-    int xI = (int)x;
-    int yI = (int)y;
-    int zI = (int)z;
-    if (zI == depth_ - 1)
-        return GetPixelBilinear(x, y);
-    float xF = Fract(x);
-    float yF = Fract(y);
-    float zF = Fract(z);
-
-    Color topColorNear = GetPixel(xI, yI, zI).Lerp(GetPixel(xI + 1, yI, zI), xF);
-    Color bottomColorNear = GetPixel(xI, yI + 1, zI).Lerp(GetPixel(xI + 1, yI + 1, zI), xF);
-    Color colorNear = topColorNear.Lerp(bottomColorNear, yF);
-    Color topColorFar = GetPixel(xI, yI, zI + 1).Lerp(GetPixel(xI + 1, yI, zI + 1), xF);
-    Color bottomColorFar = GetPixel(xI, yI + 1, zI + 1).Lerp(GetPixel(xI + 1, yI + 1, zI + 1), xF);
-    Color colorFar = topColorFar.Lerp(bottomColorFar, yF);
-    return colorNear.Lerp(colorFar, zF);
-}
-
-SharedPtr<Image> Image::GetNextLevel() const
-{
-    if (IsCompressed())
-    {
-        ATOMIC_LOGERROR("Can not generate mip level from compressed data");
-        return SharedPtr<Image>();
-    }
-    if (components_ < 1 || components_ > 4)
-    {
-        ATOMIC_LOGERROR("Illegal number of image components for mip level generation");
-        return SharedPtr<Image>();
-    }
-
-    if (nextLevel_)
-        return nextLevel_;
-
-    ATOMIC_PROFILE(CalculateImageMipLevel);
-
-    int widthOut = width_ / 2;
-    int heightOut = height_ / 2;
-    int depthOut = depth_ / 2;
-
-    if (widthOut < 1)
-        widthOut = 1;
-    if (heightOut < 1)
-        heightOut = 1;
-    if (depthOut < 1)
-        depthOut = 1;
-
-    SharedPtr<Image> mipImage(new Image(context_));
-
-    if (depth_ > 1)
-        mipImage->SetSize(widthOut, heightOut, depthOut, components_);
-    else
-        mipImage->SetSize(widthOut, heightOut, components_);
-
-    const unsigned char* pixelDataIn = data_.Get();
-    unsigned char* pixelDataOut = mipImage->data_.Get();
-
-    // 1D case
-    if (depth_ == 1 && (height_ == 1 || width_ == 1))
-    {
-        // Loop using the larger dimension
-        if (widthOut < heightOut)
-            widthOut = heightOut;
-
-        switch (components_)
-        {
-        case 1:
-            for (int x = 0; x < widthOut; ++x)
-                pixelDataOut[x] = (unsigned char)(((unsigned)pixelDataIn[x * 2] + pixelDataIn[x * 2 + 1]) >> 1);
-            break;
-
-        case 2:
-            for (int x = 0; x < widthOut * 2; x += 2)
-            {
-                pixelDataOut[x] = (unsigned char)(((unsigned)pixelDataIn[x * 2] + pixelDataIn[x * 2 + 2]) >> 1);
-                pixelDataOut[x + 1] = (unsigned char)(((unsigned)pixelDataIn[x * 2 + 1] + pixelDataIn[x * 2 + 3]) >> 1);
-            }
-            break;
-
-        case 3:
-            for (int x = 0; x < widthOut * 3; x += 3)
-            {
-                pixelDataOut[x] = (unsigned char)(((unsigned)pixelDataIn[x * 2] + pixelDataIn[x * 2 + 3]) >> 1);
-                pixelDataOut[x + 1] = (unsigned char)(((unsigned)pixelDataIn[x * 2 + 1] + pixelDataIn[x * 2 + 4]) >> 1);
-                pixelDataOut[x + 2] = (unsigned char)(((unsigned)pixelDataIn[x * 2 + 2] + pixelDataIn[x * 2 + 5]) >> 1);
-            }
-            break;
-
-        case 4:
-            for (int x = 0; x < widthOut * 4; x += 4)
-            {
-                pixelDataOut[x] = (unsigned char)(((unsigned)pixelDataIn[x * 2] + pixelDataIn[x * 2 + 4]) >> 1);
-                pixelDataOut[x + 1] = (unsigned char)(((unsigned)pixelDataIn[x * 2 + 1] + pixelDataIn[x * 2 + 5]) >> 1);
-                pixelDataOut[x + 2] = (unsigned char)(((unsigned)pixelDataIn[x * 2 + 2] + pixelDataIn[x * 2 + 6]) >> 1);
-                pixelDataOut[x + 3] = (unsigned char)(((unsigned)pixelDataIn[x * 2 + 3] + pixelDataIn[x * 2 + 7]) >> 1);
-            }
-            break;
-
-        default:
-            assert(false);  // Should never reach here
-            break;
-        }
-    }
-    // 2D case
-    else if (depth_ == 1)
-    {
-        switch (components_)
-        {
-        case 1:
-            for (int y = 0; y < heightOut; ++y)
-            {
-                const unsigned char* inUpper = &pixelDataIn[(y * 2) * width_];
-                const unsigned char* inLower = &pixelDataIn[(y * 2 + 1) * width_];
-                unsigned char* out = &pixelDataOut[y * widthOut];
-
-                for (int x = 0; x < widthOut; ++x)
-                {
-                    out[x] = (unsigned char)(((unsigned)inUpper[x * 2] + inUpper[x * 2 + 1] +
-                                              inLower[x * 2] + inLower[x * 2 + 1]) >> 2);
-                }
-            }
-            break;
-
-        case 2:
-            for (int y = 0; y < heightOut; ++y)
-            {
-                const unsigned char* inUpper = &pixelDataIn[(y * 2) * width_ * 2];
-                const unsigned char* inLower = &pixelDataIn[(y * 2 + 1) * width_ * 2];
-                unsigned char* out = &pixelDataOut[y * widthOut * 2];
-
-                for (int x = 0; x < widthOut * 2; x += 2)
-                {
-                    out[x] = (unsigned char)(((unsigned)inUpper[x * 2] + inUpper[x * 2 + 2] +
-                                              inLower[x * 2] + inLower[x * 2 + 2]) >> 2);
-                    out[x + 1] = (unsigned char)(((unsigned)inUpper[x * 2 + 1] + inUpper[x * 2 + 3] +
-                                                  inLower[x * 2 + 1] + inLower[x * 2 + 3]) >> 2);
-                }
-            }
-            break;
-
-        case 3:
-            for (int y = 0; y < heightOut; ++y)
-            {
-                const unsigned char* inUpper = &pixelDataIn[(y * 2) * width_ * 3];
-                const unsigned char* inLower = &pixelDataIn[(y * 2 + 1) * width_ * 3];
-                unsigned char* out = &pixelDataOut[y * widthOut * 3];
-
-                for (int x = 0; x < widthOut * 3; x += 3)
-                {
-                    out[x] = (unsigned char)(((unsigned)inUpper[x * 2] + inUpper[x * 2 + 3] +
-                                              inLower[x * 2] + inLower[x * 2 + 3]) >> 2);
-                    out[x + 1] = (unsigned char)(((unsigned)inUpper[x * 2 + 1] + inUpper[x * 2 + 4] +
-                                                  inLower[x * 2 + 1] + inLower[x * 2 + 4]) >> 2);
-                    out[x + 2] = (unsigned char)(((unsigned)inUpper[x * 2 + 2] + inUpper[x * 2 + 5] +
-                                                  inLower[x * 2 + 2] + inLower[x * 2 + 5]) >> 2);
-                }
-            }
-            break;
-
-        case 4:
-            for (int y = 0; y < heightOut; ++y)
-            {
-                const unsigned char* inUpper = &pixelDataIn[(y * 2) * width_ * 4];
-                const unsigned char* inLower = &pixelDataIn[(y * 2 + 1) * width_ * 4];
-                unsigned char* out = &pixelDataOut[y * widthOut * 4];
-
-                for (int x = 0; x < widthOut * 4; x += 4)
-                {
-                    out[x] = (unsigned char)(((unsigned)inUpper[x * 2] + inUpper[x * 2 + 4] +
-                                              inLower[x * 2] + inLower[x * 2 + 4]) >> 2);
-                    out[x + 1] = (unsigned char)(((unsigned)inUpper[x * 2 + 1] + inUpper[x * 2 + 5] +
-                                                  inLower[x * 2 + 1] + inLower[x * 2 + 5]) >> 2);
-                    out[x + 2] = (unsigned char)(((unsigned)inUpper[x * 2 + 2] + inUpper[x * 2 + 6] +
-                                                  inLower[x * 2 + 2] + inLower[x * 2 + 6]) >> 2);
-                    out[x + 3] = (unsigned char)(((unsigned)inUpper[x * 2 + 3] + inUpper[x * 2 + 7] +
-                                                  inLower[x * 2 + 3] + inLower[x * 2 + 7]) >> 2);
-                }
-            }
-            break;
-
-        default:
-            assert(false);  // Should never reach here
-            break;
-        }
-    }
-    // 3D case
-    else
-    {
-        switch (components_)
-        {
-        case 1:
-            for (int z = 0; z < depthOut; ++z)
-            {
-                const unsigned char* inOuter = &pixelDataIn[(z * 2) * width_ * height_];
-                const unsigned char* inInner = &pixelDataIn[(z * 2 + 1) * width_ * height_];
-
-                for (int y = 0; y < heightOut; ++y)
-                {
-                    const unsigned char* inOuterUpper = &inOuter[(y * 2) * width_];
-                    const unsigned char* inOuterLower = &inOuter[(y * 2 + 1) * width_];
-                    const unsigned char* inInnerUpper = &inInner[(y * 2) * width_];
-                    const unsigned char* inInnerLower = &inInner[(y * 2 + 1) * width_];
-                    unsigned char* out = &pixelDataOut[z * widthOut * heightOut + y * widthOut];
-
-                    for (int x = 0; x < widthOut; ++x)
-                    {
-                        out[x] = (unsigned char)(((unsigned)inOuterUpper[x * 2] + inOuterUpper[x * 2 + 1] +
-                                                  inOuterLower[x * 2] + inOuterLower[x * 2 + 1] +
-                                                  inInnerUpper[x * 2] + inInnerUpper[x * 2 + 1] +
-                                                  inInnerLower[x * 2] + inInnerLower[x * 2 + 1]) >> 3);
-                    }
-                }
-            }
-            break;
-
-        case 2:
-            for (int z = 0; z < depthOut; ++z)
-            {
-                const unsigned char* inOuter = &pixelDataIn[(z * 2) * width_ * height_ * 2];
-                const unsigned char* inInner = &pixelDataIn[(z * 2 + 1) * width_ * height_ * 2];
-
-                for (int y = 0; y < heightOut; ++y)
-                {
-                    const unsigned char* inOuterUpper = &inOuter[(y * 2) * width_ * 2];
-                    const unsigned char* inOuterLower = &inOuter[(y * 2 + 1) * width_ * 2];
-                    const unsigned char* inInnerUpper = &inInner[(y * 2) * width_ * 2];
-                    const unsigned char* inInnerLower = &inInner[(y * 2 + 1) * width_ * 2];
-                    unsigned char* out = &pixelDataOut[z * widthOut * heightOut * 2 + y * widthOut * 2];
-
-                    for (int x = 0; x < widthOut * 2; x += 2)
-                    {
-                        out[x] = (unsigned char)(((unsigned)inOuterUpper[x * 2] + inOuterUpper[x * 2 + 2] +
-                                                  inOuterLower[x * 2] + inOuterLower[x * 2 + 2] +
-                                                  inInnerUpper[x * 2] + inInnerUpper[x * 2 + 2] +
-                                                  inInnerLower[x * 2] + inInnerLower[x * 2 + 2]) >> 3);
-                        out[x + 1] = (unsigned char)(((unsigned)inOuterUpper[x * 2 + 1] + inOuterUpper[x * 2 + 3] +
-                                                      inOuterLower[x * 2 + 1] + inOuterLower[x * 2 + 3] +
-                                                      inInnerUpper[x * 2 + 1] + inInnerUpper[x * 2 + 3] +
-                                                      inInnerLower[x * 2 + 1] + inInnerLower[x * 2 + 3]) >> 3);
-                    }
-                }
-            }
-            break;
-
-        case 3:
-            for (int z = 0; z < depthOut; ++z)
-            {
-                const unsigned char* inOuter = &pixelDataIn[(z * 2) * width_ * height_ * 3];
-                const unsigned char* inInner = &pixelDataIn[(z * 2 + 1) * width_ * height_ * 3];
-
-                for (int y = 0; y < heightOut; ++y)
-                {
-                    const unsigned char* inOuterUpper = &inOuter[(y * 2) * width_ * 3];
-                    const unsigned char* inOuterLower = &inOuter[(y * 2 + 1) * width_ * 3];
-                    const unsigned char* inInnerUpper = &inInner[(y * 2) * width_ * 3];
-                    const unsigned char* inInnerLower = &inInner[(y * 2 + 1) * width_ * 3];
-                    unsigned char* out = &pixelDataOut[z * widthOut * heightOut * 3 + y * widthOut * 3];
-
-                    for (int x = 0; x < widthOut * 3; x += 3)
-                    {
-                        out[x] = (unsigned char)(((unsigned)inOuterUpper[x * 2] + inOuterUpper[x * 2 + 3] +
-                                                  inOuterLower[x * 2] + inOuterLower[x * 2 + 3] +
-                                                  inInnerUpper[x * 2] + inInnerUpper[x * 2 + 3] +
-                                                  inInnerLower[x * 2] + inInnerLower[x * 2 + 3]) >> 3);
-                        out[x + 1] = (unsigned char)(((unsigned)inOuterUpper[x * 2 + 1] + inOuterUpper[x * 2 + 4] +
-                                                      inOuterLower[x * 2 + 1] + inOuterLower[x * 2 + 4] +
-                                                      inInnerUpper[x * 2 + 1] + inInnerUpper[x * 2 + 4] +
-                                                      inInnerLower[x * 2 + 1] + inInnerLower[x * 2 + 4]) >> 3);
-                        out[x + 2] = (unsigned char)(((unsigned)inOuterUpper[x * 2 + 2] + inOuterUpper[x * 2 + 5] +
-                                                      inOuterLower[x * 2 + 2] + inOuterLower[x * 2 + 5] +
-                                                      inInnerUpper[x * 2 + 2] + inInnerUpper[x * 2 + 5] +
-                                                      inInnerLower[x * 2 + 2] + inInnerLower[x * 2 + 5]) >> 3);
-                    }
-                }
-            }
-            break;
-
-        case 4:
-            for (int z = 0; z < depthOut; ++z)
-            {
-                const unsigned char* inOuter = &pixelDataIn[(z * 2) * width_ * height_ * 4];
-                const unsigned char* inInner = &pixelDataIn[(z * 2 + 1) * width_ * height_ * 4];
-
-                for (int y = 0; y < heightOut; ++y)
-                {
-                    const unsigned char* inOuterUpper = &inOuter[(y * 2) * width_ * 4];
-                    const unsigned char* inOuterLower = &inOuter[(y * 2 + 1) * width_ * 4];
-                    const unsigned char* inInnerUpper = &inInner[(y * 2) * width_ * 4];
-                    const unsigned char* inInnerLower = &inInner[(y * 2 + 1) * width_ * 4];
-                    unsigned char* out = &pixelDataOut[z * widthOut * heightOut * 4 + y * widthOut * 4];
-
-                    for (int x = 0; x < widthOut * 4; x += 4)
-                    {
-                        out[x] = (unsigned char)(((unsigned)inOuterUpper[x * 2] + inOuterUpper[x * 2 + 4] +
-                                                  inOuterLower[x * 2] + inOuterLower[x * 2 + 4] +
-                                                  inInnerUpper[x * 2] + inInnerUpper[x * 2 + 4] +
-                                                  inInnerLower[x * 2] + inInnerLower[x * 2 + 4]) >> 3);
-                        out[x + 1] = (unsigned char)(((unsigned)inOuterUpper[x * 2 + 1] + inOuterUpper[x * 2 + 5] +
-                                                      inOuterLower[x * 2 + 1] + inOuterLower[x * 2 + 5] +
-                                                      inInnerUpper[x * 2 + 1] + inInnerUpper[x * 2 + 5] +
-                                                      inInnerLower[x * 2 + 1] + inInnerLower[x * 2 + 5]) >> 3);
-                        out[x + 2] = (unsigned char)(((unsigned)inOuterUpper[x * 2 + 2] + inOuterUpper[x * 2 + 6] +
-                                                      inOuterLower[x * 2 + 2] + inOuterLower[x * 2 + 6] +
-                                                      inInnerUpper[x * 2 + 2] + inInnerUpper[x * 2 + 6] +
-                                                      inInnerLower[x * 2 + 2] + inInnerLower[x * 2 + 6]) >> 3);
-                    }
-                }
-            }
-            break;
-
-        default:
-            assert(false);  // Should never reach here
-            break;
-        }
-    }
-
-    return mipImage;
-}
-
-SharedPtr<Image> Image::ConvertToRGBA() const
-{
-    if (IsCompressed())
-    {
-        ATOMIC_LOGERROR("Can not convert compressed image to RGBA");
-        return SharedPtr<Image>();
-    }
-    if (components_ < 1 || components_ > 4)
-    {
-        ATOMIC_LOGERROR("Illegal number of image components for conversion to RGBA");
-        return SharedPtr<Image>();
-    }
-    if (!data_)
-    {
-        ATOMIC_LOGERROR("Can not convert image without data to RGBA");
-        return SharedPtr<Image>();
-    }
-
-    // Already RGBA?
-    if (components_ == 4)
-        return SharedPtr<Image>(const_cast<Image*>(this));
-
-    SharedPtr<Image> ret(new Image(context_));
-    ret->SetSize(width_, height_, depth_, 4);
-
-    const unsigned char* src = data_;
-    unsigned char* dest = ret->GetData();
-
-    switch (components_)
-    {
-    case 1:
-        for (unsigned i = 0; i < static_cast<unsigned>(width_ * height_ * depth_); ++i)
-        {
-            unsigned char pixel = *src++;
-            *dest++ = pixel;
-            *dest++ = pixel;
-            *dest++ = pixel;
-            *dest++ = 255;
-        }
-        break;
-
-    case 2:
-        for (unsigned i = 0; i < static_cast<unsigned>(width_ * height_ * depth_); ++i)
-        {
-            unsigned char pixel = *src++;
-            *dest++ = pixel;
-            *dest++ = pixel;
-            *dest++ = pixel;
-            *dest++ = *src++;
-        }
-        break;
-
-    case 3:
-        for (unsigned i = 0; i < static_cast<unsigned>(width_ * height_ * depth_); ++i)
-        {
-            *dest++ = *src++;
-            *dest++ = *src++;
-            *dest++ = *src++;
-            *dest++ = 255;
-        }
-        break;
-
-    default:
-        assert(false);  // Should never reach nere
-        break;
-    }
-
-    return ret;
-}
-
-CompressedLevel Image::GetCompressedLevel(unsigned index) const
-{
-    CompressedLevel level;
-
-    if (compressedFormat_ == CF_NONE)
-    {
-        ATOMIC_LOGERROR("Image is not compressed");
-        return level;
-    }
-    if (index >= numCompressedLevels_)
-    {
-        ATOMIC_LOGERROR("Compressed image mip level out of bounds");
-        return level;
-    }
-
-    level.format_ = compressedFormat_;
-    level.width_ = width_;
-    level.height_ = height_;
-    level.depth_ = depth_;
-
-    if (compressedFormat_ == CF_RGBA)
-    {
-        level.blockSize_ = 4;
-        unsigned i = 0;
-        unsigned offset = 0;
-
-        for (;;)
-        {
-            if (!level.width_)
-                level.width_ = 1;
-            if (!level.height_)
-                level.height_ = 1;
-            if (!level.depth_)
-                level.depth_ = 1;
-
-            level.rowSize_ = level.width_ * level.blockSize_;
-            level.rows_ = (unsigned)level.height_;
-            level.data_ = data_.Get() + offset;
-            level.dataSize_ = level.depth_ * level.rows_ * level.rowSize_;
-
-            if (offset + level.dataSize_ > GetMemoryUse())
-            {
-                ATOMIC_LOGERROR("Compressed level is outside image data. Offset: " + String(offset) + " Size: " + String(level.dataSize_) +
-                         " Datasize: " + String(GetMemoryUse()));
-                level.data_ = 0;
-                return level;
-            }
-
-            if (i == index)
-                return level;
-
-            offset += level.dataSize_;
-            level.width_ /= 2;
-            level.height_ /= 2;
-            level.depth_ /= 2;
-            ++i;
-        }
-    }
-    else if (compressedFormat_ < CF_PVRTC_RGB_2BPP)
-    {
-        level.blockSize_ = (compressedFormat_ == CF_DXT1 || compressedFormat_ == CF_ETC1) ? 8 : 16;
-        unsigned i = 0;
-        unsigned offset = 0;
-
-        for (;;)
-        {
-            if (!level.width_)
-                level.width_ = 1;
-            if (!level.height_)
-                level.height_ = 1;
-            if (!level.depth_)
-                level.depth_ = 1;
-
-            level.rowSize_ = ((level.width_ + 3) / 4) * level.blockSize_;
-            level.rows_ = (unsigned)((level.height_ + 3) / 4);
-            level.data_ = data_.Get() + offset;
-            level.dataSize_ = level.depth_ * level.rows_ * level.rowSize_;
-
-            if (offset + level.dataSize_ > GetMemoryUse())
-            {
-                ATOMIC_LOGERROR("Compressed level is outside image data. Offset: " + String(offset) + " Size: " + String(level.dataSize_) +
-                         " Datasize: " + String(GetMemoryUse()));
-                level.data_ = 0;
-                return level;
-            }
-
-            if (i == index)
-                return level;
-
-            offset += level.dataSize_;
-            level.width_ /= 2;
-            level.height_ /= 2;
-            level.depth_ /= 2;
-            ++i;
-        }
-    }
-    else
-    {
-        level.blockSize_ = compressedFormat_ < CF_PVRTC_RGB_4BPP ? 2 : 4;
-        unsigned i = 0;
-        unsigned offset = 0;
-
-        for (;;)
-        {
-            if (!level.width_)
-                level.width_ = 1;
-            if (!level.height_)
-                level.height_ = 1;
-
-            int dataWidth = Max(level.width_, level.blockSize_ == 2 ? 16 : 8);
-            int dataHeight = Max(level.height_, 8);
-            level.data_ = data_.Get() + offset;
-            level.dataSize_ = (dataWidth * dataHeight * level.blockSize_ + 7) >> 3;
-            level.rows_ = (unsigned)dataHeight;
-            level.rowSize_ = level.dataSize_ / level.rows_;
-
-            if (offset + level.dataSize_ > GetMemoryUse())
-            {
-                ATOMIC_LOGERROR("Compressed level is outside image data. Offset: " + String(offset) + " Size: " + String(level.dataSize_) +
-                         " Datasize: " + String(GetMemoryUse()));
-                level.data_ = 0;
-                return level;
-            }
-
-            if (i == index)
-                return level;
-
-            offset += level.dataSize_;
-            level.width_ /= 2;
-            level.height_ /= 2;
-            ++i;
-        }
-    }
-}
-
-Image* Image::GetSubimage(const IntRect& rect) const
-{
-    if (!data_)
-        return 0;
-
-    if (depth_ > 1)
-    {
-        ATOMIC_LOGERROR("Subimage not supported for 3D images");
-        return 0;
-    }
-
-    if (rect.left_ < 0 || rect.top_ < 0 || rect.right_ > width_ || rect.bottom_ > height_ || !rect.Width() || !rect.Height())
-    {
-        ATOMIC_LOGERROR("Can not get subimage from image " + GetName() + " with invalid region");
-        return 0;
-    }
-
-    if (!IsCompressed())
-    {
-        int x = rect.left_;
-        int y = rect.top_;
-        int width = rect.Width();
-        int height = rect.Height();
-
-        Image* image = new Image(context_);
-        image->SetSize(width, height, components_);
-
-        unsigned char* dest = image->GetData();
-        unsigned char* src = data_.Get() + (y * width_ + x) * components_;
-        for (int i = 0; i < height; ++i)
-        {
-            memcpy(dest, src, width * components_);
-            dest += width * components_;
-            src += width_ * components_;
-        }
-
-        return image;
-    }
-    else
-    {
-        // Pad the region to be a multiple of block size
-        IntRect paddedRect = rect;
-        paddedRect.left_ = (rect.left_ / 4) * 4;
-        paddedRect.top_ = (rect.top_ / 4) * 4;
-        paddedRect.right_ = (rect.right_ / 4) * 4;
-        paddedRect.bottom_ = (rect.bottom_ / 4) * 4;
-        IntRect currentRect = paddedRect;
-
-        PODVector<unsigned char> subimageData;
-        unsigned subimageLevels = 0;
-
-        // Save as many mips as possible until the next mip would cross a block boundary
-        for (unsigned i = 0; i < numCompressedLevels_; ++i)
-        {
-            CompressedLevel level = GetCompressedLevel(i);
-            if (!level.data_)
-                break;
-
-            // Mips are stored continuously
-            unsigned destStartOffset = subimageData.Size();
-            unsigned destRowSize = currentRect.Width() / 4 * level.blockSize_;
-            unsigned destSize = currentRect.Height() / 4 * destRowSize;
-            if (!destSize)
-                break;
-
-            subimageData.Resize(destStartOffset + destSize);
-            unsigned char* dest = &subimageData[destStartOffset];
-
-            for (int y = currentRect.top_; y < currentRect.bottom_; y += 4)
-            {
-                unsigned char* src = level.data_ + level.rowSize_ * (y / 4) + currentRect.left_ / 4 * level.blockSize_;
-                memcpy(dest, src, destRowSize);
-                dest += destRowSize;
-            }
-
-            ++subimageLevels;
-            if ((currentRect.left_ & 4) || (currentRect.right_ & 4) || (currentRect.top_ & 4) || (currentRect.bottom_ & 4))
-                break;
-            else
-            {
-                currentRect.left_ /= 2;
-                currentRect.right_ /= 2;
-                currentRect.top_ /= 2;
-                currentRect.bottom_ /= 2;
-            }
-        }
-
-        if (!subimageLevels)
-        {
-            ATOMIC_LOGERROR("Subimage region from compressed image " + GetName() + " did not produce any data");
-            return 0;
-        }
-
-        Image* image = new Image(context_);
-        image->width_ = paddedRect.Width();
-        image->height_ = paddedRect.Height();
-        image->depth_ = 1;
-        image->compressedFormat_ = compressedFormat_;
-        image->numCompressedLevels_ = subimageLevels;
-        image->components_ = components_;
-        image->data_ = new unsigned char[subimageData.Size()];
-        memcpy(image->data_.Get(), &subimageData[0], subimageData.Size());
-        image->SetMemoryUse(subimageData.Size());
-
-        return image;
-    }
-}
-
-SDL_Surface* Image::GetSDLSurface(const IntRect& rect) const
-{
-    if (!data_)
-        return 0;
-
-    if (depth_ > 1)
-    {
-        ATOMIC_LOGERROR("Can not get SDL surface from 3D image");
-        return 0;
-    }
-
-    if (IsCompressed())
-    {
-        ATOMIC_LOGERROR("Can not get SDL surface from compressed image " + GetName());
-        return 0;
-    }
-
-    if (components_ < 3)
-    {
-        ATOMIC_LOGERROR("Can not get SDL surface from image " + GetName() + " with less than 3 components");
-        return 0;
-    }
-
-    IntRect imageRect = rect;
-    // Use full image if illegal rect
-    if (imageRect.left_ < 0 || imageRect.top_ < 0 || imageRect.right_ > width_ || imageRect.bottom_ > height_ ||
-        imageRect.left_ >= imageRect.right_ || imageRect.top_ >= imageRect.bottom_)
-    {
-        imageRect.left_ = 0;
-        imageRect.top_ = 0;
-        imageRect.right_ = width_;
-        imageRect.bottom_ = height_;
-    }
-
-    int imageWidth = width_;
-    int width = imageRect.Width();
-    int height = imageRect.Height();
-
-    // Assume little-endian for all the supported platforms
-    unsigned rMask = 0x000000ff;
-    unsigned gMask = 0x0000ff00;
-    unsigned bMask = 0x00ff0000;
-    unsigned aMask = 0xff000000;
-
-    SDL_Surface* surface = SDL_CreateRGBSurface(0, width, height, components_ * 8, rMask, gMask, bMask, aMask);
-    if (surface)
-    {
-        SDL_LockSurface(surface);
-
-        unsigned char* destination = reinterpret_cast<unsigned char*>(surface->pixels);
-        unsigned char* source = data_ + components_ * (imageWidth * imageRect.top_ + imageRect.left_);
-        for (int i = 0; i < height; ++i)
-        {
-            memcpy(destination, source, components_ * width);
-            destination += surface->pitch;
-            source += components_ * imageWidth;
-        }
-
-        SDL_UnlockSurface(surface);
-    }
-    else
-        ATOMIC_LOGERROR("Failed to create SDL surface from image " + GetName());
-
-    return surface;
-}
-
-void Image::PrecalculateLevels()
-{
-    if (!data_ || IsCompressed())
-        return;
-
-    ATOMIC_PROFILE(PrecalculateImageMipLevels);
-
-    nextLevel_.Reset();
-
-    if (width_ > 1 || height_ > 1)
-    {
-        SharedPtr<Image> current = GetNextLevel();
-        nextLevel_ = current;
-        while (current && (current->width_ > 1 || current->height_ > 1))
-        {
-            current->nextLevel_ = current->GetNextLevel();
-            current = current->nextLevel_;
-        }
-    }
-}
-
-void Image::CleanupLevels()
-{
-    nextLevel_.Reset();
-}
-
-void Image::GetLevels(PODVector<Image*>& levels)
-{
-    levels.Clear();
-
-    Image* image = this;
-    while (image)
-    {
-        levels.Push(image);
-        image = image->nextLevel_;
-    }
-}
-
-void Image::GetLevels(PODVector<const Image*>& levels) const
-{
-    levels.Clear();
-
-    const Image* image = this;
-    while (image)
-    {
-        levels.Push(image);
-        image = image->nextLevel_;
-    }
-}
-
-unsigned char* Image::GetImageData(Deserializer& source, int& width, int& height, unsigned& components)
-{
-    unsigned dataSize = source.GetSize();
-
-    SharedArrayPtr<unsigned char> buffer(new unsigned char[dataSize]);
-    source.Read(buffer.Get(), dataSize);
-    return stbi_load_from_memory(buffer.Get(), dataSize, &width, &height, (int*)&components, 0);
-}
-
-void Image::FreeImageData(unsigned char* pixelData)
-{
-    if (!pixelData)
-        return;
-
-    stbi_image_free(pixelData);
-}
-
-// ATOMIC BEGIN
-
-bool Image::HasAlphaChannel() const
-{
-    return components_ > 3;
-}
-
-bool Image::SetSubimage(const Image* image, const IntRect& rect)
-{
-    if (!data_)
-        return false;
-
-    if (depth_ > 1 || IsCompressed())
-    {
-        ATOMIC_LOGERROR("SetSubimage not supported for Compressed or 3D images");
-        return false;
-    }
-
-    if (rect.left_ < 0 || rect.top_ < 0 || rect.right_ > width_ || rect.bottom_ > height_ || !rect.Width() || !rect.Height())
-    {
-        ATOMIC_LOGERROR("Can not set subimage in image " + GetName() + " with invalid region");
-        return false;
-    }
-
-    int width = rect.Width();
-    int height = rect.Height();
-    if (width == image->GetWidth() && height == image->GetHeight())
-    {
-        int components = Min((int)components_, (int)image->components_);
-
-        unsigned char* src = image->GetData();
-        unsigned char* dest = data_.Get() + (rect.top_ * width_ + rect.left_) * components_;
-        for (int i = 0; i < height; ++i)
-        {
-            memcpy(dest, src, width * components);
-
-            src += width * image->components_;
-            dest += width_ * components_;
-        }
-    }
-    else
-    {
-        unsigned uintColor;
-        unsigned char* dest = data_.Get() + (rect.top_ * width_ + rect.left_) * components_;
-        unsigned char* src = (unsigned char*)&uintColor;
-        for (int y = 0; y < height; ++y)
-        {
-            for (int x = 0; x < width; ++x)
-            {
-                // Calculate float coordinates between 0 - 1 for resampling
-                float xF = (image->width_ > 1) ? (float)x / (float)(width - 1) : 0.0f;
-                float yF = (image->height_ > 1) ? (float)y / (float)(height - 1) : 0.0f;
-                uintColor = image->GetPixelBilinear(xF, yF).ToUInt();
-
-                memcpy(dest, src, components_);
-
-                dest += components_;
-            }
-            dest += (width_ - width) * components_;
-        }
-    }
-
-    return true;
-}
-
-
-// ATOMIC END
-
-}
+//
+// Copyright (c) 2008-2017 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#include "../Precompiled.h"
+
+#include "../Core/Context.h"
+#include "../Core/Profiler.h"
+#include "../IO/File.h"
+#include "../IO/FileSystem.h"
+#include "../IO/Log.h"
+#include "../Resource/Decompress.h"
+
+#include <JO/jo_jpeg.h>
+
+// ATOMIC BEGIN
+#include <SDL/include/SDL_surface.h>
+
+#ifdef ATOMIC_PLATFORM_DESKTOP
+#include <libsquish/squish.h>
+#endif
+
+#include <STB/stb_image.h>
+#include <STB/stb_image_write.h>
+// ATOMIC END
+#ifdef ATOMIC_WEBP
+#include <webp/decode.h>
+#include <webp/encode.h>
+#include <webp/mux.h>
+#endif
+
+#include "../DebugNew.h"
+
+#ifndef MAKEFOURCC
+#define MAKEFOURCC(ch0, ch1, ch2, ch3) ((unsigned)(ch0) | ((unsigned)(ch1) << 8) | ((unsigned)(ch2) << 16) | ((unsigned)(ch3) << 24))
+#endif
+
+#define FOURCC_DXT1 (MAKEFOURCC('D','X','T','1'))
+#define FOURCC_DXT2 (MAKEFOURCC('D','X','T','2'))
+#define FOURCC_DXT3 (MAKEFOURCC('D','X','T','3'))
+#define FOURCC_DXT4 (MAKEFOURCC('D','X','T','4'))
+#define FOURCC_DXT5 (MAKEFOURCC('D','X','T','5'))
+#define FOURCC_DX10 (MAKEFOURCC('D','X','1','0'))
+
+static const unsigned DDSCAPS_COMPLEX = 0x00000008U;
+static const unsigned DDSCAPS_TEXTURE = 0x00001000U;
+static const unsigned DDSCAPS_MIPMAP = 0x00400000U;
+static const unsigned DDSCAPS2_VOLUME = 0x00200000U;
+static const unsigned DDSCAPS2_CUBEMAP = 0x00000200U;
+
+static const unsigned DDSCAPS2_CUBEMAP_POSITIVEX = 0x00000400U;
+static const unsigned DDSCAPS2_CUBEMAP_NEGATIVEX = 0x00000800U;
+static const unsigned DDSCAPS2_CUBEMAP_POSITIVEY = 0x00001000U;
+static const unsigned DDSCAPS2_CUBEMAP_NEGATIVEY = 0x00002000U;
+static const unsigned DDSCAPS2_CUBEMAP_POSITIVEZ = 0x00004000U;
+static const unsigned DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x00008000U;
+static const unsigned DDSCAPS2_CUBEMAP_ALL_FACES = 0x0000FC00U;
+
+// DX10 flags
+static const unsigned DDS_DIMENSION_TEXTURE1D = 2;
+static const unsigned DDS_DIMENSION_TEXTURE2D = 3;
+static const unsigned DDS_DIMENSION_TEXTURE3D = 4;
+
+static const unsigned DDS_RESOURCE_MISC_TEXTURECUBE = 0x4;
+
+static const unsigned DDS_DXGI_FORMAT_R8G8B8A8_UNORM = 28;
+static const unsigned DDS_DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 26;
+static const unsigned DDS_DXGI_FORMAT_BC1_UNORM = 71;
+static const unsigned DDS_DXGI_FORMAT_BC1_UNORM_SRGB = 72;
+static const unsigned DDS_DXGI_FORMAT_BC2_UNORM = 74;
+static const unsigned DDS_DXGI_FORMAT_BC2_UNORM_SRGB = 75;
+static const unsigned DDS_DXGI_FORMAT_BC3_UNORM = 77;
+static const unsigned DDS_DXGI_FORMAT_BC3_UNORM_SRGB = 78;
+
+// ATOMIC BEGIN
+static const unsigned DDSD_CAPS = 0x00000001;
+static const unsigned DDSD_HEIGHT = 0x00000002;
+static const unsigned DDSD_WIDTH = 0x00000004;
+static const unsigned DDSD_PITCH = 0x00000008;
+static const unsigned DDSD_PIXELFORMAT = 0x00001000;
+static const unsigned DDSD_MIPMAPCOUNT = 0x00020000;
+static const unsigned DDSD_LINEARSIZE = 0x00080000;
+static const unsigned DDSD_DEPTH = 0x00800000;
+
+static const unsigned DDPF_ALPHAPIXELS = 0x00000001;
+static const unsigned DDPF_ALPHA = 0x00000002;
+static const unsigned DDPF_FOURCC = 0x00000004;
+static const unsigned DDPF_PALETTEINDEXED8 = 0x00000020;
+static const unsigned DDPF_RGB = 0x00000040;
+static const unsigned DDPF_LUMINANCE = 0x00020000;
+static const unsigned DDPF_NORMAL = 0x80000000;  // nvidia specific
+// ATOMIC END
+
+
+namespace Atomic
+{
+
+/// DirectDraw color key definition.
+struct DDColorKey
+{
+    unsigned dwColorSpaceLowValue_;
+    unsigned dwColorSpaceHighValue_;
+};
+
+/// DirectDraw pixel format definition.
+struct DDPixelFormat
+{
+    unsigned dwSize_;
+    unsigned dwFlags_;
+    unsigned dwFourCC_;
+    union
+    {
+        unsigned dwRGBBitCount_;
+        unsigned dwYUVBitCount_;
+        unsigned dwZBufferBitDepth_;
+        unsigned dwAlphaBitDepth_;
+        unsigned dwLuminanceBitCount_;
+        unsigned dwBumpBitCount_;
+        unsigned dwPrivateFormatBitCount_;
+    };
+    union
+    {
+        unsigned dwRBitMask_;
+        unsigned dwYBitMask_;
+        unsigned dwStencilBitDepth_;
+        unsigned dwLuminanceBitMask_;
+        unsigned dwBumpDuBitMask_;
+        unsigned dwOperations_;
+    };
+    union
+    {
+        unsigned dwGBitMask_;
+        unsigned dwUBitMask_;
+        unsigned dwZBitMask_;
+        unsigned dwBumpDvBitMask_;
+        struct
+        {
+            unsigned short wFlipMSTypes_;
+            unsigned short wBltMSTypes_;
+        } multiSampleCaps_;
+    };
+    union
+    {
+        unsigned dwBBitMask_;
+        unsigned dwVBitMask_;
+        unsigned dwStencilBitMask_;
+        unsigned dwBumpLuminanceBitMask_;
+    };
+    union
+    {
+        unsigned dwRGBAlphaBitMask_;
+        unsigned dwYUVAlphaBitMask_;
+        unsigned dwLuminanceAlphaBitMask_;
+        unsigned dwRGBZBitMask_;
+        unsigned dwYUVZBitMask_;
+    };
+};
+
+/// DirectDraw surface capabilities.
+struct DDSCaps2
+{
+    unsigned dwCaps_;
+    unsigned dwCaps2_;
+    unsigned dwCaps3_;
+    union
+    {
+        unsigned dwCaps4_;
+        unsigned dwVolumeDepth_;
+    };
+};
+
+struct DDSHeader10
+{
+    unsigned dxgiFormat;
+    unsigned resourceDimension;
+    unsigned miscFlag;
+    unsigned arraySize;
+    unsigned reserved;
+};
+
+/// DirectDraw surface description.
+struct DDSurfaceDesc2
+{
+    unsigned dwSize_;
+    unsigned dwFlags_;
+    unsigned dwHeight_;
+    unsigned dwWidth_;
+    union
+    {
+        unsigned lPitch_;
+        unsigned dwLinearSize_;
+    };
+    union
+    {
+        unsigned dwBackBufferCount_;
+        unsigned dwDepth_;
+    };
+    union
+    {
+        unsigned dwMipMapCount_;
+        unsigned dwRefreshRate_;
+        unsigned dwSrcVBHandle_;
+    };
+    unsigned dwAlphaBitDepth_;
+    unsigned dwReserved_;
+    unsigned lpSurface_; // Do not define as a void pointer, as it is 8 bytes in a 64bit build
+    union
+    {
+        DDColorKey ddckCKDestOverlay_;
+        unsigned dwEmptyFaceColor_;
+    };
+    DDColorKey ddckCKDestBlt_;
+    DDColorKey ddckCKSrcOverlay_;
+    DDColorKey ddckCKSrcBlt_;
+    union
+    {
+        DDPixelFormat ddpfPixelFormat_;
+        unsigned dwFVF_;
+    };
+    DDSCaps2 ddsCaps_;
+    unsigned dwTextureStage_;
+};
+
+bool CompressedLevel::Decompress(unsigned char* dest)
+{
+    if (!data_)
+        return false;
+
+    switch (format_)
+    {
+    case CF_DXT1:
+    case CF_DXT3:
+    case CF_DXT5:
+        DecompressImageDXT(dest, data_, width_, height_, depth_, format_);
+        return true;
+
+    case CF_ETC1:
+        DecompressImageETC(dest, data_, width_, height_);
+        return true;
+
+    case CF_PVRTC_RGB_2BPP:
+    case CF_PVRTC_RGBA_2BPP:
+    case CF_PVRTC_RGB_4BPP:
+    case CF_PVRTC_RGBA_4BPP:
+        DecompressImagePVRTC(dest, data_, width_, height_, format_);
+        return true;
+
+    default:
+        // Unknown format
+        return false;
+    }
+}
+
+Image::Image(Context* context) :
+    Resource(context),
+    width_(0),
+    height_(0),
+    depth_(0),
+    components_(0),
+    numCompressedLevels_(0),
+    cubemap_(false),
+    array_(false),
+    sRGB_(false),
+    compressedFormat_(CF_NONE)
+{
+}
+
+Image::~Image()
+{
+}
+
+void Image::RegisterObject(Context* context)
+{
+    context->RegisterFactory<Image>();
+}
+
+bool Image::BeginLoad(Deserializer& source)
+{
+    // Check for DDS, KTX or PVR compressed format
+    String fileID = source.ReadFileID();
+
+    if (fileID == "DDS ")
+    {
+        // DDS compressed format
+        DDSurfaceDesc2 ddsd;
+        source.Read(&ddsd, sizeof(ddsd));
+
+        // DDS DX10+
+        const bool hasDXGI = ddsd.ddpfPixelFormat_.dwFourCC_ == FOURCC_DX10;
+        DDSHeader10 dxgiHeader;
+        if (hasDXGI)
+            source.Read(&dxgiHeader, sizeof(dxgiHeader));
+
+        unsigned fourCC = ddsd.ddpfPixelFormat_.dwFourCC_;
+
+        // If the DXGI header is available then remap formats and check sRGB
+        if (hasDXGI)
+        {
+            switch (dxgiHeader.dxgiFormat)
+            {
+            case DDS_DXGI_FORMAT_BC1_UNORM:
+            case DDS_DXGI_FORMAT_BC1_UNORM_SRGB:
+                fourCC = FOURCC_DXT1;
+                break;
+            case DDS_DXGI_FORMAT_BC2_UNORM:
+            case DDS_DXGI_FORMAT_BC2_UNORM_SRGB:
+                fourCC = FOURCC_DXT3;
+                break;
+            case DDS_DXGI_FORMAT_BC3_UNORM:
+            case DDS_DXGI_FORMAT_BC3_UNORM_SRGB:
+                fourCC = FOURCC_DXT5;
+                break;
+            case DDS_DXGI_FORMAT_R8G8B8A8_UNORM:
+            case DDS_DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
+                fourCC = 0;
+                break;
+            default:
+                ATOMIC_LOGERROR("Unrecognized DDS DXGI image format");
+                return false;
+            }
+
+            // Check the internal sRGB formats
+            if (dxgiHeader.dxgiFormat == DDS_DXGI_FORMAT_BC1_UNORM_SRGB ||
+                dxgiHeader.dxgiFormat == DDS_DXGI_FORMAT_BC2_UNORM_SRGB ||
+                dxgiHeader.dxgiFormat == DDS_DXGI_FORMAT_BC3_UNORM_SRGB ||
+                dxgiHeader.dxgiFormat == DDS_DXGI_FORMAT_R8G8B8A8_UNORM_SRGB)
+            {
+                sRGB_ = true;
+            }
+        }
+        switch (fourCC)
+        {
+        case FOURCC_DXT1:
+            compressedFormat_ = CF_DXT1;
+            components_ = 3;
+            break;
+
+        case FOURCC_DXT3:
+            compressedFormat_ = CF_DXT3;
+            components_ = 4;
+            break;
+
+        case FOURCC_DXT5:
+            compressedFormat_ = CF_DXT5;
+            components_ = 4;
+            break;
+
+        case 0:
+            if (ddsd.ddpfPixelFormat_.dwRGBBitCount_ != 32 && ddsd.ddpfPixelFormat_.dwRGBBitCount_ != 24 &&
+                ddsd.ddpfPixelFormat_.dwRGBBitCount_ != 16)
+            {
+                ATOMIC_LOGERROR("Unsupported DDS pixel byte size");
+                return false;
+            }
+            compressedFormat_ = CF_RGBA;
+            components_ = 4;
+            break;
+
+        default:
+            ATOMIC_LOGERROR("Unrecognized DDS image format");
+            return false;
+        }
+
+        // Is it a cube map or texture array? If so determine the size of the image chain.
+        cubemap_ = (ddsd.ddsCaps_.dwCaps2_ & DDSCAPS2_CUBEMAP_ALL_FACES) != 0 || (hasDXGI && (dxgiHeader.miscFlag & DDS_RESOURCE_MISC_TEXTURECUBE) != 0);
+        unsigned imageChainCount = 1;
+        if (cubemap_)
+            imageChainCount = 6;
+        else if (hasDXGI && dxgiHeader.arraySize > 1)
+        {
+            imageChainCount = dxgiHeader.arraySize;
+            array_ = true;
+        }
+
+        // Calculate the size of the data
+        unsigned dataSize = 0;
+        if (compressedFormat_ != CF_RGBA)
+        {
+            const unsigned blockSize = compressedFormat_ == CF_DXT1 ? 8 : 16; //DXT1/BC1 is 8 bytes, DXT3/BC2 and DXT5/BC3 are 16 bytes
+            // Add 3 to ensure valid block: ie 2x2 fits uses a whole 4x4 block
+            unsigned blocksWide = (ddsd.dwWidth_ + 3) / 4;
+            unsigned blocksHeight = (ddsd.dwHeight_ + 3) / 4;
+            dataSize = blocksWide * blocksHeight * blockSize;
+
+            // Calculate mip data size
+            unsigned x = ddsd.dwWidth_ / 2;
+            unsigned y = ddsd.dwHeight_ / 2;
+            unsigned z = ddsd.dwDepth_ / 2;
+            for (unsigned level = ddsd.dwMipMapCount_; level > 1; x /= 2, y /= 2, z /= 2, --level)
+            {
+                blocksWide = (Max(x, 1U) + 3) / 4;
+                blocksHeight = (Max(y, 1U) + 3) / 4;
+                dataSize += blockSize * blocksWide * blocksHeight * Max(z, 1U);
+            }
+        }
+        else
+        {
+            dataSize = (ddsd.ddpfPixelFormat_.dwRGBBitCount_ / 8) * ddsd.dwWidth_ * ddsd.dwHeight_ * Max(ddsd.dwDepth_, 1U);
+            // Calculate mip data size
+            unsigned x = ddsd.dwWidth_ / 2;
+            unsigned y = ddsd.dwHeight_ / 2;
+            unsigned z = ddsd.dwDepth_ / 2;
+            for (unsigned level = ddsd.dwMipMapCount_; level > 1; x /= 2, y /= 2, z /= 2, --level)
+                dataSize += (ddsd.ddpfPixelFormat_.dwRGBBitCount_ / 8) * Max(x, 1U) * Max(y, 1U) * Max(z, 1U);
+        }
+
+        // Do not use a shared ptr here, in case nothing is refcounting the image outside this function.
+        // A raw pointer is fine as the image chain (if needed) uses shared ptr's properly
+        Image* currentImage = this;
+
+        for (unsigned faceIndex = 0; faceIndex < imageChainCount; ++faceIndex)
+        {
+            currentImage->data_ = new unsigned char[dataSize];
+            currentImage->cubemap_ = cubemap_;
+            currentImage->array_ = array_;
+            currentImage->components_ = components_;
+            currentImage->compressedFormat_ = compressedFormat_;
+            currentImage->width_ = ddsd.dwWidth_;
+            currentImage->height_ = ddsd.dwHeight_;
+            currentImage->depth_ = ddsd.dwDepth_;
+
+            currentImage->numCompressedLevels_ = ddsd.dwMipMapCount_;
+            if (!currentImage->numCompressedLevels_)
+                currentImage->numCompressedLevels_ = 1;
+
+            // Memory use needs to be exact per image as it's used for verifying the data size in GetCompressedLevel()
+            // even though it would be more proper for the first image to report the size of all siblings combined
+            currentImage->SetMemoryUse(dataSize);
+
+            source.Read(currentImage->data_.Get(), dataSize);
+
+            if (faceIndex < imageChainCount - 1)
+            {
+                // Build the image chain
+                SharedPtr<Image> nextImage(new Image(context_));
+                currentImage->nextSibling_ = nextImage;
+                currentImage = nextImage;
+            }
+        }
+
+        // If uncompressed DDS, convert the data to 8bit RGBA as the texture classes can not currently use eg. RGB565 format
+        if (compressedFormat_ == CF_RGBA)
+        {
+            ATOMIC_PROFILE(ConvertDDSToRGBA);
+
+            currentImage = this;
+
+            while (currentImage)
+            {
+                unsigned sourcePixelByteSize = ddsd.ddpfPixelFormat_.dwRGBBitCount_ >> 3;
+                unsigned numPixels = dataSize / sourcePixelByteSize;
+
+#define ADJUSTSHIFT(mask, l, r) \
+                if (mask && mask >= 0x100) \
+                { \
+                    while ((mask >> r) >= 0x100) \
+                    ++r; \
+                } \
+                else if (mask && mask < 0x80) \
+                { \
+                    while ((mask << l) < 0x80) \
+                    ++l; \
+                }
+
+                unsigned rShiftL = 0, gShiftL = 0, bShiftL = 0, aShiftL = 0;
+                unsigned rShiftR = 0, gShiftR = 0, bShiftR = 0, aShiftR = 0;
+                unsigned rMask = ddsd.ddpfPixelFormat_.dwRBitMask_;
+                unsigned gMask = ddsd.ddpfPixelFormat_.dwGBitMask_;
+                unsigned bMask = ddsd.ddpfPixelFormat_.dwBBitMask_;
+                unsigned aMask = ddsd.ddpfPixelFormat_.dwRGBAlphaBitMask_;
+                ADJUSTSHIFT(rMask, rShiftL, rShiftR)
+                ADJUSTSHIFT(gMask, gShiftL, gShiftR)
+                ADJUSTSHIFT(bMask, bShiftL, bShiftR)
+                ADJUSTSHIFT(aMask, aShiftL, aShiftR)
+
+                SharedArrayPtr<unsigned char> rgbaData(new unsigned char[numPixels * 4]);
+
+                switch (sourcePixelByteSize)
+                {
+                case 4:
+                {
+                    unsigned* src = (unsigned*)currentImage->data_.Get();
+                    unsigned char* dest = rgbaData.Get();
+
+                    while (numPixels--)
+                    {
+                        unsigned pixels = *src++;
+                        *dest++ = ((pixels & rMask) << rShiftL) >> rShiftR;
+                        *dest++ = ((pixels & gMask) << gShiftL) >> gShiftR;
+                        *dest++ = ((pixels & bMask) << bShiftL) >> bShiftR;
+                        *dest++ = ((pixels & aMask) << aShiftL) >> aShiftR;
+                    }
+                }
+                break;
+
+                case 3:
+                {
+                    unsigned char* src = currentImage->data_.Get();
+                    unsigned char* dest = rgbaData.Get();
+
+                    while (numPixels--)
+                    {
+                        unsigned pixels = src[0] | (src[1] << 8) | (src[2] << 16);
+                        src += 3;
+                        *dest++ = ((pixels & rMask) << rShiftL) >> rShiftR;
+                        *dest++ = ((pixels & gMask) << gShiftL) >> gShiftR;
+                        *dest++ = ((pixels & bMask) << bShiftL) >> bShiftR;
+                        *dest++ = ((pixels & aMask) << aShiftL) >> aShiftR;
+                    }
+                }
+                break;
+
+                default:
+                {
+                    unsigned short* src = (unsigned short*)currentImage->data_.Get();
+                    unsigned char* dest = rgbaData.Get();
+
+                    while (numPixels--)
+                    {
+                        unsigned short pixels = *src++;
+                        *dest++ = ((pixels & rMask) << rShiftL) >> rShiftR;
+                        *dest++ = ((pixels & gMask) << gShiftL) >> gShiftR;
+                        *dest++ = ((pixels & bMask) << bShiftL) >> bShiftR;
+                        *dest++ = ((pixels & aMask) << aShiftL) >> aShiftR;
+                    }
+                }
+                break;
+                }
+
+                // Replace with converted data
+                currentImage->data_ = rgbaData;
+                currentImage->SetMemoryUse(numPixels * 4);
+                currentImage = currentImage->GetNextSibling();
+            }
+        }
+    }
+    else if (fileID == "\253KTX")
+    {
+        source.Seek(12);
+
+        unsigned endianness = source.ReadUInt();
+        unsigned type = source.ReadUInt();
+        /* unsigned typeSize = */ source.ReadUInt();
+        unsigned format = source.ReadUInt();
+        unsigned internalFormat = source.ReadUInt();
+        /* unsigned baseInternalFormat = */ source.ReadUInt();
+        unsigned width = source.ReadUInt();
+        unsigned height = source.ReadUInt();
+        unsigned depth = source.ReadUInt();
+        /* unsigned arrayElements = */ source.ReadUInt();
+        unsigned faces = source.ReadUInt();
+        unsigned mipmaps = source.ReadUInt();
+        unsigned keyValueBytes = source.ReadUInt();
+
+        if (endianness != 0x04030201)
+        {
+            ATOMIC_LOGERROR("Big-endian KTX files not supported");
+            return false;
+        }
+
+        if (type != 0 || format != 0)
+        {
+            ATOMIC_LOGERROR("Uncompressed KTX files not supported");
+            return false;
+        }
+
+        if (faces > 1 || depth > 1)
+        {
+            ATOMIC_LOGERROR("3D or cube KTX files not supported");
+            return false;
+        }
+
+        if (mipmaps == 0)
+        {
+            ATOMIC_LOGERROR("KTX files without explicitly specified mipmap count not supported");
+            return false;
+        }
+
+        switch (internalFormat)
+        {
+        case 0x83f1:
+            compressedFormat_ = CF_DXT1;
+            components_ = 4;
+            break;
+
+        case 0x83f2:
+            compressedFormat_ = CF_DXT3;
+            components_ = 4;
+            break;
+
+        case 0x83f3:
+            compressedFormat_ = CF_DXT5;
+            components_ = 4;
+            break;
+
+        case 0x8d64:
+            compressedFormat_ = CF_ETC1;
+            components_ = 3;
+            break;
+
+        case 0x8c00:
+            compressedFormat_ = CF_PVRTC_RGB_4BPP;
+            components_ = 3;
+            break;
+
+        case 0x8c01:
+            compressedFormat_ = CF_PVRTC_RGB_2BPP;
+            components_ = 3;
+            break;
+
+        case 0x8c02:
+            compressedFormat_ = CF_PVRTC_RGBA_4BPP;
+            components_ = 4;
+            break;
+
+        case 0x8c03:
+            compressedFormat_ = CF_PVRTC_RGBA_2BPP;
+            components_ = 4;
+            break;
+
+        default:
+            compressedFormat_ = CF_NONE;
+            break;
+        }
+
+        if (compressedFormat_ == CF_NONE)
+        {
+            ATOMIC_LOGERROR("Unsupported texture format in KTX file");
+            return false;
+        }
+
+        source.Seek(source.GetPosition() + keyValueBytes);
+        unsigned dataSize = (unsigned)(source.GetSize() - source.GetPosition() - mipmaps * sizeof(unsigned));
+
+        data_ = new unsigned char[dataSize];
+        width_ = width;
+        height_ = height;
+        numCompressedLevels_ = mipmaps;
+
+        unsigned dataOffset = 0;
+        for (unsigned i = 0; i < mipmaps; ++i)
+        {
+            unsigned levelSize = source.ReadUInt();
+            if (levelSize + dataOffset > dataSize)
+            {
+                ATOMIC_LOGERROR("KTX mipmap level data size exceeds file size");
+                return false;
+            }
+
+            source.Read(&data_[dataOffset], levelSize);
+            dataOffset += levelSize;
+            if (source.GetPosition() & 3)
+                source.Seek((source.GetPosition() + 3) & 0xfffffffc);
+        }
+
+        SetMemoryUse(dataSize);
+    }
+    else if (fileID == "PVR\3")
+    {
+        /* unsigned flags = */ source.ReadUInt();
+        unsigned pixelFormatLo = source.ReadUInt();
+        /* unsigned pixelFormatHi = */ source.ReadUInt();
+        /* unsigned colourSpace = */ source.ReadUInt();
+        /* unsigned channelType = */ source.ReadUInt();
+        unsigned height = source.ReadUInt();
+        unsigned width = source.ReadUInt();
+        unsigned depth = source.ReadUInt();
+        /* unsigned numSurfaces = */ source.ReadUInt();
+        unsigned numFaces = source.ReadUInt();
+        unsigned mipmapCount = source.ReadUInt();
+        unsigned metaDataSize = source.ReadUInt();
+
+        if (depth > 1 || numFaces > 1)
+        {
+            ATOMIC_LOGERROR("3D or cube PVR files not supported");
+            return false;
+        }
+
+        if (mipmapCount == 0)
+        {
+            ATOMIC_LOGERROR("PVR files without explicitly specified mipmap count not supported");
+            return false;
+        }
+
+        switch (pixelFormatLo)
+        {
+        case 0:
+            compressedFormat_ = CF_PVRTC_RGB_2BPP;
+            components_ = 3;
+            break;
+
+        case 1:
+            compressedFormat_ = CF_PVRTC_RGBA_2BPP;
+            components_ = 4;
+            break;
+
+        case 2:
+            compressedFormat_ = CF_PVRTC_RGB_4BPP;
+            components_ = 3;
+            break;
+
+        case 3:
+            compressedFormat_ = CF_PVRTC_RGBA_4BPP;
+            components_ = 4;
+            break;
+
+        case 6:
+            compressedFormat_ = CF_ETC1;
+            components_ = 3;
+            break;
+
+        case 7:
+            compressedFormat_ = CF_DXT1;
+            components_ = 4;
+            break;
+
+        case 9:
+            compressedFormat_ = CF_DXT3;
+            components_ = 4;
+            break;
+
+        case 11:
+            compressedFormat_ = CF_DXT5;
+            components_ = 4;
+            break;
+
+        default:
+            compressedFormat_ = CF_NONE;
+            break;
+        }
+
+        if (compressedFormat_ == CF_NONE)
+        {
+            ATOMIC_LOGERROR("Unsupported texture format in PVR file");
+            return false;
+        }
+
+        source.Seek(source.GetPosition() + metaDataSize);
+        unsigned dataSize = source.GetSize() - source.GetPosition();
+
+        data_ = new unsigned char[dataSize];
+        width_ = width;
+        height_ = height;
+        numCompressedLevels_ = mipmapCount;
+
+        source.Read(data_.Get(), dataSize);
+        SetMemoryUse(dataSize);
+    }
+#ifdef ATOMIC_WEBP
+    else if (fileID == "RIFF")
+    {
+        // WebP: https://developers.google.com/speed/webp/docs/api
+
+        // RIFF layout is:
+        //   Offset  tag
+        //   0...3   "RIFF" 4-byte tag
+        //   4...7   size of image data (including metadata) starting at offset 8
+        //   8...11  "WEBP"   our form-type signature
+        const uint8_t TAG_SIZE(4);
+
+        source.Seek(8);
+        uint8_t fourCC[TAG_SIZE];
+        memset(&fourCC, 0, sizeof(uint8_t) * TAG_SIZE);
+
+        unsigned bytesRead(source.Read(&fourCC, TAG_SIZE));
+        if (bytesRead != TAG_SIZE)
+        {
+            // Truncated.
+            ATOMIC_LOGERROR("Truncated RIFF data");
+            return false;
+        }
+        const uint8_t WEBP[TAG_SIZE] = {'W', 'E', 'B', 'P'};
+        if (memcmp(fourCC, WEBP, TAG_SIZE))
+        {
+            // VP8_STATUS_BITSTREAM_ERROR
+            ATOMIC_LOGERROR("Invalid header");
+            return false;
+        }
+
+        // Read the file to buffer.
+        size_t dataSize(source.GetSize());
+        SharedArrayPtr<uint8_t> data(new uint8_t[dataSize]);
+
+        memset(data.Get(), 0, sizeof(uint8_t) * dataSize);
+        source.Seek(0);
+        source.Read(data.Get(), dataSize);
+
+        WebPBitstreamFeatures features;
+
+        if (WebPGetFeatures(data.Get(), dataSize, &features) != VP8_STATUS_OK)
+        {
+            ATOMIC_LOGERROR("Error reading WebP image: " + source.GetName());
+            return false;
+        }
+
+        size_t imgSize(features.width * features.height * (features.has_alpha ? 4 : 3));
+        SharedArrayPtr<uint8_t> pixelData(new uint8_t[imgSize]);
+
+        bool decodeError(false);
+        if (features.has_alpha)
+        {
+            decodeError = WebPDecodeRGBAInto(data.Get(), dataSize, pixelData.Get(), imgSize, 4 * features.width) == NULL;
+        }
+        else
+        {
+            decodeError = WebPDecodeRGBInto(data.Get(), dataSize, pixelData.Get(), imgSize, 3 * features.width) == NULL;
+        }
+        if (decodeError)
+        {
+            ATOMIC_LOGERROR("Error decoding WebP image:" + source.GetName());
+            return false;
+        }
+
+        SetSize(features.width, features.height, features.has_alpha ? 4 : 3);
+        SetData(pixelData);
+    }
+#endif
+    else
+    {
+        // Not DDS, KTX or PVR, use STBImage to load other image formats as uncompressed
+        source.Seek(0);
+        int width, height;
+        unsigned components;
+        unsigned char* pixelData = GetImageData(source, width, height, components);
+        if (!pixelData)
+        {
+            ATOMIC_LOGERROR("Could not load image " + source.GetName() + ": " + String(stbi_failure_reason()));
+            return false;
+        }
+        SetSize(width, height, components);
+        SetData(pixelData);
+        FreeImageData(pixelData);
+    }
+
+    return true;
+}
+
+bool Image::Save(Serializer& dest) const
+{
+    ATOMIC_PROFILE(SaveImage);
+
+    if (IsCompressed())
+    {
+        ATOMIC_LOGERROR("Can not save compressed image " + GetName());
+        return false;
+    }
+
+    if (!data_)
+    {
+        ATOMIC_LOGERROR("Can not save zero-sized image " + GetName());
+        return false;
+    }
+
+    int len;
+    unsigned char* png = stbi_write_png_to_mem(data_.Get(), 0, width_, height_, components_, &len);
+    bool success = dest.Write(png, (unsigned)len) == (unsigned)len;
+    free(png);
+    return success;
+}
+
+bool Image::SaveFile(const String& fileName) const
+{
+    if (fileName.EndsWith(".dds", false))
+        return SaveDDS(fileName);
+    else if (fileName.EndsWith(".bmp", false))
+        return SaveBMP(fileName);
+    else if (fileName.EndsWith(".jpg", false) || fileName.EndsWith(".jpeg", false))
+        return SaveJPG(fileName, 100);
+    else if (fileName.EndsWith(".tga", false))
+        return SaveTGA(fileName);
+#ifdef ATOMIC_WEBP
+    else if (fileName.EndsWith(".webp", false))
+        return SaveWEBP(fileName, 100.0f);
+#endif
+    else
+        return SavePNG(fileName);
+}
+
+bool Image::SetSize(int width, int height, unsigned components)
+{
+    return SetSize(width, height, 1, components);
+}
+
+bool Image::SetSize(int width, int height, int depth, unsigned components)
+{
+    if (width == width_ && height == height_ && depth == depth_ && components == components_)
+        return true;
+
+    if (width <= 0 || height <= 0 || depth <= 0)
+        return false;
+
+    if (components > 4)
+    {
+        ATOMIC_LOGERROR("More than 4 color components are not supported");
+        return false;
+    }
+
+    data_ = new unsigned char[width * height * depth * components];
+    width_ = width;
+    height_ = height;
+    depth_ = depth;
+    components_ = components;
+    compressedFormat_ = CF_NONE;
+    numCompressedLevels_ = 0;
+    nextLevel_.Reset();
+
+    SetMemoryUse(width * height * depth * components);
+    return true;
+}
+
+void Image::SetPixel(int x, int y, const Color& color)
+{
+    SetPixelInt(x, y, 0, color.ToUInt());
+}
+
+void Image::SetPixel(int x, int y, int z, const Color& color)
+{
+    SetPixelInt(x, y, z, color.ToUInt());
+}
+
+void Image::SetPixelInt(int x, int y, unsigned uintColor)
+{
+    SetPixelInt(x, y, 0, uintColor);
+}
+
+void Image::SetPixelInt(int x, int y, int z, unsigned uintColor)
+{
+    if (!data_ || x < 0 || x >= width_ || y < 0 || y >= height_ || z < 0 || z >= depth_ || IsCompressed())
+        return;
+
+    unsigned char* dest = data_ + (z * width_ * height_ + y * width_ + x) * components_;
+    unsigned char* src = (unsigned char*)&uintColor;
+
+    switch (components_)
+    {
+    case 4:
+        dest[3] = src[3];
+        // Fall through
+    case 3:
+        dest[2] = src[2];
+        // Fall through
+    case 2:
+        dest[1] = src[1];
+        // Fall through
+    default:
+        dest[0] = src[0];
+        break;
+    }
+}
+
+void Image::SetData(const unsigned char* pixelData)
+{
+    if (!data_)
+        return;
+
+    if (IsCompressed())
+    {
+        ATOMIC_LOGERROR("Can not set new pixel data for a compressed image");
+        return;
+    }
+
+    memcpy(data_.Get(), pixelData, width_ * height_ * depth_ * components_);
+    nextLevel_.Reset();
+}
+
+bool Image::LoadColorLUT(Deserializer& source)
+{
+    String fileID = source.ReadFileID();
+
+    if (fileID == "DDS " || fileID == "\253KTX" || fileID == "PVR\3")
+    {
+        ATOMIC_LOGERROR("Invalid image format, can not load image");
+        return false;
+    }
+
+    source.Seek(0);
+    int width, height;
+    unsigned components;
+    unsigned char* pixelDataIn = GetImageData(source, width, height, components);
+    if (!pixelDataIn)
+    {
+        ATOMIC_LOGERROR("Could not load image " + source.GetName() + ": " + String(stbi_failure_reason()));
+        return false;
+    }
+    if (components != 3)
+    {
+        ATOMIC_LOGERROR("Invalid image format, can not load image");
+        return false;
+    }
+
+    SetSize(COLOR_LUT_SIZE, COLOR_LUT_SIZE, COLOR_LUT_SIZE, components);
+    SetMemoryUse(width_ * height_ * depth_ * components);
+
+    unsigned char* pixelDataOut = GetData();
+
+    for (int z = 0; z < depth_; ++z)
+    {
+        for (int y = 0; y < height_; ++y)
+        {
+            unsigned char* in = &pixelDataIn[z * width_ * 3 + y * width * 3];
+            unsigned char* out = &pixelDataOut[z * width_ * height_ * 3 + y * width_ * 3];
+
+            for (int x = 0; x < width_ * 3; x += 3)
+            {
+                out[x] = in[x];
+                out[x + 1] = in[x + 1];
+                out[x + 2] = in[x + 2];
+            }
+        }
+    }
+
+    FreeImageData(pixelDataIn);
+
+    return true;
+}
+
+bool Image::FlipHorizontal()
+{
+    if (!data_)
+        return false;
+
+    if (depth_ > 1)
+    {
+        ATOMIC_LOGERROR("FlipHorizontal not supported for 3D images");
+        return false;
+    }
+
+    if (!IsCompressed())
+    {
+        SharedArrayPtr<unsigned char> newData(new unsigned char[width_ * height_ * components_]);
+        unsigned rowSize = width_ * components_;
+
+        for (int y = 0; y < height_; ++y)
+        {
+            for (int x = 0; x < width_; ++x)
+            {
+                for (unsigned c = 0; c < components_; ++c)
+                    newData[y * rowSize + x * components_ + c] = data_[y * rowSize + (width_ - x - 1) * components_ + c];
+            }
+        }
+
+        data_ = newData;
+    }
+    else
+    {
+        if (compressedFormat_ > CF_DXT5)
+        {
+            ATOMIC_LOGERROR("FlipHorizontal not yet implemented for other compressed formats than RGBA & DXT1,3,5");
+            return false;
+        }
+
+        // Memory use = combined size of the compressed mip levels
+        SharedArrayPtr<unsigned char> newData(new unsigned char[GetMemoryUse()]);
+        unsigned dataOffset = 0;
+
+        for (unsigned i = 0; i < numCompressedLevels_; ++i)
+        {
+            CompressedLevel level = GetCompressedLevel(i);
+            if (!level.data_)
+            {
+                ATOMIC_LOGERROR("Got compressed level with no data, aborting horizontal flip");
+                return false;
+            }
+
+            for (unsigned y = 0; y < level.rows_; ++y)
+            {
+                for (unsigned x = 0; x < level.rowSize_; x += level.blockSize_)
+                {
+                    unsigned char* src = level.data_ + y * level.rowSize_ + (level.rowSize_ - level.blockSize_ - x);
+                    unsigned char* dest = newData.Get() + y * level.rowSize_ + x;
+                    FlipBlockHorizontal(dest, src, compressedFormat_);
+                }
+            }
+
+            dataOffset += level.dataSize_;
+        }
+
+        data_ = newData;
+    }
+
+    return true;
+}
+
+bool Image::FlipVertical()
+{
+    if (!data_)
+        return false;
+
+    if (depth_ > 1)
+    {
+        ATOMIC_LOGERROR("FlipVertical not supported for 3D images");
+        return false;
+    }
+
+    if (!IsCompressed())
+    {
+        SharedArrayPtr<unsigned char> newData(new unsigned char[width_ * height_ * components_]);
+        unsigned rowSize = width_ * components_;
+
+        for (int y = 0; y < height_; ++y)
+            memcpy(&newData[(height_ - y - 1) * rowSize], &data_[y * rowSize], rowSize);
+
+        data_ = newData;
+    }
+    else
+    {
+        if (compressedFormat_ > CF_DXT5)
+        {
+            ATOMIC_LOGERROR("FlipVertical not yet implemented for other compressed formats than DXT1,3,5");
+            return false;
+        }
+
+        // Memory use = combined size of the compressed mip levels
+        SharedArrayPtr<unsigned char> newData(new unsigned char[GetMemoryUse()]);
+        unsigned dataOffset = 0;
+
+        for (unsigned i = 0; i < numCompressedLevels_; ++i)
+        {
+            CompressedLevel level = GetCompressedLevel(i);
+            if (!level.data_)
+            {
+                ATOMIC_LOGERROR("Got compressed level with no data, aborting vertical flip");
+                return false;
+            }
+
+            for (unsigned y = 0; y < level.rows_; ++y)
+            {
+                unsigned char* src = level.data_ + y * level.rowSize_;
+                unsigned char* dest = newData.Get() + dataOffset + (level.rows_ - y - 1) * level.rowSize_;
+
+                for (unsigned x = 0; x < level.rowSize_; x += level.blockSize_)
+                    FlipBlockVertical(dest + x, src + x, compressedFormat_);
+            }
+
+            dataOffset += level.dataSize_;
+        }
+
+        data_ = newData;
+    }
+
+    return true;
+}
+
+bool Image::Resize(int width, int height)
+{
+    ATOMIC_PROFILE(ResizeImage);
+
+    if (IsCompressed())
+    {
+        ATOMIC_LOGERROR("Resize not supported for compressed images");
+        return false;
+    }
+
+    if (depth_ > 1)
+    {
+        ATOMIC_LOGERROR("Resize not supported for 3D images");
+        return false;
+    }
+
+    if (!data_ || width <= 0 || height <= 0)
+        return false;
+
+    /// \todo Reducing image size does not sample all needed pixels
+    SharedArrayPtr<unsigned char> newData(new unsigned char[width * height * components_]);
+    for (int y = 0; y < height; ++y)
+    {
+        for (int x = 0; x < width; ++x)
+        {
+            // Calculate float coordinates between 0 - 1 for resampling
+            float xF = (width_ > 1) ? (float)x / (float)(width - 1) : 0.0f;
+            float yF = (height_ > 1) ? (float)y / (float)(height - 1) : 0.0f;
+            unsigned uintColor = GetPixelBilinear(xF, yF).ToUInt();
+            unsigned char* dest = newData + (y * width + x) * components_;
+            unsigned char* src = (unsigned char*)&uintColor;
+
+            switch (components_)
+            {
+            case 4:
+                dest[3] = src[3];
+                // Fall through
+            case 3:
+                dest[2] = src[2];
+                // Fall through
+            case 2:
+                dest[1] = src[1];
+                // Fall through
+            default:
+                dest[0] = src[0];
+                break;
+            }
+        }
+    }
+
+    width_ = width;
+    height_ = height;
+    data_ = newData;
+    SetMemoryUse(width * height * depth_ * components_);
+    return true;
+}
+
+void Image::Clear(const Color& color)
+{
+    ClearInt(color.ToUInt());
+}
+
+void Image::ClearInt(unsigned uintColor)
+{
+    ATOMIC_PROFILE(ClearImage);
+
+    if (!data_)
+        return;
+
+    if (IsCompressed())
+    {
+        ATOMIC_LOGERROR("Clear not supported for compressed images");
+        return;
+    }
+
+    if (components_ == 4)
+    {
+        unsigned color = uintColor;
+        unsigned* data = (unsigned*)GetData();
+        unsigned* data_end = (unsigned*)(GetData() + width_ * height_ * depth_ * components_);
+        for (; data < data_end; ++data)
+            *data = color;
+    }
+    else
+    {
+        unsigned char* src = (unsigned char*)&uintColor;
+        for (unsigned i = 0; i < width_ * height_ * depth_ * components_; ++i)
+            data_[i] = src[i % components_];
+    }
+}
+
+bool Image::SaveBMP(const String& fileName) const
+{
+    ATOMIC_PROFILE(SaveImageBMP);
+
+    FileSystem* fileSystem = GetSubsystem<FileSystem>();
+    if (fileSystem && !fileSystem->CheckAccess(GetPath(fileName)))
+    {
+        ATOMIC_LOGERROR("Access denied to " + fileName);
+        return false;
+    }
+
+    if (IsCompressed())
+    {
+        ATOMIC_LOGERROR("Can not save compressed image to BMP");
+        return false;
+    }
+
+    if (data_)
+        return stbi_write_bmp(fileName.CString(), width_, height_, components_, data_.Get()) != 0;
+    else
+        return false;
+}
+
+bool Image::SavePNG(const String& fileName) const
+{
+    ATOMIC_PROFILE(SaveImagePNG);
+
+    File outFile(context_, fileName, FILE_WRITE);
+    if (outFile.IsOpen())
+        return Image::Save(outFile); // Save uses PNG format
+    else
+        return false;
+}
+
+bool Image::SaveTGA(const String& fileName) const
+{
+    ATOMIC_PROFILE(SaveImageTGA);
+
+    FileSystem* fileSystem = GetSubsystem<FileSystem>();
+    if (fileSystem && !fileSystem->CheckAccess(GetPath(fileName)))
+    {
+        ATOMIC_LOGERROR("Access denied to " + fileName);
+        return false;
+    }
+
+    if (IsCompressed())
+    {
+        ATOMIC_LOGERROR("Can not save compressed image to TGA");
+        return false;
+    }
+
+    if (data_)
+        return stbi_write_tga(GetNativePath(fileName).CString(), width_, height_, components_, data_.Get()) != 0;
+    else
+        return false;
+}
+
+bool Image::SaveJPG(const String& fileName, int quality) const
+{
+    ATOMIC_PROFILE(SaveImageJPG);
+
+    FileSystem* fileSystem = GetSubsystem<FileSystem>();
+    if (fileSystem && !fileSystem->CheckAccess(GetPath(fileName)))
+    {
+        ATOMIC_LOGERROR("Access denied to " + fileName);
+        return false;
+    }
+
+    if (IsCompressed())
+    {
+        ATOMIC_LOGERROR("Can not save compressed image to JPG");
+        return false;
+    }
+
+    if (data_)
+        return jo_write_jpg(GetNativePath(fileName).CString(), data_.Get(), width_, height_, components_, quality) != 0;
+    else
+        return false;
+}
+// ATOMIC BEGIN
+bool Image::SaveDDS(const String& fileName) const
+{
+#if !defined(ATOMIC_PLATFORM_DESKTOP)
+
+    ATOMIC_LOGERRORF("Image::SaveDDS - Unsupported on current platform: %s", fileName.CString());
+    return false;
+
+#else
+    ATOMIC_PROFILE(SaveImageDDS);
+
+    FileSystem* fileSystem = GetSubsystem<FileSystem>();
+    if (fileSystem && !fileSystem->CheckAccess(GetPath(fileName)))
+    {
+        ATOMIC_LOGERROR("Access denied to " + fileName);
+        return false;
+    }
+
+    if (IsCompressed())
+    {
+        ATOMIC_LOGERROR("Can not save compressed image to DDS");
+        return false;
+    }
+
+    if (data_)
+    {
+        // #623 BEGIN TODO: Should have an abstract ImageReader and ImageWriter classes
+        // with subclasses for particular image output types. Also should have image settings in the image meta.
+        // ImageReader/Writers should also support a progress callback so UI can be updated.
+
+        if (!width_ || !height_)
+        {
+            ATOMIC_LOGERRORF("Attempting to save zero width/height DDS to %s", fileName.CString());
+            return false;
+        }
+
+        squish::u8 *inputData = (squish::u8 *) data_.Get();
+
+        SharedPtr<Image> tempImage;
+
+        // libsquish expects 4 channel RGBA, so create a temporary image if necessary
+        if (components_ != 4)
+        {
+            if (components_ != 3)
+            {
+                ATOMIC_LOGERROR("Can only save images with 3 or 4 components to DDS");
+                return false;
+            }
+
+            tempImage = new Image(context_);
+            tempImage->SetSize(width_, height_, 4);
+
+            squish::u8* srcBits = (squish::u8*) data_;
+            squish::u8* dstBits = (squish::u8*) tempImage->data_;
+
+            for (unsigned i = 0; i < (unsigned) width_ * (unsigned) height_; i++)
+            {
+                *dstBits++ = *srcBits++;
+                *dstBits++ = *srcBits++;
+                *dstBits++ = *srcBits++;
+                *dstBits++ = 255;
+            }
+
+            inputData = (squish::u8 *) tempImage->data_.Get();
+        }
+
+        unsigned squishFlags = HasAlphaChannel() ? squish::kDxt5 : squish::kDxt1;
+
+        // Use a slow but high quality colour compressor (the default). (TODO: expose other settings as parameter)
+        squishFlags |= squish::kColourClusterFit;
+
+        unsigned storageRequirements = (unsigned) squish::GetStorageRequirements( width_, height_,  squishFlags);
+
+        SharedArrayPtr<unsigned char> compressedData(new unsigned char[storageRequirements]);
+
+        squish::CompressImage(inputData, width_, height_, compressedData.Get(), squishFlags);
+
+        DDSurfaceDesc2 sdesc;
+
+        if (sizeof(sdesc) != 124)
+        {
+            ATOMIC_LOGERROR("Image::SaveDDS - sizeof(DDSurfaceDesc2) != 124");
+            return false;
+        }
+        memset(&sdesc, 0, sizeof(sdesc));
+        sdesc.dwSize_ = 124;
+        sdesc.dwFlags_ = DDSD_CAPS | DDSD_PIXELFORMAT | DDSD_WIDTH | DDSD_HEIGHT;
+        sdesc.dwWidth_ = width_;
+        sdesc.dwHeight_ = height_;
+
+        sdesc.ddpfPixelFormat_.dwSize_ = 32;
+        sdesc.ddpfPixelFormat_.dwFlags_ = DDPF_FOURCC | (HasAlphaChannel() ? DDPF_ALPHAPIXELS : 0);
+        sdesc.ddpfPixelFormat_.dwFourCC_ = HasAlphaChannel() ? FOURCC_DXT5 : FOURCC_DXT1;
+
+        SharedPtr<File> dest(new File(context_, fileName, FILE_WRITE));
+
+        if (!dest->IsOpen())
+        {
+            ATOMIC_LOGERRORF("Failed to open DXT image file for writing %s", fileName.CString());
+            return false;
+        }
+        else
+        {
+
+            dest->Write((void*)"DDS ", 4);
+            dest->Write((void*)&sdesc, sizeof(sdesc));
+
+            bool success = dest->Write(compressedData.Get(), storageRequirements) == storageRequirements;
+
+            if (!success)
+                ATOMIC_LOGERRORF("Failed to write image to DXT, file size mismatch %s", fileName.CString());
+
+            return success;
+        }
+        // #623 END TODO
+    }
+#endif
+}
+
+bool Image::SaveWEBP(const String& fileName, float compression /* = 0.0f */) const
+{
+#ifdef ATOMIC_WEBP
+    ATOMIC_PROFILE(SaveImageWEBP);
+
+    FileSystem* fileSystem(GetSubsystem<FileSystem>());
+    File outFile(context_, fileName, FILE_WRITE);
+
+    if (fileSystem && !fileSystem->CheckAccess(GetPath(fileName)))
+    {
+        ATOMIC_LOGERROR("Access denied to " + fileName);
+        return false;
+    }
+
+    if (IsCompressed())
+    {
+        ATOMIC_LOGERROR("Can not save compressed image to WebP");
+        return false;
+    }
+
+    if (height_ > WEBP_MAX_DIMENSION || width_ > WEBP_MAX_DIMENSION)
+    {
+        ATOMIC_LOGERROR("Maximum dimension supported by WebP is " + String(WEBP_MAX_DIMENSION));
+        return false;
+    }
+
+    if (components_ != 4 && components_ != 3)
+    {
+        ATOMIC_LOGERRORF("Can not save image with %u components to WebP, which requires 3 or 4; Try ConvertToRGBA first?", components_);
+        return false;
+    }
+
+    if (!data_)
+    {
+        ATOMIC_LOGERROR("No image data to save");
+        return false;
+    }
+
+    WebPPicture pic;
+    WebPConfig config;
+    WebPMemoryWriter wrt;
+    int importResult(0);
+    size_t encodeResult(0);
+
+    if (!WebPConfigPreset(&config, WEBP_PRESET_DEFAULT, compression) || !WebPPictureInit(&pic))
+    {
+        ATOMIC_LOGERROR("WebP initialization failed; check installation");
+        return false;
+    }
+    config.lossless = 1;
+    config.exact = 1; // Preserve RGB values under transparency, as they may be wanted.
+
+    pic.use_argb = 1;
+    pic.width = width_;
+    pic.height = height_;
+    pic.writer = WebPMemoryWrite;
+    pic.custom_ptr = &wrt;
+    WebPMemoryWriterInit(&wrt);
+
+    if (components_ == 4)
+        importResult = WebPPictureImportRGBA(&pic, data_.Get(), components_ * width_);
+    else if (components_ == 3)
+        importResult = WebPPictureImportRGB(&pic, data_.Get(), components_ * width_);
+
+    if (!importResult)
+    {
+        ATOMIC_LOGERROR("WebP import of image data failed (truncated RGBA/RGB data or memory error?)");
+        WebPPictureFree(&pic);
+        WebPMemoryWriterClear(&wrt);
+        return false;
+    }
+
+    encodeResult = WebPEncode(&config, &pic);
+    // Check only general failure. WebPEncode() sets pic.error_code with specific error.
+    if (!encodeResult)
+    {
+        ATOMIC_LOGERRORF("WebP encoding failed (memory error?). WebPEncodingError = %d", pic.error_code);
+        WebPPictureFree(&pic);
+        WebPMemoryWriterClear(&wrt);
+        return false;
+    }
+
+    WebPPictureFree(&pic);
+    outFile.Write(wrt.mem, wrt.size);
+    WebPMemoryWriterClear(&wrt);
+
+    return true;
+#else
+    ATOMIC_LOGERROR("Cannot save in WEBP format, support not compiled in");
+    return false;
+#endif
+}
+
+Color Image::GetPixel(int x, int y) const
+{
+    return GetPixel(x, y, 0);
+}
+
+Color Image::GetPixel(int x, int y, int z) const
+{
+    if (!data_ || z < 0 || z >= depth_ || IsCompressed())
+        return Color::BLACK;
+    x = Clamp(x, 0, width_ - 1);
+    y = Clamp(y, 0, height_ - 1);
+
+    unsigned char* src = data_ + (z * width_ * height_ + y * width_ + x) * components_;
+    Color ret;
+
+    switch (components_)
+    {
+    case 4:
+        ret.a_ = (float)src[3] / 255.0f;
+        // Fall through
+    case 3:
+        ret.b_ = (float)src[2] / 255.0f;
+        // Fall through
+    case 2:
+        ret.g_ = (float)src[1] / 255.0f;
+        ret.r_ = (float)src[0] / 255.0f;
+        break;
+    default:
+        ret.r_ = ret.g_ = ret.b_ = (float)src[0] / 255.0f;
+        break;
+    }
+
+    return ret;
+}
+
+unsigned Image::GetPixelInt(int x, int y) const
+{
+    return GetPixelInt(x, y, 0);
+}
+
+unsigned Image::GetPixelInt(int x, int y, int z) const
+{
+    if (!data_ || z < 0 || z >= depth_ || IsCompressed())
+        return 0xff000000;
+    x = Clamp(x, 0, width_ - 1);
+    y = Clamp(y, 0, height_ - 1);
+
+    unsigned char* src = data_ + (z * width_ * height_ + y * width_ + x) * components_;
+    unsigned ret = 0;
+    if (components_ < 4)
+        ret |= 0xff000000;
+
+    switch (components_)
+    {
+    case 4:
+        ret |= (unsigned)src[3] << 24;
+        // Fall through
+    case 3:
+        ret |= (unsigned)src[2] << 16;
+        // Fall through
+    case 2:
+        ret |= (unsigned)src[1] << 8;
+        ret |= (unsigned)src[0];
+        break;
+    default:
+        ret |= (unsigned)src[0] << 16;
+        ret |= (unsigned)src[0] << 8;
+        ret |= (unsigned)src[0];
+        break;
+    }
+
+    return ret;
+}
+
+Color Image::GetPixelBilinear(float x, float y) const
+{
+    x = Clamp(x * width_ - 0.5f, 0.0f, (float)(width_ - 1));
+    y = Clamp(y * height_ - 0.5f, 0.0f, (float)(height_ - 1));
+
+    int xI = (int)x;
+    int yI = (int)y;
+    float xF = Fract(x);
+    float yF = Fract(y);
+
+    Color topColor = GetPixel(xI, yI).Lerp(GetPixel(xI + 1, yI), xF);
+    Color bottomColor = GetPixel(xI, yI + 1).Lerp(GetPixel(xI + 1, yI + 1), xF);
+    return topColor.Lerp(bottomColor, yF);
+}
+
+Color Image::GetPixelTrilinear(float x, float y, float z) const
+{
+    if (depth_ < 2)
+        return GetPixelBilinear(x, y);
+
+    x = Clamp(x * width_ - 0.5f, 0.0f, (float)(width_ - 1));
+    y = Clamp(y * height_ - 0.5f, 0.0f, (float)(height_ - 1));
+    z = Clamp(z * depth_ - 0.5f, 0.0f, (float)(depth_ - 1));
+
+    int xI = (int)x;
+    int yI = (int)y;
+    int zI = (int)z;
+    if (zI == depth_ - 1)
+        return GetPixelBilinear(x, y);
+    float xF = Fract(x);
+    float yF = Fract(y);
+    float zF = Fract(z);
+
+    Color topColorNear = GetPixel(xI, yI, zI).Lerp(GetPixel(xI + 1, yI, zI), xF);
+    Color bottomColorNear = GetPixel(xI, yI + 1, zI).Lerp(GetPixel(xI + 1, yI + 1, zI), xF);
+    Color colorNear = topColorNear.Lerp(bottomColorNear, yF);
+    Color topColorFar = GetPixel(xI, yI, zI + 1).Lerp(GetPixel(xI + 1, yI, zI + 1), xF);
+    Color bottomColorFar = GetPixel(xI, yI + 1, zI + 1).Lerp(GetPixel(xI + 1, yI + 1, zI + 1), xF);
+    Color colorFar = topColorFar.Lerp(bottomColorFar, yF);
+    return colorNear.Lerp(colorFar, zF);
+}
+
+SharedPtr<Image> Image::GetNextLevel() const
+{
+    if (IsCompressed())
+    {
+        ATOMIC_LOGERROR("Can not generate mip level from compressed data");
+        return SharedPtr<Image>();
+    }
+    if (components_ < 1 || components_ > 4)
+    {
+        ATOMIC_LOGERROR("Illegal number of image components for mip level generation");
+        return SharedPtr<Image>();
+    }
+
+    if (nextLevel_)
+        return nextLevel_;
+
+    ATOMIC_PROFILE(CalculateImageMipLevel);
+
+    int widthOut = width_ / 2;
+    int heightOut = height_ / 2;
+    int depthOut = depth_ / 2;
+
+    if (widthOut < 1)
+        widthOut = 1;
+    if (heightOut < 1)
+        heightOut = 1;
+    if (depthOut < 1)
+        depthOut = 1;
+
+    SharedPtr<Image> mipImage(new Image(context_));
+
+    if (depth_ > 1)
+        mipImage->SetSize(widthOut, heightOut, depthOut, components_);
+    else
+        mipImage->SetSize(widthOut, heightOut, components_);
+
+    const unsigned char* pixelDataIn = data_.Get();
+    unsigned char* pixelDataOut = mipImage->data_.Get();
+
+    // 1D case
+    if (depth_ == 1 && (height_ == 1 || width_ == 1))
+    {
+        // Loop using the larger dimension
+        if (widthOut < heightOut)
+            widthOut = heightOut;
+
+        switch (components_)
+        {
+        case 1:
+            for (int x = 0; x < widthOut; ++x)
+                pixelDataOut[x] = (unsigned char)(((unsigned)pixelDataIn[x * 2] + pixelDataIn[x * 2 + 1]) >> 1);
+            break;
+
+        case 2:
+            for (int x = 0; x < widthOut * 2; x += 2)
+            {
+                pixelDataOut[x] = (unsigned char)(((unsigned)pixelDataIn[x * 2] + pixelDataIn[x * 2 + 2]) >> 1);
+                pixelDataOut[x + 1] = (unsigned char)(((unsigned)pixelDataIn[x * 2 + 1] + pixelDataIn[x * 2 + 3]) >> 1);
+            }
+            break;
+
+        case 3:
+            for (int x = 0; x < widthOut * 3; x += 3)
+            {
+                pixelDataOut[x] = (unsigned char)(((unsigned)pixelDataIn[x * 2] + pixelDataIn[x * 2 + 3]) >> 1);
+                pixelDataOut[x + 1] = (unsigned char)(((unsigned)pixelDataIn[x * 2 + 1] + pixelDataIn[x * 2 + 4]) >> 1);
+                pixelDataOut[x + 2] = (unsigned char)(((unsigned)pixelDataIn[x * 2 + 2] + pixelDataIn[x * 2 + 5]) >> 1);
+            }
+            break;
+
+        case 4:
+            for (int x = 0; x < widthOut * 4; x += 4)
+            {
+                pixelDataOut[x] = (unsigned char)(((unsigned)pixelDataIn[x * 2] + pixelDataIn[x * 2 + 4]) >> 1);
+                pixelDataOut[x + 1] = (unsigned char)(((unsigned)pixelDataIn[x * 2 + 1] + pixelDataIn[x * 2 + 5]) >> 1);
+                pixelDataOut[x + 2] = (unsigned char)(((unsigned)pixelDataIn[x * 2 + 2] + pixelDataIn[x * 2 + 6]) >> 1);
+                pixelDataOut[x + 3] = (unsigned char)(((unsigned)pixelDataIn[x * 2 + 3] + pixelDataIn[x * 2 + 7]) >> 1);
+            }
+            break;
+
+        default:
+            assert(false);  // Should never reach here
+            break;
+        }
+    }
+    // 2D case
+    else if (depth_ == 1)
+    {
+        switch (components_)
+        {
+        case 1:
+            for (int y = 0; y < heightOut; ++y)
+            {
+                const unsigned char* inUpper = &pixelDataIn[(y * 2) * width_];
+                const unsigned char* inLower = &pixelDataIn[(y * 2 + 1) * width_];
+                unsigned char* out = &pixelDataOut[y * widthOut];
+
+                for (int x = 0; x < widthOut; ++x)
+                {
+                    out[x] = (unsigned char)(((unsigned)inUpper[x * 2] + inUpper[x * 2 + 1] +
+                                              inLower[x * 2] + inLower[x * 2 + 1]) >> 2);
+                }
+            }
+            break;
+
+        case 2:
+            for (int y = 0; y < heightOut; ++y)
+            {
+                const unsigned char* inUpper = &pixelDataIn[(y * 2) * width_ * 2];
+                const unsigned char* inLower = &pixelDataIn[(y * 2 + 1) * width_ * 2];
+                unsigned char* out = &pixelDataOut[y * widthOut * 2];
+
+                for (int x = 0; x < widthOut * 2; x += 2)
+                {
+                    out[x] = (unsigned char)(((unsigned)inUpper[x * 2] + inUpper[x * 2 + 2] +
+                                              inLower[x * 2] + inLower[x * 2 + 2]) >> 2);
+                    out[x + 1] = (unsigned char)(((unsigned)inUpper[x * 2 + 1] + inUpper[x * 2 + 3] +
+                                                  inLower[x * 2 + 1] + inLower[x * 2 + 3]) >> 2);
+                }
+            }
+            break;
+
+        case 3:
+            for (int y = 0; y < heightOut; ++y)
+            {
+                const unsigned char* inUpper = &pixelDataIn[(y * 2) * width_ * 3];
+                const unsigned char* inLower = &pixelDataIn[(y * 2 + 1) * width_ * 3];
+                unsigned char* out = &pixelDataOut[y * widthOut * 3];
+
+                for (int x = 0; x < widthOut * 3; x += 3)
+                {
+                    out[x] = (unsigned char)(((unsigned)inUpper[x * 2] + inUpper[x * 2 + 3] +
+                                              inLower[x * 2] + inLower[x * 2 + 3]) >> 2);
+                    out[x + 1] = (unsigned char)(((unsigned)inUpper[x * 2 + 1] + inUpper[x * 2 + 4] +
+                                                  inLower[x * 2 + 1] + inLower[x * 2 + 4]) >> 2);
+                    out[x + 2] = (unsigned char)(((unsigned)inUpper[x * 2 + 2] + inUpper[x * 2 + 5] +
+                                                  inLower[x * 2 + 2] + inLower[x * 2 + 5]) >> 2);
+                }
+            }
+            break;
+
+        case 4:
+            for (int y = 0; y < heightOut; ++y)
+            {
+                const unsigned char* inUpper = &pixelDataIn[(y * 2) * width_ * 4];
+                const unsigned char* inLower = &pixelDataIn[(y * 2 + 1) * width_ * 4];
+                unsigned char* out = &pixelDataOut[y * widthOut * 4];
+
+                for (int x = 0; x < widthOut * 4; x += 4)
+                {
+                    out[x] = (unsigned char)(((unsigned)inUpper[x * 2] + inUpper[x * 2 + 4] +
+                                              inLower[x * 2] + inLower[x * 2 + 4]) >> 2);
+                    out[x + 1] = (unsigned char)(((unsigned)inUpper[x * 2 + 1] + inUpper[x * 2 + 5] +
+                                                  inLower[x * 2 + 1] + inLower[x * 2 + 5]) >> 2);
+                    out[x + 2] = (unsigned char)(((unsigned)inUpper[x * 2 + 2] + inUpper[x * 2 + 6] +
+                                                  inLower[x * 2 + 2] + inLower[x * 2 + 6]) >> 2);
+                    out[x + 3] = (unsigned char)(((unsigned)inUpper[x * 2 + 3] + inUpper[x * 2 + 7] +
+                                                  inLower[x * 2 + 3] + inLower[x * 2 + 7]) >> 2);
+                }
+            }
+            break;
+
+        default:
+            assert(false);  // Should never reach here
+            break;
+        }
+    }
+    // 3D case
+    else
+    {
+        switch (components_)
+        {
+        case 1:
+            for (int z = 0; z < depthOut; ++z)
+            {
+                const unsigned char* inOuter = &pixelDataIn[(z * 2) * width_ * height_];
+                const unsigned char* inInner = &pixelDataIn[(z * 2 + 1) * width_ * height_];
+
+                for (int y = 0; y < heightOut; ++y)
+                {
+                    const unsigned char* inOuterUpper = &inOuter[(y * 2) * width_];
+                    const unsigned char* inOuterLower = &inOuter[(y * 2 + 1) * width_];
+                    const unsigned char* inInnerUpper = &inInner[(y * 2) * width_];
+                    const unsigned char* inInnerLower = &inInner[(y * 2 + 1) * width_];
+                    unsigned char* out = &pixelDataOut[z * widthOut * heightOut + y * widthOut];
+
+                    for (int x = 0; x < widthOut; ++x)
+                    {
+                        out[x] = (unsigned char)(((unsigned)inOuterUpper[x * 2] + inOuterUpper[x * 2 + 1] +
+                                                  inOuterLower[x * 2] + inOuterLower[x * 2 + 1] +
+                                                  inInnerUpper[x * 2] + inInnerUpper[x * 2 + 1] +
+                                                  inInnerLower[x * 2] + inInnerLower[x * 2 + 1]) >> 3);
+                    }
+                }
+            }
+            break;
+
+        case 2:
+            for (int z = 0; z < depthOut; ++z)
+            {
+                const unsigned char* inOuter = &pixelDataIn[(z * 2) * width_ * height_ * 2];
+                const unsigned char* inInner = &pixelDataIn[(z * 2 + 1) * width_ * height_ * 2];
+
+                for (int y = 0; y < heightOut; ++y)
+                {
+                    const unsigned char* inOuterUpper = &inOuter[(y * 2) * width_ * 2];
+                    const unsigned char* inOuterLower = &inOuter[(y * 2 + 1) * width_ * 2];
+                    const unsigned char* inInnerUpper = &inInner[(y * 2) * width_ * 2];
+                    const unsigned char* inInnerLower = &inInner[(y * 2 + 1) * width_ * 2];
+                    unsigned char* out = &pixelDataOut[z * widthOut * heightOut * 2 + y * widthOut * 2];
+
+                    for (int x = 0; x < widthOut * 2; x += 2)
+                    {
+                        out[x] = (unsigned char)(((unsigned)inOuterUpper[x * 2] + inOuterUpper[x * 2 + 2] +
+                                                  inOuterLower[x * 2] + inOuterLower[x * 2 + 2] +
+                                                  inInnerUpper[x * 2] + inInnerUpper[x * 2 + 2] +
+                                                  inInnerLower[x * 2] + inInnerLower[x * 2 + 2]) >> 3);
+                        out[x + 1] = (unsigned char)(((unsigned)inOuterUpper[x * 2 + 1] + inOuterUpper[x * 2 + 3] +
+                                                      inOuterLower[x * 2 + 1] + inOuterLower[x * 2 + 3] +
+                                                      inInnerUpper[x * 2 + 1] + inInnerUpper[x * 2 + 3] +
+                                                      inInnerLower[x * 2 + 1] + inInnerLower[x * 2 + 3]) >> 3);
+                    }
+                }
+            }
+            break;
+
+        case 3:
+            for (int z = 0; z < depthOut; ++z)
+            {
+                const unsigned char* inOuter = &pixelDataIn[(z * 2) * width_ * height_ * 3];
+                const unsigned char* inInner = &pixelDataIn[(z * 2 + 1) * width_ * height_ * 3];
+
+                for (int y = 0; y < heightOut; ++y)
+                {
+                    const unsigned char* inOuterUpper = &inOuter[(y * 2) * width_ * 3];
+                    const unsigned char* inOuterLower = &inOuter[(y * 2 + 1) * width_ * 3];
+                    const unsigned char* inInnerUpper = &inInner[(y * 2) * width_ * 3];
+                    const unsigned char* inInnerLower = &inInner[(y * 2 + 1) * width_ * 3];
+                    unsigned char* out = &pixelDataOut[z * widthOut * heightOut * 3 + y * widthOut * 3];
+
+                    for (int x = 0; x < widthOut * 3; x += 3)
+                    {
+                        out[x] = (unsigned char)(((unsigned)inOuterUpper[x * 2] + inOuterUpper[x * 2 + 3] +
+                                                  inOuterLower[x * 2] + inOuterLower[x * 2 + 3] +
+                                                  inInnerUpper[x * 2] + inInnerUpper[x * 2 + 3] +
+                                                  inInnerLower[x * 2] + inInnerLower[x * 2 + 3]) >> 3);
+                        out[x + 1] = (unsigned char)(((unsigned)inOuterUpper[x * 2 + 1] + inOuterUpper[x * 2 + 4] +
+                                                      inOuterLower[x * 2 + 1] + inOuterLower[x * 2 + 4] +
+                                                      inInnerUpper[x * 2 + 1] + inInnerUpper[x * 2 + 4] +
+                                                      inInnerLower[x * 2 + 1] + inInnerLower[x * 2 + 4]) >> 3);
+                        out[x + 2] = (unsigned char)(((unsigned)inOuterUpper[x * 2 + 2] + inOuterUpper[x * 2 + 5] +
+                                                      inOuterLower[x * 2 + 2] + inOuterLower[x * 2 + 5] +
+                                                      inInnerUpper[x * 2 + 2] + inInnerUpper[x * 2 + 5] +
+                                                      inInnerLower[x * 2 + 2] + inInnerLower[x * 2 + 5]) >> 3);
+                    }
+                }
+            }
+            break;
+
+        case 4:
+            for (int z = 0; z < depthOut; ++z)
+            {
+                const unsigned char* inOuter = &pixelDataIn[(z * 2) * width_ * height_ * 4];
+                const unsigned char* inInner = &pixelDataIn[(z * 2 + 1) * width_ * height_ * 4];
+
+                for (int y = 0; y < heightOut; ++y)
+                {
+                    const unsigned char* inOuterUpper = &inOuter[(y * 2) * width_ * 4];
+                    const unsigned char* inOuterLower = &inOuter[(y * 2 + 1) * width_ * 4];
+                    const unsigned char* inInnerUpper = &inInner[(y * 2) * width_ * 4];
+                    const unsigned char* inInnerLower = &inInner[(y * 2 + 1) * width_ * 4];
+                    unsigned char* out = &pixelDataOut[z * widthOut * heightOut * 4 + y * widthOut * 4];
+
+                    for (int x = 0; x < widthOut * 4; x += 4)
+                    {
+                        out[x] = (unsigned char)(((unsigned)inOuterUpper[x * 2] + inOuterUpper[x * 2 + 4] +
+                                                  inOuterLower[x * 2] + inOuterLower[x * 2 + 4] +
+                                                  inInnerUpper[x * 2] + inInnerUpper[x * 2 + 4] +
+                                                  inInnerLower[x * 2] + inInnerLower[x * 2 + 4]) >> 3);
+                        out[x + 1] = (unsigned char)(((unsigned)inOuterUpper[x * 2 + 1] + inOuterUpper[x * 2 + 5] +
+                                                      inOuterLower[x * 2 + 1] + inOuterLower[x * 2 + 5] +
+                                                      inInnerUpper[x * 2 + 1] + inInnerUpper[x * 2 + 5] +
+                                                      inInnerLower[x * 2 + 1] + inInnerLower[x * 2 + 5]) >> 3);
+                        out[x + 2] = (unsigned char)(((unsigned)inOuterUpper[x * 2 + 2] + inOuterUpper[x * 2 + 6] +
+                                                      inOuterLower[x * 2 + 2] + inOuterLower[x * 2 + 6] +
+                                                      inInnerUpper[x * 2 + 2] + inInnerUpper[x * 2 + 6] +
+                                                      inInnerLower[x * 2 + 2] + inInnerLower[x * 2 + 6]) >> 3);
+                    }
+                }
+            }
+            break;
+
+        default:
+            assert(false);  // Should never reach here
+            break;
+        }
+    }
+
+    return mipImage;
+}
+
+SharedPtr<Image> Image::ConvertToRGBA() const
+{
+    if (IsCompressed())
+    {
+        ATOMIC_LOGERROR("Can not convert compressed image to RGBA");
+        return SharedPtr<Image>();
+    }
+    if (components_ < 1 || components_ > 4)
+    {
+        ATOMIC_LOGERROR("Illegal number of image components for conversion to RGBA");
+        return SharedPtr<Image>();
+    }
+    if (!data_)
+    {
+        ATOMIC_LOGERROR("Can not convert image without data to RGBA");
+        return SharedPtr<Image>();
+    }
+
+    // Already RGBA?
+    if (components_ == 4)
+        return SharedPtr<Image>(const_cast<Image*>(this));
+
+    SharedPtr<Image> ret(new Image(context_));
+    ret->SetSize(width_, height_, depth_, 4);
+
+    const unsigned char* src = data_;
+    unsigned char* dest = ret->GetData();
+
+    switch (components_)
+    {
+    case 1:
+        for (unsigned i = 0; i < static_cast<unsigned>(width_ * height_ * depth_); ++i)
+        {
+            unsigned char pixel = *src++;
+            *dest++ = pixel;
+            *dest++ = pixel;
+            *dest++ = pixel;
+            *dest++ = 255;
+        }
+        break;
+
+    case 2:
+        for (unsigned i = 0; i < static_cast<unsigned>(width_ * height_ * depth_); ++i)
+        {
+            unsigned char pixel = *src++;
+            *dest++ = pixel;
+            *dest++ = pixel;
+            *dest++ = pixel;
+            *dest++ = *src++;
+        }
+        break;
+
+    case 3:
+        for (unsigned i = 0; i < static_cast<unsigned>(width_ * height_ * depth_); ++i)
+        {
+            *dest++ = *src++;
+            *dest++ = *src++;
+            *dest++ = *src++;
+            *dest++ = 255;
+        }
+        break;
+
+    default:
+        assert(false);  // Should never reach nere
+        break;
+    }
+
+    return ret;
+}
+
+CompressedLevel Image::GetCompressedLevel(unsigned index) const
+{
+    CompressedLevel level;
+
+    if (compressedFormat_ == CF_NONE)
+    {
+        ATOMIC_LOGERROR("Image is not compressed");
+        return level;
+    }
+    if (index >= numCompressedLevels_)
+    {
+        ATOMIC_LOGERROR("Compressed image mip level out of bounds");
+        return level;
+    }
+
+    level.format_ = compressedFormat_;
+    level.width_ = width_;
+    level.height_ = height_;
+    level.depth_ = depth_;
+
+    if (compressedFormat_ == CF_RGBA)
+    {
+        level.blockSize_ = 4;
+        unsigned i = 0;
+        unsigned offset = 0;
+
+        for (;;)
+        {
+            if (!level.width_)
+                level.width_ = 1;
+            if (!level.height_)
+                level.height_ = 1;
+            if (!level.depth_)
+                level.depth_ = 1;
+
+            level.rowSize_ = level.width_ * level.blockSize_;
+            level.rows_ = (unsigned)level.height_;
+            level.data_ = data_.Get() + offset;
+            level.dataSize_ = level.depth_ * level.rows_ * level.rowSize_;
+
+            if (offset + level.dataSize_ > GetMemoryUse())
+            {
+                ATOMIC_LOGERROR("Compressed level is outside image data. Offset: " + String(offset) + " Size: " + String(level.dataSize_) +
+                         " Datasize: " + String(GetMemoryUse()));
+                level.data_ = 0;
+                return level;
+            }
+
+            if (i == index)
+                return level;
+
+            offset += level.dataSize_;
+            level.width_ /= 2;
+            level.height_ /= 2;
+            level.depth_ /= 2;
+            ++i;
+        }
+    }
+    else if (compressedFormat_ < CF_PVRTC_RGB_2BPP)
+    {
+        level.blockSize_ = (compressedFormat_ == CF_DXT1 || compressedFormat_ == CF_ETC1) ? 8 : 16;
+        unsigned i = 0;
+        unsigned offset = 0;
+
+        for (;;)
+        {
+            if (!level.width_)
+                level.width_ = 1;
+            if (!level.height_)
+                level.height_ = 1;
+            if (!level.depth_)
+                level.depth_ = 1;
+
+            level.rowSize_ = ((level.width_ + 3) / 4) * level.blockSize_;
+            level.rows_ = (unsigned)((level.height_ + 3) / 4);
+            level.data_ = data_.Get() + offset;
+            level.dataSize_ = level.depth_ * level.rows_ * level.rowSize_;
+
+            if (offset + level.dataSize_ > GetMemoryUse())
+            {
+                ATOMIC_LOGERROR("Compressed level is outside image data. Offset: " + String(offset) + " Size: " + String(level.dataSize_) +
+                         " Datasize: " + String(GetMemoryUse()));
+                level.data_ = 0;
+                return level;
+            }
+
+            if (i == index)
+                return level;
+
+            offset += level.dataSize_;
+            level.width_ /= 2;
+            level.height_ /= 2;
+            level.depth_ /= 2;
+            ++i;
+        }
+    }
+    else
+    {
+        level.blockSize_ = compressedFormat_ < CF_PVRTC_RGB_4BPP ? 2 : 4;
+        unsigned i = 0;
+        unsigned offset = 0;
+
+        for (;;)
+        {
+            if (!level.width_)
+                level.width_ = 1;
+            if (!level.height_)
+                level.height_ = 1;
+
+            int dataWidth = Max(level.width_, level.blockSize_ == 2 ? 16 : 8);
+            int dataHeight = Max(level.height_, 8);
+            level.data_ = data_.Get() + offset;
+            level.dataSize_ = (dataWidth * dataHeight * level.blockSize_ + 7) >> 3;
+            level.rows_ = (unsigned)dataHeight;
+            level.rowSize_ = level.dataSize_ / level.rows_;
+
+            if (offset + level.dataSize_ > GetMemoryUse())
+            {
+                ATOMIC_LOGERROR("Compressed level is outside image data. Offset: " + String(offset) + " Size: " + String(level.dataSize_) +
+                         " Datasize: " + String(GetMemoryUse()));
+                level.data_ = 0;
+                return level;
+            }
+
+            if (i == index)
+                return level;
+
+            offset += level.dataSize_;
+            level.width_ /= 2;
+            level.height_ /= 2;
+            ++i;
+        }
+    }
+}
+
+Image* Image::GetSubimage(const IntRect& rect) const
+{
+    if (!data_)
+        return 0;
+
+    if (depth_ > 1)
+    {
+        ATOMIC_LOGERROR("Subimage not supported for 3D images");
+        return 0;
+    }
+
+    if (rect.left_ < 0 || rect.top_ < 0 || rect.right_ > width_ || rect.bottom_ > height_ || !rect.Width() || !rect.Height())
+    {
+        ATOMIC_LOGERROR("Can not get subimage from image " + GetName() + " with invalid region");
+        return 0;
+    }
+
+    if (!IsCompressed())
+    {
+        int x = rect.left_;
+        int y = rect.top_;
+        int width = rect.Width();
+        int height = rect.Height();
+
+        Image* image = new Image(context_);
+        image->SetSize(width, height, components_);
+
+        unsigned char* dest = image->GetData();
+        unsigned char* src = data_.Get() + (y * width_ + x) * components_;
+        for (int i = 0; i < height; ++i)
+        {
+            memcpy(dest, src, width * components_);
+            dest += width * components_;
+            src += width_ * components_;
+        }
+
+        return image;
+    }
+    else
+    {
+        // Pad the region to be a multiple of block size
+        IntRect paddedRect = rect;
+        paddedRect.left_ = (rect.left_ / 4) * 4;
+        paddedRect.top_ = (rect.top_ / 4) * 4;
+        paddedRect.right_ = (rect.right_ / 4) * 4;
+        paddedRect.bottom_ = (rect.bottom_ / 4) * 4;
+        IntRect currentRect = paddedRect;
+
+        PODVector<unsigned char> subimageData;
+        unsigned subimageLevels = 0;
+
+        // Save as many mips as possible until the next mip would cross a block boundary
+        for (unsigned i = 0; i < numCompressedLevels_; ++i)
+        {
+            CompressedLevel level = GetCompressedLevel(i);
+            if (!level.data_)
+                break;
+
+            // Mips are stored continuously
+            unsigned destStartOffset = subimageData.Size();
+            unsigned destRowSize = currentRect.Width() / 4 * level.blockSize_;
+            unsigned destSize = currentRect.Height() / 4 * destRowSize;
+            if (!destSize)
+                break;
+
+            subimageData.Resize(destStartOffset + destSize);
+            unsigned char* dest = &subimageData[destStartOffset];
+
+            for (int y = currentRect.top_; y < currentRect.bottom_; y += 4)
+            {
+                unsigned char* src = level.data_ + level.rowSize_ * (y / 4) + currentRect.left_ / 4 * level.blockSize_;
+                memcpy(dest, src, destRowSize);
+                dest += destRowSize;
+            }
+
+            ++subimageLevels;
+            if ((currentRect.left_ & 4) || (currentRect.right_ & 4) || (currentRect.top_ & 4) || (currentRect.bottom_ & 4))
+                break;
+            else
+            {
+                currentRect.left_ /= 2;
+                currentRect.right_ /= 2;
+                currentRect.top_ /= 2;
+                currentRect.bottom_ /= 2;
+            }
+        }
+
+        if (!subimageLevels)
+        {
+            ATOMIC_LOGERROR("Subimage region from compressed image " + GetName() + " did not produce any data");
+            return 0;
+        }
+
+        Image* image = new Image(context_);
+        image->width_ = paddedRect.Width();
+        image->height_ = paddedRect.Height();
+        image->depth_ = 1;
+        image->compressedFormat_ = compressedFormat_;
+        image->numCompressedLevels_ = subimageLevels;
+        image->components_ = components_;
+        image->data_ = new unsigned char[subimageData.Size()];
+        memcpy(image->data_.Get(), &subimageData[0], subimageData.Size());
+        image->SetMemoryUse(subimageData.Size());
+
+        return image;
+    }
+}
+
+SDL_Surface* Image::GetSDLSurface(const IntRect& rect) const
+{
+    if (!data_)
+        return 0;
+
+    if (depth_ > 1)
+    {
+        ATOMIC_LOGERROR("Can not get SDL surface from 3D image");
+        return 0;
+    }
+
+    if (IsCompressed())
+    {
+        ATOMIC_LOGERROR("Can not get SDL surface from compressed image " + GetName());
+        return 0;
+    }
+
+    if (components_ < 3)
+    {
+        ATOMIC_LOGERROR("Can not get SDL surface from image " + GetName() + " with less than 3 components");
+        return 0;
+    }
+
+    IntRect imageRect = rect;
+    // Use full image if illegal rect
+    if (imageRect.left_ < 0 || imageRect.top_ < 0 || imageRect.right_ > width_ || imageRect.bottom_ > height_ ||
+        imageRect.left_ >= imageRect.right_ || imageRect.top_ >= imageRect.bottom_)
+    {
+        imageRect.left_ = 0;
+        imageRect.top_ = 0;
+        imageRect.right_ = width_;
+        imageRect.bottom_ = height_;
+    }
+
+    int imageWidth = width_;
+    int width = imageRect.Width();
+    int height = imageRect.Height();
+
+    // Assume little-endian for all the supported platforms
+    unsigned rMask = 0x000000ff;
+    unsigned gMask = 0x0000ff00;
+    unsigned bMask = 0x00ff0000;
+    unsigned aMask = 0xff000000;
+
+    SDL_Surface* surface = SDL_CreateRGBSurface(0, width, height, components_ * 8, rMask, gMask, bMask, aMask);
+    if (surface)
+    {
+        SDL_LockSurface(surface);
+
+        unsigned char* destination = reinterpret_cast<unsigned char*>(surface->pixels);
+        unsigned char* source = data_ + components_ * (imageWidth * imageRect.top_ + imageRect.left_);
+        for (int i = 0; i < height; ++i)
+        {
+            memcpy(destination, source, components_ * width);
+            destination += surface->pitch;
+            source += components_ * imageWidth;
+        }
+
+        SDL_UnlockSurface(surface);
+    }
+    else
+        ATOMIC_LOGERROR("Failed to create SDL surface from image " + GetName());
+
+    return surface;
+}
+
+void Image::PrecalculateLevels()
+{
+    if (!data_ || IsCompressed())
+        return;
+
+    ATOMIC_PROFILE(PrecalculateImageMipLevels);
+
+    nextLevel_.Reset();
+
+    if (width_ > 1 || height_ > 1)
+    {
+        SharedPtr<Image> current = GetNextLevel();
+        nextLevel_ = current;
+        while (current && (current->width_ > 1 || current->height_ > 1))
+        {
+            current->nextLevel_ = current->GetNextLevel();
+            current = current->nextLevel_;
+        }
+    }
+}
+
+void Image::CleanupLevels()
+{
+    nextLevel_.Reset();
+}
+
+void Image::GetLevels(PODVector<Image*>& levels)
+{
+    levels.Clear();
+
+    Image* image = this;
+    while (image)
+    {
+        levels.Push(image);
+        image = image->nextLevel_;
+    }
+}
+
+void Image::GetLevels(PODVector<const Image*>& levels) const
+{
+    levels.Clear();
+
+    const Image* image = this;
+    while (image)
+    {
+        levels.Push(image);
+        image = image->nextLevel_;
+    }
+}
+
+unsigned char* Image::GetImageData(Deserializer& source, int& width, int& height, unsigned& components)
+{
+    unsigned dataSize = source.GetSize();
+
+    SharedArrayPtr<unsigned char> buffer(new unsigned char[dataSize]);
+    source.Read(buffer.Get(), dataSize);
+    return stbi_load_from_memory(buffer.Get(), dataSize, &width, &height, (int*)&components, 0);
+}
+
+void Image::FreeImageData(unsigned char* pixelData)
+{
+    if (!pixelData)
+        return;
+
+    stbi_image_free(pixelData);
+}
+
+// ATOMIC BEGIN
+
+bool Image::HasAlphaChannel() const
+{
+    return components_ > 3;
+}
+
+bool Image::SetSubimage(const Image* image, const IntRect& rect)
+{
+    if (!data_)
+        return false;
+
+    if (depth_ > 1 || IsCompressed())
+    {
+        ATOMIC_LOGERROR("SetSubimage not supported for Compressed or 3D images");
+        return false;
+    }
+
+    if (rect.left_ < 0 || rect.top_ < 0 || rect.right_ > width_ || rect.bottom_ > height_ || !rect.Width() || !rect.Height())
+    {
+        ATOMIC_LOGERROR("Can not set subimage in image " + GetName() + " with invalid region");
+        return false;
+    }
+
+    int width = rect.Width();
+    int height = rect.Height();
+    if (width == image->GetWidth() && height == image->GetHeight())
+    {
+        int components = Min((int)components_, (int)image->components_);
+
+        unsigned char* src = image->GetData();
+        unsigned char* dest = data_.Get() + (rect.top_ * width_ + rect.left_) * components_;
+        for (int i = 0; i < height; ++i)
+        {
+            memcpy(dest, src, width * components);
+
+            src += width * image->components_;
+            dest += width_ * components_;
+        }
+    }
+    else
+    {
+        unsigned uintColor;
+        unsigned char* dest = data_.Get() + (rect.top_ * width_ + rect.left_) * components_;
+        unsigned char* src = (unsigned char*)&uintColor;
+        for (int y = 0; y < height; ++y)
+        {
+            for (int x = 0; x < width; ++x)
+            {
+                // Calculate float coordinates between 0 - 1 for resampling
+                float xF = (image->width_ > 1) ? (float)x / (float)(width - 1) : 0.0f;
+                float yF = (image->height_ > 1) ? (float)y / (float)(height - 1) : 0.0f;
+                uintColor = image->GetPixelBilinear(xF, yF).ToUInt();
+
+                memcpy(dest, src, components_);
+
+                dest += components_;
+            }
+            dest += (width_ - width) * components_;
+        }
+    }
+
+    return true;
+}
+
+
+// ATOMIC END
+
+}

+ 252 - 250
Source/Atomic/Resource/Image.h

@@ -1,250 +1,252 @@
-//
-// Copyright (c) 2008-2017 the Urho3D project.
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-//
-
-#pragma once
-
-#include "../Container/ArrayPtr.h"
-#include "../Resource/Resource.h"
-
-struct SDL_Surface;
-
-namespace Atomic
-{
-
-static const int COLOR_LUT_SIZE = 16;
-
-/// Supported compressed image formats.
-enum CompressedFormat
-{
-    CF_NONE = 0,
-    CF_RGBA,
-    CF_DXT1,
-    CF_DXT3,
-    CF_DXT5,
-    CF_ETC1,
-    CF_PVRTC_RGB_2BPP,
-    CF_PVRTC_RGBA_2BPP,
-    CF_PVRTC_RGB_4BPP,
-    CF_PVRTC_RGBA_4BPP,
-};
-
-/// Compressed image mip level.
-struct CompressedLevel
-{
-    /// Construct empty.
-    CompressedLevel() :
-        data_(0),
-        format_(CF_NONE),
-        width_(0),
-        height_(0),
-        depth_(0),
-        blockSize_(0),
-        dataSize_(0),
-        rowSize_(0),
-        rows_(0)
-    {
-    }
-
-    /// Decompress to RGBA. The destination buffer required is width * height * 4 bytes. Return true if successful.
-    bool Decompress(unsigned char* dest);
-
-    /// Compressed image data.
-    unsigned char* data_;
-    /// Compression format.
-    CompressedFormat format_;
-    /// Width.
-    int width_;
-    /// Height.
-    int height_;
-    /// Depth.
-    int depth_;
-    /// Block size in bytes.
-    unsigned blockSize_;
-    /// Total data size in bytes.
-    unsigned dataSize_;
-    /// Row size in bytes.
-    unsigned rowSize_;
-    /// Number of rows.
-    unsigned rows_;
-};
-
-/// %Image resource.
-class ATOMIC_API Image : public Resource
-{
-    ATOMIC_OBJECT(Image, Resource);
-
-public:
-    /// Construct empty.
-    Image(Context* context);
-    /// Destruct.
-    virtual ~Image();
-    /// Register object factory.
-    static void RegisterObject(Context* context);
-
-    /// Load resource from stream. May be called from a worker thread. Return true if successful.
-    virtual bool BeginLoad(Deserializer& source);
-    /// Save the image to a stream. Regardless of original format, the image is saved as png. Compressed image data is not supported. Return true if successful.
-    virtual bool Save(Serializer& dest) const;
-    /// Save the image to a file. Format of the image is determined by file extension. JPG is saved with maximum quality.
-    virtual bool SaveFile(const String& fileName) const;
-
-    /// Set 2D size and number of color components. Old image data will be destroyed and new data is undefined. Return true if successful.
-    bool SetSize(int width, int height, unsigned components);
-    /// Set 3D size and number of color components. Old image data will be destroyed and new data is undefined. Return true if successful.
-    bool SetSize(int width, int height, int depth, unsigned components);
-    /// Set new image data.
-    void SetData(const unsigned char* pixelData);
-    /// Set a 2D pixel.
-    void SetPixel(int x, int y, const Color& color);
-    /// Set a 3D pixel.
-    void SetPixel(int x, int y, int z, const Color& color);
-    /// Set a 2D pixel with an integer color. R component is in the 8 lowest bits.
-    void SetPixelInt(int x, int y, unsigned uintColor);
-    /// Set a 3D pixel with an integer color. R component is in the 8 lowest bits.
-    void SetPixelInt(int x, int y, int z, unsigned uintColor);
-    /// Load as color LUT. Return true if successful.
-    bool LoadColorLUT(Deserializer& source);
-    /// Flip image horizontally. Return true if successful.
-    bool FlipHorizontal();
-    /// Flip image vertically. Return true if successful.
-    bool FlipVertical();
-    /// Resize image by bilinear resampling. Return true if successful.
-    bool Resize(int width, int height);
-    /// Clear the image with a color.
-    void Clear(const Color& color);
-    /// Clear the image with an integer color. R component is in the 8 lowest bits.
-    void ClearInt(unsigned uintColor);
-    /// Save in BMP format. Return true if successful.
-    bool SaveBMP(const String& fileName) const;
-    /// Save in PNG format. Return true if successful.
-    bool SavePNG(const String& fileName) const;
-    /// Save in TGA format. Return true if successful.
-    bool SaveTGA(const String& fileName) const;
-    /// Save in JPG format with compression quality. Return true if successful.
-    bool SaveJPG(const String& fileName, int quality) const;
-    /// Save in DDS format. Only uncompressed RGBA images are supported. Return true if successful.
-    bool SaveDDS(const String& fileName) const;
-    /// Whether this texture is detected as a cubemap, only relevant for DDS.
-    bool IsCubemap() const { return cubemap_; }
-    /// Whether this texture has been detected as a volume, only relevant for DDS.
-    bool IsArray() const { return array_; }
-    /// Whether this texture is in sRGB, only relevant for DDS.
-    bool IsSRGB() const { return sRGB_; }
-
-    /// Return a 2D pixel color.
-    Color GetPixel(int x, int y) const;
-    /// Return a 3D pixel color.
-    Color GetPixel(int x, int y, int z) const;
-    /// Return a 2D pixel integer color. R component is in the 8 lowest bits.
-    unsigned GetPixelInt(int x, int y) const;
-    /// Return a 3D pixel integer color. R component is in the 8 lowest bits.
-    unsigned GetPixelInt(int x, int y, int z) const;
-    /// Return a bilinearly sampled 2D pixel color. X and Y have the range 0-1.
-    Color GetPixelBilinear(float x, float y) const;
-    /// Return a trilinearly sampled 3D pixel color. X, Y and Z have the range 0-1.
-    Color GetPixelTrilinear(float x, float y, float z) const;
-
-    /// Return width.
-    int GetWidth() const { return width_; }
-
-    /// Return height.
-    int GetHeight() const { return height_; }
-
-    /// Return depth.
-    int GetDepth() const { return depth_; }
-
-    /// Return number of color components.
-    unsigned GetComponents() const { return components_; }
-
-    /// Return pixel data.
-    unsigned char* GetData() const { return data_; }
-
-    /// Return whether is compressed.
-    bool IsCompressed() const { return compressedFormat_ != CF_NONE; }
-
-    /// Return compressed format.
-    CompressedFormat GetCompressedFormat() const { return compressedFormat_; }
-
-    /// Return number of compressed mip levels. Returns 0 if the image is has not been loaded from a source file containing multiple mip levels.
-    unsigned GetNumCompressedLevels() const { return numCompressedLevels_; }
-
-    /// Return next mip level by bilinear filtering. Note that if the image is already 1x1x1, will keep returning an image of that size.
-    SharedPtr<Image> GetNextLevel() const;
-    /// Return the next sibling image of an array or cubemap.
-    SharedPtr<Image> GetNextSibling() const { return nextSibling_;  }
-    /// Return image converted to 4-component (RGBA) to circumvent modern rendering API's not supporting e.g. the luminance-alpha format.
-    SharedPtr<Image> ConvertToRGBA() const;
-    /// Return a compressed mip level.
-    CompressedLevel GetCompressedLevel(unsigned index) const;
-    /// Return subimage from the image by the defined rect or null if failed. 3D images are not supported. You must free the subimage yourself.
-    Image* GetSubimage(const IntRect& rect) const;
-    /// Return an SDL surface from the image, or null if failed. Only RGB images are supported. Specify rect to only return partial image. You must free the surface yourself.
-    SDL_Surface* GetSDLSurface(const IntRect& rect = IntRect::ZERO) const;
-    /// Precalculate the mip levels. Used by asynchronous texture loading.
-    void PrecalculateLevels();
-
-    // ATOMIC BEGIN
-    /// Whether this texture has an alpha channel
-    bool HasAlphaChannel() const;
-    /// Copy contents of the image into the defined rect, scaling if necessary. This image should already be large enough to include the rect. Compressed and 3D images are not supported.
-    bool SetSubimage(const Image* image, const IntRect& rect);
-    // ATOMIC END
-    /// Clean up the mip levels.
-    void CleanupLevels();
-    /// Get all stored mip levels starting from this.
-    void GetLevels(PODVector<Image*>& levels);
-    /// Get all stored mip levels starting from this.
-    void GetLevels(PODVector<const Image*>& levels) const;
-
-private:
-    /// Decode an image using stb_image.
-    static unsigned char* GetImageData(Deserializer& source, int& width, int& height, unsigned& components);
-    /// Free an image file's pixel data.
-    static void FreeImageData(unsigned char* pixelData);
-
-    /// Width.
-    int width_;
-    /// Height.
-    int height_;
-    /// Depth.
-    int depth_;
-    /// Number of color components.
-    unsigned components_;
-    /// Number of compressed mip levels.
-    unsigned numCompressedLevels_;
-    /// Cubemap status if DDS.
-    bool cubemap_;
-    /// Texture array status if DDS.
-    bool array_;
-    /// Data is sRGB.
-    bool sRGB_;
-    /// Compressed format.
-    CompressedFormat compressedFormat_;
-    /// Pixel data.
-    SharedArrayPtr<unsigned char> data_;
-    /// Precalculated mip level image.
-    SharedPtr<Image> nextLevel_;
-    /// Next texture array or cube map image.
-    SharedPtr<Image> nextSibling_;
-};
-
-}
+//
+// Copyright (c) 2008-2017 the Urho3D project.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+#pragma once
+
+#include "../Container/ArrayPtr.h"
+#include "../Resource/Resource.h"
+
+struct SDL_Surface;
+
+namespace Atomic
+{
+
+static const int COLOR_LUT_SIZE = 16;
+
+/// Supported compressed image formats.
+enum CompressedFormat
+{
+    CF_NONE = 0,
+    CF_RGBA,
+    CF_DXT1,
+    CF_DXT3,
+    CF_DXT5,
+    CF_ETC1,
+    CF_PVRTC_RGB_2BPP,
+    CF_PVRTC_RGBA_2BPP,
+    CF_PVRTC_RGB_4BPP,
+    CF_PVRTC_RGBA_4BPP,
+};
+
+/// Compressed image mip level.
+struct CompressedLevel
+{
+    /// Construct empty.
+    CompressedLevel() :
+        data_(0),
+        format_(CF_NONE),
+        width_(0),
+        height_(0),
+        depth_(0),
+        blockSize_(0),
+        dataSize_(0),
+        rowSize_(0),
+        rows_(0)
+    {
+    }
+
+    /// Decompress to RGBA. The destination buffer required is width * height * 4 bytes. Return true if successful.
+    bool Decompress(unsigned char* dest);
+
+    /// Compressed image data.
+    unsigned char* data_;
+    /// Compression format.
+    CompressedFormat format_;
+    /// Width.
+    int width_;
+    /// Height.
+    int height_;
+    /// Depth.
+    int depth_;
+    /// Block size in bytes.
+    unsigned blockSize_;
+    /// Total data size in bytes.
+    unsigned dataSize_;
+    /// Row size in bytes.
+    unsigned rowSize_;
+    /// Number of rows.
+    unsigned rows_;
+};
+
+/// %Image resource.
+class ATOMIC_API Image : public Resource
+{
+    ATOMIC_OBJECT(Image, Resource);
+
+public:
+    /// Construct empty.
+    Image(Context* context);
+    /// Destruct.
+    virtual ~Image();
+    /// Register object factory.
+    static void RegisterObject(Context* context);
+
+    /// Load resource from stream. May be called from a worker thread. Return true if successful.
+    virtual bool BeginLoad(Deserializer& source);
+    /// Save the image to a stream. Regardless of original format, the image is saved as png. Compressed image data is not supported. Return true if successful.
+    virtual bool Save(Serializer& dest) const;
+    /// Save the image to a file. Format of the image is determined by file extension. JPG is saved with maximum quality.
+    virtual bool SaveFile(const String& fileName) const;
+
+    /// Set 2D size and number of color components. Old image data will be destroyed and new data is undefined. Return true if successful.
+    bool SetSize(int width, int height, unsigned components);
+    /// Set 3D size and number of color components. Old image data will be destroyed and new data is undefined. Return true if successful.
+    bool SetSize(int width, int height, int depth, unsigned components);
+    /// Set new image data.
+    void SetData(const unsigned char* pixelData);
+    /// Set a 2D pixel.
+    void SetPixel(int x, int y, const Color& color);
+    /// Set a 3D pixel.
+    void SetPixel(int x, int y, int z, const Color& color);
+    /// Set a 2D pixel with an integer color. R component is in the 8 lowest bits.
+    void SetPixelInt(int x, int y, unsigned uintColor);
+    /// Set a 3D pixel with an integer color. R component is in the 8 lowest bits.
+    void SetPixelInt(int x, int y, int z, unsigned uintColor);
+    /// Load as color LUT. Return true if successful.
+    bool LoadColorLUT(Deserializer& source);
+    /// Flip image horizontally. Return true if successful.
+    bool FlipHorizontal();
+    /// Flip image vertically. Return true if successful.
+    bool FlipVertical();
+    /// Resize image by bilinear resampling. Return true if successful.
+    bool Resize(int width, int height);
+    /// Clear the image with a color.
+    void Clear(const Color& color);
+    /// Clear the image with an integer color. R component is in the 8 lowest bits.
+    void ClearInt(unsigned uintColor);
+    /// Save in BMP format. Return true if successful.
+    bool SaveBMP(const String& fileName) const;
+    /// Save in PNG format. Return true if successful.
+    bool SavePNG(const String& fileName) const;
+    /// Save in TGA format. Return true if successful.
+    bool SaveTGA(const String& fileName) const;
+    /// Save in JPG format with specified quality. Return true if successful.
+    bool SaveJPG(const String& fileName, int quality) const;
+    /// Save in DDS format. Only uncompressed RGBA images are supported. Return true if successful.
+    bool SaveDDS(const String& fileName) const;
+    /// Save in WebP format with minimum (fastest) or specified compression. Return true if successful. Fails always if WebP support is not compiled in.
+    bool SaveWEBP(const String& fileName, float compression = 0.0f) const;
+    /// Whether this texture is detected as a cubemap, only relevant for DDS.
+    bool IsCubemap() const { return cubemap_; }
+    /// Whether this texture has been detected as a volume, only relevant for DDS.
+    bool IsArray() const { return array_; }
+    /// Whether this texture is in sRGB, only relevant for DDS.
+    bool IsSRGB() const { return sRGB_; }
+
+    /// Return a 2D pixel color.
+    Color GetPixel(int x, int y) const;
+    /// Return a 3D pixel color.
+    Color GetPixel(int x, int y, int z) const;
+    /// Return a 2D pixel integer color. R component is in the 8 lowest bits.
+    unsigned GetPixelInt(int x, int y) const;
+    /// Return a 3D pixel integer color. R component is in the 8 lowest bits.
+    unsigned GetPixelInt(int x, int y, int z) const;
+    /// Return a bilinearly sampled 2D pixel color. X and Y have the range 0-1.
+    Color GetPixelBilinear(float x, float y) const;
+    /// Return a trilinearly sampled 3D pixel color. X, Y and Z have the range 0-1.
+    Color GetPixelTrilinear(float x, float y, float z) const;
+
+    /// Return width.
+    int GetWidth() const { return width_; }
+
+    /// Return height.
+    int GetHeight() const { return height_; }
+
+    /// Return depth.
+    int GetDepth() const { return depth_; }
+
+    /// Return number of color components.
+    unsigned GetComponents() const { return components_; }
+
+    /// Return pixel data.
+    unsigned char* GetData() const { return data_; }
+
+    /// Return whether is compressed.
+    bool IsCompressed() const { return compressedFormat_ != CF_NONE; }
+
+    /// Return compressed format.
+    CompressedFormat GetCompressedFormat() const { return compressedFormat_; }
+
+    /// Return number of compressed mip levels. Returns 0 if the image is has not been loaded from a source file containing multiple mip levels.
+    unsigned GetNumCompressedLevels() const { return numCompressedLevels_; }
+
+    /// Return next mip level by bilinear filtering. Note that if the image is already 1x1x1, will keep returning an image of that size.
+    SharedPtr<Image> GetNextLevel() const;
+    /// Return the next sibling image of an array or cubemap.
+    SharedPtr<Image> GetNextSibling() const { return nextSibling_;  }
+    /// Return image converted to 4-component (RGBA) to circumvent modern rendering API's not supporting e.g. the luminance-alpha format.
+    SharedPtr<Image> ConvertToRGBA() const;
+    /// Return a compressed mip level.
+    CompressedLevel GetCompressedLevel(unsigned index) const;
+    /// Return subimage from the image by the defined rect or null if failed. 3D images are not supported. You must free the subimage yourself.
+    Image* GetSubimage(const IntRect& rect) const;
+    /// Return an SDL surface from the image, or null if failed. Only RGB images are supported. Specify rect to only return partial image. You must free the surface yourself.
+    SDL_Surface* GetSDLSurface(const IntRect& rect = IntRect::ZERO) const;
+    /// Precalculate the mip levels. Used by asynchronous texture loading.
+    void PrecalculateLevels();
+
+    // ATOMIC BEGIN
+    /// Whether this texture has an alpha channel
+    bool HasAlphaChannel() const;
+    /// Copy contents of the image into the defined rect, scaling if necessary. This image should already be large enough to include the rect. Compressed and 3D images are not supported.
+    bool SetSubimage(const Image* image, const IntRect& rect);
+    // ATOMIC END
+    /// Clean up the mip levels.
+    void CleanupLevels();
+    /// Get all stored mip levels starting from this.
+    void GetLevels(PODVector<Image*>& levels);
+    /// Get all stored mip levels starting from this.
+    void GetLevels(PODVector<const Image*>& levels) const;
+
+private:
+    /// Decode an image using stb_image.
+    static unsigned char* GetImageData(Deserializer& source, int& width, int& height, unsigned& components);
+    /// Free an image file's pixel data.
+    static void FreeImageData(unsigned char* pixelData);
+
+    /// Width.
+    int width_;
+    /// Height.
+    int height_;
+    /// Depth.
+    int depth_;
+    /// Number of color components.
+    unsigned components_;
+    /// Number of compressed mip levels.
+    unsigned numCompressedLevels_;
+    /// Cubemap status if DDS.
+    bool cubemap_;
+    /// Texture array status if DDS.
+    bool array_;
+    /// Data is sRGB.
+    bool sRGB_;
+    /// Compressed format.
+    CompressedFormat compressedFormat_;
+    /// Pixel data.
+    SharedArrayPtr<unsigned char> data_;
+    /// Precalculated mip level image.
+    SharedPtr<Image> nextLevel_;
+    /// Next texture array or cube map image.
+    SharedPtr<Image> nextSibling_;
+};
+
+}

+ 2 - 2
Source/ThirdParty/FreeType/src/gzip/zutil.c

@@ -5,7 +5,7 @@
 
 /* @(#) $Id$ */
 
-// Modified by Yao Wei Tjong for Urho3D
+// Modified by Yao Wei Tjong & Lasse Oorni for Urho3D
 
 #include "zutil.h"
 
@@ -14,7 +14,7 @@ extern void exit OF((int));
 #endif
 
 // Urho3D: quick fix for debug build
-#ifdef DEBUG
+#if 0
 
 #  ifndef verbose
 #    define verbose 0

+ 4 - 1
Source/ThirdParty/FreeType/src/gzip/zutil.h

@@ -10,6 +10,8 @@
 
 /* @(#) $Id$ */
 
+// Modified by Lasse Oorni for Urho3D
+
 #ifndef _Z_UTIL_H
 #define _Z_UTIL_H
 
@@ -182,7 +184,8 @@ typedef unsigned long  ulg;
 #endif
 
 /* Diagnostic functions */
-#ifdef DEBUG
+// Urho3D: disabled to prevent clash with Assimp's zlib
+#if 0
 #  include <stdio.h>
    extern int z_verbose;
    extern void z_error    OF((char *m));

+ 38 - 0
Source/ThirdParty/WebP/AUTHORS

@@ -0,0 +1,38 @@
+Contributors:
+- Charles Munger (clm at google dot com)
+- Christian Duvivier (cduvivier at google dot com)
+- Djordje Pesut (djordje dot pesut at imgtec dot com)
+- Hui Su (huisu at google dot com)
+- James Zern (jzern at google dot com)
+- Jan Engelhardt (jengelh at medozas dot de)
+- Jehan (jehan at girinstud dot io)
+- Johann (johann dot koenig at duck dot com)
+- Jovan Zelincevic (jovan dot zelincevic at imgtec dot com)
+- Jyrki Alakuijala (jyrki at google dot com)
+- Lode Vandevenne (lode at google dot com)
+- Lou Quillio (louquillio at google dot com)
+- Mans Rullgard (mans at mansr dot com)
+- Marcin Kowalczyk (qrczak at google dot com)
+- Martin Olsson (mnemo at minimum dot se)
+- Mikołaj Zalewski (mikolajz at google dot com)
+- Mislav Bradac (mislavm at google dot com)
+- Nico Weber (thakis at chromium dot org)
+- Noel Chromium (noel at chromium dot org)
+- Owen Rodley (orodley at google dot com)
+- Parag Salasakar (img dot mips1 at gmail dot com)
+- Pascal Massimino (pascal dot massimino at gmail dot com)
+- Paweł Hajdan, Jr (phajdan dot jr at chromium dot org)
+- Pierre Joye (pierre dot php at gmail dot com)
+- Sam Clegg (sbc at chromium dot org)
+- Scott Hancher (seh at google dot com)
+- Scott LaVarnway (slavarnway at google dot com)
+- Scott Talbot (s at chikachow dot org)
+- Slobodan Prijic (slobodan dot prijic at imgtec dot com)
+- Somnath Banerjee (somnath dot banerjee at gmail dot com)
+- Sriraman Tallam (tmsriram at google dot com)
+- Tamar Levy (tamar dot levy at intel dot com)
+- Timothy Gu (timothygu99 at gmail dot com)
+- Urvang Joshi (urvang at google dot com)
+- Vikas Arora (vikasa at google dot com)
+- Vincent Rabaud (vrabaud at google dot com)
+- Yang Zhang (yang dot zhang at arm dot com)

+ 36 - 0
Source/ThirdParty/WebP/CMakeLists.txt

@@ -0,0 +1,36 @@
+#
+# Copyright (c) 2008-2017 the Urho3D project.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+
+# Define target name
+set (TARGET_NAME WebP)
+
+# Define source files
+define_source_files (RECURSE GLOB_CPP_PATTERNS src/*.c GLOB_H_PATTERNS src/*.h)
+
+# Define dependency libs
+#set (INCLUDE_DIRS include)
+
+# Setup target
+setup_library ()
+
+# Install headers for building the Urho3D library
+install_header_files (DIRECTORY src/webp/ DESTINATION ${DEST_INCLUDE_DIR}/ThirdParty/webp FILES_MATCHING PATTERN *.h BUILD_TREE_ONLY)  # Note: the trailing slash is significant

+ 30 - 0
Source/ThirdParty/WebP/COPYING

@@ -0,0 +1,30 @@
+Copyright (c) 2010, Google Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+  * Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+
+  * 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.
+
+  * Neither the name of Google nor the names of its contributors may
+    be used to endorse or promote products derived from this software
+    without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"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 COPYRIGHT
+HOLDER OR CONTRIBUTORS 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.
+

+ 23 - 0
Source/ThirdParty/WebP/PATENTS

@@ -0,0 +1,23 @@
+Additional IP Rights Grant (Patents)
+------------------------------------
+
+"These implementations" means the copyrightable works that implement the WebM
+codecs distributed by Google as part of the WebM Project.
+
+Google hereby grants to you a perpetual, worldwide, non-exclusive, no-charge,
+royalty-free, irrevocable (except as stated in this section) patent license to
+make, have made, use, offer to sell, sell, import, transfer, and otherwise
+run, modify and propagate the contents of these implementations of WebM, where
+such license applies only to those patent claims, both currently owned by
+Google and acquired in the future, licensable by Google that are necessarily
+infringed by these implementations of WebM. This grant does not include claims
+that would be infringed only as a consequence of further modification of these
+implementations. If you or your agent or exclusive licensee institute or order
+or agree to the institution of patent litigation or any other patent
+enforcement activity against any entity (including a cross-claim or
+counterclaim in a lawsuit) alleging that any of these implementations of WebM
+or any code incorporated within any of these implementations of WebM
+constitute direct or contributory patent infringement, or inducement of
+patent infringement, then any patent rights granted to you under this License
+for these implementations of WebM shall terminate as of the date such
+litigation is filed.

+ 232 - 0
Source/ThirdParty/WebP/src/dec/alpha_dec.c

@@ -0,0 +1,232 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Alpha-plane decompression.
+//
+// Author: Skal ([email protected])
+
+#include <stdlib.h>
+#include "./alphai_dec.h"
+#include "./vp8i_dec.h"
+#include "./vp8li_dec.h"
+#include "../dsp/dsp.h"
+#include "../utils/quant_levels_dec_utils.h"
+#include "../utils/utils.h"
+#include "../webp/format_constants.h"
+
+//------------------------------------------------------------------------------
+// ALPHDecoder object.
+
+// Allocates a new alpha decoder instance.
+static ALPHDecoder* ALPHNew(void) {
+  ALPHDecoder* const dec = (ALPHDecoder*)WebPSafeCalloc(1ULL, sizeof(*dec));
+  return dec;
+}
+
+// Clears and deallocates an alpha decoder instance.
+static void ALPHDelete(ALPHDecoder* const dec) {
+  if (dec != NULL) {
+    VP8LDelete(dec->vp8l_dec_);
+    dec->vp8l_dec_ = NULL;
+    WebPSafeFree(dec);
+  }
+}
+
+//------------------------------------------------------------------------------
+// Decoding.
+
+// Initialize alpha decoding by parsing the alpha header and decoding the image
+// header for alpha data stored using lossless compression.
+// Returns false in case of error in alpha header (data too short, invalid
+// compression method or filter, error in lossless header data etc).
+static int ALPHInit(ALPHDecoder* const dec, const uint8_t* data,
+                    size_t data_size, const VP8Io* const src_io,
+                    uint8_t* output) {
+  int ok = 0;
+  const uint8_t* const alpha_data = data + ALPHA_HEADER_LEN;
+  const size_t alpha_data_size = data_size - ALPHA_HEADER_LEN;
+  int rsrv;
+  VP8Io* const io = &dec->io_;
+
+  assert(data != NULL && output != NULL && src_io != NULL);
+
+  VP8FiltersInit();
+  dec->output_ = output;
+  dec->width_ = src_io->width;
+  dec->height_ = src_io->height;
+  assert(dec->width_ > 0 && dec->height_ > 0);
+
+  if (data_size <= ALPHA_HEADER_LEN) {
+    return 0;
+  }
+
+  dec->method_ = (data[0] >> 0) & 0x03;
+  dec->filter_ = (WEBP_FILTER_TYPE)((data[0] >> 2) & 0x03);
+  dec->pre_processing_ = (data[0] >> 4) & 0x03;
+  rsrv = (data[0] >> 6) & 0x03;
+  if (dec->method_ < ALPHA_NO_COMPRESSION ||
+      dec->method_ > ALPHA_LOSSLESS_COMPRESSION ||
+      dec->filter_ >= WEBP_FILTER_LAST ||
+      dec->pre_processing_ > ALPHA_PREPROCESSED_LEVELS ||
+      rsrv != 0) {
+    return 0;
+  }
+
+  // Copy the necessary parameters from src_io to io
+  VP8InitIo(io);
+  WebPInitCustomIo(NULL, io);
+  io->opaque = dec;
+  io->width = src_io->width;
+  io->height = src_io->height;
+
+  io->use_cropping = src_io->use_cropping;
+  io->crop_left = src_io->crop_left;
+  io->crop_right = src_io->crop_right;
+  io->crop_top = src_io->crop_top;
+  io->crop_bottom = src_io->crop_bottom;
+  // No need to copy the scaling parameters.
+
+  if (dec->method_ == ALPHA_NO_COMPRESSION) {
+    const size_t alpha_decoded_size = dec->width_ * dec->height_;
+    ok = (alpha_data_size >= alpha_decoded_size);
+  } else {
+    assert(dec->method_ == ALPHA_LOSSLESS_COMPRESSION);
+    ok = VP8LDecodeAlphaHeader(dec, alpha_data, alpha_data_size);
+  }
+
+  return ok;
+}
+
+// Decodes, unfilters and dequantizes *at least* 'num_rows' rows of alpha
+// starting from row number 'row'. It assumes that rows up to (row - 1) have
+// already been decoded.
+// Returns false in case of bitstream error.
+static int ALPHDecode(VP8Decoder* const dec, int row, int num_rows) {
+  ALPHDecoder* const alph_dec = dec->alph_dec_;
+  const int width = alph_dec->width_;
+  const int height = alph_dec->io_.crop_bottom;
+  if (alph_dec->method_ == ALPHA_NO_COMPRESSION) {
+    int y;
+    const uint8_t* prev_line = dec->alpha_prev_line_;
+    const uint8_t* deltas = dec->alpha_data_ + ALPHA_HEADER_LEN + row * width;
+    uint8_t* dst = dec->alpha_plane_ + row * width;
+    assert(deltas <= &dec->alpha_data_[dec->alpha_data_size_]);
+    if (alph_dec->filter_ != WEBP_FILTER_NONE) {
+      assert(WebPUnfilters[alph_dec->filter_] != NULL);
+      for (y = 0; y < num_rows; ++y) {
+        WebPUnfilters[alph_dec->filter_](prev_line, deltas, dst, width);
+        prev_line = dst;
+        dst += width;
+        deltas += width;
+      }
+    } else {
+      for (y = 0; y < num_rows; ++y) {
+        memcpy(dst, deltas, width * sizeof(*dst));
+        prev_line = dst;
+        dst += width;
+        deltas += width;
+      }
+    }
+    dec->alpha_prev_line_ = prev_line;
+  } else {  // alph_dec->method_ == ALPHA_LOSSLESS_COMPRESSION
+    assert(alph_dec->vp8l_dec_ != NULL);
+    if (!VP8LDecodeAlphaImageStream(alph_dec, row + num_rows)) {
+      return 0;
+    }
+  }
+
+  if (row + num_rows >= height) {
+    dec->is_alpha_decoded_ = 1;
+  }
+  return 1;
+}
+
+static int AllocateAlphaPlane(VP8Decoder* const dec, const VP8Io* const io) {
+  const int stride = io->width;
+  const int height = io->crop_bottom;
+  const uint64_t alpha_size = (uint64_t)stride * height;
+  assert(dec->alpha_plane_mem_ == NULL);
+  dec->alpha_plane_mem_ =
+      (uint8_t*)WebPSafeMalloc(alpha_size, sizeof(*dec->alpha_plane_));
+  if (dec->alpha_plane_mem_ == NULL) {
+    return 0;
+  }
+  dec->alpha_plane_ = dec->alpha_plane_mem_;
+  dec->alpha_prev_line_ = NULL;
+  return 1;
+}
+
+void WebPDeallocateAlphaMemory(VP8Decoder* const dec) {
+  assert(dec != NULL);
+  WebPSafeFree(dec->alpha_plane_mem_);
+  dec->alpha_plane_mem_ = NULL;
+  dec->alpha_plane_ = NULL;
+  ALPHDelete(dec->alph_dec_);
+  dec->alph_dec_ = NULL;
+}
+
+//------------------------------------------------------------------------------
+// Main entry point.
+
+const uint8_t* VP8DecompressAlphaRows(VP8Decoder* const dec,
+                                      const VP8Io* const io,
+                                      int row, int num_rows) {
+  const int width = io->width;
+  const int height = io->crop_bottom;
+
+  assert(dec != NULL && io != NULL);
+
+  if (row < 0 || num_rows <= 0 || row + num_rows > height) {
+    return NULL;    // sanity check.
+  }
+
+  if (!dec->is_alpha_decoded_) {
+    if (dec->alph_dec_ == NULL) {    // Initialize decoder.
+      dec->alph_dec_ = ALPHNew();
+      if (dec->alph_dec_ == NULL) return NULL;
+      if (!AllocateAlphaPlane(dec, io)) goto Error;
+      if (!ALPHInit(dec->alph_dec_, dec->alpha_data_, dec->alpha_data_size_,
+                    io, dec->alpha_plane_)) {
+        goto Error;
+      }
+      // if we allowed use of alpha dithering, check whether it's needed at all
+      if (dec->alph_dec_->pre_processing_ != ALPHA_PREPROCESSED_LEVELS) {
+        dec->alpha_dithering_ = 0;   // disable dithering
+      } else {
+        num_rows = height - row;     // decode everything in one pass
+      }
+    }
+
+    assert(dec->alph_dec_ != NULL);
+    assert(row + num_rows <= height);
+    if (!ALPHDecode(dec, row, num_rows)) goto Error;
+
+    if (dec->is_alpha_decoded_) {   // finished?
+      ALPHDelete(dec->alph_dec_);
+      dec->alph_dec_ = NULL;
+      if (dec->alpha_dithering_ > 0) {
+        uint8_t* const alpha = dec->alpha_plane_ + io->crop_top * width
+                             + io->crop_left;
+        if (!WebPDequantizeLevels(alpha,
+                                  io->crop_right - io->crop_left,
+                                  io->crop_bottom - io->crop_top,
+                                  width, dec->alpha_dithering_)) {
+          goto Error;
+        }
+      }
+    }
+  }
+
+  // Return a pointer to the current decoded row.
+  return dec->alpha_plane_ + row * width;
+
+ Error:
+  WebPDeallocateAlphaMemory(dec);
+  return NULL;
+}

+ 54 - 0
Source/ThirdParty/WebP/src/dec/alphai_dec.h

@@ -0,0 +1,54 @@
+// Copyright 2013 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Alpha decoder: internal header.
+//
+// Author: Urvang ([email protected])
+
+#ifndef WEBP_DEC_ALPHAI_H_
+#define WEBP_DEC_ALPHAI_H_
+
+#include "./webpi_dec.h"
+#include "../utils/filters_utils.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct VP8LDecoder;  // Defined in dec/vp8li.h.
+
+typedef struct ALPHDecoder ALPHDecoder;
+struct ALPHDecoder {
+  int width_;
+  int height_;
+  int method_;
+  WEBP_FILTER_TYPE filter_;
+  int pre_processing_;
+  struct VP8LDecoder* vp8l_dec_;
+  VP8Io io_;
+  int use_8b_decode_;  // Although alpha channel requires only 1 byte per
+                       // pixel, sometimes VP8LDecoder may need to allocate
+                       // 4 bytes per pixel internally during decode.
+  uint8_t* output_;
+  const uint8_t* prev_line_;   // last output row (or NULL)
+};
+
+//------------------------------------------------------------------------------
+// internal functions. Not public.
+
+// Deallocate memory associated to dec->alpha_plane_ decoding
+void WebPDeallocateAlphaMemory(VP8Decoder* const dec);
+
+//------------------------------------------------------------------------------
+
+#ifdef __cplusplus
+}    // extern "C"
+#endif
+
+#endif  /* WEBP_DEC_ALPHAI_H_ */

+ 300 - 0
Source/ThirdParty/WebP/src/dec/buffer_dec.c

@@ -0,0 +1,300 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Everything about WebPDecBuffer
+//
+// Author: Skal ([email protected])
+
+#include <stdlib.h>
+
+#include "./vp8i_dec.h"
+#include "./webpi_dec.h"
+#include "../utils/utils.h"
+
+//------------------------------------------------------------------------------
+// WebPDecBuffer
+
+// Number of bytes per pixel for the different color-spaces.
+static const int kModeBpp[MODE_LAST] = {
+  3, 4, 3, 4, 4, 2, 2,
+  4, 4, 4, 2,    // pre-multiplied modes
+  1, 1 };
+
+// Check that webp_csp_mode is within the bounds of WEBP_CSP_MODE.
+// Convert to an integer to handle both the unsigned/signed enum cases
+// without the need for casting to remove type limit warnings.
+static int IsValidColorspace(int webp_csp_mode) {
+  return (webp_csp_mode >= MODE_RGB && webp_csp_mode < MODE_LAST);
+}
+
+// strictly speaking, the very last (or first, if flipped) row
+// doesn't require padding.
+#define MIN_BUFFER_SIZE(WIDTH, HEIGHT, STRIDE)       \
+    (uint64_t)(STRIDE) * ((HEIGHT) - 1) + (WIDTH)
+
+static VP8StatusCode CheckDecBuffer(const WebPDecBuffer* const buffer) {
+  int ok = 1;
+  const WEBP_CSP_MODE mode = buffer->colorspace;
+  const int width = buffer->width;
+  const int height = buffer->height;
+  if (!IsValidColorspace(mode)) {
+    ok = 0;
+  } else if (!WebPIsRGBMode(mode)) {   // YUV checks
+    const WebPYUVABuffer* const buf = &buffer->u.YUVA;
+    const int uv_width  = (width  + 1) / 2;
+    const int uv_height = (height + 1) / 2;
+    const int y_stride = abs(buf->y_stride);
+    const int u_stride = abs(buf->u_stride);
+    const int v_stride = abs(buf->v_stride);
+    const int a_stride = abs(buf->a_stride);
+    const uint64_t y_size = MIN_BUFFER_SIZE(width, height, y_stride);
+    const uint64_t u_size = MIN_BUFFER_SIZE(uv_width, uv_height, u_stride);
+    const uint64_t v_size = MIN_BUFFER_SIZE(uv_width, uv_height, v_stride);
+    const uint64_t a_size = MIN_BUFFER_SIZE(width, height, a_stride);
+    ok &= (y_size <= buf->y_size);
+    ok &= (u_size <= buf->u_size);
+    ok &= (v_size <= buf->v_size);
+    ok &= (y_stride >= width);
+    ok &= (u_stride >= uv_width);
+    ok &= (v_stride >= uv_width);
+    ok &= (buf->y != NULL);
+    ok &= (buf->u != NULL);
+    ok &= (buf->v != NULL);
+    if (mode == MODE_YUVA) {
+      ok &= (a_stride >= width);
+      ok &= (a_size <= buf->a_size);
+      ok &= (buf->a != NULL);
+    }
+  } else {    // RGB checks
+    const WebPRGBABuffer* const buf = &buffer->u.RGBA;
+    const int stride = abs(buf->stride);
+    const uint64_t size = MIN_BUFFER_SIZE(width, height, stride);
+    ok &= (size <= buf->size);
+    ok &= (stride >= width * kModeBpp[mode]);
+    ok &= (buf->rgba != NULL);
+  }
+  return ok ? VP8_STATUS_OK : VP8_STATUS_INVALID_PARAM;
+}
+#undef MIN_BUFFER_SIZE
+
+static VP8StatusCode AllocateBuffer(WebPDecBuffer* const buffer) {
+  const int w = buffer->width;
+  const int h = buffer->height;
+  const WEBP_CSP_MODE mode = buffer->colorspace;
+
+  if (w <= 0 || h <= 0 || !IsValidColorspace(mode)) {
+    return VP8_STATUS_INVALID_PARAM;
+  }
+
+  if (buffer->is_external_memory <= 0 && buffer->private_memory == NULL) {
+    uint8_t* output;
+    int uv_stride = 0, a_stride = 0;
+    uint64_t uv_size = 0, a_size = 0, total_size;
+    // We need memory and it hasn't been allocated yet.
+    // => initialize output buffer, now that dimensions are known.
+    const int stride = w * kModeBpp[mode];
+    const uint64_t size = (uint64_t)stride * h;
+
+    if (!WebPIsRGBMode(mode)) {
+      uv_stride = (w + 1) / 2;
+      uv_size = (uint64_t)uv_stride * ((h + 1) / 2);
+      if (mode == MODE_YUVA) {
+        a_stride = w;
+        a_size = (uint64_t)a_stride * h;
+      }
+    }
+    total_size = size + 2 * uv_size + a_size;
+
+    // Security/sanity checks
+    output = (uint8_t*)WebPSafeMalloc(total_size, sizeof(*output));
+    if (output == NULL) {
+      return VP8_STATUS_OUT_OF_MEMORY;
+    }
+    buffer->private_memory = output;
+
+    if (!WebPIsRGBMode(mode)) {   // YUVA initialization
+      WebPYUVABuffer* const buf = &buffer->u.YUVA;
+      buf->y = output;
+      buf->y_stride = stride;
+      buf->y_size = (size_t)size;
+      buf->u = output + size;
+      buf->u_stride = uv_stride;
+      buf->u_size = (size_t)uv_size;
+      buf->v = output + size + uv_size;
+      buf->v_stride = uv_stride;
+      buf->v_size = (size_t)uv_size;
+      if (mode == MODE_YUVA) {
+        buf->a = output + size + 2 * uv_size;
+      }
+      buf->a_size = (size_t)a_size;
+      buf->a_stride = a_stride;
+    } else {  // RGBA initialization
+      WebPRGBABuffer* const buf = &buffer->u.RGBA;
+      buf->rgba = output;
+      buf->stride = stride;
+      buf->size = (size_t)size;
+    }
+  }
+  return CheckDecBuffer(buffer);
+}
+
+VP8StatusCode WebPFlipBuffer(WebPDecBuffer* const buffer) {
+  if (buffer == NULL) {
+    return VP8_STATUS_INVALID_PARAM;
+  }
+  if (WebPIsRGBMode(buffer->colorspace)) {
+    WebPRGBABuffer* const buf = &buffer->u.RGBA;
+    buf->rgba += (buffer->height - 1) * buf->stride;
+    buf->stride = -buf->stride;
+  } else {
+    WebPYUVABuffer* const buf = &buffer->u.YUVA;
+    const int H = buffer->height;
+    buf->y += (H - 1) * buf->y_stride;
+    buf->y_stride = -buf->y_stride;
+    buf->u += ((H - 1) >> 1) * buf->u_stride;
+    buf->u_stride = -buf->u_stride;
+    buf->v += ((H - 1) >> 1) * buf->v_stride;
+    buf->v_stride = -buf->v_stride;
+    if (buf->a != NULL) {
+      buf->a += (H - 1) * buf->a_stride;
+      buf->a_stride = -buf->a_stride;
+    }
+  }
+  return VP8_STATUS_OK;
+}
+
+VP8StatusCode WebPAllocateDecBuffer(int w, int h,
+                                    const WebPDecoderOptions* const options,
+                                    WebPDecBuffer* const out) {
+  VP8StatusCode status;
+  if (out == NULL || w <= 0 || h <= 0) {
+    return VP8_STATUS_INVALID_PARAM;
+  }
+  if (options != NULL) {    // First, apply options if there is any.
+    if (options->use_cropping) {
+      const int cw = options->crop_width;
+      const int ch = options->crop_height;
+      const int x = options->crop_left & ~1;
+      const int y = options->crop_top & ~1;
+      if (x < 0 || y < 0 || cw <= 0 || ch <= 0 || x + cw > w || y + ch > h) {
+        return VP8_STATUS_INVALID_PARAM;   // out of frame boundary.
+      }
+      w = cw;
+      h = ch;
+    }
+    if (options->use_scaling) {
+      int scaled_width = options->scaled_width;
+      int scaled_height = options->scaled_height;
+      if (!WebPRescalerGetScaledDimensions(
+              w, h, &scaled_width, &scaled_height)) {
+        return VP8_STATUS_INVALID_PARAM;
+      }
+      w = scaled_width;
+      h = scaled_height;
+    }
+  }
+  out->width = w;
+  out->height = h;
+
+  // Then, allocate buffer for real.
+  status = AllocateBuffer(out);
+  if (status != VP8_STATUS_OK) return status;
+
+  // Use the stride trick if vertical flip is needed.
+  if (options != NULL && options->flip) {
+    status = WebPFlipBuffer(out);
+  }
+  return status;
+}
+
+//------------------------------------------------------------------------------
+// constructors / destructors
+
+int WebPInitDecBufferInternal(WebPDecBuffer* buffer, int version) {
+  if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_DECODER_ABI_VERSION)) {
+    return 0;  // version mismatch
+  }
+  if (buffer == NULL) return 0;
+  memset(buffer, 0, sizeof(*buffer));
+  return 1;
+}
+
+void WebPFreeDecBuffer(WebPDecBuffer* buffer) {
+  if (buffer != NULL) {
+    if (buffer->is_external_memory <= 0) {
+      WebPSafeFree(buffer->private_memory);
+    }
+    buffer->private_memory = NULL;
+  }
+}
+
+void WebPCopyDecBuffer(const WebPDecBuffer* const src,
+                       WebPDecBuffer* const dst) {
+  if (src != NULL && dst != NULL) {
+    *dst = *src;
+    if (src->private_memory != NULL) {
+      dst->is_external_memory = 1;   // dst buffer doesn't own the memory.
+      dst->private_memory = NULL;
+    }
+  }
+}
+
+// Copy and transfer ownership from src to dst (beware of parameter order!)
+void WebPGrabDecBuffer(WebPDecBuffer* const src, WebPDecBuffer* const dst) {
+  if (src != NULL && dst != NULL) {
+    *dst = *src;
+    if (src->private_memory != NULL) {
+      src->is_external_memory = 1;   // src relinquishes ownership
+      src->private_memory = NULL;
+    }
+  }
+}
+
+VP8StatusCode WebPCopyDecBufferPixels(const WebPDecBuffer* const src_buf,
+                                      WebPDecBuffer* const dst_buf) {
+  assert(src_buf != NULL && dst_buf != NULL);
+  assert(src_buf->colorspace == dst_buf->colorspace);
+
+  dst_buf->width = src_buf->width;
+  dst_buf->height = src_buf->height;
+  if (CheckDecBuffer(dst_buf) != VP8_STATUS_OK) {
+    return VP8_STATUS_INVALID_PARAM;
+  }
+  if (WebPIsRGBMode(src_buf->colorspace)) {
+    const WebPRGBABuffer* const src = &src_buf->u.RGBA;
+    const WebPRGBABuffer* const dst = &dst_buf->u.RGBA;
+    WebPCopyPlane(src->rgba, src->stride, dst->rgba, dst->stride,
+                  src_buf->width * kModeBpp[src_buf->colorspace],
+                  src_buf->height);
+  } else {
+    const WebPYUVABuffer* const src = &src_buf->u.YUVA;
+    const WebPYUVABuffer* const dst = &dst_buf->u.YUVA;
+    WebPCopyPlane(src->y, src->y_stride, dst->y, dst->y_stride,
+                  src_buf->width, src_buf->height);
+    WebPCopyPlane(src->u, src->u_stride, dst->u, dst->u_stride,
+                  (src_buf->width + 1) / 2, (src_buf->height + 1) / 2);
+    WebPCopyPlane(src->v, src->v_stride, dst->v, dst->v_stride,
+                  (src_buf->width + 1) / 2, (src_buf->height + 1) / 2);
+    if (WebPIsAlphaMode(src_buf->colorspace)) {
+      WebPCopyPlane(src->a, src->a_stride, dst->a, dst->a_stride,
+                    src_buf->width, src_buf->height);
+    }
+  }
+  return VP8_STATUS_OK;
+}
+
+int WebPAvoidSlowMemory(const WebPDecBuffer* const output,
+                        const WebPBitstreamFeatures* const features) {
+  assert(output != NULL);
+  return (output->is_external_memory >= 2) &&
+         WebPIsPremultipliedMode(output->colorspace) &&
+         (features != NULL && features->has_alpha);
+}
+
+//------------------------------------------------------------------------------

+ 54 - 0
Source/ThirdParty/WebP/src/dec/common_dec.h

@@ -0,0 +1,54 @@
+// Copyright 2015 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Definitions and macros common to encoding and decoding
+//
+// Author: Skal ([email protected])
+
+#ifndef WEBP_DEC_COMMON_H_
+#define WEBP_DEC_COMMON_H_
+
+// intra prediction modes
+enum { B_DC_PRED = 0,   // 4x4 modes
+       B_TM_PRED = 1,
+       B_VE_PRED = 2,
+       B_HE_PRED = 3,
+       B_RD_PRED = 4,
+       B_VR_PRED = 5,
+       B_LD_PRED = 6,
+       B_VL_PRED = 7,
+       B_HD_PRED = 8,
+       B_HU_PRED = 9,
+       NUM_BMODES = B_HU_PRED + 1 - B_DC_PRED,  // = 10
+
+       // Luma16 or UV modes
+       DC_PRED = B_DC_PRED, V_PRED = B_VE_PRED,
+       H_PRED = B_HE_PRED, TM_PRED = B_TM_PRED,
+       B_PRED = NUM_BMODES,   // refined I4x4 mode
+       NUM_PRED_MODES = 4,
+
+       // special modes
+       B_DC_PRED_NOTOP = 4,
+       B_DC_PRED_NOLEFT = 5,
+       B_DC_PRED_NOTOPLEFT = 6,
+       NUM_B_DC_MODES = 7 };
+
+enum { MB_FEATURE_TREE_PROBS = 3,
+       NUM_MB_SEGMENTS = 4,
+       NUM_REF_LF_DELTAS = 4,
+       NUM_MODE_LF_DELTAS = 4,    // I4x4, ZERO, *, SPLIT
+       MAX_NUM_PARTITIONS = 8,
+       // Probabilities
+       NUM_TYPES = 4,   // 0: i16-AC,  1: i16-DC,  2:chroma-AC,  3:i4-AC
+       NUM_BANDS = 8,
+       NUM_CTX = 3,
+       NUM_PROBAS = 11
+     };
+
+#endif    // WEBP_DEC_COMMON_H_

+ 812 - 0
Source/ThirdParty/WebP/src/dec/frame_dec.c

@@ -0,0 +1,812 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Frame-reconstruction function. Memory allocation.
+//
+// Author: Skal ([email protected])
+
+#include <stdlib.h>
+#include "./vp8i_dec.h"
+#include "../utils/utils.h"
+
+//------------------------------------------------------------------------------
+// Main reconstruction function.
+
+static const int kScan[16] = {
+  0 +  0 * BPS,  4 +  0 * BPS, 8 +  0 * BPS, 12 +  0 * BPS,
+  0 +  4 * BPS,  4 +  4 * BPS, 8 +  4 * BPS, 12 +  4 * BPS,
+  0 +  8 * BPS,  4 +  8 * BPS, 8 +  8 * BPS, 12 +  8 * BPS,
+  0 + 12 * BPS,  4 + 12 * BPS, 8 + 12 * BPS, 12 + 12 * BPS
+};
+
+static int CheckMode(int mb_x, int mb_y, int mode) {
+  if (mode == B_DC_PRED) {
+    if (mb_x == 0) {
+      return (mb_y == 0) ? B_DC_PRED_NOTOPLEFT : B_DC_PRED_NOLEFT;
+    } else {
+      return (mb_y == 0) ? B_DC_PRED_NOTOP : B_DC_PRED;
+    }
+  }
+  return mode;
+}
+
+static void Copy32b(uint8_t* const dst, const uint8_t* const src) {
+  memcpy(dst, src, 4);
+}
+
+static WEBP_INLINE void DoTransform(uint32_t bits, const int16_t* const src,
+                                    uint8_t* const dst) {
+  switch (bits >> 30) {
+    case 3:
+      VP8Transform(src, dst, 0);
+      break;
+    case 2:
+      VP8TransformAC3(src, dst);
+      break;
+    case 1:
+      VP8TransformDC(src, dst);
+      break;
+    default:
+      break;
+  }
+}
+
+static void DoUVTransform(uint32_t bits, const int16_t* const src,
+                          uint8_t* const dst) {
+  if (bits & 0xff) {    // any non-zero coeff at all?
+    if (bits & 0xaa) {  // any non-zero AC coefficient?
+      VP8TransformUV(src, dst);   // note we don't use the AC3 variant for U/V
+    } else {
+      VP8TransformDCUV(src, dst);
+    }
+  }
+}
+
+static void ReconstructRow(const VP8Decoder* const dec,
+                           const VP8ThreadContext* ctx) {
+  int j;
+  int mb_x;
+  const int mb_y = ctx->mb_y_;
+  const int cache_id = ctx->id_;
+  uint8_t* const y_dst = dec->yuv_b_ + Y_OFF;
+  uint8_t* const u_dst = dec->yuv_b_ + U_OFF;
+  uint8_t* const v_dst = dec->yuv_b_ + V_OFF;
+
+  // Initialize left-most block.
+  for (j = 0; j < 16; ++j) {
+    y_dst[j * BPS - 1] = 129;
+  }
+  for (j = 0; j < 8; ++j) {
+    u_dst[j * BPS - 1] = 129;
+    v_dst[j * BPS - 1] = 129;
+  }
+
+  // Init top-left sample on left column too.
+  if (mb_y > 0) {
+    y_dst[-1 - BPS] = u_dst[-1 - BPS] = v_dst[-1 - BPS] = 129;
+  } else {
+    // we only need to do this init once at block (0,0).
+    // Afterward, it remains valid for the whole topmost row.
+    memset(y_dst - BPS - 1, 127, 16 + 4 + 1);
+    memset(u_dst - BPS - 1, 127, 8 + 1);
+    memset(v_dst - BPS - 1, 127, 8 + 1);
+  }
+
+  // Reconstruct one row.
+  for (mb_x = 0; mb_x < dec->mb_w_; ++mb_x) {
+    const VP8MBData* const block = ctx->mb_data_ + mb_x;
+
+    // Rotate in the left samples from previously decoded block. We move four
+    // pixels at a time for alignment reason, and because of in-loop filter.
+    if (mb_x > 0) {
+      for (j = -1; j < 16; ++j) {
+        Copy32b(&y_dst[j * BPS - 4], &y_dst[j * BPS + 12]);
+      }
+      for (j = -1; j < 8; ++j) {
+        Copy32b(&u_dst[j * BPS - 4], &u_dst[j * BPS + 4]);
+        Copy32b(&v_dst[j * BPS - 4], &v_dst[j * BPS + 4]);
+      }
+    }
+    {
+      // bring top samples into the cache
+      VP8TopSamples* const top_yuv = dec->yuv_t_ + mb_x;
+      const int16_t* const coeffs = block->coeffs_;
+      uint32_t bits = block->non_zero_y_;
+      int n;
+
+      if (mb_y > 0) {
+        memcpy(y_dst - BPS, top_yuv[0].y, 16);
+        memcpy(u_dst - BPS, top_yuv[0].u, 8);
+        memcpy(v_dst - BPS, top_yuv[0].v, 8);
+      }
+
+      // predict and add residuals
+      if (block->is_i4x4_) {   // 4x4
+        uint32_t* const top_right = (uint32_t*)(y_dst - BPS + 16);
+
+        if (mb_y > 0) {
+          if (mb_x >= dec->mb_w_ - 1) {    // on rightmost border
+            memset(top_right, top_yuv[0].y[15], sizeof(*top_right));
+          } else {
+            memcpy(top_right, top_yuv[1].y, sizeof(*top_right));
+          }
+        }
+        // replicate the top-right pixels below
+        top_right[BPS] = top_right[2 * BPS] = top_right[3 * BPS] = top_right[0];
+
+        // predict and add residuals for all 4x4 blocks in turn.
+        for (n = 0; n < 16; ++n, bits <<= 2) {
+          uint8_t* const dst = y_dst + kScan[n];
+          VP8PredLuma4[block->imodes_[n]](dst);
+          DoTransform(bits, coeffs + n * 16, dst);
+        }
+      } else {    // 16x16
+        const int pred_func = CheckMode(mb_x, mb_y, block->imodes_[0]);
+        VP8PredLuma16[pred_func](y_dst);
+        if (bits != 0) {
+          for (n = 0; n < 16; ++n, bits <<= 2) {
+            DoTransform(bits, coeffs + n * 16, y_dst + kScan[n]);
+          }
+        }
+      }
+      {
+        // Chroma
+        const uint32_t bits_uv = block->non_zero_uv_;
+        const int pred_func = CheckMode(mb_x, mb_y, block->uvmode_);
+        VP8PredChroma8[pred_func](u_dst);
+        VP8PredChroma8[pred_func](v_dst);
+        DoUVTransform(bits_uv >> 0, coeffs + 16 * 16, u_dst);
+        DoUVTransform(bits_uv >> 8, coeffs + 20 * 16, v_dst);
+      }
+
+      // stash away top samples for next block
+      if (mb_y < dec->mb_h_ - 1) {
+        memcpy(top_yuv[0].y, y_dst + 15 * BPS, 16);
+        memcpy(top_yuv[0].u, u_dst +  7 * BPS,  8);
+        memcpy(top_yuv[0].v, v_dst +  7 * BPS,  8);
+      }
+    }
+    // Transfer reconstructed samples from yuv_b_ cache to final destination.
+    {
+      const int y_offset = cache_id * 16 * dec->cache_y_stride_;
+      const int uv_offset = cache_id * 8 * dec->cache_uv_stride_;
+      uint8_t* const y_out = dec->cache_y_ + mb_x * 16 + y_offset;
+      uint8_t* const u_out = dec->cache_u_ + mb_x * 8 + uv_offset;
+      uint8_t* const v_out = dec->cache_v_ + mb_x * 8 + uv_offset;
+      for (j = 0; j < 16; ++j) {
+        memcpy(y_out + j * dec->cache_y_stride_, y_dst + j * BPS, 16);
+      }
+      for (j = 0; j < 8; ++j) {
+        memcpy(u_out + j * dec->cache_uv_stride_, u_dst + j * BPS, 8);
+        memcpy(v_out + j * dec->cache_uv_stride_, v_dst + j * BPS, 8);
+      }
+    }
+  }
+}
+
+//------------------------------------------------------------------------------
+// Filtering
+
+// kFilterExtraRows[] = How many extra lines are needed on the MB boundary
+// for caching, given a filtering level.
+// Simple filter:  up to 2 luma samples are read and 1 is written.
+// Complex filter: up to 4 luma samples are read and 3 are written. Same for
+//                 U/V, so it's 8 samples total (because of the 2x upsampling).
+static const uint8_t kFilterExtraRows[3] = { 0, 2, 8 };
+
+static void DoFilter(const VP8Decoder* const dec, int mb_x, int mb_y) {
+  const VP8ThreadContext* const ctx = &dec->thread_ctx_;
+  const int cache_id = ctx->id_;
+  const int y_bps = dec->cache_y_stride_;
+  const VP8FInfo* const f_info = ctx->f_info_ + mb_x;
+  uint8_t* const y_dst = dec->cache_y_ + cache_id * 16 * y_bps + mb_x * 16;
+  const int ilevel = f_info->f_ilevel_;
+  const int limit = f_info->f_limit_;
+  if (limit == 0) {
+    return;
+  }
+  assert(limit >= 3);
+  if (dec->filter_type_ == 1) {   // simple
+    if (mb_x > 0) {
+      VP8SimpleHFilter16(y_dst, y_bps, limit + 4);
+    }
+    if (f_info->f_inner_) {
+      VP8SimpleHFilter16i(y_dst, y_bps, limit);
+    }
+    if (mb_y > 0) {
+      VP8SimpleVFilter16(y_dst, y_bps, limit + 4);
+    }
+    if (f_info->f_inner_) {
+      VP8SimpleVFilter16i(y_dst, y_bps, limit);
+    }
+  } else {    // complex
+    const int uv_bps = dec->cache_uv_stride_;
+    uint8_t* const u_dst = dec->cache_u_ + cache_id * 8 * uv_bps + mb_x * 8;
+    uint8_t* const v_dst = dec->cache_v_ + cache_id * 8 * uv_bps + mb_x * 8;
+    const int hev_thresh = f_info->hev_thresh_;
+    if (mb_x > 0) {
+      VP8HFilter16(y_dst, y_bps, limit + 4, ilevel, hev_thresh);
+      VP8HFilter8(u_dst, v_dst, uv_bps, limit + 4, ilevel, hev_thresh);
+    }
+    if (f_info->f_inner_) {
+      VP8HFilter16i(y_dst, y_bps, limit, ilevel, hev_thresh);
+      VP8HFilter8i(u_dst, v_dst, uv_bps, limit, ilevel, hev_thresh);
+    }
+    if (mb_y > 0) {
+      VP8VFilter16(y_dst, y_bps, limit + 4, ilevel, hev_thresh);
+      VP8VFilter8(u_dst, v_dst, uv_bps, limit + 4, ilevel, hev_thresh);
+    }
+    if (f_info->f_inner_) {
+      VP8VFilter16i(y_dst, y_bps, limit, ilevel, hev_thresh);
+      VP8VFilter8i(u_dst, v_dst, uv_bps, limit, ilevel, hev_thresh);
+    }
+  }
+}
+
+// Filter the decoded macroblock row (if needed)
+static void FilterRow(const VP8Decoder* const dec) {
+  int mb_x;
+  const int mb_y = dec->thread_ctx_.mb_y_;
+  assert(dec->thread_ctx_.filter_row_);
+  for (mb_x = dec->tl_mb_x_; mb_x < dec->br_mb_x_; ++mb_x) {
+    DoFilter(dec, mb_x, mb_y);
+  }
+}
+
+//------------------------------------------------------------------------------
+// Precompute the filtering strength for each segment and each i4x4/i16x16 mode.
+
+static void PrecomputeFilterStrengths(VP8Decoder* const dec) {
+  if (dec->filter_type_ > 0) {
+    int s;
+    const VP8FilterHeader* const hdr = &dec->filter_hdr_;
+    for (s = 0; s < NUM_MB_SEGMENTS; ++s) {
+      int i4x4;
+      // First, compute the initial level
+      int base_level;
+      if (dec->segment_hdr_.use_segment_) {
+        base_level = dec->segment_hdr_.filter_strength_[s];
+        if (!dec->segment_hdr_.absolute_delta_) {
+          base_level += hdr->level_;
+        }
+      } else {
+        base_level = hdr->level_;
+      }
+      for (i4x4 = 0; i4x4 <= 1; ++i4x4) {
+        VP8FInfo* const info = &dec->fstrengths_[s][i4x4];
+        int level = base_level;
+        if (hdr->use_lf_delta_) {
+          level += hdr->ref_lf_delta_[0];
+          if (i4x4) {
+            level += hdr->mode_lf_delta_[0];
+          }
+        }
+        level = (level < 0) ? 0 : (level > 63) ? 63 : level;
+        if (level > 0) {
+          int ilevel = level;
+          if (hdr->sharpness_ > 0) {
+            if (hdr->sharpness_ > 4) {
+              ilevel >>= 2;
+            } else {
+              ilevel >>= 1;
+            }
+            if (ilevel > 9 - hdr->sharpness_) {
+              ilevel = 9 - hdr->sharpness_;
+            }
+          }
+          if (ilevel < 1) ilevel = 1;
+          info->f_ilevel_ = ilevel;
+          info->f_limit_ = 2 * level + ilevel;
+          info->hev_thresh_ = (level >= 40) ? 2 : (level >= 15) ? 1 : 0;
+        } else {
+          info->f_limit_ = 0;  // no filtering
+        }
+        info->f_inner_ = i4x4;
+      }
+    }
+  }
+}
+
+//------------------------------------------------------------------------------
+// Dithering
+
+// minimal amp that will provide a non-zero dithering effect
+#define MIN_DITHER_AMP 4
+
+#define DITHER_AMP_TAB_SIZE 12
+static const int kQuantToDitherAmp[DITHER_AMP_TAB_SIZE] = {
+  // roughly, it's dqm->uv_mat_[1]
+  8, 7, 6, 4, 4, 2, 2, 2, 1, 1, 1, 1
+};
+
+void VP8InitDithering(const WebPDecoderOptions* const options,
+                      VP8Decoder* const dec) {
+  assert(dec != NULL);
+  if (options != NULL) {
+    const int d = options->dithering_strength;
+    const int max_amp = (1 << VP8_RANDOM_DITHER_FIX) - 1;
+    const int f = (d < 0) ? 0 : (d > 100) ? max_amp : (d * max_amp / 100);
+    if (f > 0) {
+      int s;
+      int all_amp = 0;
+      for (s = 0; s < NUM_MB_SEGMENTS; ++s) {
+        VP8QuantMatrix* const dqm = &dec->dqm_[s];
+        if (dqm->uv_quant_ < DITHER_AMP_TAB_SIZE) {
+          // TODO(skal): should we specially dither more for uv_quant_ < 0?
+          const int idx = (dqm->uv_quant_ < 0) ? 0 : dqm->uv_quant_;
+          dqm->dither_ = (f * kQuantToDitherAmp[idx]) >> 3;
+        }
+        all_amp |= dqm->dither_;
+      }
+      if (all_amp != 0) {
+        VP8InitRandom(&dec->dithering_rg_, 1.0f);
+        dec->dither_ = 1;
+      }
+    }
+    // potentially allow alpha dithering
+    dec->alpha_dithering_ = options->alpha_dithering_strength;
+    if (dec->alpha_dithering_ > 100) {
+      dec->alpha_dithering_ = 100;
+    } else if (dec->alpha_dithering_ < 0) {
+      dec->alpha_dithering_ = 0;
+    }
+  }
+}
+
+// Convert to range: [-2,2] for dither=50, [-4,4] for dither=100
+static void Dither8x8(VP8Random* const rg, uint8_t* dst, int bps, int amp) {
+  uint8_t dither[64];
+  int i;
+  for (i = 0; i < 8 * 8; ++i) {
+    dither[i] = VP8RandomBits2(rg, VP8_DITHER_AMP_BITS + 1, amp);
+  }
+  VP8DitherCombine8x8(dither, dst, bps);
+}
+
+static void DitherRow(VP8Decoder* const dec) {
+  int mb_x;
+  assert(dec->dither_);
+  for (mb_x = dec->tl_mb_x_; mb_x < dec->br_mb_x_; ++mb_x) {
+    const VP8ThreadContext* const ctx = &dec->thread_ctx_;
+    const VP8MBData* const data = ctx->mb_data_ + mb_x;
+    const int cache_id = ctx->id_;
+    const int uv_bps = dec->cache_uv_stride_;
+    if (data->dither_ >= MIN_DITHER_AMP) {
+      uint8_t* const u_dst = dec->cache_u_ + cache_id * 8 * uv_bps + mb_x * 8;
+      uint8_t* const v_dst = dec->cache_v_ + cache_id * 8 * uv_bps + mb_x * 8;
+      Dither8x8(&dec->dithering_rg_, u_dst, uv_bps, data->dither_);
+      Dither8x8(&dec->dithering_rg_, v_dst, uv_bps, data->dither_);
+    }
+  }
+}
+
+//------------------------------------------------------------------------------
+// This function is called after a row of macroblocks is finished decoding.
+// It also takes into account the following restrictions:
+//  * In case of in-loop filtering, we must hold off sending some of the bottom
+//    pixels as they are yet unfiltered. They will be when the next macroblock
+//    row is decoded. Meanwhile, we must preserve them by rotating them in the
+//    cache area. This doesn't hold for the very bottom row of the uncropped
+//    picture of course.
+//  * we must clip the remaining pixels against the cropping area. The VP8Io
+//    struct must have the following fields set correctly before calling put():
+
+#define MACROBLOCK_VPOS(mb_y)  ((mb_y) * 16)    // vertical position of a MB
+
+// Finalize and transmit a complete row. Return false in case of user-abort.
+static int FinishRow(VP8Decoder* const dec, VP8Io* const io) {
+  int ok = 1;
+  const VP8ThreadContext* const ctx = &dec->thread_ctx_;
+  const int cache_id = ctx->id_;
+  const int extra_y_rows = kFilterExtraRows[dec->filter_type_];
+  const int ysize = extra_y_rows * dec->cache_y_stride_;
+  const int uvsize = (extra_y_rows / 2) * dec->cache_uv_stride_;
+  const int y_offset = cache_id * 16 * dec->cache_y_stride_;
+  const int uv_offset = cache_id * 8 * dec->cache_uv_stride_;
+  uint8_t* const ydst = dec->cache_y_ - ysize + y_offset;
+  uint8_t* const udst = dec->cache_u_ - uvsize + uv_offset;
+  uint8_t* const vdst = dec->cache_v_ - uvsize + uv_offset;
+  const int mb_y = ctx->mb_y_;
+  const int is_first_row = (mb_y == 0);
+  const int is_last_row = (mb_y >= dec->br_mb_y_ - 1);
+
+  if (dec->mt_method_ == 2) {
+    ReconstructRow(dec, ctx);
+  }
+
+  if (ctx->filter_row_) {
+    FilterRow(dec);
+  }
+
+  if (dec->dither_) {
+    DitherRow(dec);
+  }
+
+  if (io->put != NULL) {
+    int y_start = MACROBLOCK_VPOS(mb_y);
+    int y_end = MACROBLOCK_VPOS(mb_y + 1);
+    if (!is_first_row) {
+      y_start -= extra_y_rows;
+      io->y = ydst;
+      io->u = udst;
+      io->v = vdst;
+    } else {
+      io->y = dec->cache_y_ + y_offset;
+      io->u = dec->cache_u_ + uv_offset;
+      io->v = dec->cache_v_ + uv_offset;
+    }
+
+    if (!is_last_row) {
+      y_end -= extra_y_rows;
+    }
+    if (y_end > io->crop_bottom) {
+      y_end = io->crop_bottom;    // make sure we don't overflow on last row.
+    }
+    io->a = NULL;
+    if (dec->alpha_data_ != NULL && y_start < y_end) {
+      // TODO(skal): testing presence of alpha with dec->alpha_data_ is not a
+      // good idea.
+      io->a = VP8DecompressAlphaRows(dec, io, y_start, y_end - y_start);
+      if (io->a == NULL) {
+        return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR,
+                           "Could not decode alpha data.");
+      }
+    }
+    if (y_start < io->crop_top) {
+      const int delta_y = io->crop_top - y_start;
+      y_start = io->crop_top;
+      assert(!(delta_y & 1));
+      io->y += dec->cache_y_stride_ * delta_y;
+      io->u += dec->cache_uv_stride_ * (delta_y >> 1);
+      io->v += dec->cache_uv_stride_ * (delta_y >> 1);
+      if (io->a != NULL) {
+        io->a += io->width * delta_y;
+      }
+    }
+    if (y_start < y_end) {
+      io->y += io->crop_left;
+      io->u += io->crop_left >> 1;
+      io->v += io->crop_left >> 1;
+      if (io->a != NULL) {
+        io->a += io->crop_left;
+      }
+      io->mb_y = y_start - io->crop_top;
+      io->mb_w = io->crop_right - io->crop_left;
+      io->mb_h = y_end - y_start;
+      ok = io->put(io);
+    }
+  }
+  // rotate top samples if needed
+  if (cache_id + 1 == dec->num_caches_) {
+    if (!is_last_row) {
+      memcpy(dec->cache_y_ - ysize, ydst + 16 * dec->cache_y_stride_, ysize);
+      memcpy(dec->cache_u_ - uvsize, udst + 8 * dec->cache_uv_stride_, uvsize);
+      memcpy(dec->cache_v_ - uvsize, vdst + 8 * dec->cache_uv_stride_, uvsize);
+    }
+  }
+
+  return ok;
+}
+
+#undef MACROBLOCK_VPOS
+
+//------------------------------------------------------------------------------
+
+int VP8ProcessRow(VP8Decoder* const dec, VP8Io* const io) {
+  int ok = 1;
+  VP8ThreadContext* const ctx = &dec->thread_ctx_;
+  const int filter_row =
+      (dec->filter_type_ > 0) &&
+      (dec->mb_y_ >= dec->tl_mb_y_) && (dec->mb_y_ <= dec->br_mb_y_);
+  if (dec->mt_method_ == 0) {
+    // ctx->id_ and ctx->f_info_ are already set
+    ctx->mb_y_ = dec->mb_y_;
+    ctx->filter_row_ = filter_row;
+    ReconstructRow(dec, ctx);
+    ok = FinishRow(dec, io);
+  } else {
+    WebPWorker* const worker = &dec->worker_;
+    // Finish previous job *before* updating context
+    ok &= WebPGetWorkerInterface()->Sync(worker);
+    assert(worker->status_ == OK);
+    if (ok) {   // spawn a new deblocking/output job
+      ctx->io_ = *io;
+      ctx->id_ = dec->cache_id_;
+      ctx->mb_y_ = dec->mb_y_;
+      ctx->filter_row_ = filter_row;
+      if (dec->mt_method_ == 2) {  // swap macroblock data
+        VP8MBData* const tmp = ctx->mb_data_;
+        ctx->mb_data_ = dec->mb_data_;
+        dec->mb_data_ = tmp;
+      } else {
+        // perform reconstruction directly in main thread
+        ReconstructRow(dec, ctx);
+      }
+      if (filter_row) {            // swap filter info
+        VP8FInfo* const tmp = ctx->f_info_;
+        ctx->f_info_ = dec->f_info_;
+        dec->f_info_ = tmp;
+      }
+      // (reconstruct)+filter in parallel
+      WebPGetWorkerInterface()->Launch(worker);
+      if (++dec->cache_id_ == dec->num_caches_) {
+        dec->cache_id_ = 0;
+      }
+    }
+  }
+  return ok;
+}
+
+//------------------------------------------------------------------------------
+// Finish setting up the decoding parameter once user's setup() is called.
+
+VP8StatusCode VP8EnterCritical(VP8Decoder* const dec, VP8Io* const io) {
+  // Call setup() first. This may trigger additional decoding features on 'io'.
+  // Note: Afterward, we must call teardown() no matter what.
+  if (io->setup != NULL && !io->setup(io)) {
+    VP8SetError(dec, VP8_STATUS_USER_ABORT, "Frame setup failed");
+    return dec->status_;
+  }
+
+  // Disable filtering per user request
+  if (io->bypass_filtering) {
+    dec->filter_type_ = 0;
+  }
+  // TODO(skal): filter type / strength / sharpness forcing
+
+  // Define the area where we can skip in-loop filtering, in case of cropping.
+  //
+  // 'Simple' filter reads two luma samples outside of the macroblock
+  // and filters one. It doesn't filter the chroma samples. Hence, we can
+  // avoid doing the in-loop filtering before crop_top/crop_left position.
+  // For the 'Complex' filter, 3 samples are read and up to 3 are filtered.
+  // Means: there's a dependency chain that goes all the way up to the
+  // top-left corner of the picture (MB #0). We must filter all the previous
+  // macroblocks.
+  // TODO(skal): add an 'approximate_decoding' option, that won't produce
+  // a 1:1 bit-exactness for complex filtering?
+  {
+    const int extra_pixels = kFilterExtraRows[dec->filter_type_];
+    if (dec->filter_type_ == 2) {
+      // For complex filter, we need to preserve the dependency chain.
+      dec->tl_mb_x_ = 0;
+      dec->tl_mb_y_ = 0;
+    } else {
+      // For simple filter, we can filter only the cropped region.
+      // We include 'extra_pixels' on the other side of the boundary, since
+      // vertical or horizontal filtering of the previous macroblock can
+      // modify some abutting pixels.
+      dec->tl_mb_x_ = (io->crop_left - extra_pixels) >> 4;
+      dec->tl_mb_y_ = (io->crop_top - extra_pixels) >> 4;
+      if (dec->tl_mb_x_ < 0) dec->tl_mb_x_ = 0;
+      if (dec->tl_mb_y_ < 0) dec->tl_mb_y_ = 0;
+    }
+    // We need some 'extra' pixels on the right/bottom.
+    dec->br_mb_y_ = (io->crop_bottom + 15 + extra_pixels) >> 4;
+    dec->br_mb_x_ = (io->crop_right + 15 + extra_pixels) >> 4;
+    if (dec->br_mb_x_ > dec->mb_w_) {
+      dec->br_mb_x_ = dec->mb_w_;
+    }
+    if (dec->br_mb_y_ > dec->mb_h_) {
+      dec->br_mb_y_ = dec->mb_h_;
+    }
+  }
+  PrecomputeFilterStrengths(dec);
+  return VP8_STATUS_OK;
+}
+
+int VP8ExitCritical(VP8Decoder* const dec, VP8Io* const io) {
+  int ok = 1;
+  if (dec->mt_method_ > 0) {
+    ok = WebPGetWorkerInterface()->Sync(&dec->worker_);
+  }
+
+  if (io->teardown != NULL) {
+    io->teardown(io);
+  }
+  return ok;
+}
+
+//------------------------------------------------------------------------------
+// For multi-threaded decoding we need to use 3 rows of 16 pixels as delay line.
+//
+// Reason is: the deblocking filter cannot deblock the bottom horizontal edges
+// immediately, and needs to wait for first few rows of the next macroblock to
+// be decoded. Hence, deblocking is lagging behind by 4 or 8 pixels (depending
+// on strength).
+// With two threads, the vertical positions of the rows being decoded are:
+// Decode:  [ 0..15][16..31][32..47][48..63][64..79][...
+// Deblock:         [ 0..11][12..27][28..43][44..59][...
+// If we use two threads and two caches of 16 pixels, the sequence would be:
+// Decode:  [ 0..15][16..31][ 0..15!!][16..31][ 0..15][...
+// Deblock:         [ 0..11][12..27!!][-4..11][12..27][...
+// The problem occurs during row [12..15!!] that both the decoding and
+// deblocking threads are writing simultaneously.
+// With 3 cache lines, one get a safe write pattern:
+// Decode:  [ 0..15][16..31][32..47][ 0..15][16..31][32..47][0..
+// Deblock:         [ 0..11][12..27][28..43][-4..11][12..27][28...
+// Note that multi-threaded output _without_ deblocking can make use of two
+// cache lines of 16 pixels only, since there's no lagging behind. The decoding
+// and output process have non-concurrent writing:
+// Decode:  [ 0..15][16..31][ 0..15][16..31][...
+// io->put:         [ 0..15][16..31][ 0..15][...
+
+#define MT_CACHE_LINES 3
+#define ST_CACHE_LINES 1   // 1 cache row only for single-threaded case
+
+// Initialize multi/single-thread worker
+static int InitThreadContext(VP8Decoder* const dec) {
+  dec->cache_id_ = 0;
+  if (dec->mt_method_ > 0) {
+    WebPWorker* const worker = &dec->worker_;
+    if (!WebPGetWorkerInterface()->Reset(worker)) {
+      return VP8SetError(dec, VP8_STATUS_OUT_OF_MEMORY,
+                         "thread initialization failed.");
+    }
+    worker->data1 = dec;
+    worker->data2 = (void*)&dec->thread_ctx_.io_;
+    worker->hook = (WebPWorkerHook)FinishRow;
+    dec->num_caches_ =
+      (dec->filter_type_ > 0) ? MT_CACHE_LINES : MT_CACHE_LINES - 1;
+  } else {
+    dec->num_caches_ = ST_CACHE_LINES;
+  }
+  return 1;
+}
+
+int VP8GetThreadMethod(const WebPDecoderOptions* const options,
+                       const WebPHeaderStructure* const headers,
+                       int width, int height) {
+  if (options == NULL || options->use_threads == 0) {
+    return 0;
+  }
+  (void)headers;
+  (void)width;
+  (void)height;
+  assert(headers == NULL || !headers->is_lossless);
+#if defined(WEBP_USE_THREAD)
+  if (width < MIN_WIDTH_FOR_THREADS) return 0;
+  // TODO(skal): tune the heuristic further
+#if 0
+  if (height < 2 * width) return 2;
+#endif
+  return 2;
+#else   // !WEBP_USE_THREAD
+  return 0;
+#endif
+}
+
+#undef MT_CACHE_LINES
+#undef ST_CACHE_LINES
+
+//------------------------------------------------------------------------------
+// Memory setup
+
+static int AllocateMemory(VP8Decoder* const dec) {
+  const int num_caches = dec->num_caches_;
+  const int mb_w = dec->mb_w_;
+  // Note: we use 'size_t' when there's no overflow risk, uint64_t otherwise.
+  const size_t intra_pred_mode_size = 4 * mb_w * sizeof(uint8_t);
+  const size_t top_size = sizeof(VP8TopSamples) * mb_w;
+  const size_t mb_info_size = (mb_w + 1) * sizeof(VP8MB);
+  const size_t f_info_size =
+      (dec->filter_type_ > 0) ?
+          mb_w * (dec->mt_method_ > 0 ? 2 : 1) * sizeof(VP8FInfo)
+        : 0;
+  const size_t yuv_size = YUV_SIZE * sizeof(*dec->yuv_b_);
+  const size_t mb_data_size =
+      (dec->mt_method_ == 2 ? 2 : 1) * mb_w * sizeof(*dec->mb_data_);
+  const size_t cache_height = (16 * num_caches
+                            + kFilterExtraRows[dec->filter_type_]) * 3 / 2;
+  const size_t cache_size = top_size * cache_height;
+  // alpha_size is the only one that scales as width x height.
+  const uint64_t alpha_size = (dec->alpha_data_ != NULL) ?
+      (uint64_t)dec->pic_hdr_.width_ * dec->pic_hdr_.height_ : 0ULL;
+  const uint64_t needed = (uint64_t)intra_pred_mode_size
+                        + top_size + mb_info_size + f_info_size
+                        + yuv_size + mb_data_size
+                        + cache_size + alpha_size + WEBP_ALIGN_CST;
+  uint8_t* mem;
+
+  if (needed != (size_t)needed) return 0;  // check for overflow
+  if (needed > dec->mem_size_) {
+    WebPSafeFree(dec->mem_);
+    dec->mem_size_ = 0;
+    dec->mem_ = WebPSafeMalloc(needed, sizeof(uint8_t));
+    if (dec->mem_ == NULL) {
+      return VP8SetError(dec, VP8_STATUS_OUT_OF_MEMORY,
+                         "no memory during frame initialization.");
+    }
+    // down-cast is ok, thanks to WebPSafeMalloc() above.
+    dec->mem_size_ = (size_t)needed;
+  }
+
+  mem = (uint8_t*)dec->mem_;
+  dec->intra_t_ = (uint8_t*)mem;
+  mem += intra_pred_mode_size;
+
+  dec->yuv_t_ = (VP8TopSamples*)mem;
+  mem += top_size;
+
+  dec->mb_info_ = ((VP8MB*)mem) + 1;
+  mem += mb_info_size;
+
+  dec->f_info_ = f_info_size ? (VP8FInfo*)mem : NULL;
+  mem += f_info_size;
+  dec->thread_ctx_.id_ = 0;
+  dec->thread_ctx_.f_info_ = dec->f_info_;
+  if (dec->mt_method_ > 0) {
+    // secondary cache line. The deblocking process need to make use of the
+    // filtering strength from previous macroblock row, while the new ones
+    // are being decoded in parallel. We'll just swap the pointers.
+    dec->thread_ctx_.f_info_ += mb_w;
+  }
+
+  mem = (uint8_t*)WEBP_ALIGN(mem);
+  assert((yuv_size & WEBP_ALIGN_CST) == 0);
+  dec->yuv_b_ = (uint8_t*)mem;
+  mem += yuv_size;
+
+  dec->mb_data_ = (VP8MBData*)mem;
+  dec->thread_ctx_.mb_data_ = (VP8MBData*)mem;
+  if (dec->mt_method_ == 2) {
+    dec->thread_ctx_.mb_data_ += mb_w;
+  }
+  mem += mb_data_size;
+
+  dec->cache_y_stride_ = 16 * mb_w;
+  dec->cache_uv_stride_ = 8 * mb_w;
+  {
+    const int extra_rows = kFilterExtraRows[dec->filter_type_];
+    const int extra_y = extra_rows * dec->cache_y_stride_;
+    const int extra_uv = (extra_rows / 2) * dec->cache_uv_stride_;
+    dec->cache_y_ = ((uint8_t*)mem) + extra_y;
+    dec->cache_u_ = dec->cache_y_
+                  + 16 * num_caches * dec->cache_y_stride_ + extra_uv;
+    dec->cache_v_ = dec->cache_u_
+                  + 8 * num_caches * dec->cache_uv_stride_ + extra_uv;
+    dec->cache_id_ = 0;
+  }
+  mem += cache_size;
+
+  // alpha plane
+  dec->alpha_plane_ = alpha_size ? (uint8_t*)mem : NULL;
+  mem += alpha_size;
+  assert(mem <= (uint8_t*)dec->mem_ + dec->mem_size_);
+
+  // note: left/top-info is initialized once for all.
+  memset(dec->mb_info_ - 1, 0, mb_info_size);
+  VP8InitScanline(dec);   // initialize left too.
+
+  // initialize top
+  memset(dec->intra_t_, B_DC_PRED, intra_pred_mode_size);
+
+  return 1;
+}
+
+static void InitIo(VP8Decoder* const dec, VP8Io* io) {
+  // prepare 'io'
+  io->mb_y = 0;
+  io->y = dec->cache_y_;
+  io->u = dec->cache_u_;
+  io->v = dec->cache_v_;
+  io->y_stride = dec->cache_y_stride_;
+  io->uv_stride = dec->cache_uv_stride_;
+  io->a = NULL;
+}
+
+int VP8InitFrame(VP8Decoder* const dec, VP8Io* const io) {
+  if (!InitThreadContext(dec)) return 0;  // call first. Sets dec->num_caches_.
+  if (!AllocateMemory(dec)) return 0;
+  InitIo(dec, io);
+  VP8DspInit();  // Init critical function pointers and look-up tables.
+  return 1;
+}
+
+//------------------------------------------------------------------------------

+ 892 - 0
Source/ThirdParty/WebP/src/dec/idec_dec.c

@@ -0,0 +1,892 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Incremental decoding
+//
+// Author: [email protected] (Somnath Banerjee)
+
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "./alphai_dec.h"
+#include "./webpi_dec.h"
+#include "./vp8i_dec.h"
+#include "../utils/utils.h"
+
+// In append mode, buffer allocations increase as multiples of this value.
+// Needs to be a power of 2.
+#define CHUNK_SIZE 4096
+#define MAX_MB_SIZE 4096
+
+//------------------------------------------------------------------------------
+// Data structures for memory and states
+
+// Decoding states. State normally flows as:
+// WEBP_HEADER->VP8_HEADER->VP8_PARTS0->VP8_DATA->DONE for a lossy image, and
+// WEBP_HEADER->VP8L_HEADER->VP8L_DATA->DONE for a lossless image.
+// If there is any error the decoder goes into state ERROR.
+typedef enum {
+  STATE_WEBP_HEADER,  // All the data before that of the VP8/VP8L chunk.
+  STATE_VP8_HEADER,   // The VP8 Frame header (within the VP8 chunk).
+  STATE_VP8_PARTS0,
+  STATE_VP8_DATA,
+  STATE_VP8L_HEADER,
+  STATE_VP8L_DATA,
+  STATE_DONE,
+  STATE_ERROR
+} DecState;
+
+// Operating state for the MemBuffer
+typedef enum {
+  MEM_MODE_NONE = 0,
+  MEM_MODE_APPEND,
+  MEM_MODE_MAP
+} MemBufferMode;
+
+// storage for partition #0 and partial data (in a rolling fashion)
+typedef struct {
+  MemBufferMode mode_;  // Operation mode
+  size_t start_;        // start location of the data to be decoded
+  size_t end_;          // end location
+  size_t buf_size_;     // size of the allocated buffer
+  uint8_t* buf_;        // We don't own this buffer in case WebPIUpdate()
+
+  size_t part0_size_;         // size of partition #0
+  const uint8_t* part0_buf_;  // buffer to store partition #0
+} MemBuffer;
+
+struct WebPIDecoder {
+  DecState state_;         // current decoding state
+  WebPDecParams params_;   // Params to store output info
+  int is_lossless_;        // for down-casting 'dec_'.
+  void* dec_;              // either a VP8Decoder or a VP8LDecoder instance
+  VP8Io io_;
+
+  MemBuffer mem_;          // input memory buffer.
+  WebPDecBuffer output_;   // output buffer (when no external one is supplied,
+                           // or if the external one has slow-memory)
+  WebPDecBuffer* final_output_;  // Slow-memory output to copy to eventually.
+  size_t chunk_size_;      // Compressed VP8/VP8L size extracted from Header.
+
+  int last_mb_y_;          // last row reached for intra-mode decoding
+};
+
+// MB context to restore in case VP8DecodeMB() fails
+typedef struct {
+  VP8MB left_;
+  VP8MB info_;
+  VP8BitReader token_br_;
+} MBContext;
+
+//------------------------------------------------------------------------------
+// MemBuffer: incoming data handling
+
+static WEBP_INLINE size_t MemDataSize(const MemBuffer* mem) {
+  return (mem->end_ - mem->start_);
+}
+
+// Check if we need to preserve the compressed alpha data, as it may not have
+// been decoded yet.
+static int NeedCompressedAlpha(const WebPIDecoder* const idec) {
+  if (idec->state_ == STATE_WEBP_HEADER) {
+    // We haven't parsed the headers yet, so we don't know whether the image is
+    // lossy or lossless. This also means that we haven't parsed the ALPH chunk.
+    return 0;
+  }
+  if (idec->is_lossless_) {
+    return 0;  // ALPH chunk is not present for lossless images.
+  } else {
+    const VP8Decoder* const dec = (VP8Decoder*)idec->dec_;
+    assert(dec != NULL);  // Must be true as idec->state_ != STATE_WEBP_HEADER.
+    return (dec->alpha_data_ != NULL) && !dec->is_alpha_decoded_;
+  }
+}
+
+static void DoRemap(WebPIDecoder* const idec, ptrdiff_t offset) {
+  MemBuffer* const mem = &idec->mem_;
+  const uint8_t* const new_base = mem->buf_ + mem->start_;
+  // note: for VP8, setting up idec->io_ is only really needed at the beginning
+  // of the decoding, till partition #0 is complete.
+  idec->io_.data = new_base;
+  idec->io_.data_size = MemDataSize(mem);
+
+  if (idec->dec_ != NULL) {
+    if (!idec->is_lossless_) {
+      VP8Decoder* const dec = (VP8Decoder*)idec->dec_;
+      const uint32_t last_part = dec->num_parts_minus_one_;
+      if (offset != 0) {
+        uint32_t p;
+        for (p = 0; p <= last_part; ++p) {
+          VP8RemapBitReader(dec->parts_ + p, offset);
+        }
+        // Remap partition #0 data pointer to new offset, but only in MAP
+        // mode (in APPEND mode, partition #0 is copied into a fixed memory).
+        if (mem->mode_ == MEM_MODE_MAP) {
+          VP8RemapBitReader(&dec->br_, offset);
+        }
+      }
+      {
+        const uint8_t* const last_start = dec->parts_[last_part].buf_;
+        VP8BitReaderSetBuffer(&dec->parts_[last_part], last_start,
+                              mem->buf_ + mem->end_ - last_start);
+      }
+      if (NeedCompressedAlpha(idec)) {
+        ALPHDecoder* const alph_dec = dec->alph_dec_;
+        dec->alpha_data_ += offset;
+        if (alph_dec != NULL) {
+          if (alph_dec->method_ == ALPHA_LOSSLESS_COMPRESSION) {
+            VP8LDecoder* const alph_vp8l_dec = alph_dec->vp8l_dec_;
+            assert(alph_vp8l_dec != NULL);
+            assert(dec->alpha_data_size_ >= ALPHA_HEADER_LEN);
+            VP8LBitReaderSetBuffer(&alph_vp8l_dec->br_,
+                                   dec->alpha_data_ + ALPHA_HEADER_LEN,
+                                   dec->alpha_data_size_ - ALPHA_HEADER_LEN);
+          } else {  // alph_dec->method_ == ALPHA_NO_COMPRESSION
+            // Nothing special to do in this case.
+          }
+        }
+      }
+    } else {    // Resize lossless bitreader
+      VP8LDecoder* const dec = (VP8LDecoder*)idec->dec_;
+      VP8LBitReaderSetBuffer(&dec->br_, new_base, MemDataSize(mem));
+    }
+  }
+}
+
+// Appends data to the end of MemBuffer->buf_. It expands the allocated memory
+// size if required and also updates VP8BitReader's if new memory is allocated.
+static int AppendToMemBuffer(WebPIDecoder* const idec,
+                             const uint8_t* const data, size_t data_size) {
+  VP8Decoder* const dec = (VP8Decoder*)idec->dec_;
+  MemBuffer* const mem = &idec->mem_;
+  const int need_compressed_alpha = NeedCompressedAlpha(idec);
+  const uint8_t* const old_start = mem->buf_ + mem->start_;
+  const uint8_t* const old_base =
+      need_compressed_alpha ? dec->alpha_data_ : old_start;
+  assert(mem->mode_ == MEM_MODE_APPEND);
+  if (data_size > MAX_CHUNK_PAYLOAD) {
+    // security safeguard: trying to allocate more than what the format
+    // allows for a chunk should be considered a smoke smell.
+    return 0;
+  }
+
+  if (mem->end_ + data_size > mem->buf_size_) {  // Need some free memory
+    const size_t new_mem_start = old_start - old_base;
+    const size_t current_size = MemDataSize(mem) + new_mem_start;
+    const uint64_t new_size = (uint64_t)current_size + data_size;
+    const uint64_t extra_size = (new_size + CHUNK_SIZE - 1) & ~(CHUNK_SIZE - 1);
+    uint8_t* const new_buf =
+        (uint8_t*)WebPSafeMalloc(extra_size, sizeof(*new_buf));
+    if (new_buf == NULL) return 0;
+    memcpy(new_buf, old_base, current_size);
+    WebPSafeFree(mem->buf_);
+    mem->buf_ = new_buf;
+    mem->buf_size_ = (size_t)extra_size;
+    mem->start_ = new_mem_start;
+    mem->end_ = current_size;
+  }
+
+  memcpy(mem->buf_ + mem->end_, data, data_size);
+  mem->end_ += data_size;
+  assert(mem->end_ <= mem->buf_size_);
+
+  DoRemap(idec, mem->buf_ + mem->start_ - old_start);
+  return 1;
+}
+
+static int RemapMemBuffer(WebPIDecoder* const idec,
+                          const uint8_t* const data, size_t data_size) {
+  MemBuffer* const mem = &idec->mem_;
+  const uint8_t* const old_buf = mem->buf_;
+  const uint8_t* const old_start = old_buf + mem->start_;
+  assert(mem->mode_ == MEM_MODE_MAP);
+
+  if (data_size < mem->buf_size_) return 0;  // can't remap to a shorter buffer!
+
+  mem->buf_ = (uint8_t*)data;
+  mem->end_ = mem->buf_size_ = data_size;
+
+  DoRemap(idec, mem->buf_ + mem->start_ - old_start);
+  return 1;
+}
+
+static void InitMemBuffer(MemBuffer* const mem) {
+  mem->mode_       = MEM_MODE_NONE;
+  mem->buf_        = NULL;
+  mem->buf_size_   = 0;
+  mem->part0_buf_  = NULL;
+  mem->part0_size_ = 0;
+}
+
+static void ClearMemBuffer(MemBuffer* const mem) {
+  assert(mem);
+  if (mem->mode_ == MEM_MODE_APPEND) {
+    WebPSafeFree(mem->buf_);
+    WebPSafeFree((void*)mem->part0_buf_);
+  }
+}
+
+static int CheckMemBufferMode(MemBuffer* const mem, MemBufferMode expected) {
+  if (mem->mode_ == MEM_MODE_NONE) {
+    mem->mode_ = expected;    // switch to the expected mode
+  } else if (mem->mode_ != expected) {
+    return 0;         // we mixed the modes => error
+  }
+  assert(mem->mode_ == expected);   // mode is ok
+  return 1;
+}
+
+// To be called last.
+static VP8StatusCode FinishDecoding(WebPIDecoder* const idec) {
+  const WebPDecoderOptions* const options = idec->params_.options;
+  WebPDecBuffer* const output = idec->params_.output;
+
+  idec->state_ = STATE_DONE;
+  if (options != NULL && options->flip) {
+    const VP8StatusCode status = WebPFlipBuffer(output);
+    if (status != VP8_STATUS_OK) return status;
+  }
+  if (idec->final_output_ != NULL) {
+    WebPCopyDecBufferPixels(output, idec->final_output_);  // do the slow-copy
+    WebPFreeDecBuffer(&idec->output_);
+    *output = *idec->final_output_;
+    idec->final_output_ = NULL;
+  }
+  return VP8_STATUS_OK;
+}
+
+//------------------------------------------------------------------------------
+// Macroblock-decoding contexts
+
+static void SaveContext(const VP8Decoder* dec, const VP8BitReader* token_br,
+                        MBContext* const context) {
+  context->left_ = dec->mb_info_[-1];
+  context->info_ = dec->mb_info_[dec->mb_x_];
+  context->token_br_ = *token_br;
+}
+
+static void RestoreContext(const MBContext* context, VP8Decoder* const dec,
+                           VP8BitReader* const token_br) {
+  dec->mb_info_[-1] = context->left_;
+  dec->mb_info_[dec->mb_x_] = context->info_;
+  *token_br = context->token_br_;
+}
+
+//------------------------------------------------------------------------------
+
+static VP8StatusCode IDecError(WebPIDecoder* const idec, VP8StatusCode error) {
+  if (idec->state_ == STATE_VP8_DATA) {
+    VP8Io* const io = &idec->io_;
+    if (io->teardown != NULL) {
+      io->teardown(io);
+    }
+  }
+  idec->state_ = STATE_ERROR;
+  return error;
+}
+
+static void ChangeState(WebPIDecoder* const idec, DecState new_state,
+                        size_t consumed_bytes) {
+  MemBuffer* const mem = &idec->mem_;
+  idec->state_ = new_state;
+  mem->start_ += consumed_bytes;
+  assert(mem->start_ <= mem->end_);
+  idec->io_.data = mem->buf_ + mem->start_;
+  idec->io_.data_size = MemDataSize(mem);
+}
+
+// Headers
+static VP8StatusCode DecodeWebPHeaders(WebPIDecoder* const idec) {
+  MemBuffer* const mem = &idec->mem_;
+  const uint8_t* data = mem->buf_ + mem->start_;
+  size_t curr_size = MemDataSize(mem);
+  VP8StatusCode status;
+  WebPHeaderStructure headers;
+
+  headers.data = data;
+  headers.data_size = curr_size;
+  headers.have_all_data = 0;
+  status = WebPParseHeaders(&headers);
+  if (status == VP8_STATUS_NOT_ENOUGH_DATA) {
+    return VP8_STATUS_SUSPENDED;  // We haven't found a VP8 chunk yet.
+  } else if (status != VP8_STATUS_OK) {
+    return IDecError(idec, status);
+  }
+
+  idec->chunk_size_ = headers.compressed_size;
+  idec->is_lossless_ = headers.is_lossless;
+  if (!idec->is_lossless_) {
+    VP8Decoder* const dec = VP8New();
+    if (dec == NULL) {
+      return VP8_STATUS_OUT_OF_MEMORY;
+    }
+    idec->dec_ = dec;
+    dec->alpha_data_ = headers.alpha_data;
+    dec->alpha_data_size_ = headers.alpha_data_size;
+    ChangeState(idec, STATE_VP8_HEADER, headers.offset);
+  } else {
+    VP8LDecoder* const dec = VP8LNew();
+    if (dec == NULL) {
+      return VP8_STATUS_OUT_OF_MEMORY;
+    }
+    idec->dec_ = dec;
+    ChangeState(idec, STATE_VP8L_HEADER, headers.offset);
+  }
+  return VP8_STATUS_OK;
+}
+
+static VP8StatusCode DecodeVP8FrameHeader(WebPIDecoder* const idec) {
+  const uint8_t* data = idec->mem_.buf_ + idec->mem_.start_;
+  const size_t curr_size = MemDataSize(&idec->mem_);
+  int width, height;
+  uint32_t bits;
+
+  if (curr_size < VP8_FRAME_HEADER_SIZE) {
+    // Not enough data bytes to extract VP8 Frame Header.
+    return VP8_STATUS_SUSPENDED;
+  }
+  if (!VP8GetInfo(data, curr_size, idec->chunk_size_, &width, &height)) {
+    return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR);
+  }
+
+  bits = data[0] | (data[1] << 8) | (data[2] << 16);
+  idec->mem_.part0_size_ = (bits >> 5) + VP8_FRAME_HEADER_SIZE;
+
+  idec->io_.data = data;
+  idec->io_.data_size = curr_size;
+  idec->state_ = STATE_VP8_PARTS0;
+  return VP8_STATUS_OK;
+}
+
+// Partition #0
+static VP8StatusCode CopyParts0Data(WebPIDecoder* const idec) {
+  VP8Decoder* const dec = (VP8Decoder*)idec->dec_;
+  VP8BitReader* const br = &dec->br_;
+  const size_t part_size = br->buf_end_ - br->buf_;
+  MemBuffer* const mem = &idec->mem_;
+  assert(!idec->is_lossless_);
+  assert(mem->part0_buf_ == NULL);
+  // the following is a format limitation, no need for runtime check:
+  assert(part_size <= mem->part0_size_);
+  if (part_size == 0) {   // can't have zero-size partition #0
+    return VP8_STATUS_BITSTREAM_ERROR;
+  }
+  if (mem->mode_ == MEM_MODE_APPEND) {
+    // We copy and grab ownership of the partition #0 data.
+    uint8_t* const part0_buf = (uint8_t*)WebPSafeMalloc(1ULL, part_size);
+    if (part0_buf == NULL) {
+      return VP8_STATUS_OUT_OF_MEMORY;
+    }
+    memcpy(part0_buf, br->buf_, part_size);
+    mem->part0_buf_ = part0_buf;
+    VP8BitReaderSetBuffer(br, part0_buf, part_size);
+  } else {
+    // Else: just keep pointers to the partition #0's data in dec_->br_.
+  }
+  mem->start_ += part_size;
+  return VP8_STATUS_OK;
+}
+
+static VP8StatusCode DecodePartition0(WebPIDecoder* const idec) {
+  VP8Decoder* const dec = (VP8Decoder*)idec->dec_;
+  VP8Io* const io = &idec->io_;
+  const WebPDecParams* const params = &idec->params_;
+  WebPDecBuffer* const output = params->output;
+
+  // Wait till we have enough data for the whole partition #0
+  if (MemDataSize(&idec->mem_) < idec->mem_.part0_size_) {
+    return VP8_STATUS_SUSPENDED;
+  }
+
+  if (!VP8GetHeaders(dec, io)) {
+    const VP8StatusCode status = dec->status_;
+    if (status == VP8_STATUS_SUSPENDED ||
+        status == VP8_STATUS_NOT_ENOUGH_DATA) {
+      // treating NOT_ENOUGH_DATA as SUSPENDED state
+      return VP8_STATUS_SUSPENDED;
+    }
+    return IDecError(idec, status);
+  }
+
+  // Allocate/Verify output buffer now
+  dec->status_ = WebPAllocateDecBuffer(io->width, io->height, params->options,
+                                       output);
+  if (dec->status_ != VP8_STATUS_OK) {
+    return IDecError(idec, dec->status_);
+  }
+  // This change must be done before calling VP8InitFrame()
+  dec->mt_method_ = VP8GetThreadMethod(params->options, NULL,
+                                       io->width, io->height);
+  VP8InitDithering(params->options, dec);
+
+  dec->status_ = CopyParts0Data(idec);
+  if (dec->status_ != VP8_STATUS_OK) {
+    return IDecError(idec, dec->status_);
+  }
+
+  // Finish setting up the decoding parameters. Will call io->setup().
+  if (VP8EnterCritical(dec, io) != VP8_STATUS_OK) {
+    return IDecError(idec, dec->status_);
+  }
+
+  // Note: past this point, teardown() must always be called
+  // in case of error.
+  idec->state_ = STATE_VP8_DATA;
+  // Allocate memory and prepare everything.
+  if (!VP8InitFrame(dec, io)) {
+    return IDecError(idec, dec->status_);
+  }
+  return VP8_STATUS_OK;
+}
+
+// Remaining partitions
+static VP8StatusCode DecodeRemaining(WebPIDecoder* const idec) {
+  VP8Decoder* const dec = (VP8Decoder*)idec->dec_;
+  VP8Io* const io = &idec->io_;
+
+  assert(dec->ready_);
+  for (; dec->mb_y_ < dec->mb_h_; ++dec->mb_y_) {
+    if (idec->last_mb_y_ != dec->mb_y_) {
+      if (!VP8ParseIntraModeRow(&dec->br_, dec)) {
+        // note: normally, error shouldn't occur since we already have the whole
+        // partition0 available here in DecodeRemaining(). Reaching EOF while
+        // reading intra modes really means a BITSTREAM_ERROR.
+        return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR);
+      }
+      idec->last_mb_y_ = dec->mb_y_;
+    }
+    for (; dec->mb_x_ < dec->mb_w_; ++dec->mb_x_) {
+      VP8BitReader* const token_br =
+          &dec->parts_[dec->mb_y_ & dec->num_parts_minus_one_];
+      MBContext context;
+      SaveContext(dec, token_br, &context);
+      if (!VP8DecodeMB(dec, token_br)) {
+        // We shouldn't fail when MAX_MB data was available
+        if (dec->num_parts_minus_one_ == 0 &&
+            MemDataSize(&idec->mem_) > MAX_MB_SIZE) {
+          return IDecError(idec, VP8_STATUS_BITSTREAM_ERROR);
+        }
+        RestoreContext(&context, dec, token_br);
+        return VP8_STATUS_SUSPENDED;
+      }
+      // Release buffer only if there is only one partition
+      if (dec->num_parts_minus_one_ == 0) {
+        idec->mem_.start_ = token_br->buf_ - idec->mem_.buf_;
+        assert(idec->mem_.start_ <= idec->mem_.end_);
+      }
+    }
+    VP8InitScanline(dec);   // Prepare for next scanline
+
+    // Reconstruct, filter and emit the row.
+    if (!VP8ProcessRow(dec, io)) {
+      return IDecError(idec, VP8_STATUS_USER_ABORT);
+    }
+  }
+  // Synchronize the thread and check for errors.
+  if (!VP8ExitCritical(dec, io)) {
+    return IDecError(idec, VP8_STATUS_USER_ABORT);
+  }
+  dec->ready_ = 0;
+  return FinishDecoding(idec);
+}
+
+static VP8StatusCode ErrorStatusLossless(WebPIDecoder* const idec,
+                                         VP8StatusCode status) {
+  if (status == VP8_STATUS_SUSPENDED || status == VP8_STATUS_NOT_ENOUGH_DATA) {
+    return VP8_STATUS_SUSPENDED;
+  }
+  return IDecError(idec, status);
+}
+
+static VP8StatusCode DecodeVP8LHeader(WebPIDecoder* const idec) {
+  VP8Io* const io = &idec->io_;
+  VP8LDecoder* const dec = (VP8LDecoder*)idec->dec_;
+  const WebPDecParams* const params = &idec->params_;
+  WebPDecBuffer* const output = params->output;
+  size_t curr_size = MemDataSize(&idec->mem_);
+  assert(idec->is_lossless_);
+
+  // Wait until there's enough data for decoding header.
+  if (curr_size < (idec->chunk_size_ >> 3)) {
+    dec->status_ = VP8_STATUS_SUSPENDED;
+    return ErrorStatusLossless(idec, dec->status_);
+  }
+
+  if (!VP8LDecodeHeader(dec, io)) {
+    if (dec->status_ == VP8_STATUS_BITSTREAM_ERROR &&
+        curr_size < idec->chunk_size_) {
+      dec->status_ = VP8_STATUS_SUSPENDED;
+    }
+    return ErrorStatusLossless(idec, dec->status_);
+  }
+  // Allocate/verify output buffer now.
+  dec->status_ = WebPAllocateDecBuffer(io->width, io->height, params->options,
+                                       output);
+  if (dec->status_ != VP8_STATUS_OK) {
+    return IDecError(idec, dec->status_);
+  }
+
+  idec->state_ = STATE_VP8L_DATA;
+  return VP8_STATUS_OK;
+}
+
+static VP8StatusCode DecodeVP8LData(WebPIDecoder* const idec) {
+  VP8LDecoder* const dec = (VP8LDecoder*)idec->dec_;
+  const size_t curr_size = MemDataSize(&idec->mem_);
+  assert(idec->is_lossless_);
+
+  // Switch to incremental decoding if we don't have all the bytes available.
+  dec->incremental_ = (curr_size < idec->chunk_size_);
+
+  if (!VP8LDecodeImage(dec)) {
+    return ErrorStatusLossless(idec, dec->status_);
+  }
+  assert(dec->status_ == VP8_STATUS_OK || dec->status_ == VP8_STATUS_SUSPENDED);
+  return (dec->status_ == VP8_STATUS_SUSPENDED) ? dec->status_
+                                                : FinishDecoding(idec);
+}
+
+  // Main decoding loop
+static VP8StatusCode IDecode(WebPIDecoder* idec) {
+  VP8StatusCode status = VP8_STATUS_SUSPENDED;
+
+  if (idec->state_ == STATE_WEBP_HEADER) {
+    status = DecodeWebPHeaders(idec);
+  } else {
+    if (idec->dec_ == NULL) {
+      return VP8_STATUS_SUSPENDED;    // can't continue if we have no decoder.
+    }
+  }
+  if (idec->state_ == STATE_VP8_HEADER) {
+    status = DecodeVP8FrameHeader(idec);
+  }
+  if (idec->state_ == STATE_VP8_PARTS0) {
+    status = DecodePartition0(idec);
+  }
+  if (idec->state_ == STATE_VP8_DATA) {
+    status = DecodeRemaining(idec);
+  }
+  if (idec->state_ == STATE_VP8L_HEADER) {
+    status = DecodeVP8LHeader(idec);
+  }
+  if (idec->state_ == STATE_VP8L_DATA) {
+    status = DecodeVP8LData(idec);
+  }
+  return status;
+}
+
+//------------------------------------------------------------------------------
+// Internal constructor
+
+static WebPIDecoder* NewDecoder(WebPDecBuffer* const output_buffer,
+                                const WebPBitstreamFeatures* const features) {
+  WebPIDecoder* idec = (WebPIDecoder*)WebPSafeCalloc(1ULL, sizeof(*idec));
+  if (idec == NULL) {
+    return NULL;
+  }
+
+  idec->state_ = STATE_WEBP_HEADER;
+  idec->chunk_size_ = 0;
+
+  idec->last_mb_y_ = -1;
+
+  InitMemBuffer(&idec->mem_);
+  WebPInitDecBuffer(&idec->output_);
+  VP8InitIo(&idec->io_);
+
+  WebPResetDecParams(&idec->params_);
+  if (output_buffer == NULL || WebPAvoidSlowMemory(output_buffer, features)) {
+    idec->params_.output = &idec->output_;
+    idec->final_output_ = output_buffer;
+    if (output_buffer != NULL) {
+      idec->params_.output->colorspace = output_buffer->colorspace;
+    }
+  } else {
+    idec->params_.output = output_buffer;
+    idec->final_output_ = NULL;
+  }
+  WebPInitCustomIo(&idec->params_, &idec->io_);  // Plug the I/O functions.
+
+  return idec;
+}
+
+//------------------------------------------------------------------------------
+// Public functions
+
+WebPIDecoder* WebPINewDecoder(WebPDecBuffer* output_buffer) {
+  return NewDecoder(output_buffer, NULL);
+}
+
+WebPIDecoder* WebPIDecode(const uint8_t* data, size_t data_size,
+                          WebPDecoderConfig* config) {
+  WebPIDecoder* idec;
+  WebPBitstreamFeatures tmp_features;
+  WebPBitstreamFeatures* const features =
+      (config == NULL) ? &tmp_features : &config->input;
+  memset(&tmp_features, 0, sizeof(tmp_features));
+
+  // Parse the bitstream's features, if requested:
+  if (data != NULL && data_size > 0) {
+    if (WebPGetFeatures(data, data_size, features) != VP8_STATUS_OK) {
+      return NULL;
+    }
+  }
+
+  // Create an instance of the incremental decoder
+  idec = (config != NULL) ? NewDecoder(&config->output, features)
+                          : NewDecoder(NULL, features);
+  if (idec == NULL) {
+    return NULL;
+  }
+  // Finish initialization
+  if (config != NULL) {
+    idec->params_.options = &config->options;
+  }
+  return idec;
+}
+
+void WebPIDelete(WebPIDecoder* idec) {
+  if (idec == NULL) return;
+  if (idec->dec_ != NULL) {
+    if (!idec->is_lossless_) {
+      if (idec->state_ == STATE_VP8_DATA) {
+        // Synchronize the thread, clean-up and check for errors.
+        VP8ExitCritical((VP8Decoder*)idec->dec_, &idec->io_);
+      }
+      VP8Delete((VP8Decoder*)idec->dec_);
+    } else {
+      VP8LDelete((VP8LDecoder*)idec->dec_);
+    }
+  }
+  ClearMemBuffer(&idec->mem_);
+  WebPFreeDecBuffer(&idec->output_);
+  WebPSafeFree(idec);
+}
+
+//------------------------------------------------------------------------------
+// Wrapper toward WebPINewDecoder
+
+WebPIDecoder* WebPINewRGB(WEBP_CSP_MODE mode, uint8_t* output_buffer,
+                          size_t output_buffer_size, int output_stride) {
+  const int is_external_memory = (output_buffer != NULL) ? 1 : 0;
+  WebPIDecoder* idec;
+
+  if (mode >= MODE_YUV) return NULL;
+  if (is_external_memory == 0) {    // Overwrite parameters to sane values.
+    output_buffer_size = 0;
+    output_stride = 0;
+  } else {  // A buffer was passed. Validate the other params.
+    if (output_stride == 0 || output_buffer_size == 0) {
+      return NULL;   // invalid parameter.
+    }
+  }
+  idec = WebPINewDecoder(NULL);
+  if (idec == NULL) return NULL;
+  idec->output_.colorspace = mode;
+  idec->output_.is_external_memory = is_external_memory;
+  idec->output_.u.RGBA.rgba = output_buffer;
+  idec->output_.u.RGBA.stride = output_stride;
+  idec->output_.u.RGBA.size = output_buffer_size;
+  return idec;
+}
+
+WebPIDecoder* WebPINewYUVA(uint8_t* luma, size_t luma_size, int luma_stride,
+                           uint8_t* u, size_t u_size, int u_stride,
+                           uint8_t* v, size_t v_size, int v_stride,
+                           uint8_t* a, size_t a_size, int a_stride) {
+  const int is_external_memory = (luma != NULL) ? 1 : 0;
+  WebPIDecoder* idec;
+  WEBP_CSP_MODE colorspace;
+
+  if (is_external_memory == 0) {    // Overwrite parameters to sane values.
+    luma_size = u_size = v_size = a_size = 0;
+    luma_stride = u_stride = v_stride = a_stride = 0;
+    u = v = a = NULL;
+    colorspace = MODE_YUVA;
+  } else {  // A luma buffer was passed. Validate the other parameters.
+    if (u == NULL || v == NULL) return NULL;
+    if (luma_size == 0 || u_size == 0 || v_size == 0) return NULL;
+    if (luma_stride == 0 || u_stride == 0 || v_stride == 0) return NULL;
+    if (a != NULL) {
+      if (a_size == 0 || a_stride == 0) return NULL;
+    }
+    colorspace = (a == NULL) ? MODE_YUV : MODE_YUVA;
+  }
+
+  idec = WebPINewDecoder(NULL);
+  if (idec == NULL) return NULL;
+
+  idec->output_.colorspace = colorspace;
+  idec->output_.is_external_memory = is_external_memory;
+  idec->output_.u.YUVA.y = luma;
+  idec->output_.u.YUVA.y_stride = luma_stride;
+  idec->output_.u.YUVA.y_size = luma_size;
+  idec->output_.u.YUVA.u = u;
+  idec->output_.u.YUVA.u_stride = u_stride;
+  idec->output_.u.YUVA.u_size = u_size;
+  idec->output_.u.YUVA.v = v;
+  idec->output_.u.YUVA.v_stride = v_stride;
+  idec->output_.u.YUVA.v_size = v_size;
+  idec->output_.u.YUVA.a = a;
+  idec->output_.u.YUVA.a_stride = a_stride;
+  idec->output_.u.YUVA.a_size = a_size;
+  return idec;
+}
+
+WebPIDecoder* WebPINewYUV(uint8_t* luma, size_t luma_size, int luma_stride,
+                          uint8_t* u, size_t u_size, int u_stride,
+                          uint8_t* v, size_t v_size, int v_stride) {
+  return WebPINewYUVA(luma, luma_size, luma_stride,
+                      u, u_size, u_stride,
+                      v, v_size, v_stride,
+                      NULL, 0, 0);
+}
+
+//------------------------------------------------------------------------------
+
+static VP8StatusCode IDecCheckStatus(const WebPIDecoder* const idec) {
+  assert(idec);
+  if (idec->state_ == STATE_ERROR) {
+    return VP8_STATUS_BITSTREAM_ERROR;
+  }
+  if (idec->state_ == STATE_DONE) {
+    return VP8_STATUS_OK;
+  }
+  return VP8_STATUS_SUSPENDED;
+}
+
+VP8StatusCode WebPIAppend(WebPIDecoder* idec,
+                          const uint8_t* data, size_t data_size) {
+  VP8StatusCode status;
+  if (idec == NULL || data == NULL) {
+    return VP8_STATUS_INVALID_PARAM;
+  }
+  status = IDecCheckStatus(idec);
+  if (status != VP8_STATUS_SUSPENDED) {
+    return status;
+  }
+  // Check mixed calls between RemapMemBuffer and AppendToMemBuffer.
+  if (!CheckMemBufferMode(&idec->mem_, MEM_MODE_APPEND)) {
+    return VP8_STATUS_INVALID_PARAM;
+  }
+  // Append data to memory buffer
+  if (!AppendToMemBuffer(idec, data, data_size)) {
+    return VP8_STATUS_OUT_OF_MEMORY;
+  }
+  return IDecode(idec);
+}
+
+VP8StatusCode WebPIUpdate(WebPIDecoder* idec,
+                          const uint8_t* data, size_t data_size) {
+  VP8StatusCode status;
+  if (idec == NULL || data == NULL) {
+    return VP8_STATUS_INVALID_PARAM;
+  }
+  status = IDecCheckStatus(idec);
+  if (status != VP8_STATUS_SUSPENDED) {
+    return status;
+  }
+  // Check mixed calls between RemapMemBuffer and AppendToMemBuffer.
+  if (!CheckMemBufferMode(&idec->mem_, MEM_MODE_MAP)) {
+    return VP8_STATUS_INVALID_PARAM;
+  }
+  // Make the memory buffer point to the new buffer
+  if (!RemapMemBuffer(idec, data, data_size)) {
+    return VP8_STATUS_INVALID_PARAM;
+  }
+  return IDecode(idec);
+}
+
+//------------------------------------------------------------------------------
+
+static const WebPDecBuffer* GetOutputBuffer(const WebPIDecoder* const idec) {
+  if (idec == NULL || idec->dec_ == NULL) {
+    return NULL;
+  }
+  if (idec->state_ <= STATE_VP8_PARTS0) {
+    return NULL;
+  }
+  if (idec->final_output_ != NULL) {
+    return NULL;   // not yet slow-copied
+  }
+  return idec->params_.output;
+}
+
+const WebPDecBuffer* WebPIDecodedArea(const WebPIDecoder* idec,
+                                      int* left, int* top,
+                                      int* width, int* height) {
+  const WebPDecBuffer* const src = GetOutputBuffer(idec);
+  if (left != NULL) *left = 0;
+  if (top != NULL) *top = 0;
+  if (src != NULL) {
+    if (width != NULL) *width = src->width;
+    if (height != NULL) *height = idec->params_.last_y;
+  } else {
+    if (width != NULL) *width = 0;
+    if (height != NULL) *height = 0;
+  }
+  return src;
+}
+
+uint8_t* WebPIDecGetRGB(const WebPIDecoder* idec, int* last_y,
+                        int* width, int* height, int* stride) {
+  const WebPDecBuffer* const src = GetOutputBuffer(idec);
+  if (src == NULL) return NULL;
+  if (src->colorspace >= MODE_YUV) {
+    return NULL;
+  }
+
+  if (last_y != NULL) *last_y = idec->params_.last_y;
+  if (width != NULL) *width = src->width;
+  if (height != NULL) *height = src->height;
+  if (stride != NULL) *stride = src->u.RGBA.stride;
+
+  return src->u.RGBA.rgba;
+}
+
+uint8_t* WebPIDecGetYUVA(const WebPIDecoder* idec, int* last_y,
+                         uint8_t** u, uint8_t** v, uint8_t** a,
+                         int* width, int* height,
+                         int* stride, int* uv_stride, int* a_stride) {
+  const WebPDecBuffer* const src = GetOutputBuffer(idec);
+  if (src == NULL) return NULL;
+  if (src->colorspace < MODE_YUV) {
+    return NULL;
+  }
+
+  if (last_y != NULL) *last_y = idec->params_.last_y;
+  if (u != NULL) *u = src->u.YUVA.u;
+  if (v != NULL) *v = src->u.YUVA.v;
+  if (a != NULL) *a = src->u.YUVA.a;
+  if (width != NULL) *width = src->width;
+  if (height != NULL) *height = src->height;
+  if (stride != NULL) *stride = src->u.YUVA.y_stride;
+  if (uv_stride != NULL) *uv_stride = src->u.YUVA.u_stride;
+  if (a_stride != NULL) *a_stride = src->u.YUVA.a_stride;
+
+  return src->u.YUVA.y;
+}
+
+int WebPISetIOHooks(WebPIDecoder* const idec,
+                    VP8IoPutHook put,
+                    VP8IoSetupHook setup,
+                    VP8IoTeardownHook teardown,
+                    void* user_data) {
+  if (idec == NULL || idec->state_ > STATE_WEBP_HEADER) {
+    return 0;
+  }
+
+  idec->io_.put = put;
+  idec->io_.setup = setup;
+  idec->io_.teardown = teardown;
+  idec->io_.opaque = user_data;
+
+  return 1;
+}

+ 645 - 0
Source/ThirdParty/WebP/src/dec/io_dec.c

@@ -0,0 +1,645 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// functions for sample output.
+//
+// Author: Skal ([email protected])
+
+#include <assert.h>
+#include <stdlib.h>
+#include "../dec/vp8i_dec.h"
+#include "./webpi_dec.h"
+#include "../dsp/dsp.h"
+#include "../dsp/yuv.h"
+#include "../utils/utils.h"
+
+//------------------------------------------------------------------------------
+// Main YUV<->RGB conversion functions
+
+static int EmitYUV(const VP8Io* const io, WebPDecParams* const p) {
+  WebPDecBuffer* output = p->output;
+  const WebPYUVABuffer* const buf = &output->u.YUVA;
+  uint8_t* const y_dst = buf->y + io->mb_y * buf->y_stride;
+  uint8_t* const u_dst = buf->u + (io->mb_y >> 1) * buf->u_stride;
+  uint8_t* const v_dst = buf->v + (io->mb_y >> 1) * buf->v_stride;
+  const int mb_w = io->mb_w;
+  const int mb_h = io->mb_h;
+  const int uv_w = (mb_w + 1) / 2;
+  const int uv_h = (mb_h + 1) / 2;
+  int j;
+  for (j = 0; j < mb_h; ++j) {
+    memcpy(y_dst + j * buf->y_stride, io->y + j * io->y_stride, mb_w);
+  }
+  for (j = 0; j < uv_h; ++j) {
+    memcpy(u_dst + j * buf->u_stride, io->u + j * io->uv_stride, uv_w);
+    memcpy(v_dst + j * buf->v_stride, io->v + j * io->uv_stride, uv_w);
+  }
+  return io->mb_h;
+}
+
+// Point-sampling U/V sampler.
+static int EmitSampledRGB(const VP8Io* const io, WebPDecParams* const p) {
+  WebPDecBuffer* const output = p->output;
+  WebPRGBABuffer* const buf = &output->u.RGBA;
+  uint8_t* const dst = buf->rgba + io->mb_y * buf->stride;
+  WebPSamplerProcessPlane(io->y, io->y_stride,
+                          io->u, io->v, io->uv_stride,
+                          dst, buf->stride, io->mb_w, io->mb_h,
+                          WebPSamplers[output->colorspace]);
+  return io->mb_h;
+}
+
+//------------------------------------------------------------------------------
+// Fancy upsampling
+
+#ifdef FANCY_UPSAMPLING
+static int EmitFancyRGB(const VP8Io* const io, WebPDecParams* const p) {
+  int num_lines_out = io->mb_h;   // a priori guess
+  const WebPRGBABuffer* const buf = &p->output->u.RGBA;
+  uint8_t* dst = buf->rgba + io->mb_y * buf->stride;
+  WebPUpsampleLinePairFunc upsample = WebPUpsamplers[p->output->colorspace];
+  const uint8_t* cur_y = io->y;
+  const uint8_t* cur_u = io->u;
+  const uint8_t* cur_v = io->v;
+  const uint8_t* top_u = p->tmp_u;
+  const uint8_t* top_v = p->tmp_v;
+  int y = io->mb_y;
+  const int y_end = io->mb_y + io->mb_h;
+  const int mb_w = io->mb_w;
+  const int uv_w = (mb_w + 1) / 2;
+
+  if (y == 0) {
+    // First line is special cased. We mirror the u/v samples at boundary.
+    upsample(cur_y, NULL, cur_u, cur_v, cur_u, cur_v, dst, NULL, mb_w);
+  } else {
+    // We can finish the left-over line from previous call.
+    upsample(p->tmp_y, cur_y, top_u, top_v, cur_u, cur_v,
+             dst - buf->stride, dst, mb_w);
+    ++num_lines_out;
+  }
+  // Loop over each output pairs of row.
+  for (; y + 2 < y_end; y += 2) {
+    top_u = cur_u;
+    top_v = cur_v;
+    cur_u += io->uv_stride;
+    cur_v += io->uv_stride;
+    dst += 2 * buf->stride;
+    cur_y += 2 * io->y_stride;
+    upsample(cur_y - io->y_stride, cur_y,
+             top_u, top_v, cur_u, cur_v,
+             dst - buf->stride, dst, mb_w);
+  }
+  // move to last row
+  cur_y += io->y_stride;
+  if (io->crop_top + y_end < io->crop_bottom) {
+    // Save the unfinished samples for next call (as we're not done yet).
+    memcpy(p->tmp_y, cur_y, mb_w * sizeof(*p->tmp_y));
+    memcpy(p->tmp_u, cur_u, uv_w * sizeof(*p->tmp_u));
+    memcpy(p->tmp_v, cur_v, uv_w * sizeof(*p->tmp_v));
+    // The fancy upsampler leaves a row unfinished behind
+    // (except for the very last row)
+    num_lines_out--;
+  } else {
+    // Process the very last row of even-sized picture
+    if (!(y_end & 1)) {
+      upsample(cur_y, NULL, cur_u, cur_v, cur_u, cur_v,
+               dst + buf->stride, NULL, mb_w);
+    }
+  }
+  return num_lines_out;
+}
+
+#endif    /* FANCY_UPSAMPLING */
+
+//------------------------------------------------------------------------------
+
+static void FillAlphaPlane(uint8_t* dst, int w, int h, int stride) {
+  int j;
+  for (j = 0; j < h; ++j) {
+    memset(dst, 0xff, w * sizeof(*dst));
+    dst += stride;
+  }
+}
+
+static int EmitAlphaYUV(const VP8Io* const io, WebPDecParams* const p,
+                        int expected_num_lines_out) {
+  const uint8_t* alpha = io->a;
+  const WebPYUVABuffer* const buf = &p->output->u.YUVA;
+  const int mb_w = io->mb_w;
+  const int mb_h = io->mb_h;
+  uint8_t* dst = buf->a + io->mb_y * buf->a_stride;
+  int j;
+  (void)expected_num_lines_out;
+  assert(expected_num_lines_out == mb_h);
+  if (alpha != NULL) {
+    for (j = 0; j < mb_h; ++j) {
+      memcpy(dst, alpha, mb_w * sizeof(*dst));
+      alpha += io->width;
+      dst += buf->a_stride;
+    }
+  } else if (buf->a != NULL) {
+    // the user requested alpha, but there is none, set it to opaque.
+    FillAlphaPlane(dst, mb_w, mb_h, buf->a_stride);
+  }
+  return 0;
+}
+
+static int GetAlphaSourceRow(const VP8Io* const io,
+                             const uint8_t** alpha, int* const num_rows) {
+  int start_y = io->mb_y;
+  *num_rows = io->mb_h;
+
+  // Compensate for the 1-line delay of the fancy upscaler.
+  // This is similar to EmitFancyRGB().
+  if (io->fancy_upsampling) {
+    if (start_y == 0) {
+      // We don't process the last row yet. It'll be done during the next call.
+      --*num_rows;
+    } else {
+      --start_y;
+      // Fortunately, *alpha data is persistent, so we can go back
+      // one row and finish alpha blending, now that the fancy upscaler
+      // completed the YUV->RGB interpolation.
+      *alpha -= io->width;
+    }
+    if (io->crop_top + io->mb_y + io->mb_h == io->crop_bottom) {
+      // If it's the very last call, we process all the remaining rows!
+      *num_rows = io->crop_bottom - io->crop_top - start_y;
+    }
+  }
+  return start_y;
+}
+
+static int EmitAlphaRGB(const VP8Io* const io, WebPDecParams* const p,
+                        int expected_num_lines_out) {
+  const uint8_t* alpha = io->a;
+  if (alpha != NULL) {
+    const int mb_w = io->mb_w;
+    const WEBP_CSP_MODE colorspace = p->output->colorspace;
+    const int alpha_first =
+        (colorspace == MODE_ARGB || colorspace == MODE_Argb);
+    const WebPRGBABuffer* const buf = &p->output->u.RGBA;
+    int num_rows;
+    const int start_y = GetAlphaSourceRow(io, &alpha, &num_rows);
+    uint8_t* const base_rgba = buf->rgba + start_y * buf->stride;
+    uint8_t* const dst = base_rgba + (alpha_first ? 0 : 3);
+    const int has_alpha = WebPDispatchAlpha(alpha, io->width, mb_w,
+                                            num_rows, dst, buf->stride);
+    (void)expected_num_lines_out;
+    assert(expected_num_lines_out == num_rows);
+    // has_alpha is true if there's non-trivial alpha to premultiply with.
+    if (has_alpha && WebPIsPremultipliedMode(colorspace)) {
+      WebPApplyAlphaMultiply(base_rgba, alpha_first,
+                             mb_w, num_rows, buf->stride);
+    }
+  }
+  return 0;
+}
+
+static int EmitAlphaRGBA4444(const VP8Io* const io, WebPDecParams* const p,
+                             int expected_num_lines_out) {
+  const uint8_t* alpha = io->a;
+  if (alpha != NULL) {
+    const int mb_w = io->mb_w;
+    const WEBP_CSP_MODE colorspace = p->output->colorspace;
+    const WebPRGBABuffer* const buf = &p->output->u.RGBA;
+    int num_rows;
+    const int start_y = GetAlphaSourceRow(io, &alpha, &num_rows);
+    uint8_t* const base_rgba = buf->rgba + start_y * buf->stride;
+#ifdef WEBP_SWAP_16BIT_CSP
+    uint8_t* alpha_dst = base_rgba;
+#else
+    uint8_t* alpha_dst = base_rgba + 1;
+#endif
+    uint32_t alpha_mask = 0x0f;
+    int i, j;
+    for (j = 0; j < num_rows; ++j) {
+      for (i = 0; i < mb_w; ++i) {
+        // Fill in the alpha value (converted to 4 bits).
+        const uint32_t alpha_value = alpha[i] >> 4;
+        alpha_dst[2 * i] = (alpha_dst[2 * i] & 0xf0) | alpha_value;
+        alpha_mask &= alpha_value;
+      }
+      alpha += io->width;
+      alpha_dst += buf->stride;
+    }
+    (void)expected_num_lines_out;
+    assert(expected_num_lines_out == num_rows);
+    if (alpha_mask != 0x0f && WebPIsPremultipliedMode(colorspace)) {
+      WebPApplyAlphaMultiply4444(base_rgba, mb_w, num_rows, buf->stride);
+    }
+  }
+  return 0;
+}
+
+//------------------------------------------------------------------------------
+// YUV rescaling (no final RGB conversion needed)
+
+static int Rescale(const uint8_t* src, int src_stride,
+                   int new_lines, WebPRescaler* const wrk) {
+  int num_lines_out = 0;
+  while (new_lines > 0) {    // import new contributions of source rows.
+    const int lines_in = WebPRescalerImport(wrk, new_lines, src, src_stride);
+    src += lines_in * src_stride;
+    new_lines -= lines_in;
+    num_lines_out += WebPRescalerExport(wrk);    // emit output row(s)
+  }
+  return num_lines_out;
+}
+
+static int EmitRescaledYUV(const VP8Io* const io, WebPDecParams* const p) {
+  const int mb_h = io->mb_h;
+  const int uv_mb_h = (mb_h + 1) >> 1;
+  WebPRescaler* const scaler = p->scaler_y;
+  int num_lines_out = 0;
+  if (WebPIsAlphaMode(p->output->colorspace) && io->a != NULL) {
+    // Before rescaling, we premultiply the luma directly into the io->y
+    // internal buffer. This is OK since these samples are not used for
+    // intra-prediction (the top samples are saved in cache_y_/u_/v_).
+    // But we need to cast the const away, though.
+    WebPMultRows((uint8_t*)io->y, io->y_stride,
+                 io->a, io->width, io->mb_w, mb_h, 0);
+  }
+  num_lines_out = Rescale(io->y, io->y_stride, mb_h, scaler);
+  Rescale(io->u, io->uv_stride, uv_mb_h, p->scaler_u);
+  Rescale(io->v, io->uv_stride, uv_mb_h, p->scaler_v);
+  return num_lines_out;
+}
+
+static int EmitRescaledAlphaYUV(const VP8Io* const io, WebPDecParams* const p,
+                                int expected_num_lines_out) {
+  const WebPYUVABuffer* const buf = &p->output->u.YUVA;
+  uint8_t* const dst_a = buf->a + p->last_y * buf->a_stride;
+  if (io->a != NULL) {
+    uint8_t* const dst_y = buf->y + p->last_y * buf->y_stride;
+    const int num_lines_out = Rescale(io->a, io->width, io->mb_h, p->scaler_a);
+    assert(expected_num_lines_out == num_lines_out);
+    if (num_lines_out > 0) {   // unmultiply the Y
+      WebPMultRows(dst_y, buf->y_stride, dst_a, buf->a_stride,
+                   p->scaler_a->dst_width, num_lines_out, 1);
+    }
+  } else if (buf->a != NULL) {
+    // the user requested alpha, but there is none, set it to opaque.
+    assert(p->last_y + expected_num_lines_out <= io->scaled_height);
+    FillAlphaPlane(dst_a, io->scaled_width, expected_num_lines_out,
+                   buf->a_stride);
+  }
+  return 0;
+}
+
+static int InitYUVRescaler(const VP8Io* const io, WebPDecParams* const p) {
+  const int has_alpha = WebPIsAlphaMode(p->output->colorspace);
+  const WebPYUVABuffer* const buf = &p->output->u.YUVA;
+  const int out_width  = io->scaled_width;
+  const int out_height = io->scaled_height;
+  const int uv_out_width  = (out_width + 1) >> 1;
+  const int uv_out_height = (out_height + 1) >> 1;
+  const int uv_in_width  = (io->mb_w + 1) >> 1;
+  const int uv_in_height = (io->mb_h + 1) >> 1;
+  const size_t work_size = 2 * out_width;   // scratch memory for luma rescaler
+  const size_t uv_work_size = 2 * uv_out_width;  // and for each u/v ones
+  size_t tmp_size, rescaler_size;
+  rescaler_t* work;
+  WebPRescaler* scalers;
+  const int num_rescalers = has_alpha ? 4 : 3;
+
+  tmp_size = (work_size + 2 * uv_work_size) * sizeof(*work);
+  if (has_alpha) {
+    tmp_size += work_size * sizeof(*work);
+  }
+  rescaler_size = num_rescalers * sizeof(*p->scaler_y) + WEBP_ALIGN_CST;
+
+  p->memory = WebPSafeMalloc(1ULL, tmp_size + rescaler_size);
+  if (p->memory == NULL) {
+    return 0;   // memory error
+  }
+  work = (rescaler_t*)p->memory;
+
+  scalers = (WebPRescaler*)WEBP_ALIGN((const uint8_t*)work + tmp_size);
+  p->scaler_y = &scalers[0];
+  p->scaler_u = &scalers[1];
+  p->scaler_v = &scalers[2];
+  p->scaler_a = has_alpha ? &scalers[3] : NULL;
+
+  WebPRescalerInit(p->scaler_y, io->mb_w, io->mb_h,
+                   buf->y, out_width, out_height, buf->y_stride, 1,
+                   work);
+  WebPRescalerInit(p->scaler_u, uv_in_width, uv_in_height,
+                   buf->u, uv_out_width, uv_out_height, buf->u_stride, 1,
+                   work + work_size);
+  WebPRescalerInit(p->scaler_v, uv_in_width, uv_in_height,
+                   buf->v, uv_out_width, uv_out_height, buf->v_stride, 1,
+                   work + work_size + uv_work_size);
+  p->emit = EmitRescaledYUV;
+
+  if (has_alpha) {
+    WebPRescalerInit(p->scaler_a, io->mb_w, io->mb_h,
+                     buf->a, out_width, out_height, buf->a_stride, 1,
+                     work + work_size + 2 * uv_work_size);
+    p->emit_alpha = EmitRescaledAlphaYUV;
+    WebPInitAlphaProcessing();
+  }
+  return 1;
+}
+
+//------------------------------------------------------------------------------
+// RGBA rescaling
+
+static int ExportRGB(WebPDecParams* const p, int y_pos) {
+  const WebPYUV444Converter convert =
+      WebPYUV444Converters[p->output->colorspace];
+  const WebPRGBABuffer* const buf = &p->output->u.RGBA;
+  uint8_t* dst = buf->rgba + y_pos * buf->stride;
+  int num_lines_out = 0;
+  // For RGB rescaling, because of the YUV420, current scan position
+  // U/V can be +1/-1 line from the Y one.  Hence the double test.
+  while (WebPRescalerHasPendingOutput(p->scaler_y) &&
+         WebPRescalerHasPendingOutput(p->scaler_u)) {
+    assert(y_pos + num_lines_out < p->output->height);
+    assert(p->scaler_u->y_accum == p->scaler_v->y_accum);
+    WebPRescalerExportRow(p->scaler_y);
+    WebPRescalerExportRow(p->scaler_u);
+    WebPRescalerExportRow(p->scaler_v);
+    convert(p->scaler_y->dst, p->scaler_u->dst, p->scaler_v->dst,
+            dst, p->scaler_y->dst_width);
+    dst += buf->stride;
+    ++num_lines_out;
+  }
+  return num_lines_out;
+}
+
+static int EmitRescaledRGB(const VP8Io* const io, WebPDecParams* const p) {
+  const int mb_h = io->mb_h;
+  const int uv_mb_h = (mb_h + 1) >> 1;
+  int j = 0, uv_j = 0;
+  int num_lines_out = 0;
+  while (j < mb_h) {
+    const int y_lines_in =
+        WebPRescalerImport(p->scaler_y, mb_h - j,
+                           io->y + j * io->y_stride, io->y_stride);
+    j += y_lines_in;
+    if (WebPRescaleNeededLines(p->scaler_u, uv_mb_h - uv_j)) {
+      const int u_lines_in =
+          WebPRescalerImport(p->scaler_u, uv_mb_h - uv_j,
+                             io->u + uv_j * io->uv_stride, io->uv_stride);
+      const int v_lines_in =
+          WebPRescalerImport(p->scaler_v, uv_mb_h - uv_j,
+                             io->v + uv_j * io->uv_stride, io->uv_stride);
+      (void)v_lines_in;   // remove a gcc warning
+      assert(u_lines_in == v_lines_in);
+      uv_j += u_lines_in;
+    }
+    num_lines_out += ExportRGB(p, p->last_y + num_lines_out);
+  }
+  return num_lines_out;
+}
+
+static int ExportAlpha(WebPDecParams* const p, int y_pos, int max_lines_out) {
+  const WebPRGBABuffer* const buf = &p->output->u.RGBA;
+  uint8_t* const base_rgba = buf->rgba + y_pos * buf->stride;
+  const WEBP_CSP_MODE colorspace = p->output->colorspace;
+  const int alpha_first =
+      (colorspace == MODE_ARGB || colorspace == MODE_Argb);
+  uint8_t* dst = base_rgba + (alpha_first ? 0 : 3);
+  int num_lines_out = 0;
+  const int is_premult_alpha = WebPIsPremultipliedMode(colorspace);
+  uint32_t non_opaque = 0;
+  const int width = p->scaler_a->dst_width;
+
+  while (WebPRescalerHasPendingOutput(p->scaler_a) &&
+         num_lines_out < max_lines_out) {
+    assert(y_pos + num_lines_out < p->output->height);
+    WebPRescalerExportRow(p->scaler_a);
+    non_opaque |= WebPDispatchAlpha(p->scaler_a->dst, 0, width, 1, dst, 0);
+    dst += buf->stride;
+    ++num_lines_out;
+  }
+  if (is_premult_alpha && non_opaque) {
+    WebPApplyAlphaMultiply(base_rgba, alpha_first,
+                           width, num_lines_out, buf->stride);
+  }
+  return num_lines_out;
+}
+
+static int ExportAlphaRGBA4444(WebPDecParams* const p, int y_pos,
+                               int max_lines_out) {
+  const WebPRGBABuffer* const buf = &p->output->u.RGBA;
+  uint8_t* const base_rgba = buf->rgba + y_pos * buf->stride;
+#ifdef WEBP_SWAP_16BIT_CSP
+  uint8_t* alpha_dst = base_rgba;
+#else
+  uint8_t* alpha_dst = base_rgba + 1;
+#endif
+  int num_lines_out = 0;
+  const WEBP_CSP_MODE colorspace = p->output->colorspace;
+  const int width = p->scaler_a->dst_width;
+  const int is_premult_alpha = WebPIsPremultipliedMode(colorspace);
+  uint32_t alpha_mask = 0x0f;
+
+  while (WebPRescalerHasPendingOutput(p->scaler_a) &&
+         num_lines_out < max_lines_out) {
+    int i;
+    assert(y_pos + num_lines_out < p->output->height);
+    WebPRescalerExportRow(p->scaler_a);
+    for (i = 0; i < width; ++i) {
+      // Fill in the alpha value (converted to 4 bits).
+      const uint32_t alpha_value = p->scaler_a->dst[i] >> 4;
+      alpha_dst[2 * i] = (alpha_dst[2 * i] & 0xf0) | alpha_value;
+      alpha_mask &= alpha_value;
+    }
+    alpha_dst += buf->stride;
+    ++num_lines_out;
+  }
+  if (is_premult_alpha && alpha_mask != 0x0f) {
+    WebPApplyAlphaMultiply4444(base_rgba, width, num_lines_out, buf->stride);
+  }
+  return num_lines_out;
+}
+
+static int EmitRescaledAlphaRGB(const VP8Io* const io, WebPDecParams* const p,
+                                int expected_num_out_lines) {
+  if (io->a != NULL) {
+    WebPRescaler* const scaler = p->scaler_a;
+    int lines_left = expected_num_out_lines;
+    const int y_end = p->last_y + lines_left;
+    while (lines_left > 0) {
+      const int row_offset = scaler->src_y - io->mb_y;
+      WebPRescalerImport(scaler, io->mb_h + io->mb_y - scaler->src_y,
+                         io->a + row_offset * io->width, io->width);
+      lines_left -= p->emit_alpha_row(p, y_end - lines_left, lines_left);
+    }
+  }
+  return 0;
+}
+
+static int InitRGBRescaler(const VP8Io* const io, WebPDecParams* const p) {
+  const int has_alpha = WebPIsAlphaMode(p->output->colorspace);
+  const int out_width  = io->scaled_width;
+  const int out_height = io->scaled_height;
+  const int uv_in_width  = (io->mb_w + 1) >> 1;
+  const int uv_in_height = (io->mb_h + 1) >> 1;
+  const size_t work_size = 2 * out_width;   // scratch memory for one rescaler
+  rescaler_t* work;  // rescalers work area
+  uint8_t* tmp;   // tmp storage for scaled YUV444 samples before RGB conversion
+  size_t tmp_size1, tmp_size2, total_size, rescaler_size;
+  WebPRescaler* scalers;
+  const int num_rescalers = has_alpha ? 4 : 3;
+
+  tmp_size1 = 3 * work_size;
+  tmp_size2 = 3 * out_width;
+  if (has_alpha) {
+    tmp_size1 += work_size;
+    tmp_size2 += out_width;
+  }
+  total_size = tmp_size1 * sizeof(*work) + tmp_size2 * sizeof(*tmp);
+  rescaler_size = num_rescalers * sizeof(*p->scaler_y) + WEBP_ALIGN_CST;
+
+  p->memory = WebPSafeMalloc(1ULL, total_size + rescaler_size);
+  if (p->memory == NULL) {
+    return 0;   // memory error
+  }
+  work = (rescaler_t*)p->memory;
+  tmp = (uint8_t*)(work + tmp_size1);
+
+  scalers = (WebPRescaler*)WEBP_ALIGN((const uint8_t*)work + total_size);
+  p->scaler_y = &scalers[0];
+  p->scaler_u = &scalers[1];
+  p->scaler_v = &scalers[2];
+  p->scaler_a = has_alpha ? &scalers[3] : NULL;
+
+  WebPRescalerInit(p->scaler_y, io->mb_w, io->mb_h,
+                   tmp + 0 * out_width, out_width, out_height, 0, 1,
+                   work + 0 * work_size);
+  WebPRescalerInit(p->scaler_u, uv_in_width, uv_in_height,
+                   tmp + 1 * out_width, out_width, out_height, 0, 1,
+                   work + 1 * work_size);
+  WebPRescalerInit(p->scaler_v, uv_in_width, uv_in_height,
+                   tmp + 2 * out_width, out_width, out_height, 0, 1,
+                   work + 2 * work_size);
+  p->emit = EmitRescaledRGB;
+  WebPInitYUV444Converters();
+
+  if (has_alpha) {
+    WebPRescalerInit(p->scaler_a, io->mb_w, io->mb_h,
+                     tmp + 3 * out_width, out_width, out_height, 0, 1,
+                     work + 3 * work_size);
+    p->emit_alpha = EmitRescaledAlphaRGB;
+    if (p->output->colorspace == MODE_RGBA_4444 ||
+        p->output->colorspace == MODE_rgbA_4444) {
+      p->emit_alpha_row = ExportAlphaRGBA4444;
+    } else {
+      p->emit_alpha_row = ExportAlpha;
+    }
+    WebPInitAlphaProcessing();
+  }
+  return 1;
+}
+
+//------------------------------------------------------------------------------
+// Default custom functions
+
+static int CustomSetup(VP8Io* io) {
+  WebPDecParams* const p = (WebPDecParams*)io->opaque;
+  const WEBP_CSP_MODE colorspace = p->output->colorspace;
+  const int is_rgb = WebPIsRGBMode(colorspace);
+  const int is_alpha = WebPIsAlphaMode(colorspace);
+
+  p->memory = NULL;
+  p->emit = NULL;
+  p->emit_alpha = NULL;
+  p->emit_alpha_row = NULL;
+  if (!WebPIoInitFromOptions(p->options, io, is_alpha ? MODE_YUV : MODE_YUVA)) {
+    return 0;
+  }
+  if (is_alpha && WebPIsPremultipliedMode(colorspace)) {
+    WebPInitUpsamplers();
+  }
+  if (io->use_scaling) {
+    const int ok = is_rgb ? InitRGBRescaler(io, p) : InitYUVRescaler(io, p);
+    if (!ok) {
+      return 0;    // memory error
+    }
+  } else {
+    if (is_rgb) {
+      WebPInitSamplers();
+      p->emit = EmitSampledRGB;   // default
+      if (io->fancy_upsampling) {
+#ifdef FANCY_UPSAMPLING
+        const int uv_width = (io->mb_w + 1) >> 1;
+        p->memory = WebPSafeMalloc(1ULL, (size_t)(io->mb_w + 2 * uv_width));
+        if (p->memory == NULL) {
+          return 0;   // memory error.
+        }
+        p->tmp_y = (uint8_t*)p->memory;
+        p->tmp_u = p->tmp_y + io->mb_w;
+        p->tmp_v = p->tmp_u + uv_width;
+        p->emit = EmitFancyRGB;
+        WebPInitUpsamplers();
+#endif
+      }
+    } else {
+      p->emit = EmitYUV;
+    }
+    if (is_alpha) {  // need transparency output
+      p->emit_alpha =
+          (colorspace == MODE_RGBA_4444 || colorspace == MODE_rgbA_4444) ?
+              EmitAlphaRGBA4444
+          : is_rgb ? EmitAlphaRGB
+          : EmitAlphaYUV;
+      if (is_rgb) {
+        WebPInitAlphaProcessing();
+      }
+    }
+  }
+
+  if (is_rgb) {
+    VP8YUVInit();
+  }
+  return 1;
+}
+
+//------------------------------------------------------------------------------
+
+static int CustomPut(const VP8Io* io) {
+  WebPDecParams* const p = (WebPDecParams*)io->opaque;
+  const int mb_w = io->mb_w;
+  const int mb_h = io->mb_h;
+  int num_lines_out;
+  assert(!(io->mb_y & 1));
+
+  if (mb_w <= 0 || mb_h <= 0) {
+    return 0;
+  }
+  num_lines_out = p->emit(io, p);
+  if (p->emit_alpha != NULL) {
+    p->emit_alpha(io, p, num_lines_out);
+  }
+  p->last_y += num_lines_out;
+  return 1;
+}
+
+//------------------------------------------------------------------------------
+
+static void CustomTeardown(const VP8Io* io) {
+  WebPDecParams* const p = (WebPDecParams*)io->opaque;
+  WebPSafeFree(p->memory);
+  p->memory = NULL;
+}
+
+//------------------------------------------------------------------------------
+// Main entry point
+
+void WebPInitCustomIo(WebPDecParams* const params, VP8Io* const io) {
+  io->put      = CustomPut;
+  io->setup    = CustomSetup;
+  io->teardown = CustomTeardown;
+  io->opaque   = params;
+}
+
+//------------------------------------------------------------------------------

+ 110 - 0
Source/ThirdParty/WebP/src/dec/quant_dec.c

@@ -0,0 +1,110 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Quantizer initialization
+//
+// Author: Skal ([email protected])
+
+#include "./vp8i_dec.h"
+
+static WEBP_INLINE int clip(int v, int M) {
+  return v < 0 ? 0 : v > M ? M : v;
+}
+
+// Paragraph 14.1
+static const uint8_t kDcTable[128] = {
+  4,     5,   6,   7,   8,   9,  10,  10,
+  11,   12,  13,  14,  15,  16,  17,  17,
+  18,   19,  20,  20,  21,  21,  22,  22,
+  23,   23,  24,  25,  25,  26,  27,  28,
+  29,   30,  31,  32,  33,  34,  35,  36,
+  37,   37,  38,  39,  40,  41,  42,  43,
+  44,   45,  46,  46,  47,  48,  49,  50,
+  51,   52,  53,  54,  55,  56,  57,  58,
+  59,   60,  61,  62,  63,  64,  65,  66,
+  67,   68,  69,  70,  71,  72,  73,  74,
+  75,   76,  76,  77,  78,  79,  80,  81,
+  82,   83,  84,  85,  86,  87,  88,  89,
+  91,   93,  95,  96,  98, 100, 101, 102,
+  104, 106, 108, 110, 112, 114, 116, 118,
+  122, 124, 126, 128, 130, 132, 134, 136,
+  138, 140, 143, 145, 148, 151, 154, 157
+};
+
+static const uint16_t kAcTable[128] = {
+  4,     5,   6,   7,   8,   9,  10,  11,
+  12,   13,  14,  15,  16,  17,  18,  19,
+  20,   21,  22,  23,  24,  25,  26,  27,
+  28,   29,  30,  31,  32,  33,  34,  35,
+  36,   37,  38,  39,  40,  41,  42,  43,
+  44,   45,  46,  47,  48,  49,  50,  51,
+  52,   53,  54,  55,  56,  57,  58,  60,
+  62,   64,  66,  68,  70,  72,  74,  76,
+  78,   80,  82,  84,  86,  88,  90,  92,
+  94,   96,  98, 100, 102, 104, 106, 108,
+  110, 112, 114, 116, 119, 122, 125, 128,
+  131, 134, 137, 140, 143, 146, 149, 152,
+  155, 158, 161, 164, 167, 170, 173, 177,
+  181, 185, 189, 193, 197, 201, 205, 209,
+  213, 217, 221, 225, 229, 234, 239, 245,
+  249, 254, 259, 264, 269, 274, 279, 284
+};
+
+//------------------------------------------------------------------------------
+// Paragraph 9.6
+
+void VP8ParseQuant(VP8Decoder* const dec) {
+  VP8BitReader* const br = &dec->br_;
+  const int base_q0 = VP8GetValue(br, 7);
+  const int dqy1_dc = VP8Get(br) ? VP8GetSignedValue(br, 4) : 0;
+  const int dqy2_dc = VP8Get(br) ? VP8GetSignedValue(br, 4) : 0;
+  const int dqy2_ac = VP8Get(br) ? VP8GetSignedValue(br, 4) : 0;
+  const int dquv_dc = VP8Get(br) ? VP8GetSignedValue(br, 4) : 0;
+  const int dquv_ac = VP8Get(br) ? VP8GetSignedValue(br, 4) : 0;
+
+  const VP8SegmentHeader* const hdr = &dec->segment_hdr_;
+  int i;
+
+  for (i = 0; i < NUM_MB_SEGMENTS; ++i) {
+    int q;
+    if (hdr->use_segment_) {
+      q = hdr->quantizer_[i];
+      if (!hdr->absolute_delta_) {
+        q += base_q0;
+      }
+    } else {
+      if (i > 0) {
+        dec->dqm_[i] = dec->dqm_[0];
+        continue;
+      } else {
+        q = base_q0;
+      }
+    }
+    {
+      VP8QuantMatrix* const m = &dec->dqm_[i];
+      m->y1_mat_[0] = kDcTable[clip(q + dqy1_dc, 127)];
+      m->y1_mat_[1] = kAcTable[clip(q + 0,       127)];
+
+      m->y2_mat_[0] = kDcTable[clip(q + dqy2_dc, 127)] * 2;
+      // For all x in [0..284], x*155/100 is bitwise equal to (x*101581) >> 16.
+      // The smallest precision for that is '(x*6349) >> 12' but 16 is a good
+      // word size.
+      m->y2_mat_[1] = (kAcTable[clip(q + dqy2_ac, 127)] * 101581) >> 16;
+      if (m->y2_mat_[1] < 8) m->y2_mat_[1] = 8;
+
+      m->uv_mat_[0] = kDcTable[clip(q + dquv_dc, 117)];
+      m->uv_mat_[1] = kAcTable[clip(q + dquv_ac, 127)];
+
+      m->uv_quant_ = q + dquv_ac;   // for dithering strength evaluation
+    }
+  }
+}
+
+//------------------------------------------------------------------------------
+

+ 528 - 0
Source/ThirdParty/WebP/src/dec/tree_dec.c

@@ -0,0 +1,528 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Coding trees and probas
+//
+// Author: Skal ([email protected])
+
+#include "./vp8i_dec.h"
+#include "../utils/bit_reader_inl_utils.h"
+
+#if !defined(__arm__) && !defined(_M_ARM) && !defined(__aarch64__)
+// using a table is ~1-2% slower on ARM. Prefer the coded-tree approach then.
+#define USE_GENERIC_TREE
+#endif
+
+#ifdef USE_GENERIC_TREE
+static const int8_t kYModesIntra4[18] = {
+  -B_DC_PRED, 1,
+    -B_TM_PRED, 2,
+      -B_VE_PRED, 3,
+        4, 6,
+          -B_HE_PRED, 5,
+            -B_RD_PRED, -B_VR_PRED,
+        -B_LD_PRED, 7,
+          -B_VL_PRED, 8,
+            -B_HD_PRED, -B_HU_PRED
+};
+#endif
+
+//------------------------------------------------------------------------------
+// Default probabilities
+
+// Paragraph 13.5
+static const uint8_t
+  CoeffsProba0[NUM_TYPES][NUM_BANDS][NUM_CTX][NUM_PROBAS] = {
+  { { { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 },
+      { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 },
+      { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }
+    },
+    { { 253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128 },
+      { 189, 129, 242, 255, 227, 213, 255, 219, 128, 128, 128 },
+      { 106, 126, 227, 252, 214, 209, 255, 255, 128, 128, 128 }
+    },
+    { { 1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128 },
+      { 181, 133, 238, 254, 221, 234, 255, 154, 128, 128, 128 },
+      { 78, 134, 202, 247, 198, 180, 255, 219, 128, 128, 128 },
+    },
+    { { 1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128 },
+      { 184, 150, 247, 255, 236, 224, 128, 128, 128, 128, 128 },
+      { 77, 110, 216, 255, 236, 230, 128, 128, 128, 128, 128 },
+    },
+    { { 1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128 },
+      { 170, 139, 241, 252, 236, 209, 255, 255, 128, 128, 128 },
+      { 37, 116, 196, 243, 228, 255, 255, 255, 128, 128, 128 }
+    },
+    { { 1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128 },
+      { 207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128 },
+      { 102, 103, 231, 255, 211, 171, 128, 128, 128, 128, 128 }
+    },
+    { { 1, 152, 252, 255, 240, 255, 128, 128, 128, 128, 128 },
+      { 177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128 },
+      { 80, 129, 211, 255, 194, 224, 128, 128, 128, 128, 128 }
+    },
+    { { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 },
+      { 246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 },
+      { 255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }
+    }
+  },
+  { { { 198, 35, 237, 223, 193, 187, 162, 160, 145, 155, 62 },
+      { 131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1 },
+      { 68, 47, 146, 208, 149, 167, 221, 162, 255, 223, 128 }
+    },
+    { { 1, 149, 241, 255, 221, 224, 255, 255, 128, 128, 128 },
+      { 184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128 },
+      { 81, 99, 181, 242, 176, 190, 249, 202, 255, 255, 128 }
+    },
+    { { 1, 129, 232, 253, 214, 197, 242, 196, 255, 255, 128 },
+      { 99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128 },
+      { 23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128 }
+    },
+    { { 1, 200, 246, 255, 234, 255, 128, 128, 128, 128, 128 },
+      { 109, 178, 241, 255, 231, 245, 255, 255, 128, 128, 128 },
+      { 44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128 }
+    },
+    { { 1, 132, 239, 251, 219, 209, 255, 165, 128, 128, 128 },
+      { 94, 136, 225, 251, 218, 190, 255, 255, 128, 128, 128 },
+      { 22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128 }
+    },
+    { { 1, 182, 249, 255, 232, 235, 128, 128, 128, 128, 128 },
+      { 124, 143, 241, 255, 227, 234, 128, 128, 128, 128, 128 },
+      { 35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128 }
+    },
+    { { 1, 157, 247, 255, 236, 231, 255, 255, 128, 128, 128 },
+      { 121, 141, 235, 255, 225, 227, 255, 255, 128, 128, 128 },
+      { 45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128 }
+    },
+    { { 1, 1, 251, 255, 213, 255, 128, 128, 128, 128, 128 },
+      { 203, 1, 248, 255, 255, 128, 128, 128, 128, 128, 128 },
+      { 137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128 }
+    }
+  },
+  { { { 253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128 },
+      { 175, 13, 224, 243, 193, 185, 249, 198, 255, 255, 128 },
+      { 73, 17, 171, 221, 161, 179, 236, 167, 255, 234, 128 }
+    },
+    { { 1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128 },
+      { 239, 90, 244, 250, 211, 209, 255, 255, 128, 128, 128 },
+      { 155, 77, 195, 248, 188, 195, 255, 255, 128, 128, 128 }
+    },
+    { { 1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128 },
+      { 201, 51, 219, 255, 196, 186, 128, 128, 128, 128, 128 },
+      { 69, 46, 190, 239, 201, 218, 255, 228, 128, 128, 128 }
+    },
+    { { 1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128 },
+      { 223, 165, 249, 255, 213, 255, 128, 128, 128, 128, 128 },
+      { 141, 124, 248, 255, 255, 128, 128, 128, 128, 128, 128 }
+    },
+    { { 1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128 },
+      { 190, 36, 230, 255, 236, 255, 128, 128, 128, 128, 128 },
+      { 149, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }
+    },
+    { { 1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128 },
+      { 247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128 },
+      { 240, 128, 255, 128, 128, 128, 128, 128, 128, 128, 128 }
+    },
+    { { 1, 134, 252, 255, 255, 128, 128, 128, 128, 128, 128 },
+      { 213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128 },
+      { 55, 93, 255, 128, 128, 128, 128, 128, 128, 128, 128 }
+    },
+    { { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 },
+      { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 },
+      { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 }
+    }
+  },
+  { { { 202, 24, 213, 235, 186, 191, 220, 160, 240, 175, 255 },
+      { 126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128 },
+      { 61, 46, 138, 219, 151, 178, 240, 170, 255, 216, 128 }
+    },
+    { { 1, 112, 230, 250, 199, 191, 247, 159, 255, 255, 128 },
+      { 166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128 },
+      { 39, 77, 162, 232, 172, 180, 245, 178, 255, 255, 128 }
+    },
+    { { 1, 52, 220, 246, 198, 199, 249, 220, 255, 255, 128 },
+      { 124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128 },
+      { 24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128 }
+    },
+    { { 1, 182, 225, 249, 219, 240, 255, 224, 128, 128, 128 },
+      { 149, 150, 226, 252, 216, 205, 255, 171, 128, 128, 128 },
+      { 28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128 }
+    },
+    { { 1, 81, 230, 252, 204, 203, 255, 192, 128, 128, 128 },
+      { 123, 102, 209, 247, 188, 196, 255, 233, 128, 128, 128 },
+      { 20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128 }
+    },
+    { { 1, 222, 248, 255, 216, 213, 128, 128, 128, 128, 128 },
+      { 168, 175, 246, 252, 235, 205, 255, 255, 128, 128, 128 },
+      { 47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128 }
+    },
+    { { 1, 121, 236, 253, 212, 214, 255, 255, 128, 128, 128 },
+      { 141, 84, 213, 252, 201, 202, 255, 219, 128, 128, 128 },
+      { 42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128 }
+    },
+    { { 1, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 },
+      { 244, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 },
+      { 238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128 }
+    }
+  }
+};
+
+// Paragraph 11.5
+static const uint8_t kBModesProba[NUM_BMODES][NUM_BMODES][NUM_BMODES - 1] = {
+  { { 231, 120, 48, 89, 115, 113, 120, 152, 112 },
+    { 152, 179, 64, 126, 170, 118, 46, 70, 95 },
+    { 175, 69, 143, 80, 85, 82, 72, 155, 103 },
+    { 56, 58, 10, 171, 218, 189, 17, 13, 152 },
+    { 114, 26, 17, 163, 44, 195, 21, 10, 173 },
+    { 121, 24, 80, 195, 26, 62, 44, 64, 85 },
+    { 144, 71, 10, 38, 171, 213, 144, 34, 26 },
+    { 170, 46, 55, 19, 136, 160, 33, 206, 71 },
+    { 63, 20, 8, 114, 114, 208, 12, 9, 226 },
+    { 81, 40, 11, 96, 182, 84, 29, 16, 36 } },
+  { { 134, 183, 89, 137, 98, 101, 106, 165, 148 },
+    { 72, 187, 100, 130, 157, 111, 32, 75, 80 },
+    { 66, 102, 167, 99, 74, 62, 40, 234, 128 },
+    { 41, 53, 9, 178, 241, 141, 26, 8, 107 },
+    { 74, 43, 26, 146, 73, 166, 49, 23, 157 },
+    { 65, 38, 105, 160, 51, 52, 31, 115, 128 },
+    { 104, 79, 12, 27, 217, 255, 87, 17, 7 },
+    { 87, 68, 71, 44, 114, 51, 15, 186, 23 },
+    { 47, 41, 14, 110, 182, 183, 21, 17, 194 },
+    { 66, 45, 25, 102, 197, 189, 23, 18, 22 } },
+  { { 88, 88, 147, 150, 42, 46, 45, 196, 205 },
+    { 43, 97, 183, 117, 85, 38, 35, 179, 61 },
+    { 39, 53, 200, 87, 26, 21, 43, 232, 171 },
+    { 56, 34, 51, 104, 114, 102, 29, 93, 77 },
+    { 39, 28, 85, 171, 58, 165, 90, 98, 64 },
+    { 34, 22, 116, 206, 23, 34, 43, 166, 73 },
+    { 107, 54, 32, 26, 51, 1, 81, 43, 31 },
+    { 68, 25, 106, 22, 64, 171, 36, 225, 114 },
+    { 34, 19, 21, 102, 132, 188, 16, 76, 124 },
+    { 62, 18, 78, 95, 85, 57, 50, 48, 51 } },
+  { { 193, 101, 35, 159, 215, 111, 89, 46, 111 },
+    { 60, 148, 31, 172, 219, 228, 21, 18, 111 },
+    { 112, 113, 77, 85, 179, 255, 38, 120, 114 },
+    { 40, 42, 1, 196, 245, 209, 10, 25, 109 },
+    { 88, 43, 29, 140, 166, 213, 37, 43, 154 },
+    { 61, 63, 30, 155, 67, 45, 68, 1, 209 },
+    { 100, 80, 8, 43, 154, 1, 51, 26, 71 },
+    { 142, 78, 78, 16, 255, 128, 34, 197, 171 },
+    { 41, 40, 5, 102, 211, 183, 4, 1, 221 },
+    { 51, 50, 17, 168, 209, 192, 23, 25, 82 } },
+  { { 138, 31, 36, 171, 27, 166, 38, 44, 229 },
+    { 67, 87, 58, 169, 82, 115, 26, 59, 179 },
+    { 63, 59, 90, 180, 59, 166, 93, 73, 154 },
+    { 40, 40, 21, 116, 143, 209, 34, 39, 175 },
+    { 47, 15, 16, 183, 34, 223, 49, 45, 183 },
+    { 46, 17, 33, 183, 6, 98, 15, 32, 183 },
+    { 57, 46, 22, 24, 128, 1, 54, 17, 37 },
+    { 65, 32, 73, 115, 28, 128, 23, 128, 205 },
+    { 40, 3, 9, 115, 51, 192, 18, 6, 223 },
+    { 87, 37, 9, 115, 59, 77, 64, 21, 47 } },
+  { { 104, 55, 44, 218, 9, 54, 53, 130, 226 },
+    { 64, 90, 70, 205, 40, 41, 23, 26, 57 },
+    { 54, 57, 112, 184, 5, 41, 38, 166, 213 },
+    { 30, 34, 26, 133, 152, 116, 10, 32, 134 },
+    { 39, 19, 53, 221, 26, 114, 32, 73, 255 },
+    { 31, 9, 65, 234, 2, 15, 1, 118, 73 },
+    { 75, 32, 12, 51, 192, 255, 160, 43, 51 },
+    { 88, 31, 35, 67, 102, 85, 55, 186, 85 },
+    { 56, 21, 23, 111, 59, 205, 45, 37, 192 },
+    { 55, 38, 70, 124, 73, 102, 1, 34, 98 } },
+  { { 125, 98, 42, 88, 104, 85, 117, 175, 82 },
+    { 95, 84, 53, 89, 128, 100, 113, 101, 45 },
+    { 75, 79, 123, 47, 51, 128, 81, 171, 1 },
+    { 57, 17, 5, 71, 102, 57, 53, 41, 49 },
+    { 38, 33, 13, 121, 57, 73, 26, 1, 85 },
+    { 41, 10, 67, 138, 77, 110, 90, 47, 114 },
+    { 115, 21, 2, 10, 102, 255, 166, 23, 6 },
+    { 101, 29, 16, 10, 85, 128, 101, 196, 26 },
+    { 57, 18, 10, 102, 102, 213, 34, 20, 43 },
+    { 117, 20, 15, 36, 163, 128, 68, 1, 26 } },
+  { { 102, 61, 71, 37, 34, 53, 31, 243, 192 },
+    { 69, 60, 71, 38, 73, 119, 28, 222, 37 },
+    { 68, 45, 128, 34, 1, 47, 11, 245, 171 },
+    { 62, 17, 19, 70, 146, 85, 55, 62, 70 },
+    { 37, 43, 37, 154, 100, 163, 85, 160, 1 },
+    { 63, 9, 92, 136, 28, 64, 32, 201, 85 },
+    { 75, 15, 9, 9, 64, 255, 184, 119, 16 },
+    { 86, 6, 28, 5, 64, 255, 25, 248, 1 },
+    { 56, 8, 17, 132, 137, 255, 55, 116, 128 },
+    { 58, 15, 20, 82, 135, 57, 26, 121, 40 } },
+  { { 164, 50, 31, 137, 154, 133, 25, 35, 218 },
+    { 51, 103, 44, 131, 131, 123, 31, 6, 158 },
+    { 86, 40, 64, 135, 148, 224, 45, 183, 128 },
+    { 22, 26, 17, 131, 240, 154, 14, 1, 209 },
+    { 45, 16, 21, 91, 64, 222, 7, 1, 197 },
+    { 56, 21, 39, 155, 60, 138, 23, 102, 213 },
+    { 83, 12, 13, 54, 192, 255, 68, 47, 28 },
+    { 85, 26, 85, 85, 128, 128, 32, 146, 171 },
+    { 18, 11, 7, 63, 144, 171, 4, 4, 246 },
+    { 35, 27, 10, 146, 174, 171, 12, 26, 128 } },
+  { { 190, 80, 35, 99, 180, 80, 126, 54, 45 },
+    { 85, 126, 47, 87, 176, 51, 41, 20, 32 },
+    { 101, 75, 128, 139, 118, 146, 116, 128, 85 },
+    { 56, 41, 15, 176, 236, 85, 37, 9, 62 },
+    { 71, 30, 17, 119, 118, 255, 17, 18, 138 },
+    { 101, 38, 60, 138, 55, 70, 43, 26, 142 },
+    { 146, 36, 19, 30, 171, 255, 97, 27, 20 },
+    { 138, 45, 61, 62, 219, 1, 81, 188, 64 },
+    { 32, 41, 20, 117, 151, 142, 20, 21, 163 },
+    { 112, 19, 12, 61, 195, 128, 48, 4, 24 } }
+};
+
+void VP8ResetProba(VP8Proba* const proba) {
+  memset(proba->segments_, 255u, sizeof(proba->segments_));
+  // proba->bands_[][] is initialized later
+}
+
+static void ParseIntraMode(VP8BitReader* const br,
+                           VP8Decoder* const dec, int mb_x) {
+  uint8_t* const top = dec->intra_t_ + 4 * mb_x;
+  uint8_t* const left = dec->intra_l_;
+  VP8MBData* const block = dec->mb_data_ + mb_x;
+
+  // Note: we don't save segment map (yet), as we don't expect
+  // to decode more than 1 keyframe.
+  if (dec->segment_hdr_.update_map_) {
+    // Hardcoded tree parsing
+    block->segment_ = !VP8GetBit(br, dec->proba_.segments_[0])
+                    ? VP8GetBit(br, dec->proba_.segments_[1])
+                    : 2 + VP8GetBit(br, dec->proba_.segments_[2]);
+  } else {
+    block->segment_ = 0;  // default for intra
+  }
+  if (dec->use_skip_proba_) block->skip_ = VP8GetBit(br, dec->skip_p_);
+
+  block->is_i4x4_ = !VP8GetBit(br, 145);   // decide for B_PRED first
+  if (!block->is_i4x4_) {
+    // Hardcoded 16x16 intra-mode decision tree.
+    const int ymode =
+        VP8GetBit(br, 156) ? (VP8GetBit(br, 128) ? TM_PRED : H_PRED)
+                           : (VP8GetBit(br, 163) ? V_PRED : DC_PRED);
+    block->imodes_[0] = ymode;
+    memset(top, ymode, 4 * sizeof(*top));
+    memset(left, ymode, 4 * sizeof(*left));
+  } else {
+    uint8_t* modes = block->imodes_;
+    int y;
+    for (y = 0; y < 4; ++y) {
+      int ymode = left[y];
+      int x;
+      for (x = 0; x < 4; ++x) {
+        const uint8_t* const prob = kBModesProba[top[x]][ymode];
+#ifdef USE_GENERIC_TREE
+        // Generic tree-parsing
+        int i = kYModesIntra4[VP8GetBit(br, prob[0])];
+        while (i > 0) {
+          i = kYModesIntra4[2 * i + VP8GetBit(br, prob[i])];
+        }
+        ymode = -i;
+#else
+        // Hardcoded tree parsing
+        ymode = !VP8GetBit(br, prob[0]) ? B_DC_PRED :
+                  !VP8GetBit(br, prob[1]) ? B_TM_PRED :
+                    !VP8GetBit(br, prob[2]) ? B_VE_PRED :
+                      !VP8GetBit(br, prob[3]) ?
+                        (!VP8GetBit(br, prob[4]) ? B_HE_PRED :
+                          (!VP8GetBit(br, prob[5]) ? B_RD_PRED : B_VR_PRED)) :
+                        (!VP8GetBit(br, prob[6]) ? B_LD_PRED :
+                          (!VP8GetBit(br, prob[7]) ? B_VL_PRED :
+                            (!VP8GetBit(br, prob[8]) ? B_HD_PRED : B_HU_PRED)));
+#endif    // USE_GENERIC_TREE
+        top[x] = ymode;
+      }
+      memcpy(modes, top, 4 * sizeof(*top));
+      modes += 4;
+      left[y] = ymode;
+    }
+  }
+  // Hardcoded UVMode decision tree
+  block->uvmode_ = !VP8GetBit(br, 142) ? DC_PRED
+                 : !VP8GetBit(br, 114) ? V_PRED
+                 : VP8GetBit(br, 183) ? TM_PRED : H_PRED;
+}
+
+int VP8ParseIntraModeRow(VP8BitReader* const br, VP8Decoder* const dec) {
+  int mb_x;
+  for (mb_x = 0; mb_x < dec->mb_w_; ++mb_x) {
+    ParseIntraMode(br, dec, mb_x);
+  }
+  return !dec->br_.eof_;
+}
+
+//------------------------------------------------------------------------------
+// Paragraph 13
+
+static const uint8_t
+    CoeffsUpdateProba[NUM_TYPES][NUM_BANDS][NUM_CTX][NUM_PROBAS] = {
+  { { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 223, 241, 252, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 249, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 234, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 239, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 251, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 254, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 254, 253, 255, 254, 255, 255, 255, 255, 255, 255 },
+      { 250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255 },
+      { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    }
+  },
+  { { { 217, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255 },
+      { 234, 250, 241, 250, 253, 255, 253, 254, 255, 255, 255 }
+    },
+    { { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 238, 253, 254, 254, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 253, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 247, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 252, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 253, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    }
+  },
+  { { { 186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 234, 251, 244, 254, 255, 255, 255, 255, 255, 255, 255 },
+      { 251, 251, 243, 253, 254, 255, 254, 255, 255, 255, 255 }
+    },
+    { { 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 236, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 251, 253, 253, 254, 254, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    }
+  },
+  { { { 248, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255 },
+      { 248, 254, 249, 253, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 252, 254, 251, 254, 254, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 254, 252, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 245, 251, 254, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 251, 253, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 252, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 252, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 249, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 255, 253, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 250, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    },
+    { { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 },
+      { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255 }
+    }
+  }
+};
+
+// Paragraph 9.9
+
+static const int kBands[16 + 1] = {
+  0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7,
+  0  // extra entry as sentinel
+};
+
+void VP8ParseProba(VP8BitReader* const br, VP8Decoder* const dec) {
+  VP8Proba* const proba = &dec->proba_;
+  int t, b, c, p;
+  for (t = 0; t < NUM_TYPES; ++t) {
+    for (b = 0; b < NUM_BANDS; ++b) {
+      for (c = 0; c < NUM_CTX; ++c) {
+        for (p = 0; p < NUM_PROBAS; ++p) {
+          const int v = VP8GetBit(br, CoeffsUpdateProba[t][b][c][p]) ?
+                        VP8GetValue(br, 8) : CoeffsProba0[t][b][c][p];
+          proba->bands_[t][b].probas_[c][p] = v;
+        }
+      }
+    }
+    for (b = 0; b < 16 + 1; ++b) {
+      proba->bands_ptr_[t][b] = &proba->bands_[t][kBands[b]];
+    }
+  }
+  dec->use_skip_proba_ = VP8Get(br);
+  if (dec->use_skip_proba_) {
+    dec->skip_p_ = VP8GetValue(br, 8);
+  }
+}
+

+ 721 - 0
Source/ThirdParty/WebP/src/dec/vp8_dec.c

@@ -0,0 +1,721 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// main entry for the decoder
+//
+// Author: Skal ([email protected])
+
+#include <stdlib.h>
+
+#include "./alphai_dec.h"
+#include "./vp8i_dec.h"
+#include "./vp8li_dec.h"
+#include "./webpi_dec.h"
+#include "../utils/bit_reader_inl_utils.h"
+#include "../utils/utils.h"
+
+//------------------------------------------------------------------------------
+
+int WebPGetDecoderVersion(void) {
+  return (DEC_MAJ_VERSION << 16) | (DEC_MIN_VERSION << 8) | DEC_REV_VERSION;
+}
+
+//------------------------------------------------------------------------------
+// Signature and pointer-to-function for GetCoeffs() variants below.
+
+typedef int (*GetCoeffsFunc)(VP8BitReader* const br,
+                             const VP8BandProbas* const prob[],
+                             int ctx, const quant_t dq, int n, int16_t* out);
+static volatile GetCoeffsFunc GetCoeffs = NULL;
+
+static void InitGetCoeffs(void);
+
+//------------------------------------------------------------------------------
+// VP8Decoder
+
+static void SetOk(VP8Decoder* const dec) {
+  dec->status_ = VP8_STATUS_OK;
+  dec->error_msg_ = "OK";
+}
+
+int VP8InitIoInternal(VP8Io* const io, int version) {
+  if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_DECODER_ABI_VERSION)) {
+    return 0;  // mismatch error
+  }
+  if (io != NULL) {
+    memset(io, 0, sizeof(*io));
+  }
+  return 1;
+}
+
+VP8Decoder* VP8New(void) {
+  VP8Decoder* const dec = (VP8Decoder*)WebPSafeCalloc(1ULL, sizeof(*dec));
+  if (dec != NULL) {
+    SetOk(dec);
+    WebPGetWorkerInterface()->Init(&dec->worker_);
+    dec->ready_ = 0;
+    dec->num_parts_minus_one_ = 0;
+    InitGetCoeffs();
+  }
+  return dec;
+}
+
+VP8StatusCode VP8Status(VP8Decoder* const dec) {
+  if (!dec) return VP8_STATUS_INVALID_PARAM;
+  return dec->status_;
+}
+
+const char* VP8StatusMessage(VP8Decoder* const dec) {
+  if (dec == NULL) return "no object";
+  if (!dec->error_msg_) return "OK";
+  return dec->error_msg_;
+}
+
+void VP8Delete(VP8Decoder* const dec) {
+  if (dec != NULL) {
+    VP8Clear(dec);
+    WebPSafeFree(dec);
+  }
+}
+
+int VP8SetError(VP8Decoder* const dec,
+                VP8StatusCode error, const char* const msg) {
+  // The oldest error reported takes precedence over the new one.
+  if (dec->status_ == VP8_STATUS_OK) {
+    dec->status_ = error;
+    dec->error_msg_ = msg;
+    dec->ready_ = 0;
+  }
+  return 0;
+}
+
+//------------------------------------------------------------------------------
+
+int VP8CheckSignature(const uint8_t* const data, size_t data_size) {
+  return (data_size >= 3 &&
+          data[0] == 0x9d && data[1] == 0x01 && data[2] == 0x2a);
+}
+
+int VP8GetInfo(const uint8_t* data, size_t data_size, size_t chunk_size,
+               int* const width, int* const height) {
+  if (data == NULL || data_size < VP8_FRAME_HEADER_SIZE) {
+    return 0;         // not enough data
+  }
+  // check signature
+  if (!VP8CheckSignature(data + 3, data_size - 3)) {
+    return 0;         // Wrong signature.
+  } else {
+    const uint32_t bits = data[0] | (data[1] << 8) | (data[2] << 16);
+    const int key_frame = !(bits & 1);
+    const int w = ((data[7] << 8) | data[6]) & 0x3fff;
+    const int h = ((data[9] << 8) | data[8]) & 0x3fff;
+
+    if (!key_frame) {   // Not a keyframe.
+      return 0;
+    }
+
+    if (((bits >> 1) & 7) > 3) {
+      return 0;         // unknown profile
+    }
+    if (!((bits >> 4) & 1)) {
+      return 0;         // first frame is invisible!
+    }
+    if (((bits >> 5)) >= chunk_size) {  // partition_length
+      return 0;         // inconsistent size information.
+    }
+    if (w == 0 || h == 0) {
+      return 0;         // We don't support both width and height to be zero.
+    }
+
+    if (width) {
+      *width = w;
+    }
+    if (height) {
+      *height = h;
+    }
+
+    return 1;
+  }
+}
+
+//------------------------------------------------------------------------------
+// Header parsing
+
+static void ResetSegmentHeader(VP8SegmentHeader* const hdr) {
+  assert(hdr != NULL);
+  hdr->use_segment_ = 0;
+  hdr->update_map_ = 0;
+  hdr->absolute_delta_ = 1;
+  memset(hdr->quantizer_, 0, sizeof(hdr->quantizer_));
+  memset(hdr->filter_strength_, 0, sizeof(hdr->filter_strength_));
+}
+
+// Paragraph 9.3
+static int ParseSegmentHeader(VP8BitReader* br,
+                              VP8SegmentHeader* hdr, VP8Proba* proba) {
+  assert(br != NULL);
+  assert(hdr != NULL);
+  hdr->use_segment_ = VP8Get(br);
+  if (hdr->use_segment_) {
+    hdr->update_map_ = VP8Get(br);
+    if (VP8Get(br)) {   // update data
+      int s;
+      hdr->absolute_delta_ = VP8Get(br);
+      for (s = 0; s < NUM_MB_SEGMENTS; ++s) {
+        hdr->quantizer_[s] = VP8Get(br) ? VP8GetSignedValue(br, 7) : 0;
+      }
+      for (s = 0; s < NUM_MB_SEGMENTS; ++s) {
+        hdr->filter_strength_[s] = VP8Get(br) ? VP8GetSignedValue(br, 6) : 0;
+      }
+    }
+    if (hdr->update_map_) {
+      int s;
+      for (s = 0; s < MB_FEATURE_TREE_PROBS; ++s) {
+        proba->segments_[s] = VP8Get(br) ? VP8GetValue(br, 8) : 255u;
+      }
+    }
+  } else {
+    hdr->update_map_ = 0;
+  }
+  return !br->eof_;
+}
+
+// Paragraph 9.5
+// This function returns VP8_STATUS_SUSPENDED if we don't have all the
+// necessary data in 'buf'.
+// This case is not necessarily an error (for incremental decoding).
+// Still, no bitreader is ever initialized to make it possible to read
+// unavailable memory.
+// If we don't even have the partitions' sizes, than VP8_STATUS_NOT_ENOUGH_DATA
+// is returned, and this is an unrecoverable error.
+// If the partitions were positioned ok, VP8_STATUS_OK is returned.
+static VP8StatusCode ParsePartitions(VP8Decoder* const dec,
+                                     const uint8_t* buf, size_t size) {
+  VP8BitReader* const br = &dec->br_;
+  const uint8_t* sz = buf;
+  const uint8_t* buf_end = buf + size;
+  const uint8_t* part_start;
+  size_t size_left = size;
+  size_t last_part;
+  size_t p;
+
+  dec->num_parts_minus_one_ = (1 << VP8GetValue(br, 2)) - 1;
+  last_part = dec->num_parts_minus_one_;
+  if (size < 3 * last_part) {
+    // we can't even read the sizes with sz[]! That's a failure.
+    return VP8_STATUS_NOT_ENOUGH_DATA;
+  }
+  part_start = buf + last_part * 3;
+  size_left -= last_part * 3;
+  for (p = 0; p < last_part; ++p) {
+    size_t psize = sz[0] | (sz[1] << 8) | (sz[2] << 16);
+    if (psize > size_left) psize = size_left;
+    VP8InitBitReader(dec->parts_ + p, part_start, psize);
+    part_start += psize;
+    size_left -= psize;
+    sz += 3;
+  }
+  VP8InitBitReader(dec->parts_ + last_part, part_start, size_left);
+  return (part_start < buf_end) ? VP8_STATUS_OK :
+           VP8_STATUS_SUSPENDED;   // Init is ok, but there's not enough data
+}
+
+// Paragraph 9.4
+static int ParseFilterHeader(VP8BitReader* br, VP8Decoder* const dec) {
+  VP8FilterHeader* const hdr = &dec->filter_hdr_;
+  hdr->simple_    = VP8Get(br);
+  hdr->level_     = VP8GetValue(br, 6);
+  hdr->sharpness_ = VP8GetValue(br, 3);
+  hdr->use_lf_delta_ = VP8Get(br);
+  if (hdr->use_lf_delta_) {
+    if (VP8Get(br)) {   // update lf-delta?
+      int i;
+      for (i = 0; i < NUM_REF_LF_DELTAS; ++i) {
+        if (VP8Get(br)) {
+          hdr->ref_lf_delta_[i] = VP8GetSignedValue(br, 6);
+        }
+      }
+      for (i = 0; i < NUM_MODE_LF_DELTAS; ++i) {
+        if (VP8Get(br)) {
+          hdr->mode_lf_delta_[i] = VP8GetSignedValue(br, 6);
+        }
+      }
+    }
+  }
+  dec->filter_type_ = (hdr->level_ == 0) ? 0 : hdr->simple_ ? 1 : 2;
+  return !br->eof_;
+}
+
+// Topmost call
+int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io) {
+  const uint8_t* buf;
+  size_t buf_size;
+  VP8FrameHeader* frm_hdr;
+  VP8PictureHeader* pic_hdr;
+  VP8BitReader* br;
+  VP8StatusCode status;
+
+  if (dec == NULL) {
+    return 0;
+  }
+  SetOk(dec);
+  if (io == NULL) {
+    return VP8SetError(dec, VP8_STATUS_INVALID_PARAM,
+                       "null VP8Io passed to VP8GetHeaders()");
+  }
+  buf = io->data;
+  buf_size = io->data_size;
+  if (buf_size < 4) {
+    return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA,
+                       "Truncated header.");
+  }
+
+  // Paragraph 9.1
+  {
+    const uint32_t bits = buf[0] | (buf[1] << 8) | (buf[2] << 16);
+    frm_hdr = &dec->frm_hdr_;
+    frm_hdr->key_frame_ = !(bits & 1);
+    frm_hdr->profile_ = (bits >> 1) & 7;
+    frm_hdr->show_ = (bits >> 4) & 1;
+    frm_hdr->partition_length_ = (bits >> 5);
+    if (frm_hdr->profile_ > 3) {
+      return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR,
+                         "Incorrect keyframe parameters.");
+    }
+    if (!frm_hdr->show_) {
+      return VP8SetError(dec, VP8_STATUS_UNSUPPORTED_FEATURE,
+                         "Frame not displayable.");
+    }
+    buf += 3;
+    buf_size -= 3;
+  }
+
+  pic_hdr = &dec->pic_hdr_;
+  if (frm_hdr->key_frame_) {
+    // Paragraph 9.2
+    if (buf_size < 7) {
+      return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA,
+                         "cannot parse picture header");
+    }
+    if (!VP8CheckSignature(buf, buf_size)) {
+      return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR,
+                         "Bad code word");
+    }
+    pic_hdr->width_ = ((buf[4] << 8) | buf[3]) & 0x3fff;
+    pic_hdr->xscale_ = buf[4] >> 6;   // ratio: 1, 5/4 5/3 or 2
+    pic_hdr->height_ = ((buf[6] << 8) | buf[5]) & 0x3fff;
+    pic_hdr->yscale_ = buf[6] >> 6;
+    buf += 7;
+    buf_size -= 7;
+
+    dec->mb_w_ = (pic_hdr->width_ + 15) >> 4;
+    dec->mb_h_ = (pic_hdr->height_ + 15) >> 4;
+
+    // Setup default output area (can be later modified during io->setup())
+    io->width = pic_hdr->width_;
+    io->height = pic_hdr->height_;
+    // IMPORTANT! use some sane dimensions in crop_* and scaled_* fields.
+    // So they can be used interchangeably without always testing for
+    // 'use_cropping'.
+    io->use_cropping = 0;
+    io->crop_top  = 0;
+    io->crop_left = 0;
+    io->crop_right  = io->width;
+    io->crop_bottom = io->height;
+    io->use_scaling  = 0;
+    io->scaled_width = io->width;
+    io->scaled_height = io->height;
+
+    io->mb_w = io->width;   // sanity check
+    io->mb_h = io->height;  // ditto
+
+    VP8ResetProba(&dec->proba_);
+    ResetSegmentHeader(&dec->segment_hdr_);
+  }
+
+  // Check if we have all the partition #0 available, and initialize dec->br_
+  // to read this partition (and this partition only).
+  if (frm_hdr->partition_length_ > buf_size) {
+    return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA,
+                       "bad partition length");
+  }
+
+  br = &dec->br_;
+  VP8InitBitReader(br, buf, frm_hdr->partition_length_);
+  buf += frm_hdr->partition_length_;
+  buf_size -= frm_hdr->partition_length_;
+
+  if (frm_hdr->key_frame_) {
+    pic_hdr->colorspace_ = VP8Get(br);
+    pic_hdr->clamp_type_ = VP8Get(br);
+  }
+  if (!ParseSegmentHeader(br, &dec->segment_hdr_, &dec->proba_)) {
+    return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR,
+                       "cannot parse segment header");
+  }
+  // Filter specs
+  if (!ParseFilterHeader(br, dec)) {
+    return VP8SetError(dec, VP8_STATUS_BITSTREAM_ERROR,
+                       "cannot parse filter header");
+  }
+  status = ParsePartitions(dec, buf, buf_size);
+  if (status != VP8_STATUS_OK) {
+    return VP8SetError(dec, status, "cannot parse partitions");
+  }
+
+  // quantizer change
+  VP8ParseQuant(dec);
+
+  // Frame buffer marking
+  if (!frm_hdr->key_frame_) {
+    return VP8SetError(dec, VP8_STATUS_UNSUPPORTED_FEATURE,
+                       "Not a key frame.");
+  }
+
+  VP8Get(br);   // ignore the value of update_proba_
+
+  VP8ParseProba(br, dec);
+
+  // sanitized state
+  dec->ready_ = 1;
+  return 1;
+}
+
+//------------------------------------------------------------------------------
+// Residual decoding (Paragraph 13.2 / 13.3)
+
+static const uint8_t kCat3[] = { 173, 148, 140, 0 };
+static const uint8_t kCat4[] = { 176, 155, 140, 135, 0 };
+static const uint8_t kCat5[] = { 180, 157, 141, 134, 130, 0 };
+static const uint8_t kCat6[] =
+  { 254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129, 0 };
+static const uint8_t* const kCat3456[] = { kCat3, kCat4, kCat5, kCat6 };
+static const uint8_t kZigzag[16] = {
+  0, 1, 4, 8,  5, 2, 3, 6,  9, 12, 13, 10,  7, 11, 14, 15
+};
+
+// See section 13-2: http://tools.ietf.org/html/rfc6386#section-13.2
+static int GetLargeValue(VP8BitReader* const br, const uint8_t* const p) {
+  int v;
+  if (!VP8GetBit(br, p[3])) {
+    if (!VP8GetBit(br, p[4])) {
+      v = 2;
+    } else {
+      v = 3 + VP8GetBit(br, p[5]);
+    }
+  } else {
+    if (!VP8GetBit(br, p[6])) {
+      if (!VP8GetBit(br, p[7])) {
+        v = 5 + VP8GetBit(br, 159);
+      } else {
+        v = 7 + 2 * VP8GetBit(br, 165);
+        v += VP8GetBit(br, 145);
+      }
+    } else {
+      const uint8_t* tab;
+      const int bit1 = VP8GetBit(br, p[8]);
+      const int bit0 = VP8GetBit(br, p[9 + bit1]);
+      const int cat = 2 * bit1 + bit0;
+      v = 0;
+      for (tab = kCat3456[cat]; *tab; ++tab) {
+        v += v + VP8GetBit(br, *tab);
+      }
+      v += 3 + (8 << cat);
+    }
+  }
+  return v;
+}
+
+// Returns the position of the last non-zero coeff plus one
+static int GetCoeffsFast(VP8BitReader* const br,
+                         const VP8BandProbas* const prob[],
+                         int ctx, const quant_t dq, int n, int16_t* out) {
+  const uint8_t* p = prob[n]->probas_[ctx];
+  for (; n < 16; ++n) {
+    if (!VP8GetBit(br, p[0])) {
+      return n;  // previous coeff was last non-zero coeff
+    }
+    while (!VP8GetBit(br, p[1])) {       // sequence of zero coeffs
+      p = prob[++n]->probas_[0];
+      if (n == 16) return 16;
+    }
+    {        // non zero coeff
+      const VP8ProbaArray* const p_ctx = &prob[n + 1]->probas_[0];
+      int v;
+      if (!VP8GetBit(br, p[2])) {
+        v = 1;
+        p = p_ctx[1];
+      } else {
+        v = GetLargeValue(br, p);
+        p = p_ctx[2];
+      }
+      out[kZigzag[n]] = VP8GetSigned(br, v) * dq[n > 0];
+    }
+  }
+  return 16;
+}
+
+// This version of GetCoeffs() uses VP8GetBitAlt() which is an alternate version
+// of VP8GetBitAlt() targeting specific platforms.
+static int GetCoeffsAlt(VP8BitReader* const br,
+                        const VP8BandProbas* const prob[],
+                        int ctx, const quant_t dq, int n, int16_t* out) {
+  const uint8_t* p = prob[n]->probas_[ctx];
+  for (; n < 16; ++n) {
+    if (!VP8GetBitAlt(br, p[0])) {
+      return n;  // previous coeff was last non-zero coeff
+    }
+    while (!VP8GetBitAlt(br, p[1])) {       // sequence of zero coeffs
+      p = prob[++n]->probas_[0];
+      if (n == 16) return 16;
+    }
+    {        // non zero coeff
+      const VP8ProbaArray* const p_ctx = &prob[n + 1]->probas_[0];
+      int v;
+      if (!VP8GetBitAlt(br, p[2])) {
+        v = 1;
+        p = p_ctx[1];
+      } else {
+        v = GetLargeValue(br, p);
+        p = p_ctx[2];
+      }
+      out[kZigzag[n]] = VP8GetSigned(br, v) * dq[n > 0];
+    }
+  }
+  return 16;
+}
+
+WEBP_TSAN_IGNORE_FUNCTION static void InitGetCoeffs(void) {
+  if (GetCoeffs == NULL) {
+    if (VP8GetCPUInfo != NULL && VP8GetCPUInfo(kSlowSSSE3)) {
+      GetCoeffs = GetCoeffsAlt;
+    } else {
+      GetCoeffs = GetCoeffsFast;
+    }
+  }
+}
+
+static WEBP_INLINE uint32_t NzCodeBits(uint32_t nz_coeffs, int nz, int dc_nz) {
+  nz_coeffs <<= 2;
+  nz_coeffs |= (nz > 3) ? 3 : (nz > 1) ? 2 : dc_nz;
+  return nz_coeffs;
+}
+
+static int ParseResiduals(VP8Decoder* const dec,
+                          VP8MB* const mb, VP8BitReader* const token_br) {
+  const VP8BandProbas* (* const bands)[16 + 1] = dec->proba_.bands_ptr_;
+  const VP8BandProbas* const * ac_proba;
+  VP8MBData* const block = dec->mb_data_ + dec->mb_x_;
+  const VP8QuantMatrix* const q = &dec->dqm_[block->segment_];
+  int16_t* dst = block->coeffs_;
+  VP8MB* const left_mb = dec->mb_info_ - 1;
+  uint8_t tnz, lnz;
+  uint32_t non_zero_y = 0;
+  uint32_t non_zero_uv = 0;
+  int x, y, ch;
+  uint32_t out_t_nz, out_l_nz;
+  int first;
+
+  memset(dst, 0, 384 * sizeof(*dst));
+  if (!block->is_i4x4_) {    // parse DC
+    int16_t dc[16] = { 0 };
+    const int ctx = mb->nz_dc_ + left_mb->nz_dc_;
+    const int nz = GetCoeffs(token_br, bands[1], ctx, q->y2_mat_, 0, dc);
+    mb->nz_dc_ = left_mb->nz_dc_ = (nz > 0);
+    if (nz > 1) {   // more than just the DC -> perform the full transform
+      VP8TransformWHT(dc, dst);
+    } else {        // only DC is non-zero -> inlined simplified transform
+      int i;
+      const int dc0 = (dc[0] + 3) >> 3;
+      for (i = 0; i < 16 * 16; i += 16) dst[i] = dc0;
+    }
+    first = 1;
+    ac_proba = bands[0];
+  } else {
+    first = 0;
+    ac_proba = bands[3];
+  }
+
+  tnz = mb->nz_ & 0x0f;
+  lnz = left_mb->nz_ & 0x0f;
+  for (y = 0; y < 4; ++y) {
+    int l = lnz & 1;
+    uint32_t nz_coeffs = 0;
+    for (x = 0; x < 4; ++x) {
+      const int ctx = l + (tnz & 1);
+      const int nz = GetCoeffs(token_br, ac_proba, ctx, q->y1_mat_, first, dst);
+      l = (nz > first);
+      tnz = (tnz >> 1) | (l << 7);
+      nz_coeffs = NzCodeBits(nz_coeffs, nz, dst[0] != 0);
+      dst += 16;
+    }
+    tnz >>= 4;
+    lnz = (lnz >> 1) | (l << 7);
+    non_zero_y = (non_zero_y << 8) | nz_coeffs;
+  }
+  out_t_nz = tnz;
+  out_l_nz = lnz >> 4;
+
+  for (ch = 0; ch < 4; ch += 2) {
+    uint32_t nz_coeffs = 0;
+    tnz = mb->nz_ >> (4 + ch);
+    lnz = left_mb->nz_ >> (4 + ch);
+    for (y = 0; y < 2; ++y) {
+      int l = lnz & 1;
+      for (x = 0; x < 2; ++x) {
+        const int ctx = l + (tnz & 1);
+        const int nz = GetCoeffs(token_br, bands[2], ctx, q->uv_mat_, 0, dst);
+        l = (nz > 0);
+        tnz = (tnz >> 1) | (l << 3);
+        nz_coeffs = NzCodeBits(nz_coeffs, nz, dst[0] != 0);
+        dst += 16;
+      }
+      tnz >>= 2;
+      lnz = (lnz >> 1) | (l << 5);
+    }
+    // Note: we don't really need the per-4x4 details for U/V blocks.
+    non_zero_uv |= nz_coeffs << (4 * ch);
+    out_t_nz |= (tnz << 4) << ch;
+    out_l_nz |= (lnz & 0xf0) << ch;
+  }
+  mb->nz_ = out_t_nz;
+  left_mb->nz_ = out_l_nz;
+
+  block->non_zero_y_ = non_zero_y;
+  block->non_zero_uv_ = non_zero_uv;
+
+  // We look at the mode-code of each block and check if some blocks have less
+  // than three non-zero coeffs (code < 2). This is to avoid dithering flat and
+  // empty blocks.
+  block->dither_ = (non_zero_uv & 0xaaaa) ? 0 : q->dither_;
+
+  return !(non_zero_y | non_zero_uv);  // will be used for further optimization
+}
+
+//------------------------------------------------------------------------------
+// Main loop
+
+int VP8DecodeMB(VP8Decoder* const dec, VP8BitReader* const token_br) {
+  VP8MB* const left = dec->mb_info_ - 1;
+  VP8MB* const mb = dec->mb_info_ + dec->mb_x_;
+  VP8MBData* const block = dec->mb_data_ + dec->mb_x_;
+  int skip = dec->use_skip_proba_ ? block->skip_ : 0;
+
+  if (!skip) {
+    skip = ParseResiduals(dec, mb, token_br);
+  } else {
+    left->nz_ = mb->nz_ = 0;
+    if (!block->is_i4x4_) {
+      left->nz_dc_ = mb->nz_dc_ = 0;
+    }
+    block->non_zero_y_ = 0;
+    block->non_zero_uv_ = 0;
+    block->dither_ = 0;
+  }
+
+  if (dec->filter_type_ > 0) {  // store filter info
+    VP8FInfo* const finfo = dec->f_info_ + dec->mb_x_;
+    *finfo = dec->fstrengths_[block->segment_][block->is_i4x4_];
+    finfo->f_inner_ |= !skip;
+  }
+
+  return !token_br->eof_;
+}
+
+void VP8InitScanline(VP8Decoder* const dec) {
+  VP8MB* const left = dec->mb_info_ - 1;
+  left->nz_ = 0;
+  left->nz_dc_ = 0;
+  memset(dec->intra_l_, B_DC_PRED, sizeof(dec->intra_l_));
+  dec->mb_x_ = 0;
+}
+
+static int ParseFrame(VP8Decoder* const dec, VP8Io* io) {
+  for (dec->mb_y_ = 0; dec->mb_y_ < dec->br_mb_y_; ++dec->mb_y_) {
+    // Parse bitstream for this row.
+    VP8BitReader* const token_br =
+        &dec->parts_[dec->mb_y_ & dec->num_parts_minus_one_];
+    if (!VP8ParseIntraModeRow(&dec->br_, dec)) {
+      return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA,
+                         "Premature end-of-partition0 encountered.");
+    }
+    for (; dec->mb_x_ < dec->mb_w_; ++dec->mb_x_) {
+      if (!VP8DecodeMB(dec, token_br)) {
+        return VP8SetError(dec, VP8_STATUS_NOT_ENOUGH_DATA,
+                           "Premature end-of-file encountered.");
+      }
+    }
+    VP8InitScanline(dec);   // Prepare for next scanline
+
+    // Reconstruct, filter and emit the row.
+    if (!VP8ProcessRow(dec, io)) {
+      return VP8SetError(dec, VP8_STATUS_USER_ABORT, "Output aborted.");
+    }
+  }
+  if (dec->mt_method_ > 0) {
+    if (!WebPGetWorkerInterface()->Sync(&dec->worker_)) return 0;
+  }
+
+  return 1;
+}
+
+// Main entry point
+int VP8Decode(VP8Decoder* const dec, VP8Io* const io) {
+  int ok = 0;
+  if (dec == NULL) {
+    return 0;
+  }
+  if (io == NULL) {
+    return VP8SetError(dec, VP8_STATUS_INVALID_PARAM,
+                       "NULL VP8Io parameter in VP8Decode().");
+  }
+
+  if (!dec->ready_) {
+    if (!VP8GetHeaders(dec, io)) {
+      return 0;
+    }
+  }
+  assert(dec->ready_);
+
+  // Finish setting up the decoding parameter. Will call io->setup().
+  ok = (VP8EnterCritical(dec, io) == VP8_STATUS_OK);
+  if (ok) {   // good to go.
+    // Will allocate memory and prepare everything.
+    if (ok) ok = VP8InitFrame(dec, io);
+
+    // Main decoding loop
+    if (ok) ok = ParseFrame(dec, io);
+
+    // Exit.
+    ok &= VP8ExitCritical(dec, io);
+  }
+
+  if (!ok) {
+    VP8Clear(dec);
+    return 0;
+  }
+
+  dec->ready_ = 0;
+  return ok;
+}
+
+void VP8Clear(VP8Decoder* const dec) {
+  if (dec == NULL) {
+    return;
+  }
+  WebPGetWorkerInterface()->End(&dec->worker_);
+  WebPDeallocateAlphaMemory(dec);
+  WebPSafeFree(dec->mem_);
+  dec->mem_ = NULL;
+  dec->mem_size_ = 0;
+  memset(&dec->br_, 0, sizeof(dec->br_));
+  dec->ready_ = 0;
+}
+
+//------------------------------------------------------------------------------

+ 185 - 0
Source/ThirdParty/WebP/src/dec/vp8_dec.h

@@ -0,0 +1,185 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+//  Low-level API for VP8 decoder
+//
+// Author: Skal ([email protected])
+
+#ifndef WEBP_WEBP_DECODE_VP8_H_
+#define WEBP_WEBP_DECODE_VP8_H_
+
+#include "../webp/decode.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//------------------------------------------------------------------------------
+// Lower-level API
+//
+// These functions provide fine-grained control of the decoding process.
+// The call flow should resemble:
+//
+//   VP8Io io;
+//   VP8InitIo(&io);
+//   io.data = data;
+//   io.data_size = size;
+//   /* customize io's functions (setup()/put()/teardown()) if needed. */
+//
+//   VP8Decoder* dec = VP8New();
+//   int ok = VP8Decode(dec, &io);
+//   if (!ok) printf("Error: %s\n", VP8StatusMessage(dec));
+//   VP8Delete(dec);
+//   return ok;
+
+// Input / Output
+typedef struct VP8Io VP8Io;
+typedef int (*VP8IoPutHook)(const VP8Io* io);
+typedef int (*VP8IoSetupHook)(VP8Io* io);
+typedef void (*VP8IoTeardownHook)(const VP8Io* io);
+
+struct VP8Io {
+  // set by VP8GetHeaders()
+  int width, height;         // picture dimensions, in pixels (invariable).
+                             // These are the original, uncropped dimensions.
+                             // The actual area passed to put() is stored
+                             // in mb_w / mb_h fields.
+
+  // set before calling put()
+  int mb_y;                  // position of the current rows (in pixels)
+  int mb_w;                  // number of columns in the sample
+  int mb_h;                  // number of rows in the sample
+  const uint8_t* y, *u, *v;  // rows to copy (in yuv420 format)
+  int y_stride;              // row stride for luma
+  int uv_stride;             // row stride for chroma
+
+  void* opaque;              // user data
+
+  // called when fresh samples are available. Currently, samples are in
+  // YUV420 format, and can be up to width x 24 in size (depending on the
+  // in-loop filtering level, e.g.). Should return false in case of error
+  // or abort request. The actual size of the area to update is mb_w x mb_h
+  // in size, taking cropping into account.
+  VP8IoPutHook put;
+
+  // called just before starting to decode the blocks.
+  // Must return false in case of setup error, true otherwise. If false is
+  // returned, teardown() will NOT be called. But if the setup succeeded
+  // and true is returned, then teardown() will always be called afterward.
+  VP8IoSetupHook setup;
+
+  // Called just after block decoding is finished (or when an error occurred
+  // during put()). Is NOT called if setup() failed.
+  VP8IoTeardownHook teardown;
+
+  // this is a recommendation for the user-side yuv->rgb converter. This flag
+  // is set when calling setup() hook and can be overwritten by it. It then
+  // can be taken into consideration during the put() method.
+  int fancy_upsampling;
+
+  // Input buffer.
+  size_t data_size;
+  const uint8_t* data;
+
+  // If true, in-loop filtering will not be performed even if present in the
+  // bitstream. Switching off filtering may speed up decoding at the expense
+  // of more visible blocking. Note that output will also be non-compliant
+  // with the VP8 specifications.
+  int bypass_filtering;
+
+  // Cropping parameters.
+  int use_cropping;
+  int crop_left, crop_right, crop_top, crop_bottom;
+
+  // Scaling parameters.
+  int use_scaling;
+  int scaled_width, scaled_height;
+
+  // If non NULL, pointer to the alpha data (if present) corresponding to the
+  // start of the current row (That is: it is pre-offset by mb_y and takes
+  // cropping into account).
+  const uint8_t* a;
+};
+
+// Internal, version-checked, entry point
+int VP8InitIoInternal(VP8Io* const, int);
+
+// Set the custom IO function pointers and user-data. The setter for IO hooks
+// should be called before initiating incremental decoding. Returns true if
+// WebPIDecoder object is successfully modified, false otherwise.
+int WebPISetIOHooks(WebPIDecoder* const idec,
+                    VP8IoPutHook put,
+                    VP8IoSetupHook setup,
+                    VP8IoTeardownHook teardown,
+                    void* user_data);
+
+// Main decoding object. This is an opaque structure.
+typedef struct VP8Decoder VP8Decoder;
+
+// Create a new decoder object.
+VP8Decoder* VP8New(void);
+
+// Must be called to make sure 'io' is initialized properly.
+// Returns false in case of version mismatch. Upon such failure, no other
+// decoding function should be called (VP8Decode, VP8GetHeaders, ...)
+static WEBP_INLINE int VP8InitIo(VP8Io* const io) {
+  return VP8InitIoInternal(io, WEBP_DECODER_ABI_VERSION);
+}
+
+// Decode the VP8 frame header. Returns true if ok.
+// Note: 'io->data' must be pointing to the start of the VP8 frame header.
+int VP8GetHeaders(VP8Decoder* const dec, VP8Io* const io);
+
+// Decode a picture. Will call VP8GetHeaders() if it wasn't done already.
+// Returns false in case of error.
+int VP8Decode(VP8Decoder* const dec, VP8Io* const io);
+
+// Return current status of the decoder:
+VP8StatusCode VP8Status(VP8Decoder* const dec);
+
+// return readable string corresponding to the last status.
+const char* VP8StatusMessage(VP8Decoder* const dec);
+
+// Resets the decoder in its initial state, reclaiming memory.
+// Not a mandatory call between calls to VP8Decode().
+void VP8Clear(VP8Decoder* const dec);
+
+// Destroy the decoder object.
+void VP8Delete(VP8Decoder* const dec);
+
+//------------------------------------------------------------------------------
+// Miscellaneous VP8/VP8L bitstream probing functions.
+
+// Returns true if the next 3 bytes in data contain the VP8 signature.
+WEBP_EXTERN(int) VP8CheckSignature(const uint8_t* const data, size_t data_size);
+
+// Validates the VP8 data-header and retrieves basic header information viz
+// width and height. Returns 0 in case of formatting error. *width/*height
+// can be passed NULL.
+WEBP_EXTERN(int) VP8GetInfo(
+    const uint8_t* data,
+    size_t data_size,    // data available so far
+    size_t chunk_size,   // total data size expected in the chunk
+    int* const width, int* const height);
+
+// Returns true if the next byte(s) in data is a VP8L signature.
+WEBP_EXTERN(int) VP8LCheckSignature(const uint8_t* const data, size_t size);
+
+// Validates the VP8L data-header and retrieves basic header information viz
+// width, height and alpha. Returns 0 in case of formatting error.
+// width/height/has_alpha can be passed NULL.
+WEBP_EXTERN(int) VP8LGetInfo(
+    const uint8_t* data, size_t data_size,  // data available so far
+    int* const width, int* const height, int* const has_alpha);
+
+#ifdef __cplusplus
+}    // extern "C"
+#endif
+
+#endif  /* WEBP_WEBP_DECODE_VP8_H_ */

+ 320 - 0
Source/ThirdParty/WebP/src/dec/vp8i_dec.h

@@ -0,0 +1,320 @@
+// Copyright 2010 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// VP8 decoder: internal header.
+//
+// Author: Skal ([email protected])
+
+#ifndef WEBP_DEC_VP8I_H_
+#define WEBP_DEC_VP8I_H_
+
+#include <string.h>     // for memcpy()
+#include "./common_dec.h"
+#include "./vp8li_dec.h"
+#include "../utils/bit_reader_utils.h"
+#include "../utils/random_utils.h"
+#include "../utils/thread_utils.h"
+#include "../dsp/dsp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//------------------------------------------------------------------------------
+// Various defines and enums
+
+// version numbers
+#define DEC_MAJ_VERSION 0
+#define DEC_MIN_VERSION 6
+#define DEC_REV_VERSION 0
+
+// YUV-cache parameters. Cache is 32-bytes wide (= one cacheline).
+// Constraints are: We need to store one 16x16 block of luma samples (y),
+// and two 8x8 chroma blocks (u/v). These are better be 16-bytes aligned,
+// in order to be SIMD-friendly. We also need to store the top, left and
+// top-left samples (from previously decoded blocks), along with four
+// extra top-right samples for luma (intra4x4 prediction only).
+// One possible layout is, using 32 * (17 + 9) bytes:
+//
+//   .+------   <- only 1 pixel high
+//   .|yyyyt.
+//   .|yyyyt.
+//   .|yyyyt.
+//   .|yyyy..
+//   .+--.+--   <- only 1 pixel high
+//   .|uu.|vv
+//   .|uu.|vv
+//
+// Every character is a 4x4 block, with legend:
+//  '.' = unused
+//  'y' = y-samples   'u' = u-samples     'v' = u-samples
+//  '|' = left sample,   '-' = top sample,    '+' = top-left sample
+//  't' = extra top-right sample for 4x4 modes
+#define YUV_SIZE (BPS * 17 + BPS * 9)
+#define Y_SIZE   (BPS * 17)
+#define Y_OFF    (BPS * 1 + 8)
+#define U_OFF    (Y_OFF + BPS * 16 + BPS)
+#define V_OFF    (U_OFF + 16)
+
+// minimal width under which lossy multi-threading is always disabled
+#define MIN_WIDTH_FOR_THREADS 512
+
+//------------------------------------------------------------------------------
+// Headers
+
+typedef struct {
+  uint8_t key_frame_;
+  uint8_t profile_;
+  uint8_t show_;
+  uint32_t partition_length_;
+} VP8FrameHeader;
+
+typedef struct {
+  uint16_t width_;
+  uint16_t height_;
+  uint8_t xscale_;
+  uint8_t yscale_;
+  uint8_t colorspace_;   // 0 = YCbCr
+  uint8_t clamp_type_;
+} VP8PictureHeader;
+
+// segment features
+typedef struct {
+  int use_segment_;
+  int update_map_;        // whether to update the segment map or not
+  int absolute_delta_;    // absolute or delta values for quantizer and filter
+  int8_t quantizer_[NUM_MB_SEGMENTS];        // quantization changes
+  int8_t filter_strength_[NUM_MB_SEGMENTS];  // filter strength for segments
+} VP8SegmentHeader;
+
+// probas associated to one of the contexts
+typedef uint8_t VP8ProbaArray[NUM_PROBAS];
+
+typedef struct {   // all the probas associated to one band
+  VP8ProbaArray probas_[NUM_CTX];
+} VP8BandProbas;
+
+// Struct collecting all frame-persistent probabilities.
+typedef struct {
+  uint8_t segments_[MB_FEATURE_TREE_PROBS];
+  // Type: 0:Intra16-AC  1:Intra16-DC   2:Chroma   3:Intra4
+  VP8BandProbas bands_[NUM_TYPES][NUM_BANDS];
+  const VP8BandProbas* bands_ptr_[NUM_TYPES][16 + 1];
+} VP8Proba;
+
+// Filter parameters
+typedef struct {
+  int simple_;                  // 0=complex, 1=simple
+  int level_;                   // [0..63]
+  int sharpness_;               // [0..7]
+  int use_lf_delta_;
+  int ref_lf_delta_[NUM_REF_LF_DELTAS];
+  int mode_lf_delta_[NUM_MODE_LF_DELTAS];
+} VP8FilterHeader;
+
+//------------------------------------------------------------------------------
+// Informations about the macroblocks.
+
+typedef struct {  // filter specs
+  uint8_t f_limit_;      // filter limit in [3..189], or 0 if no filtering
+  uint8_t f_ilevel_;     // inner limit in [1..63]
+  uint8_t f_inner_;      // do inner filtering?
+  uint8_t hev_thresh_;   // high edge variance threshold in [0..2]
+} VP8FInfo;
+
+typedef struct {  // Top/Left Contexts used for syntax-parsing
+  uint8_t nz_;        // non-zero AC/DC coeffs (4bit for luma + 4bit for chroma)
+  uint8_t nz_dc_;     // non-zero DC coeff (1bit)
+} VP8MB;
+
+// Dequantization matrices
+typedef int quant_t[2];      // [DC / AC].  Can be 'uint16_t[2]' too (~slower).
+typedef struct {
+  quant_t y1_mat_, y2_mat_, uv_mat_;
+
+  int uv_quant_;   // U/V quantizer value
+  int dither_;     // dithering amplitude (0 = off, max=255)
+} VP8QuantMatrix;
+
+// Data needed to reconstruct a macroblock
+typedef struct {
+  int16_t coeffs_[384];   // 384 coeffs = (16+4+4) * 4*4
+  uint8_t is_i4x4_;       // true if intra4x4
+  uint8_t imodes_[16];    // one 16x16 mode (#0) or sixteen 4x4 modes
+  uint8_t uvmode_;        // chroma prediction mode
+  // bit-wise info about the content of each sub-4x4 blocks (in decoding order).
+  // Each of the 4x4 blocks for y/u/v is associated with a 2b code according to:
+  //   code=0 -> no coefficient
+  //   code=1 -> only DC
+  //   code=2 -> first three coefficients are non-zero
+  //   code=3 -> more than three coefficients are non-zero
+  // This allows to call specialized transform functions.
+  uint32_t non_zero_y_;
+  uint32_t non_zero_uv_;
+  uint8_t dither_;      // local dithering strength (deduced from non_zero_*)
+  uint8_t skip_;
+  uint8_t segment_;
+} VP8MBData;
+
+// Persistent information needed by the parallel processing
+typedef struct {
+  int id_;              // cache row to process (in [0..2])
+  int mb_y_;            // macroblock position of the row
+  int filter_row_;      // true if row-filtering is needed
+  VP8FInfo* f_info_;    // filter strengths (swapped with dec->f_info_)
+  VP8MBData* mb_data_;  // reconstruction data (swapped with dec->mb_data_)
+  VP8Io io_;            // copy of the VP8Io to pass to put()
+} VP8ThreadContext;
+
+// Saved top samples, per macroblock. Fits into a cache-line.
+typedef struct {
+  uint8_t y[16], u[8], v[8];
+} VP8TopSamples;
+
+//------------------------------------------------------------------------------
+// VP8Decoder: the main opaque structure handed over to user
+
+struct VP8Decoder {
+  VP8StatusCode status_;
+  int ready_;     // true if ready to decode a picture with VP8Decode()
+  const char* error_msg_;  // set when status_ is not OK.
+
+  // Main data source
+  VP8BitReader br_;
+
+  // headers
+  VP8FrameHeader   frm_hdr_;
+  VP8PictureHeader pic_hdr_;
+  VP8FilterHeader  filter_hdr_;
+  VP8SegmentHeader segment_hdr_;
+
+  // Worker
+  WebPWorker worker_;
+  int mt_method_;      // multi-thread method: 0=off, 1=[parse+recon][filter]
+                       // 2=[parse][recon+filter]
+  int cache_id_;       // current cache row
+  int num_caches_;     // number of cached rows of 16 pixels (1, 2 or 3)
+  VP8ThreadContext thread_ctx_;  // Thread context
+
+  // dimension, in macroblock units.
+  int mb_w_, mb_h_;
+
+  // Macroblock to process/filter, depending on cropping and filter_type.
+  int tl_mb_x_, tl_mb_y_;  // top-left MB that must be in-loop filtered
+  int br_mb_x_, br_mb_y_;  // last bottom-right MB that must be decoded
+
+  // number of partitions minus one.
+  uint32_t num_parts_minus_one_;
+  // per-partition boolean decoders.
+  VP8BitReader parts_[MAX_NUM_PARTITIONS];
+
+  // Dithering strength, deduced from decoding options
+  int dither_;                // whether to use dithering or not
+  VP8Random dithering_rg_;    // random generator for dithering
+
+  // dequantization (one set of DC/AC dequant factor per segment)
+  VP8QuantMatrix dqm_[NUM_MB_SEGMENTS];
+
+  // probabilities
+  VP8Proba proba_;
+  int use_skip_proba_;
+  uint8_t skip_p_;
+
+  // Boundary data cache and persistent buffers.
+  uint8_t* intra_t_;      // top intra modes values: 4 * mb_w_
+  uint8_t  intra_l_[4];   // left intra modes values
+
+  VP8TopSamples* yuv_t_;  // top y/u/v samples
+
+  VP8MB* mb_info_;        // contextual macroblock info (mb_w_ + 1)
+  VP8FInfo* f_info_;      // filter strength info
+  uint8_t* yuv_b_;        // main block for Y/U/V (size = YUV_SIZE)
+
+  uint8_t* cache_y_;      // macroblock row for storing unfiltered samples
+  uint8_t* cache_u_;
+  uint8_t* cache_v_;
+  int cache_y_stride_;
+  int cache_uv_stride_;
+
+  // main memory chunk for the above data. Persistent.
+  void* mem_;
+  size_t mem_size_;
+
+  // Per macroblock non-persistent infos.
+  int mb_x_, mb_y_;       // current position, in macroblock units
+  VP8MBData* mb_data_;    // parsed reconstruction data
+
+  // Filtering side-info
+  int filter_type_;                          // 0=off, 1=simple, 2=complex
+  VP8FInfo fstrengths_[NUM_MB_SEGMENTS][2];  // precalculated per-segment/type
+
+  // Alpha
+  struct ALPHDecoder* alph_dec_;  // alpha-plane decoder object
+  const uint8_t* alpha_data_;     // compressed alpha data (if present)
+  size_t alpha_data_size_;
+  int is_alpha_decoded_;      // true if alpha_data_ is decoded in alpha_plane_
+  uint8_t* alpha_plane_mem_;  // memory allocated for alpha_plane_
+  uint8_t* alpha_plane_;      // output. Persistent, contains the whole data.
+  const uint8_t* alpha_prev_line_;  // last decoded alpha row (or NULL)
+  int alpha_dithering_;       // derived from decoding options (0=off, 100=full)
+};
+
+//------------------------------------------------------------------------------
+// internal functions. Not public.
+
+// in vp8.c
+int VP8SetError(VP8Decoder* const dec,
+                VP8StatusCode error, const char* const msg);
+
+// in tree.c
+void VP8ResetProba(VP8Proba* const proba);
+void VP8ParseProba(VP8BitReader* const br, VP8Decoder* const dec);
+// parses one row of intra mode data in partition 0, returns !eof
+int VP8ParseIntraModeRow(VP8BitReader* const br, VP8Decoder* const dec);
+
+// in quant.c
+void VP8ParseQuant(VP8Decoder* const dec);
+
+// in frame.c
+int VP8InitFrame(VP8Decoder* const dec, VP8Io* const io);
+// Call io->setup() and finish setting up scan parameters.
+// After this call returns, one must always call VP8ExitCritical() with the
+// same parameters. Both functions should be used in pair. Returns VP8_STATUS_OK
+// if ok, otherwise sets and returns the error status on *dec.
+VP8StatusCode VP8EnterCritical(VP8Decoder* const dec, VP8Io* const io);
+// Must always be called in pair with VP8EnterCritical().
+// Returns false in case of error.
+int VP8ExitCritical(VP8Decoder* const dec, VP8Io* const io);
+// Return the multi-threading method to use (0=off), depending
+// on options and bitstream size. Only for lossy decoding.
+int VP8GetThreadMethod(const WebPDecoderOptions* const options,
+                       const WebPHeaderStructure* const headers,
+                       int width, int height);
+// Initialize dithering post-process if needed.
+void VP8InitDithering(const WebPDecoderOptions* const options,
+                      VP8Decoder* const dec);
+// Process the last decoded row (filtering + output).
+int VP8ProcessRow(VP8Decoder* const dec, VP8Io* const io);
+// To be called at the start of a new scanline, to initialize predictors.
+void VP8InitScanline(VP8Decoder* const dec);
+// Decode one macroblock. Returns false if there is not enough data.
+int VP8DecodeMB(VP8Decoder* const dec, VP8BitReader* const token_br);
+
+// in alpha.c
+const uint8_t* VP8DecompressAlphaRows(VP8Decoder* const dec,
+                                      const VP8Io* const io,
+                                      int row, int num_rows);
+
+//------------------------------------------------------------------------------
+
+#ifdef __cplusplus
+}    // extern "C"
+#endif
+
+#endif  /* WEBP_DEC_VP8I_H_ */

+ 1673 - 0
Source/ThirdParty/WebP/src/dec/vp8l_dec.c

@@ -0,0 +1,1673 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// main entry for the decoder
+//
+// Authors: Vikas Arora ([email protected])
+//          Jyrki Alakuijala ([email protected])
+
+#include <stdlib.h>
+
+#include "./alphai_dec.h"
+#include "./vp8li_dec.h"
+#include "../dsp/dsp.h"
+#include "../dsp/lossless.h"
+#include "../dsp/lossless_common.h"
+#include "../dsp/yuv.h"
+#include "../utils/endian_inl_utils.h"
+#include "../utils/huffman_utils.h"
+#include "../utils/utils.h"
+
+#define NUM_ARGB_CACHE_ROWS          16
+
+static const int kCodeLengthLiterals = 16;
+static const int kCodeLengthRepeatCode = 16;
+static const int kCodeLengthExtraBits[3] = { 2, 3, 7 };
+static const int kCodeLengthRepeatOffsets[3] = { 3, 3, 11 };
+
+// -----------------------------------------------------------------------------
+//  Five Huffman codes are used at each meta code:
+//  1. green + length prefix codes + color cache codes,
+//  2. alpha,
+//  3. red,
+//  4. blue, and,
+//  5. distance prefix codes.
+typedef enum {
+  GREEN = 0,
+  RED   = 1,
+  BLUE  = 2,
+  ALPHA = 3,
+  DIST  = 4
+} HuffIndex;
+
+static const uint16_t kAlphabetSize[HUFFMAN_CODES_PER_META_CODE] = {
+  NUM_LITERAL_CODES + NUM_LENGTH_CODES,
+  NUM_LITERAL_CODES, NUM_LITERAL_CODES, NUM_LITERAL_CODES,
+  NUM_DISTANCE_CODES
+};
+
+static const uint8_t kLiteralMap[HUFFMAN_CODES_PER_META_CODE] = {
+  0, 1, 1, 1, 0
+};
+
+#define NUM_CODE_LENGTH_CODES       19
+static const uint8_t kCodeLengthCodeOrder[NUM_CODE_LENGTH_CODES] = {
+  17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
+};
+
+#define CODE_TO_PLANE_CODES        120
+static const uint8_t kCodeToPlane[CODE_TO_PLANE_CODES] = {
+  0x18, 0x07, 0x17, 0x19, 0x28, 0x06, 0x27, 0x29, 0x16, 0x1a,
+  0x26, 0x2a, 0x38, 0x05, 0x37, 0x39, 0x15, 0x1b, 0x36, 0x3a,
+  0x25, 0x2b, 0x48, 0x04, 0x47, 0x49, 0x14, 0x1c, 0x35, 0x3b,
+  0x46, 0x4a, 0x24, 0x2c, 0x58, 0x45, 0x4b, 0x34, 0x3c, 0x03,
+  0x57, 0x59, 0x13, 0x1d, 0x56, 0x5a, 0x23, 0x2d, 0x44, 0x4c,
+  0x55, 0x5b, 0x33, 0x3d, 0x68, 0x02, 0x67, 0x69, 0x12, 0x1e,
+  0x66, 0x6a, 0x22, 0x2e, 0x54, 0x5c, 0x43, 0x4d, 0x65, 0x6b,
+  0x32, 0x3e, 0x78, 0x01, 0x77, 0x79, 0x53, 0x5d, 0x11, 0x1f,
+  0x64, 0x6c, 0x42, 0x4e, 0x76, 0x7a, 0x21, 0x2f, 0x75, 0x7b,
+  0x31, 0x3f, 0x63, 0x6d, 0x52, 0x5e, 0x00, 0x74, 0x7c, 0x41,
+  0x4f, 0x10, 0x20, 0x62, 0x6e, 0x30, 0x73, 0x7d, 0x51, 0x5f,
+  0x40, 0x72, 0x7e, 0x61, 0x6f, 0x50, 0x71, 0x7f, 0x60, 0x70
+};
+
+// Memory needed for lookup tables of one Huffman tree group. Red, blue, alpha
+// and distance alphabets are constant (256 for red, blue and alpha, 40 for
+// distance) and lookup table sizes for them in worst case are 630 and 410
+// respectively. Size of green alphabet depends on color cache size and is equal
+// to 256 (green component values) + 24 (length prefix values)
+// + color_cache_size (between 0 and 2048).
+// All values computed for 8-bit first level lookup with Mark Adler's tool:
+// http://www.hdfgroup.org/ftp/lib-external/zlib/zlib-1.2.5/examples/enough.c
+#define FIXED_TABLE_SIZE (630 * 3 + 410)
+static const int kTableSize[12] = {
+  FIXED_TABLE_SIZE + 654,
+  FIXED_TABLE_SIZE + 656,
+  FIXED_TABLE_SIZE + 658,
+  FIXED_TABLE_SIZE + 662,
+  FIXED_TABLE_SIZE + 670,
+  FIXED_TABLE_SIZE + 686,
+  FIXED_TABLE_SIZE + 718,
+  FIXED_TABLE_SIZE + 782,
+  FIXED_TABLE_SIZE + 912,
+  FIXED_TABLE_SIZE + 1168,
+  FIXED_TABLE_SIZE + 1680,
+  FIXED_TABLE_SIZE + 2704
+};
+
+static int DecodeImageStream(int xsize, int ysize,
+                             int is_level0,
+                             VP8LDecoder* const dec,
+                             uint32_t** const decoded_data);
+
+//------------------------------------------------------------------------------
+
+int VP8LCheckSignature(const uint8_t* const data, size_t size) {
+  return (size >= VP8L_FRAME_HEADER_SIZE &&
+          data[0] == VP8L_MAGIC_BYTE &&
+          (data[4] >> 5) == 0);  // version
+}
+
+static int ReadImageInfo(VP8LBitReader* const br,
+                         int* const width, int* const height,
+                         int* const has_alpha) {
+  if (VP8LReadBits(br, 8) != VP8L_MAGIC_BYTE) return 0;
+  *width = VP8LReadBits(br, VP8L_IMAGE_SIZE_BITS) + 1;
+  *height = VP8LReadBits(br, VP8L_IMAGE_SIZE_BITS) + 1;
+  *has_alpha = VP8LReadBits(br, 1);
+  if (VP8LReadBits(br, VP8L_VERSION_BITS) != 0) return 0;
+  return !br->eos_;
+}
+
+int VP8LGetInfo(const uint8_t* data, size_t data_size,
+                int* const width, int* const height, int* const has_alpha) {
+  if (data == NULL || data_size < VP8L_FRAME_HEADER_SIZE) {
+    return 0;         // not enough data
+  } else if (!VP8LCheckSignature(data, data_size)) {
+    return 0;         // bad signature
+  } else {
+    int w, h, a;
+    VP8LBitReader br;
+    VP8LInitBitReader(&br, data, data_size);
+    if (!ReadImageInfo(&br, &w, &h, &a)) {
+      return 0;
+    }
+    if (width != NULL) *width = w;
+    if (height != NULL) *height = h;
+    if (has_alpha != NULL) *has_alpha = a;
+    return 1;
+  }
+}
+
+//------------------------------------------------------------------------------
+
+static WEBP_INLINE int GetCopyDistance(int distance_symbol,
+                                       VP8LBitReader* const br) {
+  int extra_bits, offset;
+  if (distance_symbol < 4) {
+    return distance_symbol + 1;
+  }
+  extra_bits = (distance_symbol - 2) >> 1;
+  offset = (2 + (distance_symbol & 1)) << extra_bits;
+  return offset + VP8LReadBits(br, extra_bits) + 1;
+}
+
+static WEBP_INLINE int GetCopyLength(int length_symbol,
+                                     VP8LBitReader* const br) {
+  // Length and distance prefixes are encoded the same way.
+  return GetCopyDistance(length_symbol, br);
+}
+
+static WEBP_INLINE int PlaneCodeToDistance(int xsize, int plane_code) {
+  if (plane_code > CODE_TO_PLANE_CODES) {
+    return plane_code - CODE_TO_PLANE_CODES;
+  } else {
+    const int dist_code = kCodeToPlane[plane_code - 1];
+    const int yoffset = dist_code >> 4;
+    const int xoffset = 8 - (dist_code & 0xf);
+    const int dist = yoffset * xsize + xoffset;
+    return (dist >= 1) ? dist : 1;  // dist<1 can happen if xsize is very small
+  }
+}
+
+//------------------------------------------------------------------------------
+// Decodes the next Huffman code from bit-stream.
+// FillBitWindow(br) needs to be called at minimum every second call
+// to ReadSymbol, in order to pre-fetch enough bits.
+static WEBP_INLINE int ReadSymbol(const HuffmanCode* table,
+                                  VP8LBitReader* const br) {
+  int nbits;
+  uint32_t val = VP8LPrefetchBits(br);
+  table += val & HUFFMAN_TABLE_MASK;
+  nbits = table->bits - HUFFMAN_TABLE_BITS;
+  if (nbits > 0) {
+    VP8LSetBitPos(br, br->bit_pos_ + HUFFMAN_TABLE_BITS);
+    val = VP8LPrefetchBits(br);
+    table += table->value;
+    table += val & ((1 << nbits) - 1);
+  }
+  VP8LSetBitPos(br, br->bit_pos_ + table->bits);
+  return table->value;
+}
+
+// Reads packed symbol depending on GREEN channel
+#define BITS_SPECIAL_MARKER 0x100  // something large enough (and a bit-mask)
+#define PACKED_NON_LITERAL_CODE 0  // must be < NUM_LITERAL_CODES
+static WEBP_INLINE int ReadPackedSymbols(const HTreeGroup* group,
+                                         VP8LBitReader* const br,
+                                         uint32_t* const dst) {
+  const uint32_t val = VP8LPrefetchBits(br) & (HUFFMAN_PACKED_TABLE_SIZE - 1);
+  const HuffmanCode32 code = group->packed_table[val];
+  assert(group->use_packed_table);
+  if (code.bits < BITS_SPECIAL_MARKER) {
+    VP8LSetBitPos(br, br->bit_pos_ + code.bits);
+    *dst = code.value;
+    return PACKED_NON_LITERAL_CODE;
+  } else {
+    VP8LSetBitPos(br, br->bit_pos_ + code.bits - BITS_SPECIAL_MARKER);
+    assert(code.value >= NUM_LITERAL_CODES);
+    return code.value;
+  }
+}
+
+static int AccumulateHCode(HuffmanCode hcode, int shift,
+                           HuffmanCode32* const huff) {
+  huff->bits += hcode.bits;
+  huff->value |= (uint32_t)hcode.value << shift;
+  assert(huff->bits <= HUFFMAN_TABLE_BITS);
+  return hcode.bits;
+}
+
+static void BuildPackedTable(HTreeGroup* const htree_group) {
+  uint32_t code;
+  for (code = 0; code < HUFFMAN_PACKED_TABLE_SIZE; ++code) {
+    uint32_t bits = code;
+    HuffmanCode32* const huff = &htree_group->packed_table[bits];
+    HuffmanCode hcode = htree_group->htrees[GREEN][bits];
+    if (hcode.value >= NUM_LITERAL_CODES) {
+      huff->bits = hcode.bits + BITS_SPECIAL_MARKER;
+      huff->value = hcode.value;
+    } else {
+      huff->bits = 0;
+      huff->value = 0;
+      bits >>= AccumulateHCode(hcode, 8, huff);
+      bits >>= AccumulateHCode(htree_group->htrees[RED][bits], 16, huff);
+      bits >>= AccumulateHCode(htree_group->htrees[BLUE][bits], 0, huff);
+      bits >>= AccumulateHCode(htree_group->htrees[ALPHA][bits], 24, huff);
+      (void)bits;
+    }
+  }
+}
+
+static int ReadHuffmanCodeLengths(
+    VP8LDecoder* const dec, const int* const code_length_code_lengths,
+    int num_symbols, int* const code_lengths) {
+  int ok = 0;
+  VP8LBitReader* const br = &dec->br_;
+  int symbol;
+  int max_symbol;
+  int prev_code_len = DEFAULT_CODE_LENGTH;
+  HuffmanCode table[1 << LENGTHS_TABLE_BITS];
+
+  if (!VP8LBuildHuffmanTable(table, LENGTHS_TABLE_BITS,
+                             code_length_code_lengths,
+                             NUM_CODE_LENGTH_CODES)) {
+    goto End;
+  }
+
+  if (VP8LReadBits(br, 1)) {    // use length
+    const int length_nbits = 2 + 2 * VP8LReadBits(br, 3);
+    max_symbol = 2 + VP8LReadBits(br, length_nbits);
+    if (max_symbol > num_symbols) {
+      goto End;
+    }
+  } else {
+    max_symbol = num_symbols;
+  }
+
+  symbol = 0;
+  while (symbol < num_symbols) {
+    const HuffmanCode* p;
+    int code_len;
+    if (max_symbol-- == 0) break;
+    VP8LFillBitWindow(br);
+    p = &table[VP8LPrefetchBits(br) & LENGTHS_TABLE_MASK];
+    VP8LSetBitPos(br, br->bit_pos_ + p->bits);
+    code_len = p->value;
+    if (code_len < kCodeLengthLiterals) {
+      code_lengths[symbol++] = code_len;
+      if (code_len != 0) prev_code_len = code_len;
+    } else {
+      const int use_prev = (code_len == kCodeLengthRepeatCode);
+      const int slot = code_len - kCodeLengthLiterals;
+      const int extra_bits = kCodeLengthExtraBits[slot];
+      const int repeat_offset = kCodeLengthRepeatOffsets[slot];
+      int repeat = VP8LReadBits(br, extra_bits) + repeat_offset;
+      if (symbol + repeat > num_symbols) {
+        goto End;
+      } else {
+        const int length = use_prev ? prev_code_len : 0;
+        while (repeat-- > 0) code_lengths[symbol++] = length;
+      }
+    }
+  }
+  ok = 1;
+
+ End:
+  if (!ok) dec->status_ = VP8_STATUS_BITSTREAM_ERROR;
+  return ok;
+}
+
+// 'code_lengths' is pre-allocated temporary buffer, used for creating Huffman
+// tree.
+static int ReadHuffmanCode(int alphabet_size, VP8LDecoder* const dec,
+                           int* const code_lengths, HuffmanCode* const table) {
+  int ok = 0;
+  int size = 0;
+  VP8LBitReader* const br = &dec->br_;
+  const int simple_code = VP8LReadBits(br, 1);
+
+  memset(code_lengths, 0, alphabet_size * sizeof(*code_lengths));
+
+  if (simple_code) {  // Read symbols, codes & code lengths directly.
+    const int num_symbols = VP8LReadBits(br, 1) + 1;
+    const int first_symbol_len_code = VP8LReadBits(br, 1);
+    // The first code is either 1 bit or 8 bit code.
+    int symbol = VP8LReadBits(br, (first_symbol_len_code == 0) ? 1 : 8);
+    code_lengths[symbol] = 1;
+    // The second code (if present), is always 8 bit long.
+    if (num_symbols == 2) {
+      symbol = VP8LReadBits(br, 8);
+      code_lengths[symbol] = 1;
+    }
+    ok = 1;
+  } else {  // Decode Huffman-coded code lengths.
+    int i;
+    int code_length_code_lengths[NUM_CODE_LENGTH_CODES] = { 0 };
+    const int num_codes = VP8LReadBits(br, 4) + 4;
+    if (num_codes > NUM_CODE_LENGTH_CODES) {
+      dec->status_ = VP8_STATUS_BITSTREAM_ERROR;
+      return 0;
+    }
+
+    for (i = 0; i < num_codes; ++i) {
+      code_length_code_lengths[kCodeLengthCodeOrder[i]] = VP8LReadBits(br, 3);
+    }
+    ok = ReadHuffmanCodeLengths(dec, code_length_code_lengths, alphabet_size,
+                                code_lengths);
+  }
+
+  ok = ok && !br->eos_;
+  if (ok) {
+    size = VP8LBuildHuffmanTable(table, HUFFMAN_TABLE_BITS,
+                                 code_lengths, alphabet_size);
+  }
+  if (!ok || size == 0) {
+    dec->status_ = VP8_STATUS_BITSTREAM_ERROR;
+    return 0;
+  }
+  return size;
+}
+
+static int ReadHuffmanCodes(VP8LDecoder* const dec, int xsize, int ysize,
+                            int color_cache_bits, int allow_recursion) {
+  int i, j;
+  VP8LBitReader* const br = &dec->br_;
+  VP8LMetadata* const hdr = &dec->hdr_;
+  uint32_t* huffman_image = NULL;
+  HTreeGroup* htree_groups = NULL;
+  HuffmanCode* huffman_tables = NULL;
+  HuffmanCode* next = NULL;
+  int num_htree_groups = 1;
+  int max_alphabet_size = 0;
+  int* code_lengths = NULL;
+  const int table_size = kTableSize[color_cache_bits];
+
+  if (allow_recursion && VP8LReadBits(br, 1)) {
+    // use meta Huffman codes.
+    const int huffman_precision = VP8LReadBits(br, 3) + 2;
+    const int huffman_xsize = VP8LSubSampleSize(xsize, huffman_precision);
+    const int huffman_ysize = VP8LSubSampleSize(ysize, huffman_precision);
+    const int huffman_pixs = huffman_xsize * huffman_ysize;
+    if (!DecodeImageStream(huffman_xsize, huffman_ysize, 0, dec,
+                           &huffman_image)) {
+      goto Error;
+    }
+    hdr->huffman_subsample_bits_ = huffman_precision;
+    for (i = 0; i < huffman_pixs; ++i) {
+      // The huffman data is stored in red and green bytes.
+      const int group = (huffman_image[i] >> 8) & 0xffff;
+      huffman_image[i] = group;
+      if (group >= num_htree_groups) {
+        num_htree_groups = group + 1;
+      }
+    }
+  }
+
+  if (br->eos_) goto Error;
+
+  // Find maximum alphabet size for the htree group.
+  for (j = 0; j < HUFFMAN_CODES_PER_META_CODE; ++j) {
+    int alphabet_size = kAlphabetSize[j];
+    if (j == 0 && color_cache_bits > 0) {
+      alphabet_size += 1 << color_cache_bits;
+    }
+    if (max_alphabet_size < alphabet_size) {
+      max_alphabet_size = alphabet_size;
+    }
+  }
+
+  huffman_tables = (HuffmanCode*)WebPSafeMalloc(num_htree_groups * table_size,
+                                                sizeof(*huffman_tables));
+  htree_groups = VP8LHtreeGroupsNew(num_htree_groups);
+  code_lengths = (int*)WebPSafeCalloc((uint64_t)max_alphabet_size,
+                                      sizeof(*code_lengths));
+
+  if (htree_groups == NULL || code_lengths == NULL || huffman_tables == NULL) {
+    dec->status_ = VP8_STATUS_OUT_OF_MEMORY;
+    goto Error;
+  }
+
+  next = huffman_tables;
+  for (i = 0; i < num_htree_groups; ++i) {
+    HTreeGroup* const htree_group = &htree_groups[i];
+    HuffmanCode** const htrees = htree_group->htrees;
+    int size;
+    int total_size = 0;
+    int is_trivial_literal = 1;
+    int max_bits = 0;
+    for (j = 0; j < HUFFMAN_CODES_PER_META_CODE; ++j) {
+      int alphabet_size = kAlphabetSize[j];
+      htrees[j] = next;
+      if (j == 0 && color_cache_bits > 0) {
+        alphabet_size += 1 << color_cache_bits;
+      }
+      size = ReadHuffmanCode(alphabet_size, dec, code_lengths, next);
+      if (size == 0) {
+        goto Error;
+      }
+      if (is_trivial_literal && kLiteralMap[j] == 1) {
+        is_trivial_literal = (next->bits == 0);
+      }
+      total_size += next->bits;
+      next += size;
+      if (j <= ALPHA) {
+        int local_max_bits = code_lengths[0];
+        int k;
+        for (k = 1; k < alphabet_size; ++k) {
+          if (code_lengths[k] > local_max_bits) {
+            local_max_bits = code_lengths[k];
+          }
+        }
+        max_bits += local_max_bits;
+      }
+    }
+    htree_group->is_trivial_literal = is_trivial_literal;
+    htree_group->is_trivial_code = 0;
+    if (is_trivial_literal) {
+      const int red = htrees[RED][0].value;
+      const int blue = htrees[BLUE][0].value;
+      const int alpha = htrees[ALPHA][0].value;
+      htree_group->literal_arb =
+          ((uint32_t)alpha << 24) | (red << 16) | blue;
+      if (total_size == 0 && htrees[GREEN][0].value < NUM_LITERAL_CODES) {
+        htree_group->is_trivial_code = 1;
+        htree_group->literal_arb |= htrees[GREEN][0].value << 8;
+      }
+    }
+    htree_group->use_packed_table = !htree_group->is_trivial_code &&
+                                    (max_bits < HUFFMAN_PACKED_BITS);
+    if (htree_group->use_packed_table) BuildPackedTable(htree_group);
+  }
+  WebPSafeFree(code_lengths);
+
+  // All OK. Finalize pointers and return.
+  hdr->huffman_image_ = huffman_image;
+  hdr->num_htree_groups_ = num_htree_groups;
+  hdr->htree_groups_ = htree_groups;
+  hdr->huffman_tables_ = huffman_tables;
+  return 1;
+
+ Error:
+  WebPSafeFree(code_lengths);
+  WebPSafeFree(huffman_image);
+  WebPSafeFree(huffman_tables);
+  VP8LHtreeGroupsFree(htree_groups);
+  return 0;
+}
+
+//------------------------------------------------------------------------------
+// Scaling.
+
+static int AllocateAndInitRescaler(VP8LDecoder* const dec, VP8Io* const io) {
+  const int num_channels = 4;
+  const int in_width = io->mb_w;
+  const int out_width = io->scaled_width;
+  const int in_height = io->mb_h;
+  const int out_height = io->scaled_height;
+  const uint64_t work_size = 2 * num_channels * (uint64_t)out_width;
+  rescaler_t* work;        // Rescaler work area.
+  const uint64_t scaled_data_size = (uint64_t)out_width;
+  uint32_t* scaled_data;  // Temporary storage for scaled BGRA data.
+  const uint64_t memory_size = sizeof(*dec->rescaler) +
+                               work_size * sizeof(*work) +
+                               scaled_data_size * sizeof(*scaled_data);
+  uint8_t* memory = (uint8_t*)WebPSafeMalloc(memory_size, sizeof(*memory));
+  if (memory == NULL) {
+    dec->status_ = VP8_STATUS_OUT_OF_MEMORY;
+    return 0;
+  }
+  assert(dec->rescaler_memory == NULL);
+  dec->rescaler_memory = memory;
+
+  dec->rescaler = (WebPRescaler*)memory;
+  memory += sizeof(*dec->rescaler);
+  work = (rescaler_t*)memory;
+  memory += work_size * sizeof(*work);
+  scaled_data = (uint32_t*)memory;
+
+  WebPRescalerInit(dec->rescaler, in_width, in_height, (uint8_t*)scaled_data,
+                   out_width, out_height, 0, num_channels, work);
+  return 1;
+}
+
+//------------------------------------------------------------------------------
+// Export to ARGB
+
+// We have special "export" function since we need to convert from BGRA
+static int Export(WebPRescaler* const rescaler, WEBP_CSP_MODE colorspace,
+                  int rgba_stride, uint8_t* const rgba) {
+  uint32_t* const src = (uint32_t*)rescaler->dst;
+  const int dst_width = rescaler->dst_width;
+  int num_lines_out = 0;
+  while (WebPRescalerHasPendingOutput(rescaler)) {
+    uint8_t* const dst = rgba + num_lines_out * rgba_stride;
+    WebPRescalerExportRow(rescaler);
+    WebPMultARGBRow(src, dst_width, 1);
+    VP8LConvertFromBGRA(src, dst_width, colorspace, dst);
+    ++num_lines_out;
+  }
+  return num_lines_out;
+}
+
+// Emit scaled rows.
+static int EmitRescaledRowsRGBA(const VP8LDecoder* const dec,
+                                uint8_t* in, int in_stride, int mb_h,
+                                uint8_t* const out, int out_stride) {
+  const WEBP_CSP_MODE colorspace = dec->output_->colorspace;
+  int num_lines_in = 0;
+  int num_lines_out = 0;
+  while (num_lines_in < mb_h) {
+    uint8_t* const row_in = in + num_lines_in * in_stride;
+    uint8_t* const row_out = out + num_lines_out * out_stride;
+    const int lines_left = mb_h - num_lines_in;
+    const int needed_lines = WebPRescaleNeededLines(dec->rescaler, lines_left);
+    int lines_imported;
+    assert(needed_lines > 0 && needed_lines <= lines_left);
+    WebPMultARGBRows(row_in, in_stride,
+                     dec->rescaler->src_width, needed_lines, 0);
+    lines_imported =
+        WebPRescalerImport(dec->rescaler, lines_left, row_in, in_stride);
+    assert(lines_imported == needed_lines);
+    num_lines_in += lines_imported;
+    num_lines_out += Export(dec->rescaler, colorspace, out_stride, row_out);
+  }
+  return num_lines_out;
+}
+
+// Emit rows without any scaling.
+static int EmitRows(WEBP_CSP_MODE colorspace,
+                    const uint8_t* row_in, int in_stride,
+                    int mb_w, int mb_h,
+                    uint8_t* const out, int out_stride) {
+  int lines = mb_h;
+  uint8_t* row_out = out;
+  while (lines-- > 0) {
+    VP8LConvertFromBGRA((const uint32_t*)row_in, mb_w, colorspace, row_out);
+    row_in += in_stride;
+    row_out += out_stride;
+  }
+  return mb_h;  // Num rows out == num rows in.
+}
+
+//------------------------------------------------------------------------------
+// Export to YUVA
+
+static void ConvertToYUVA(const uint32_t* const src, int width, int y_pos,
+                          const WebPDecBuffer* const output) {
+  const WebPYUVABuffer* const buf = &output->u.YUVA;
+
+  // first, the luma plane
+  WebPConvertARGBToY(src, buf->y + y_pos * buf->y_stride, width);
+
+  // then U/V planes
+  {
+    uint8_t* const u = buf->u + (y_pos >> 1) * buf->u_stride;
+    uint8_t* const v = buf->v + (y_pos >> 1) * buf->v_stride;
+    // even lines: store values
+    // odd lines: average with previous values
+    WebPConvertARGBToUV(src, u, v, width, !(y_pos & 1));
+  }
+  // Lastly, store alpha if needed.
+  if (buf->a != NULL) {
+    uint8_t* const a = buf->a + y_pos * buf->a_stride;
+#if defined(WORDS_BIGENDIAN)
+    WebPExtractAlpha((uint8_t*)src + 0, 0, width, 1, a, 0);
+#else
+    WebPExtractAlpha((uint8_t*)src + 3, 0, width, 1, a, 0);
+#endif
+  }
+}
+
+static int ExportYUVA(const VP8LDecoder* const dec, int y_pos) {
+  WebPRescaler* const rescaler = dec->rescaler;
+  uint32_t* const src = (uint32_t*)rescaler->dst;
+  const int dst_width = rescaler->dst_width;
+  int num_lines_out = 0;
+  while (WebPRescalerHasPendingOutput(rescaler)) {
+    WebPRescalerExportRow(rescaler);
+    WebPMultARGBRow(src, dst_width, 1);
+    ConvertToYUVA(src, dst_width, y_pos, dec->output_);
+    ++y_pos;
+    ++num_lines_out;
+  }
+  return num_lines_out;
+}
+
+static int EmitRescaledRowsYUVA(const VP8LDecoder* const dec,
+                                uint8_t* in, int in_stride, int mb_h) {
+  int num_lines_in = 0;
+  int y_pos = dec->last_out_row_;
+  while (num_lines_in < mb_h) {
+    const int lines_left = mb_h - num_lines_in;
+    const int needed_lines = WebPRescaleNeededLines(dec->rescaler, lines_left);
+    int lines_imported;
+    WebPMultARGBRows(in, in_stride, dec->rescaler->src_width, needed_lines, 0);
+    lines_imported =
+        WebPRescalerImport(dec->rescaler, lines_left, in, in_stride);
+    assert(lines_imported == needed_lines);
+    num_lines_in += lines_imported;
+    in += needed_lines * in_stride;
+    y_pos += ExportYUVA(dec, y_pos);
+  }
+  return y_pos;
+}
+
+static int EmitRowsYUVA(const VP8LDecoder* const dec,
+                        const uint8_t* in, int in_stride,
+                        int mb_w, int num_rows) {
+  int y_pos = dec->last_out_row_;
+  while (num_rows-- > 0) {
+    ConvertToYUVA((const uint32_t*)in, mb_w, y_pos, dec->output_);
+    in += in_stride;
+    ++y_pos;
+  }
+  return y_pos;
+}
+
+//------------------------------------------------------------------------------
+// Cropping.
+
+// Sets io->mb_y, io->mb_h & io->mb_w according to start row, end row and
+// crop options. Also updates the input data pointer, so that it points to the
+// start of the cropped window. Note that pixels are in ARGB format even if
+// 'in_data' is uint8_t*.
+// Returns true if the crop window is not empty.
+static int SetCropWindow(VP8Io* const io, int y_start, int y_end,
+                         uint8_t** const in_data, int pixel_stride) {
+  assert(y_start < y_end);
+  assert(io->crop_left < io->crop_right);
+  if (y_end > io->crop_bottom) {
+    y_end = io->crop_bottom;  // make sure we don't overflow on last row.
+  }
+  if (y_start < io->crop_top) {
+    const int delta = io->crop_top - y_start;
+    y_start = io->crop_top;
+    *in_data += delta * pixel_stride;
+  }
+  if (y_start >= y_end) return 0;  // Crop window is empty.
+
+  *in_data += io->crop_left * sizeof(uint32_t);
+
+  io->mb_y = y_start - io->crop_top;
+  io->mb_w = io->crop_right - io->crop_left;
+  io->mb_h = y_end - y_start;
+  return 1;  // Non-empty crop window.
+}
+
+//------------------------------------------------------------------------------
+
+static WEBP_INLINE int GetMetaIndex(
+    const uint32_t* const image, int xsize, int bits, int x, int y) {
+  if (bits == 0) return 0;
+  return image[xsize * (y >> bits) + (x >> bits)];
+}
+
+static WEBP_INLINE HTreeGroup* GetHtreeGroupForPos(VP8LMetadata* const hdr,
+                                                   int x, int y) {
+  const int meta_index = GetMetaIndex(hdr->huffman_image_, hdr->huffman_xsize_,
+                                      hdr->huffman_subsample_bits_, x, y);
+  assert(meta_index < hdr->num_htree_groups_);
+  return hdr->htree_groups_ + meta_index;
+}
+
+//------------------------------------------------------------------------------
+// Main loop, with custom row-processing function
+
+typedef void (*ProcessRowsFunc)(VP8LDecoder* const dec, int row);
+
+static void ApplyInverseTransforms(VP8LDecoder* const dec, int num_rows,
+                                   const uint32_t* const rows) {
+  int n = dec->next_transform_;
+  const int cache_pixs = dec->width_ * num_rows;
+  const int start_row = dec->last_row_;
+  const int end_row = start_row + num_rows;
+  const uint32_t* rows_in = rows;
+  uint32_t* const rows_out = dec->argb_cache_;
+
+  // Inverse transforms.
+  while (n-- > 0) {
+    VP8LTransform* const transform = &dec->transforms_[n];
+    VP8LInverseTransform(transform, start_row, end_row, rows_in, rows_out);
+    rows_in = rows_out;
+  }
+  if (rows_in != rows_out) {
+    // No transform called, hence just copy.
+    memcpy(rows_out, rows_in, cache_pixs * sizeof(*rows_out));
+  }
+}
+
+// Processes (transforms, scales & color-converts) the rows decoded after the
+// last call.
+static void ProcessRows(VP8LDecoder* const dec, int row) {
+  const uint32_t* const rows = dec->pixels_ + dec->width_ * dec->last_row_;
+  const int num_rows = row - dec->last_row_;
+
+  assert(row <= dec->io_->crop_bottom);
+  // We can't process more than NUM_ARGB_CACHE_ROWS at a time (that's the size
+  // of argb_cache_), but we currently don't need more than that.
+  assert(num_rows <= NUM_ARGB_CACHE_ROWS);
+  if (num_rows > 0) {    // Emit output.
+    VP8Io* const io = dec->io_;
+    uint8_t* rows_data = (uint8_t*)dec->argb_cache_;
+    const int in_stride = io->width * sizeof(uint32_t);  // in unit of RGBA
+
+    ApplyInverseTransforms(dec, num_rows, rows);
+    if (!SetCropWindow(io, dec->last_row_, row, &rows_data, in_stride)) {
+      // Nothing to output (this time).
+    } else {
+      const WebPDecBuffer* const output = dec->output_;
+      if (WebPIsRGBMode(output->colorspace)) {  // convert to RGBA
+        const WebPRGBABuffer* const buf = &output->u.RGBA;
+        uint8_t* const rgba = buf->rgba + dec->last_out_row_ * buf->stride;
+        const int num_rows_out = io->use_scaling ?
+            EmitRescaledRowsRGBA(dec, rows_data, in_stride, io->mb_h,
+                                 rgba, buf->stride) :
+            EmitRows(output->colorspace, rows_data, in_stride,
+                     io->mb_w, io->mb_h, rgba, buf->stride);
+        // Update 'last_out_row_'.
+        dec->last_out_row_ += num_rows_out;
+      } else {                              // convert to YUVA
+        dec->last_out_row_ = io->use_scaling ?
+            EmitRescaledRowsYUVA(dec, rows_data, in_stride, io->mb_h) :
+            EmitRowsYUVA(dec, rows_data, in_stride, io->mb_w, io->mb_h);
+      }
+      assert(dec->last_out_row_ <= output->height);
+    }
+  }
+
+  // Update 'last_row_'.
+  dec->last_row_ = row;
+  assert(dec->last_row_ <= dec->height_);
+}
+
+// Row-processing for the special case when alpha data contains only one
+// transform (color indexing), and trivial non-green literals.
+static int Is8bOptimizable(const VP8LMetadata* const hdr) {
+  int i;
+  if (hdr->color_cache_size_ > 0) return 0;
+  // When the Huffman tree contains only one symbol, we can skip the
+  // call to ReadSymbol() for red/blue/alpha channels.
+  for (i = 0; i < hdr->num_htree_groups_; ++i) {
+    HuffmanCode** const htrees = hdr->htree_groups_[i].htrees;
+    if (htrees[RED][0].bits > 0) return 0;
+    if (htrees[BLUE][0].bits > 0) return 0;
+    if (htrees[ALPHA][0].bits > 0) return 0;
+  }
+  return 1;
+}
+
+static void AlphaApplyFilter(ALPHDecoder* const alph_dec,
+                             int first_row, int last_row,
+                             uint8_t* out, int stride) {
+  if (alph_dec->filter_ != WEBP_FILTER_NONE) {
+    int y;
+    const uint8_t* prev_line = alph_dec->prev_line_;
+    assert(WebPUnfilters[alph_dec->filter_] != NULL);
+    for (y = first_row; y < last_row; ++y) {
+      WebPUnfilters[alph_dec->filter_](prev_line, out, out, stride);
+      prev_line = out;
+      out += stride;
+    }
+    alph_dec->prev_line_ = prev_line;
+  }
+}
+
+static void ExtractPalettedAlphaRows(VP8LDecoder* const dec, int last_row) {
+  // For vertical and gradient filtering, we need to decode the part above the
+  // crop_top row, in order to have the correct spatial predictors.
+  ALPHDecoder* const alph_dec = (ALPHDecoder*)dec->io_->opaque;
+  const int top_row =
+      (alph_dec->filter_ == WEBP_FILTER_NONE ||
+       alph_dec->filter_ == WEBP_FILTER_HORIZONTAL) ? dec->io_->crop_top
+                                                    : dec->last_row_;
+  const int first_row = (dec->last_row_ < top_row) ? top_row : dec->last_row_;
+  assert(last_row <= dec->io_->crop_bottom);
+  if (last_row > first_row) {
+    // Special method for paletted alpha data. We only process the cropped area.
+    const int width = dec->io_->width;
+    uint8_t* out = alph_dec->output_ + width * first_row;
+    const uint8_t* const in =
+      (uint8_t*)dec->pixels_ + dec->width_ * first_row;
+    VP8LTransform* const transform = &dec->transforms_[0];
+    assert(dec->next_transform_ == 1);
+    assert(transform->type_ == COLOR_INDEXING_TRANSFORM);
+    VP8LColorIndexInverseTransformAlpha(transform, first_row, last_row,
+                                        in, out);
+    AlphaApplyFilter(alph_dec, first_row, last_row, out, width);
+  }
+  dec->last_row_ = dec->last_out_row_ = last_row;
+}
+
+//------------------------------------------------------------------------------
+// Helper functions for fast pattern copy (8b and 32b)
+
+// cyclic rotation of pattern word
+static WEBP_INLINE uint32_t Rotate8b(uint32_t V) {
+#if defined(WORDS_BIGENDIAN)
+  return ((V & 0xff000000u) >> 24) | (V << 8);
+#else
+  return ((V & 0xffu) << 24) | (V >> 8);
+#endif
+}
+
+// copy 1, 2 or 4-bytes pattern
+static WEBP_INLINE void CopySmallPattern8b(const uint8_t* src, uint8_t* dst,
+                                           int length, uint32_t pattern) {
+  int i;
+  // align 'dst' to 4-bytes boundary. Adjust the pattern along the way.
+  while ((uintptr_t)dst & 3) {
+    *dst++ = *src++;
+    pattern = Rotate8b(pattern);
+    --length;
+  }
+  // Copy the pattern 4 bytes at a time.
+  for (i = 0; i < (length >> 2); ++i) {
+    ((uint32_t*)dst)[i] = pattern;
+  }
+  // Finish with left-overs. 'pattern' is still correctly positioned,
+  // so no Rotate8b() call is needed.
+  for (i <<= 2; i < length; ++i) {
+    dst[i] = src[i];
+  }
+}
+
+static WEBP_INLINE void CopyBlock8b(uint8_t* const dst, int dist, int length) {
+  const uint8_t* src = dst - dist;
+  if (length >= 8) {
+    uint32_t pattern = 0;
+    switch (dist) {
+      case 1:
+        pattern = src[0];
+#if defined(__arm__) || defined(_M_ARM)   // arm doesn't like multiply that much
+        pattern |= pattern << 8;
+        pattern |= pattern << 16;
+#elif defined(WEBP_USE_MIPS_DSP_R2)
+        __asm__ volatile ("replv.qb %0, %0" : "+r"(pattern));
+#else
+        pattern = 0x01010101u * pattern;
+#endif
+        break;
+      case 2:
+        memcpy(&pattern, src, sizeof(uint16_t));
+#if defined(__arm__) || defined(_M_ARM)
+        pattern |= pattern << 16;
+#elif defined(WEBP_USE_MIPS_DSP_R2)
+        __asm__ volatile ("replv.ph %0, %0" : "+r"(pattern));
+#else
+        pattern = 0x00010001u * pattern;
+#endif
+        break;
+      case 4:
+        memcpy(&pattern, src, sizeof(uint32_t));
+        break;
+      default:
+        goto Copy;
+        break;
+    }
+    CopySmallPattern8b(src, dst, length, pattern);
+    return;
+  }
+ Copy:
+  if (dist >= length) {  // no overlap -> use memcpy()
+    memcpy(dst, src, length * sizeof(*dst));
+  } else {
+    int i;
+    for (i = 0; i < length; ++i) dst[i] = src[i];
+  }
+}
+
+// copy pattern of 1 or 2 uint32_t's
+static WEBP_INLINE void CopySmallPattern32b(const uint32_t* src,
+                                            uint32_t* dst,
+                                            int length, uint64_t pattern) {
+  int i;
+  if ((uintptr_t)dst & 4) {           // Align 'dst' to 8-bytes boundary.
+    *dst++ = *src++;
+    pattern = (pattern >> 32) | (pattern << 32);
+    --length;
+  }
+  assert(0 == ((uintptr_t)dst & 7));
+  for (i = 0; i < (length >> 1); ++i) {
+    ((uint64_t*)dst)[i] = pattern;    // Copy the pattern 8 bytes at a time.
+  }
+  if (length & 1) {                   // Finish with left-over.
+    dst[i << 1] = src[i << 1];
+  }
+}
+
+static WEBP_INLINE void CopyBlock32b(uint32_t* const dst,
+                                     int dist, int length) {
+  const uint32_t* const src = dst - dist;
+  if (dist <= 2 && length >= 4 && ((uintptr_t)dst & 3) == 0) {
+    uint64_t pattern;
+    if (dist == 1) {
+      pattern = (uint64_t)src[0];
+      pattern |= pattern << 32;
+    } else {
+      memcpy(&pattern, src, sizeof(pattern));
+    }
+    CopySmallPattern32b(src, dst, length, pattern);
+  } else if (dist >= length) {  // no overlap
+    memcpy(dst, src, length * sizeof(*dst));
+  } else {
+    int i;
+    for (i = 0; i < length; ++i) dst[i] = src[i];
+  }
+}
+
+//------------------------------------------------------------------------------
+
+static int DecodeAlphaData(VP8LDecoder* const dec, uint8_t* const data,
+                           int width, int height, int last_row) {
+  int ok = 1;
+  int row = dec->last_pixel_ / width;
+  int col = dec->last_pixel_ % width;
+  VP8LBitReader* const br = &dec->br_;
+  VP8LMetadata* const hdr = &dec->hdr_;
+  int pos = dec->last_pixel_;         // current position
+  const int end = width * height;     // End of data
+  const int last = width * last_row;  // Last pixel to decode
+  const int len_code_limit = NUM_LITERAL_CODES + NUM_LENGTH_CODES;
+  const int mask = hdr->huffman_mask_;
+  const HTreeGroup* htree_group =
+      (pos < last) ? GetHtreeGroupForPos(hdr, col, row) : NULL;
+  assert(pos <= end);
+  assert(last_row <= height);
+  assert(Is8bOptimizable(hdr));
+
+  while (!br->eos_ && pos < last) {
+    int code;
+    // Only update when changing tile.
+    if ((col & mask) == 0) {
+      htree_group = GetHtreeGroupForPos(hdr, col, row);
+    }
+    assert(htree_group != NULL);
+    VP8LFillBitWindow(br);
+    code = ReadSymbol(htree_group->htrees[GREEN], br);
+    if (code < NUM_LITERAL_CODES) {  // Literal
+      data[pos] = code;
+      ++pos;
+      ++col;
+      if (col >= width) {
+        col = 0;
+        ++row;
+        if (row <= last_row && (row % NUM_ARGB_CACHE_ROWS == 0)) {
+          ExtractPalettedAlphaRows(dec, row);
+        }
+      }
+    } else if (code < len_code_limit) {  // Backward reference
+      int dist_code, dist;
+      const int length_sym = code - NUM_LITERAL_CODES;
+      const int length = GetCopyLength(length_sym, br);
+      const int dist_symbol = ReadSymbol(htree_group->htrees[DIST], br);
+      VP8LFillBitWindow(br);
+      dist_code = GetCopyDistance(dist_symbol, br);
+      dist = PlaneCodeToDistance(width, dist_code);
+      if (pos >= dist && end - pos >= length) {
+        CopyBlock8b(data + pos, dist, length);
+      } else {
+        ok = 0;
+        goto End;
+      }
+      pos += length;
+      col += length;
+      while (col >= width) {
+        col -= width;
+        ++row;
+        if (row <= last_row && (row % NUM_ARGB_CACHE_ROWS == 0)) {
+          ExtractPalettedAlphaRows(dec, row);
+        }
+      }
+      if (pos < last && (col & mask)) {
+        htree_group = GetHtreeGroupForPos(hdr, col, row);
+      }
+    } else {  // Not reached
+      ok = 0;
+      goto End;
+    }
+    br->eos_ = VP8LIsEndOfStream(br);
+  }
+  // Process the remaining rows corresponding to last row-block.
+  ExtractPalettedAlphaRows(dec, row > last_row ? last_row : row);
+
+ End:
+  br->eos_ = VP8LIsEndOfStream(br);
+  if (!ok || (br->eos_ && pos < end)) {
+    ok = 0;
+    dec->status_ = br->eos_ ? VP8_STATUS_SUSPENDED
+                            : VP8_STATUS_BITSTREAM_ERROR;
+  } else {
+    dec->last_pixel_ = pos;
+  }
+  return ok;
+}
+
+static void SaveState(VP8LDecoder* const dec, int last_pixel) {
+  assert(dec->incremental_);
+  dec->saved_br_ = dec->br_;
+  dec->saved_last_pixel_ = last_pixel;
+  if (dec->hdr_.color_cache_size_ > 0) {
+    VP8LColorCacheCopy(&dec->hdr_.color_cache_, &dec->hdr_.saved_color_cache_);
+  }
+}
+
+static void RestoreState(VP8LDecoder* const dec) {
+  assert(dec->br_.eos_);
+  dec->status_ = VP8_STATUS_SUSPENDED;
+  dec->br_ = dec->saved_br_;
+  dec->last_pixel_ = dec->saved_last_pixel_;
+  if (dec->hdr_.color_cache_size_ > 0) {
+    VP8LColorCacheCopy(&dec->hdr_.saved_color_cache_, &dec->hdr_.color_cache_);
+  }
+}
+
+#define SYNC_EVERY_N_ROWS 8  // minimum number of rows between check-points
+static int DecodeImageData(VP8LDecoder* const dec, uint32_t* const data,
+                           int width, int height, int last_row,
+                           ProcessRowsFunc process_func) {
+  int row = dec->last_pixel_ / width;
+  int col = dec->last_pixel_ % width;
+  VP8LBitReader* const br = &dec->br_;
+  VP8LMetadata* const hdr = &dec->hdr_;
+  uint32_t* src = data + dec->last_pixel_;
+  uint32_t* last_cached = src;
+  uint32_t* const src_end = data + width * height;     // End of data
+  uint32_t* const src_last = data + width * last_row;  // Last pixel to decode
+  const int len_code_limit = NUM_LITERAL_CODES + NUM_LENGTH_CODES;
+  const int color_cache_limit = len_code_limit + hdr->color_cache_size_;
+  int next_sync_row = dec->incremental_ ? row : 1 << 24;
+  VP8LColorCache* const color_cache =
+      (hdr->color_cache_size_ > 0) ? &hdr->color_cache_ : NULL;
+  const int mask = hdr->huffman_mask_;
+  const HTreeGroup* htree_group =
+      (src < src_last) ? GetHtreeGroupForPos(hdr, col, row) : NULL;
+  assert(dec->last_row_ < last_row);
+  assert(src_last <= src_end);
+
+  while (src < src_last) {
+    int code;
+    if (row >= next_sync_row) {
+      SaveState(dec, (int)(src - data));
+      next_sync_row = row + SYNC_EVERY_N_ROWS;
+    }
+    // Only update when changing tile. Note we could use this test:
+    // if "((((prev_col ^ col) | prev_row ^ row)) > mask)" -> tile changed
+    // but that's actually slower and needs storing the previous col/row.
+    if ((col & mask) == 0) {
+      htree_group = GetHtreeGroupForPos(hdr, col, row);
+    }
+    assert(htree_group != NULL);
+    if (htree_group->is_trivial_code) {
+      *src = htree_group->literal_arb;
+      goto AdvanceByOne;
+    }
+    VP8LFillBitWindow(br);
+    if (htree_group->use_packed_table) {
+      code = ReadPackedSymbols(htree_group, br, src);
+      if (VP8LIsEndOfStream(br)) break;
+      if (code == PACKED_NON_LITERAL_CODE) goto AdvanceByOne;
+    } else {
+      code = ReadSymbol(htree_group->htrees[GREEN], br);
+    }
+    if (VP8LIsEndOfStream(br)) break;
+    if (code < NUM_LITERAL_CODES) {  // Literal
+      if (htree_group->is_trivial_literal) {
+        *src = htree_group->literal_arb | (code << 8);
+      } else {
+        int red, blue, alpha;
+        red = ReadSymbol(htree_group->htrees[RED], br);
+        VP8LFillBitWindow(br);
+        blue = ReadSymbol(htree_group->htrees[BLUE], br);
+        alpha = ReadSymbol(htree_group->htrees[ALPHA], br);
+        if (VP8LIsEndOfStream(br)) break;
+        *src = ((uint32_t)alpha << 24) | (red << 16) | (code << 8) | blue;
+      }
+    AdvanceByOne:
+      ++src;
+      ++col;
+      if (col >= width) {
+        col = 0;
+        ++row;
+        if (process_func != NULL) {
+          if (row <= last_row && (row % NUM_ARGB_CACHE_ROWS == 0)) {
+            process_func(dec, row);
+          }
+        }
+        if (color_cache != NULL) {
+          while (last_cached < src) {
+            VP8LColorCacheInsert(color_cache, *last_cached++);
+          }
+        }
+      }
+    } else if (code < len_code_limit) {  // Backward reference
+      int dist_code, dist;
+      const int length_sym = code - NUM_LITERAL_CODES;
+      const int length = GetCopyLength(length_sym, br);
+      const int dist_symbol = ReadSymbol(htree_group->htrees[DIST], br);
+      VP8LFillBitWindow(br);
+      dist_code = GetCopyDistance(dist_symbol, br);
+      dist = PlaneCodeToDistance(width, dist_code);
+      if (VP8LIsEndOfStream(br)) break;
+      if (src - data < (ptrdiff_t)dist || src_end - src < (ptrdiff_t)length) {
+        goto Error;
+      } else {
+        CopyBlock32b(src, dist, length);
+      }
+      src += length;
+      col += length;
+      while (col >= width) {
+        col -= width;
+        ++row;
+        if (process_func != NULL) {
+          if (row <= last_row && (row % NUM_ARGB_CACHE_ROWS == 0)) {
+            process_func(dec, row);
+          }
+        }
+      }
+      // Because of the check done above (before 'src' was incremented by
+      // 'length'), the following holds true.
+      assert(src <= src_end);
+      if (col & mask) htree_group = GetHtreeGroupForPos(hdr, col, row);
+      if (color_cache != NULL) {
+        while (last_cached < src) {
+          VP8LColorCacheInsert(color_cache, *last_cached++);
+        }
+      }
+    } else if (code < color_cache_limit) {  // Color cache
+      const int key = code - len_code_limit;
+      assert(color_cache != NULL);
+      while (last_cached < src) {
+        VP8LColorCacheInsert(color_cache, *last_cached++);
+      }
+      *src = VP8LColorCacheLookup(color_cache, key);
+      goto AdvanceByOne;
+    } else {  // Not reached
+      goto Error;
+    }
+  }
+
+  br->eos_ = VP8LIsEndOfStream(br);
+  if (dec->incremental_ && br->eos_ && src < src_end) {
+    RestoreState(dec);
+  } else if (!br->eos_) {
+    // Process the remaining rows corresponding to last row-block.
+    if (process_func != NULL) {
+      process_func(dec, row > last_row ? last_row : row);
+    }
+    dec->status_ = VP8_STATUS_OK;
+    dec->last_pixel_ = (int)(src - data);  // end-of-scan marker
+  } else {
+    // if not incremental, and we are past the end of buffer (eos_=1), then this
+    // is a real bitstream error.
+    goto Error;
+  }
+  return 1;
+
+ Error:
+  dec->status_ = VP8_STATUS_BITSTREAM_ERROR;
+  return 0;
+}
+
+// -----------------------------------------------------------------------------
+// VP8LTransform
+
+static void ClearTransform(VP8LTransform* const transform) {
+  WebPSafeFree(transform->data_);
+  transform->data_ = NULL;
+}
+
+// For security reason, we need to remap the color map to span
+// the total possible bundled values, and not just the num_colors.
+static int ExpandColorMap(int num_colors, VP8LTransform* const transform) {
+  int i;
+  const int final_num_colors = 1 << (8 >> transform->bits_);
+  uint32_t* const new_color_map =
+      (uint32_t*)WebPSafeMalloc((uint64_t)final_num_colors,
+                                sizeof(*new_color_map));
+  if (new_color_map == NULL) {
+    return 0;
+  } else {
+    uint8_t* const data = (uint8_t*)transform->data_;
+    uint8_t* const new_data = (uint8_t*)new_color_map;
+    new_color_map[0] = transform->data_[0];
+    for (i = 4; i < 4 * num_colors; ++i) {
+      // Equivalent to AddPixelEq(), on a byte-basis.
+      new_data[i] = (data[i] + new_data[i - 4]) & 0xff;
+    }
+    for (; i < 4 * final_num_colors; ++i) {
+      new_data[i] = 0;  // black tail.
+    }
+    WebPSafeFree(transform->data_);
+    transform->data_ = new_color_map;
+  }
+  return 1;
+}
+
+static int ReadTransform(int* const xsize, int const* ysize,
+                         VP8LDecoder* const dec) {
+  int ok = 1;
+  VP8LBitReader* const br = &dec->br_;
+  VP8LTransform* transform = &dec->transforms_[dec->next_transform_];
+  const VP8LImageTransformType type =
+      (VP8LImageTransformType)VP8LReadBits(br, 2);
+
+  // Each transform type can only be present once in the stream.
+  if (dec->transforms_seen_ & (1U << type)) {
+    return 0;  // Already there, let's not accept the second same transform.
+  }
+  dec->transforms_seen_ |= (1U << type);
+
+  transform->type_ = type;
+  transform->xsize_ = *xsize;
+  transform->ysize_ = *ysize;
+  transform->data_ = NULL;
+  ++dec->next_transform_;
+  assert(dec->next_transform_ <= NUM_TRANSFORMS);
+
+  switch (type) {
+    case PREDICTOR_TRANSFORM:
+    case CROSS_COLOR_TRANSFORM:
+      transform->bits_ = VP8LReadBits(br, 3) + 2;
+      ok = DecodeImageStream(VP8LSubSampleSize(transform->xsize_,
+                                               transform->bits_),
+                             VP8LSubSampleSize(transform->ysize_,
+                                               transform->bits_),
+                             0, dec, &transform->data_);
+      break;
+    case COLOR_INDEXING_TRANSFORM: {
+       const int num_colors = VP8LReadBits(br, 8) + 1;
+       const int bits = (num_colors > 16) ? 0
+                      : (num_colors > 4) ? 1
+                      : (num_colors > 2) ? 2
+                      : 3;
+       *xsize = VP8LSubSampleSize(transform->xsize_, bits);
+       transform->bits_ = bits;
+       ok = DecodeImageStream(num_colors, 1, 0, dec, &transform->data_);
+       ok = ok && ExpandColorMap(num_colors, transform);
+      break;
+    }
+    case SUBTRACT_GREEN:
+      break;
+    default:
+      assert(0);    // can't happen
+      break;
+  }
+
+  return ok;
+}
+
+// -----------------------------------------------------------------------------
+// VP8LMetadata
+
+static void InitMetadata(VP8LMetadata* const hdr) {
+  assert(hdr != NULL);
+  memset(hdr, 0, sizeof(*hdr));
+}
+
+static void ClearMetadata(VP8LMetadata* const hdr) {
+  assert(hdr != NULL);
+
+  WebPSafeFree(hdr->huffman_image_);
+  WebPSafeFree(hdr->huffman_tables_);
+  VP8LHtreeGroupsFree(hdr->htree_groups_);
+  VP8LColorCacheClear(&hdr->color_cache_);
+  VP8LColorCacheClear(&hdr->saved_color_cache_);
+  InitMetadata(hdr);
+}
+
+// -----------------------------------------------------------------------------
+// VP8LDecoder
+
+VP8LDecoder* VP8LNew(void) {
+  VP8LDecoder* const dec = (VP8LDecoder*)WebPSafeCalloc(1ULL, sizeof(*dec));
+  if (dec == NULL) return NULL;
+  dec->status_ = VP8_STATUS_OK;
+  dec->state_ = READ_DIM;
+
+  VP8LDspInit();  // Init critical function pointers.
+
+  return dec;
+}
+
+void VP8LClear(VP8LDecoder* const dec) {
+  int i;
+  if (dec == NULL) return;
+  ClearMetadata(&dec->hdr_);
+
+  WebPSafeFree(dec->pixels_);
+  dec->pixels_ = NULL;
+  for (i = 0; i < dec->next_transform_; ++i) {
+    ClearTransform(&dec->transforms_[i]);
+  }
+  dec->next_transform_ = 0;
+  dec->transforms_seen_ = 0;
+
+  WebPSafeFree(dec->rescaler_memory);
+  dec->rescaler_memory = NULL;
+
+  dec->output_ = NULL;   // leave no trace behind
+}
+
+void VP8LDelete(VP8LDecoder* const dec) {
+  if (dec != NULL) {
+    VP8LClear(dec);
+    WebPSafeFree(dec);
+  }
+}
+
+static void UpdateDecoder(VP8LDecoder* const dec, int width, int height) {
+  VP8LMetadata* const hdr = &dec->hdr_;
+  const int num_bits = hdr->huffman_subsample_bits_;
+  dec->width_ = width;
+  dec->height_ = height;
+
+  hdr->huffman_xsize_ = VP8LSubSampleSize(width, num_bits);
+  hdr->huffman_mask_ = (num_bits == 0) ? ~0 : (1 << num_bits) - 1;
+}
+
+static int DecodeImageStream(int xsize, int ysize,
+                             int is_level0,
+                             VP8LDecoder* const dec,
+                             uint32_t** const decoded_data) {
+  int ok = 1;
+  int transform_xsize = xsize;
+  int transform_ysize = ysize;
+  VP8LBitReader* const br = &dec->br_;
+  VP8LMetadata* const hdr = &dec->hdr_;
+  uint32_t* data = NULL;
+  int color_cache_bits = 0;
+
+  // Read the transforms (may recurse).
+  if (is_level0) {
+    while (ok && VP8LReadBits(br, 1)) {
+      ok = ReadTransform(&transform_xsize, &transform_ysize, dec);
+    }
+  }
+
+  // Color cache
+  if (ok && VP8LReadBits(br, 1)) {
+    color_cache_bits = VP8LReadBits(br, 4);
+    ok = (color_cache_bits >= 1 && color_cache_bits <= MAX_CACHE_BITS);
+    if (!ok) {
+      dec->status_ = VP8_STATUS_BITSTREAM_ERROR;
+      goto End;
+    }
+  }
+
+  // Read the Huffman codes (may recurse).
+  ok = ok && ReadHuffmanCodes(dec, transform_xsize, transform_ysize,
+                              color_cache_bits, is_level0);
+  if (!ok) {
+    dec->status_ = VP8_STATUS_BITSTREAM_ERROR;
+    goto End;
+  }
+
+  // Finish setting up the color-cache
+  if (color_cache_bits > 0) {
+    hdr->color_cache_size_ = 1 << color_cache_bits;
+    if (!VP8LColorCacheInit(&hdr->color_cache_, color_cache_bits)) {
+      dec->status_ = VP8_STATUS_OUT_OF_MEMORY;
+      ok = 0;
+      goto End;
+    }
+  } else {
+    hdr->color_cache_size_ = 0;
+  }
+  UpdateDecoder(dec, transform_xsize, transform_ysize);
+
+  if (is_level0) {   // level 0 complete
+    dec->state_ = READ_HDR;
+    goto End;
+  }
+
+  {
+    const uint64_t total_size = (uint64_t)transform_xsize * transform_ysize;
+    data = (uint32_t*)WebPSafeMalloc(total_size, sizeof(*data));
+    if (data == NULL) {
+      dec->status_ = VP8_STATUS_OUT_OF_MEMORY;
+      ok = 0;
+      goto End;
+    }
+  }
+
+  // Use the Huffman trees to decode the LZ77 encoded data.
+  ok = DecodeImageData(dec, data, transform_xsize, transform_ysize,
+                       transform_ysize, NULL);
+  ok = ok && !br->eos_;
+
+ End:
+  if (!ok) {
+    WebPSafeFree(data);
+    ClearMetadata(hdr);
+  } else {
+    if (decoded_data != NULL) {
+      *decoded_data = data;
+    } else {
+      // We allocate image data in this function only for transforms. At level 0
+      // (that is: not the transforms), we shouldn't have allocated anything.
+      assert(data == NULL);
+      assert(is_level0);
+    }
+    dec->last_pixel_ = 0;  // Reset for future DECODE_DATA_FUNC() calls.
+    if (!is_level0) ClearMetadata(hdr);  // Clean up temporary data behind.
+  }
+  return ok;
+}
+
+//------------------------------------------------------------------------------
+// Allocate internal buffers dec->pixels_ and dec->argb_cache_.
+static int AllocateInternalBuffers32b(VP8LDecoder* const dec, int final_width) {
+  const uint64_t num_pixels = (uint64_t)dec->width_ * dec->height_;
+  // Scratch buffer corresponding to top-prediction row for transforming the
+  // first row in the row-blocks. Not needed for paletted alpha.
+  const uint64_t cache_top_pixels = (uint16_t)final_width;
+  // Scratch buffer for temporary BGRA storage. Not needed for paletted alpha.
+  const uint64_t cache_pixels = (uint64_t)final_width * NUM_ARGB_CACHE_ROWS;
+  const uint64_t total_num_pixels =
+      num_pixels + cache_top_pixels + cache_pixels;
+
+  assert(dec->width_ <= final_width);
+  dec->pixels_ = (uint32_t*)WebPSafeMalloc(total_num_pixels, sizeof(uint32_t));
+  if (dec->pixels_ == NULL) {
+    dec->argb_cache_ = NULL;    // for sanity check
+    dec->status_ = VP8_STATUS_OUT_OF_MEMORY;
+    return 0;
+  }
+  dec->argb_cache_ = dec->pixels_ + num_pixels + cache_top_pixels;
+  return 1;
+}
+
+static int AllocateInternalBuffers8b(VP8LDecoder* const dec) {
+  const uint64_t total_num_pixels = (uint64_t)dec->width_ * dec->height_;
+  dec->argb_cache_ = NULL;    // for sanity check
+  dec->pixels_ = (uint32_t*)WebPSafeMalloc(total_num_pixels, sizeof(uint8_t));
+  if (dec->pixels_ == NULL) {
+    dec->status_ = VP8_STATUS_OUT_OF_MEMORY;
+    return 0;
+  }
+  return 1;
+}
+
+//------------------------------------------------------------------------------
+
+// Special row-processing that only stores the alpha data.
+static void ExtractAlphaRows(VP8LDecoder* const dec, int last_row) {
+  int cur_row = dec->last_row_;
+  int num_rows = last_row - cur_row;
+  const uint32_t* in = dec->pixels_ + dec->width_ * cur_row;
+
+  assert(last_row <= dec->io_->crop_bottom);
+  while (num_rows > 0) {
+    const int num_rows_to_process =
+        (num_rows > NUM_ARGB_CACHE_ROWS) ? NUM_ARGB_CACHE_ROWS : num_rows;
+    // Extract alpha (which is stored in the green plane).
+    ALPHDecoder* const alph_dec = (ALPHDecoder*)dec->io_->opaque;
+    uint8_t* const output = alph_dec->output_;
+    const int width = dec->io_->width;      // the final width (!= dec->width_)
+    const int cache_pixs = width * num_rows_to_process;
+    uint8_t* const dst = output + width * cur_row;
+    const uint32_t* const src = dec->argb_cache_;
+    ApplyInverseTransforms(dec, num_rows_to_process, in);
+    WebPExtractGreen(src, dst, cache_pixs);
+    AlphaApplyFilter(alph_dec,
+                     cur_row, cur_row + num_rows_to_process, dst, width);
+    num_rows -= num_rows_to_process;
+    in += num_rows_to_process * dec->width_;
+    cur_row += num_rows_to_process;
+  }
+  assert(cur_row == last_row);
+  dec->last_row_ = dec->last_out_row_ = last_row;
+}
+
+int VP8LDecodeAlphaHeader(ALPHDecoder* const alph_dec,
+                          const uint8_t* const data, size_t data_size) {
+  int ok = 0;
+  VP8LDecoder* dec = VP8LNew();
+
+  if (dec == NULL) return 0;
+
+  assert(alph_dec != NULL);
+  alph_dec->vp8l_dec_ = dec;
+
+  dec->width_ = alph_dec->width_;
+  dec->height_ = alph_dec->height_;
+  dec->io_ = &alph_dec->io_;
+  dec->io_->opaque = alph_dec;
+  dec->io_->width = alph_dec->width_;
+  dec->io_->height = alph_dec->height_;
+
+  dec->status_ = VP8_STATUS_OK;
+  VP8LInitBitReader(&dec->br_, data, data_size);
+
+  if (!DecodeImageStream(alph_dec->width_, alph_dec->height_, 1, dec, NULL)) {
+    goto Err;
+  }
+
+  // Special case: if alpha data uses only the color indexing transform and
+  // doesn't use color cache (a frequent case), we will use DecodeAlphaData()
+  // method that only needs allocation of 1 byte per pixel (alpha channel).
+  if (dec->next_transform_ == 1 &&
+      dec->transforms_[0].type_ == COLOR_INDEXING_TRANSFORM &&
+      Is8bOptimizable(&dec->hdr_)) {
+    alph_dec->use_8b_decode_ = 1;
+    ok = AllocateInternalBuffers8b(dec);
+  } else {
+    // Allocate internal buffers (note that dec->width_ may have changed here).
+    alph_dec->use_8b_decode_ = 0;
+    ok = AllocateInternalBuffers32b(dec, alph_dec->width_);
+  }
+
+  if (!ok) goto Err;
+
+  return 1;
+
+ Err:
+  VP8LDelete(alph_dec->vp8l_dec_);
+  alph_dec->vp8l_dec_ = NULL;
+  return 0;
+}
+
+int VP8LDecodeAlphaImageStream(ALPHDecoder* const alph_dec, int last_row) {
+  VP8LDecoder* const dec = alph_dec->vp8l_dec_;
+  assert(dec != NULL);
+  assert(last_row <= dec->height_);
+
+  if (dec->last_row_ >= last_row) {
+    return 1;  // done
+  }
+
+  if (!alph_dec->use_8b_decode_) WebPInitAlphaProcessing();
+
+  // Decode (with special row processing).
+  return alph_dec->use_8b_decode_ ?
+      DecodeAlphaData(dec, (uint8_t*)dec->pixels_, dec->width_, dec->height_,
+                      last_row) :
+      DecodeImageData(dec, dec->pixels_, dec->width_, dec->height_,
+                      last_row, ExtractAlphaRows);
+}
+
+//------------------------------------------------------------------------------
+
+int VP8LDecodeHeader(VP8LDecoder* const dec, VP8Io* const io) {
+  int width, height, has_alpha;
+
+  if (dec == NULL) return 0;
+  if (io == NULL) {
+    dec->status_ = VP8_STATUS_INVALID_PARAM;
+    return 0;
+  }
+
+  dec->io_ = io;
+  dec->status_ = VP8_STATUS_OK;
+  VP8LInitBitReader(&dec->br_, io->data, io->data_size);
+  if (!ReadImageInfo(&dec->br_, &width, &height, &has_alpha)) {
+    dec->status_ = VP8_STATUS_BITSTREAM_ERROR;
+    goto Error;
+  }
+  dec->state_ = READ_DIM;
+  io->width = width;
+  io->height = height;
+
+  if (!DecodeImageStream(width, height, 1, dec, NULL)) goto Error;
+  return 1;
+
+ Error:
+  VP8LClear(dec);
+  assert(dec->status_ != VP8_STATUS_OK);
+  return 0;
+}
+
+int VP8LDecodeImage(VP8LDecoder* const dec) {
+  VP8Io* io = NULL;
+  WebPDecParams* params = NULL;
+
+  // Sanity checks.
+  if (dec == NULL) return 0;
+
+  assert(dec->hdr_.huffman_tables_ != NULL);
+  assert(dec->hdr_.htree_groups_ != NULL);
+  assert(dec->hdr_.num_htree_groups_ > 0);
+
+  io = dec->io_;
+  assert(io != NULL);
+  params = (WebPDecParams*)io->opaque;
+  assert(params != NULL);
+
+  // Initialization.
+  if (dec->state_ != READ_DATA) {
+    dec->output_ = params->output;
+    assert(dec->output_ != NULL);
+
+    if (!WebPIoInitFromOptions(params->options, io, MODE_BGRA)) {
+      dec->status_ = VP8_STATUS_INVALID_PARAM;
+      goto Err;
+    }
+
+    if (!AllocateInternalBuffers32b(dec, io->width)) goto Err;
+
+    if (io->use_scaling && !AllocateAndInitRescaler(dec, io)) goto Err;
+
+    if (io->use_scaling || WebPIsPremultipliedMode(dec->output_->colorspace)) {
+      // need the alpha-multiply functions for premultiplied output or rescaling
+      WebPInitAlphaProcessing();
+    }
+    if (!WebPIsRGBMode(dec->output_->colorspace)) {
+      WebPInitConvertARGBToYUV();
+      if (dec->output_->u.YUVA.a != NULL) WebPInitAlphaProcessing();
+    }
+    if (dec->incremental_) {
+      if (dec->hdr_.color_cache_size_ > 0 &&
+          dec->hdr_.saved_color_cache_.colors_ == NULL) {
+        if (!VP8LColorCacheInit(&dec->hdr_.saved_color_cache_,
+                                dec->hdr_.color_cache_.hash_bits_)) {
+          dec->status_ = VP8_STATUS_OUT_OF_MEMORY;
+          goto Err;
+        }
+      }
+    }
+    dec->state_ = READ_DATA;
+  }
+
+  // Decode.
+  if (!DecodeImageData(dec, dec->pixels_, dec->width_, dec->height_,
+                       io->crop_bottom, ProcessRows)) {
+    goto Err;
+  }
+
+  params->last_y = dec->last_out_row_;
+  return 1;
+
+ Err:
+  VP8LClear(dec);
+  assert(dec->status_ != VP8_STATUS_OK);
+  return 0;
+}
+
+//------------------------------------------------------------------------------

+ 135 - 0
Source/ThirdParty/WebP/src/dec/vp8li_dec.h

@@ -0,0 +1,135 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Use of this source code is governed by a BSD-style license
+// that can be found in the COPYING file in the root of the source
+// tree. An additional intellectual property rights grant can be found
+// in the file PATENTS. All contributing project authors may
+// be found in the AUTHORS file in the root of the source tree.
+// -----------------------------------------------------------------------------
+//
+// Lossless decoder: internal header.
+//
+// Author: Skal ([email protected])
+//         Vikas Arora([email protected])
+
+#ifndef WEBP_DEC_VP8LI_H_
+#define WEBP_DEC_VP8LI_H_
+
+#include <string.h>     // for memcpy()
+#include "./webpi_dec.h"
+#include "../utils/bit_reader_utils.h"
+#include "../utils/color_cache_utils.h"
+#include "../utils/huffman_utils.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+  READ_DATA = 0,
+  READ_HDR = 1,
+  READ_DIM = 2
+} VP8LDecodeState;
+
+typedef struct VP8LTransform VP8LTransform;
+struct VP8LTransform {
+  VP8LImageTransformType type_;   // transform type.
+  int                    bits_;   // subsampling bits defining transform window.
+  int                    xsize_;  // transform window X index.
+  int                    ysize_;  // transform window Y index.
+  uint32_t              *data_;   // transform data.
+};
+
+typedef struct {
+  int             color_cache_size_;
+  VP8LColorCache  color_cache_;
+  VP8LColorCache  saved_color_cache_;  // for incremental
+
+  int             huffman_mask_;
+  int             huffman_subsample_bits_;
+  int             huffman_xsize_;
+  uint32_t       *huffman_image_;
+  int             num_htree_groups_;
+  HTreeGroup     *htree_groups_;
+  HuffmanCode    *huffman_tables_;
+} VP8LMetadata;
+
+typedef struct VP8LDecoder VP8LDecoder;
+struct VP8LDecoder {
+  VP8StatusCode    status_;
+  VP8LDecodeState  state_;
+  VP8Io           *io_;
+
+  const WebPDecBuffer *output_;    // shortcut to io->opaque->output
+
+  uint32_t        *pixels_;        // Internal data: either uint8_t* for alpha
+                                   // or uint32_t* for BGRA.
+  uint32_t        *argb_cache_;    // Scratch buffer for temporary BGRA storage.
+
+  VP8LBitReader    br_;
+  int              incremental_;   // if true, incremental decoding is expected
+  VP8LBitReader    saved_br_;      // note: could be local variables too
+  int              saved_last_pixel_;
+
+  int              width_;
+  int              height_;
+  int              last_row_;      // last input row decoded so far.
+  int              last_pixel_;    // last pixel decoded so far. However, it may
+                                   // not be transformed, scaled and
+                                   // color-converted yet.
+  int              last_out_row_;  // last row output so far.
+
+  VP8LMetadata     hdr_;
+
+  int              next_transform_;
+  VP8LTransform    transforms_[NUM_TRANSFORMS];
+  // or'd bitset storing the transforms types.
+  uint32_t         transforms_seen_;
+
+  uint8_t         *rescaler_memory;  // Working memory for rescaling work.
+  WebPRescaler    *rescaler;         // Common rescaler for all channels.
+};
+
+//------------------------------------------------------------------------------
+// internal functions. Not public.
+
+struct ALPHDecoder;  // Defined in dec/alphai.h.
+
+// in vp8l.c
+
+// Decodes image header for alpha data stored using lossless compression.
+// Returns false in case of error.
+int VP8LDecodeAlphaHeader(struct ALPHDecoder* const alph_dec,
+                          const uint8_t* const data, size_t data_size);
+
+// Decodes *at least* 'last_row' rows of alpha. If some of the initial rows are
+// already decoded in previous call(s), it will resume decoding from where it
+// was paused.
+// Returns false in case of bitstream error.
+int VP8LDecodeAlphaImageStream(struct ALPHDecoder* const alph_dec,
+                               int last_row);
+
+// Allocates and initialize a new lossless decoder instance.
+VP8LDecoder* VP8LNew(void);
+
+// Decodes the image header. Returns false in case of error.
+int VP8LDecodeHeader(VP8LDecoder* const dec, VP8Io* const io);
+
+// Decodes an image. It's required to decode the lossless header before calling
+// this function. Returns false in case of error, with updated dec->status_.
+int VP8LDecodeImage(VP8LDecoder* const dec);
+
+// Resets the decoder in its initial state, reclaiming memory.
+// Preserves the dec->status_ value.
+void VP8LClear(VP8LDecoder* const dec);
+
+// Clears and deallocate a lossless decoder instance.
+void VP8LDelete(VP8LDecoder* const dec);
+
+//------------------------------------------------------------------------------
+
+#ifdef __cplusplus
+}    // extern "C"
+#endif
+
+#endif  /* WEBP_DEC_VP8LI_H_ */

Some files were not shown because too many files changed in this diff