From 77b5ddc6fd5c42371c7f575c2547aa78c1755e16 Mon Sep 17 00:00:00 2001 From: "t.artikov" Date: Tue, 2 Aug 2022 01:58:51 +0700 Subject: [PATCH] public release --- .gitignore | 14 + .gitmodules | 6 + LICENSE | 21 + README.md | 12 + app/.gitignore | 1 + app/build.gradle | 55 + app/proguard-rules.pro | 21 + app/src/main/AndroidManifest.xml | 24 + app/src/main/assets/grid.fs | 96 + app/src/main/assets/grid.vs | 13 + app/src/main/assets/map.fs | 51 + app/src/main/assets/map.vs | 13 + .../java/me/tartikov/neuromap/Application.kt | 11 + .../java/me/tartikov/neuromap/AssetUtils.kt | 38 + .../me/tartikov/neuromap/CameraController.kt | 109 + .../java/me/tartikov/neuromap/CameraState.kt | 24 + .../me/tartikov/neuromap/EncodingSettings.kt | 44 + .../java/me/tartikov/neuromap/FpsMeasurer.kt | 24 + .../main/java/me/tartikov/neuromap/GLUtils.kt | 57 + .../java/me/tartikov/neuromap/GridSettings.kt | 5 + .../java/me/tartikov/neuromap/GridShader.kt | 49 + app/src/main/java/me/tartikov/neuromap/Log.kt | 3 + .../java/me/tartikov/neuromap/MainActivity.kt | 11 + .../java/me/tartikov/neuromap/MapRenderer.kt | 82 + .../java/me/tartikov/neuromap/MapShader.kt | 32 + .../main/java/me/tartikov/neuromap/MapView.kt | 94 + .../me/tartikov/neuromap/NetworkSettings.kt | 10 + .../main/java/me/tartikov/neuromap/Quad.kt | 57 + .../main/java/me/tartikov/neuromap/Rect.kt | 10 + .../java/me/tartikov/neuromap/RenderTarget.kt | 69 + .../me/tartikov/neuromap/TextureBuffer.kt | 44 + .../res/drawable/ic_launcher_background.xml | 10 + .../res/drawable/ic_launcher_foreground.xml | 59 + app/src/main/res/layout/activity_main.xml | 12 + app/src/main/res/layout/map_view.xml | 38 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3319 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2084 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4682 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7435 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10351 bytes .../res/values/ic_launcher_background.xml | 4 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/themes.xml | 5 + build.gradle | 17 + gradle.properties | 23 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 185 + gradlew.bat | 89 + screenshot.png | Bin 0 -> 198398 bytes settings.gradle | 16 + tools/CMakeLists.txt | 12 + tools/cmake/cuda.cmake | 11 + tools/inference/CMakeLists.txt | 13 + tools/inference/hf.cpp | 152 + tools/inference/hf.h | 4 + tools/inference/main.cpp | 503 ++ tools/thirdparty/CMakeLists.txt | 15 + tools/thirdparty/cxxopts | 1 + tools/thirdparty/stbi/CMakeLists.txt | 5 + tools/thirdparty/stbi/include/stb_image.h | 7897 +++++++++++++++++ .../thirdparty/stbi/include/stb_image_write.h | 1724 ++++ tools/thirdparty/stbi/src/stbi.cpp | 5 + tools/thirdparty/tiny-cuda-nn | 1 + tools/tiles_to_image/CMakeLists.txt | 3 + tools/tiles_to_image/src/main.cpp | 222 + tools/trainer/Accuracy.cu | 72 + tools/trainer/Accuracy.h | 11 + tools/trainer/CMakeLists.txt | 21 + tools/trainer/CombinedTilesSampler.cu | 24 + tools/trainer/CombinedTilesSampler.h | 17 + tools/trainer/Common.h | 29 + tools/trainer/DetailedTilesSampler.cu | 129 + tools/trainer/DetailedTilesSampler.h | 71 + tools/trainer/EmptyTilesSampler.cu | 84 + tools/trainer/EmptyTilesSampler.h | 18 + tools/trainer/HostMemory.h | 74 + tools/trainer/Model.cu | 110 + tools/trainer/Model.h | 28 + tools/trainer/SoftmaxCrossEntropyLoss.h | 114 + tools/trainer/ThreadSafeQueue.h | 35 + tools/trainer/config.json | 34 + tools/trainer/main.cu | 84 + 83 files changed, 13090 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 LICENSE create mode 100644 README.md create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/assets/grid.fs create mode 100644 app/src/main/assets/grid.vs create mode 100644 app/src/main/assets/map.fs create mode 100644 app/src/main/assets/map.vs create mode 100644 app/src/main/java/me/tartikov/neuromap/Application.kt create mode 100644 app/src/main/java/me/tartikov/neuromap/AssetUtils.kt create mode 100644 app/src/main/java/me/tartikov/neuromap/CameraController.kt create mode 100644 app/src/main/java/me/tartikov/neuromap/CameraState.kt create mode 100644 app/src/main/java/me/tartikov/neuromap/EncodingSettings.kt create mode 100644 app/src/main/java/me/tartikov/neuromap/FpsMeasurer.kt create mode 100644 app/src/main/java/me/tartikov/neuromap/GLUtils.kt create mode 100644 app/src/main/java/me/tartikov/neuromap/GridSettings.kt create mode 100644 app/src/main/java/me/tartikov/neuromap/GridShader.kt create mode 100644 app/src/main/java/me/tartikov/neuromap/Log.kt create mode 100644 app/src/main/java/me/tartikov/neuromap/MainActivity.kt create mode 100644 app/src/main/java/me/tartikov/neuromap/MapRenderer.kt create mode 100644 app/src/main/java/me/tartikov/neuromap/MapShader.kt create mode 100644 app/src/main/java/me/tartikov/neuromap/MapView.kt create mode 100644 app/src/main/java/me/tartikov/neuromap/NetworkSettings.kt create mode 100644 app/src/main/java/me/tartikov/neuromap/Quad.kt create mode 100644 app/src/main/java/me/tartikov/neuromap/Rect.kt create mode 100644 app/src/main/java/me/tartikov/neuromap/RenderTarget.kt create mode 100644 app/src/main/java/me/tartikov/neuromap/TextureBuffer.kt create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/map_view.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/values/ic_launcher_background.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 screenshot.png create mode 100644 settings.gradle create mode 100644 tools/CMakeLists.txt create mode 100644 tools/cmake/cuda.cmake create mode 100644 tools/inference/CMakeLists.txt create mode 100644 tools/inference/hf.cpp create mode 100644 tools/inference/hf.h create mode 100644 tools/inference/main.cpp create mode 100644 tools/thirdparty/CMakeLists.txt create mode 160000 tools/thirdparty/cxxopts create mode 100644 tools/thirdparty/stbi/CMakeLists.txt create mode 100644 tools/thirdparty/stbi/include/stb_image.h create mode 100644 tools/thirdparty/stbi/include/stb_image_write.h create mode 100644 tools/thirdparty/stbi/src/stbi.cpp create mode 160000 tools/thirdparty/tiny-cuda-nn create mode 100644 tools/tiles_to_image/CMakeLists.txt create mode 100644 tools/tiles_to_image/src/main.cpp create mode 100644 tools/trainer/Accuracy.cu create mode 100644 tools/trainer/Accuracy.h create mode 100644 tools/trainer/CMakeLists.txt create mode 100644 tools/trainer/CombinedTilesSampler.cu create mode 100644 tools/trainer/CombinedTilesSampler.h create mode 100644 tools/trainer/Common.h create mode 100644 tools/trainer/DetailedTilesSampler.cu create mode 100644 tools/trainer/DetailedTilesSampler.h create mode 100644 tools/trainer/EmptyTilesSampler.cu create mode 100644 tools/trainer/EmptyTilesSampler.h create mode 100644 tools/trainer/HostMemory.h create mode 100644 tools/trainer/Model.cu create mode 100644 tools/trainer/Model.h create mode 100644 tools/trainer/SoftmaxCrossEntropyLoss.h create mode 100644 tools/trainer/ThreadSafeQueue.h create mode 100644 tools/trainer/config.json create mode 100644 tools/trainer/main.cu diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7375b84 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +tools/build +/captures +.externalNativeBuild +.cxx +local.properties +*CMakeLists.txt.user +app/src/main/assets/encoding.data +app/src/main/assets/network.data \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..2f4e2d4 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "tools/thirdparty/cxxopts"] + path = tools/thirdparty/cxxopts + url = https://github.com/jarro2783/cxxopts.git +[submodule "tools/thirdparty/tiny-cuda-nn"] + path = tools/thirdparty/tiny-cuda-nn + url = https://github.com/t-artikov/tiny-cuda-nn.git diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..14636b9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Timur Artikov + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..1f311a1 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +## Map rendering using a neural network + +Based on the great article [Instant Neural Graphics Primitives with a Multiresolution Hash Encoding](https://nvlabs.github.io/instant-ngp/assets/mueller2022instant.pdf). + +![map](https://github.com/t-artikov/neuromap/blob/master/screenshot.png) + +The repository consists of: + +- **tools/trainer** - a program for training a neural network on raster tiles. +- **tools/tiles_to_image** - converts tiles from custom format to PNG image (for testing purposes). +- **tools/inference** - generates an image from a trained neural network (for testing purposes). +- **app** - An Android application that displays a neural map in real time. \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..7b7b284 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,55 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' +} + +android { + compileSdk 32 + + defaultConfig { + applicationId "me.tartikov.neuromap" + minSdk 21 + targetSdk 32 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + signingConfig signingConfigs.debug + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + aaptOptions { + noCompress 'network.data', 'encoding.data' + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.4.2' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0' + implementation 'com.otaliastudios:zoomlayout:1.9.0' +} + +preBuild.doFirst { + def files = [ + "./src/main/assets/encoding.data", + "./src/main/assets/network.data" + ] + for(f in files) { + if (!file(f).exists()) { + throw new GradleException("$f is missing") + } + } +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..f4dcb68 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/grid.fs b/app/src/main/assets/grid.fs new file mode 100644 index 0000000..54a94d7 --- /dev/null +++ b/app/src/main/assets/grid.fs @@ -0,0 +1,96 @@ +#version 310 es +#extension GL_EXT_texture_buffer : require + +precision highp float; +precision highp int; + +in vec2 v_position; + +uniform highp samplerBuffer u_encodingTexture; + +uniform int u_level; +uniform float u_levelOpacity; + +const int encodingLevelCount = 12; +const int encodingFeaturesPerLevel = 4; +const int encodingCount = encodingLevelCount * encodingFeaturesPerLevel; +const int encodingPackedCount = encodingCount / 4; +uniform int u_encodingResolutions[encodingLevelCount]; +uniform int u_encodingParamCounts[encodingLevelCount]; +uniform int u_encodingParamOffsets[encodingLevelCount]; + +const int networkInputCount = encodingCount; +const int networkInputPackedCount = networkInputCount / 4; +const int networkHiddenCount = 8; +const int networkHiddenPackedCount = networkHiddenCount / 4; +const int networkWeightCount = networkInputCount * networkHiddenCount; +const int networkWeightPackedCount = networkWeightCount / 4; +uniform vec4 u_networkWeights[networkWeightPackedCount]; + +layout(location = 0) out vec4 f_color0; +layout(location = 1) out vec4 f_color1; + + +uint hash(uint x, uint y, uint resolution, uint paramCount) { + if (resolution * resolution > paramCount) { + return (y * 2654435761u) ^ x; + } else { + return y * resolution + x; + } +} + +vec4 getEncoding(int x, int y, int level) { + uint resolution = uint(u_encodingResolutions[level]); + uint paramCount = uint(u_encodingParamCounts[level]); + uint paramOffset = uint(u_encodingParamOffsets[level]); + + uint index = hash(uint(x), uint(y), resolution, paramCount) % paramCount; + return texelFetch(u_encodingTexture, int(index + paramOffset)); +} + +vec4 getEncodingInterpolated(vec2 position, int level) { + if (level > u_level) { + return vec4(0.0); + } + int resolution = u_encodingResolutions[level]; + vec2 scaledPosition = position * float(resolution - 1); + vec2 intPosition; + vec2 fracPosition = modf(scaledPosition, intPosition); + int ix = int(intPosition.x); + int iy = int(intPosition.y); + + vec4 v00 = getEncoding(ix, iy, level); + vec4 v10 = getEncoding(ix + 1, iy, level); + vec4 v01 = getEncoding(ix, iy + 1, level); + vec4 v11 = getEncoding(ix + 1, iy + 1, level); + + vec4 v0 = mix(v00, v10, fracPosition.x); + vec4 v1 = mix(v01, v11, fracPosition.x); + return mix(v0, v1, fracPosition.y); +} + +void main() { + vec2 position = clamp(v_position, vec2(0.0), vec2(1.0)); + + vec4 encodings[encodingPackedCount]; + for (int i = 0; i < encodingLevelCount; i++) { + encodings[i] = getEncodingInterpolated(position, i); + } + encodings[u_level] *= u_levelOpacity; + + vec4 hidden[networkHiddenPackedCount]; + for (int i = 0; i < networkHiddenPackedCount; i++) { + hidden[i] = vec4(0.0); + for (int j = 0; j < networkInputPackedCount; j++) { + vec4 inputValue = encodings[j]; + hidden[i] += vec4( + dot(inputValue, u_networkWeights[(i * 4) * networkInputPackedCount + j]), + dot(inputValue, u_networkWeights[(i * 4 + 1) * networkInputPackedCount + j]), + dot(inputValue, u_networkWeights[(i * 4 + 2) * networkInputPackedCount + j]), + dot(inputValue, u_networkWeights[(i * 4 + 3) * networkInputPackedCount + j]) + ); + } + } + f_color0 = hidden[0]; + f_color1 = hidden[1]; +} diff --git a/app/src/main/assets/grid.vs b/app/src/main/assets/grid.vs new file mode 100644 index 0000000..54a2c4b --- /dev/null +++ b/app/src/main/assets/grid.vs @@ -0,0 +1,13 @@ +#version 310 es + +precision highp float; + +in vec2 a_position; +out vec2 v_position; + +uniform vec4 u_gridRect; + +void main() { + gl_Position = vec4(a_position * 2.0 - vec2(1.0, 1.0), 0.0, 1.0); + v_position = a_position * u_gridRect.zw + u_gridRect.xy; +} diff --git a/app/src/main/assets/map.fs b/app/src/main/assets/map.fs new file mode 100644 index 0000000..5b1c2b3 --- /dev/null +++ b/app/src/main/assets/map.fs @@ -0,0 +1,51 @@ +#version 310 es + +precision highp float; + +in vec2 v_position; + +const int gridTextureCount = 2; +uniform highp sampler2D u_gridTextures[gridTextureCount]; + +const int networkHiddenCount = 8; +const int networkOutputCount = 4; +const int networkWeightCount = networkHiddenCount * networkOutputCount; + +const int networkHiddenPackedCount = networkHiddenCount / 4; +const int networkWeightPackedCount = networkWeightCount / 4; + +uniform vec4 u_networkWeights[networkWeightPackedCount]; + +out vec4 f_color; + +vec4 get_color(vec4 v) { + const vec4 r = vec4(246.0/255.0, 242.0/255.0, 217.0/255.0, 1.0); + const vec4 g = vec4(209.0/255.0, 230.0/255.0, 161.0/255.0, 1.0); + const vec4 b = vec4(164.0/255.0, 216.0/255.0, 235.0/255.0, 1.0); + if (v.r > v.g) { + if (v.r > v.b) return r; + else return b; + } + else { + if (v.g > v.b) return g; + else return b; + } +} + +void main() { + vec4 hidden[networkHiddenPackedCount]; + hidden[0] = texture(u_gridTextures[0], v_position); + hidden[1] = texture(u_gridTextures[1], v_position); + + vec4 outputs = vec4(0.0); + for (int j = 0; j < networkHiddenPackedCount; j++) { + vec4 inputValue = tanh(hidden[j]); + outputs += vec4( + dot(inputValue, u_networkWeights[j]), + dot(inputValue, u_networkWeights[networkHiddenPackedCount + j]), + dot(inputValue, u_networkWeights[2 * networkHiddenPackedCount + j]), + dot(inputValue, u_networkWeights[3 * networkHiddenPackedCount + j]) + ); + } + f_color = get_color(outputs); +} diff --git a/app/src/main/assets/map.vs b/app/src/main/assets/map.vs new file mode 100644 index 0000000..5e1d521 --- /dev/null +++ b/app/src/main/assets/map.vs @@ -0,0 +1,13 @@ +#version 310 es + +precision highp float; + +in vec2 a_position; +out vec2 v_position; + +uniform vec4 u_screenSpaceGridRect; + +void main() { + gl_Position = vec4((a_position * 2.0 - vec2(1.0)) * vec2(1.0, -1.0), 0.0, 1.0); + v_position = a_position * u_screenSpaceGridRect.zw + u_screenSpaceGridRect.xy; +} diff --git a/app/src/main/java/me/tartikov/neuromap/Application.kt b/app/src/main/java/me/tartikov/neuromap/Application.kt new file mode 100644 index 0000000..da47f42 --- /dev/null +++ b/app/src/main/java/me/tartikov/neuromap/Application.kt @@ -0,0 +1,11 @@ +package me.tartikov.neuromap + +class Application : android.app.Application() { + init { + instance = this + } + companion object { + lateinit var instance: Application + private set + } +} \ No newline at end of file diff --git a/app/src/main/java/me/tartikov/neuromap/AssetUtils.kt b/app/src/main/java/me/tartikov/neuromap/AssetUtils.kt new file mode 100644 index 0000000..83231f4 --- /dev/null +++ b/app/src/main/java/me/tartikov/neuromap/AssetUtils.kt @@ -0,0 +1,38 @@ +package me.tartikov.neuromap + +import java.io.BufferedReader +import java.io.FileInputStream +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.FloatBuffer +import java.nio.ShortBuffer +import java.nio.channels.FileChannel + +fun loadStringFromAsset(fileName: String): String { + return Application.instance.assets + .open(fileName).bufferedReader().use(BufferedReader::readText) +} + +fun loadBufferFromAsset(fileName: String): ByteBuffer { + Application.instance.assets.openFd(fileName).use { descriptor -> + FileInputStream(descriptor.fileDescriptor).use { stream -> + return stream.channel.map( + FileChannel.MapMode.READ_ONLY, + descriptor.startOffset, + descriptor.declaredLength + ) + } + } +} + +fun loadFloatBufferFromAsset(fileName: String): FloatBuffer { + return loadBufferFromAsset(fileName).apply { + order(ByteOrder.nativeOrder()) + }.asFloatBuffer() +} + +fun loadShortBufferFromAsset(fileName: String): ShortBuffer { + return loadBufferFromAsset(fileName).apply { + order(ByteOrder.nativeOrder()) + }.asShortBuffer() +} diff --git a/app/src/main/java/me/tartikov/neuromap/CameraController.kt b/app/src/main/java/me/tartikov/neuromap/CameraController.kt new file mode 100644 index 0000000..b7aaec4 --- /dev/null +++ b/app/src/main/java/me/tartikov/neuromap/CameraController.kt @@ -0,0 +1,109 @@ +package me.tartikov.neuromap + +import android.content.Context +import android.graphics.Matrix +import android.util.DisplayMetrics +import android.view.MotionEvent +import android.view.View +import android.view.WindowManager +import com.otaliastudios.zoom.ZoomEngine +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import java.lang.RuntimeException +import kotlin.math.floor +import kotlin.math.max +import kotlin.math.min + +class CameraController(view: View) { + private val zoomEngine = ZoomEngine(view.context, view) + + private val _cameraState = MutableStateFlow(CameraState.DEFAULT) + val cameraState: StateFlow = _cameraState + + init { + val size = getScreenSize(view.context) + zoomEngine.apply { + setOverScrollHorizontal(false) + setOverScrollVertical(false) + setOverPinchable(false) + setContentSize(size, size) + setMaxZoom(20000.0f) + setMinZoom(2.0f) + setTwoFingersScrollEnabled(false) + addListener(object : ZoomEngine.Listener { + override fun onIdle(engine: ZoomEngine) { + + } + + override fun onUpdate(engine: ZoomEngine, matrix: Matrix) { + val rect = getCameraRect(engine) + _cameraState.value = calculateCameraState(rect) + } + }) + } + } + + fun onTouchEvent(event: MotionEvent): Boolean { + return zoomEngine.onTouchEvent(event) + } +} + +private fun getCameraRect(engine: ZoomEngine): Rect { + val w = engine.containerWidth.toDouble() / engine.contentWidth / engine.realZoom + val h = engine.containerHeight.toDouble() / engine.contentHeight / engine.realZoom + val x = -engine.scaledPanX.toDouble() / engine.containerWidth * w + val y = -engine.scaledPanY.toDouble() / engine.containerHeight * h + return Rect(x, y, w, h) +} + +@Suppress("DEPRECATION") +private fun getScreenSize(context: Context): Float { + val metrics = DisplayMetrics() + val windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager + windowManager.defaultDisplay.getMetrics(metrics) + return min(metrics.widthPixels, metrics.heightPixels).toFloat() +} + +private fun getRelativeRect(rect: Rect, basis: Rect): Rect { + val x = (rect.x - basis.x) / basis.w + val y = (rect.y - basis.y) / basis.h + val w = rect.w / basis.w + val h = rect.h / basis.h + return Rect(x, y, w, h) +} + +private fun calculateCameraState(cameraRect: Rect): CameraState { + val lowestLevel = 4 + for (level in EncodingSettings.levelCount - 1 downTo lowestLevel) { + val resolution = EncodingSettings.resolutions[level] + val tileSize = 1.0 / (resolution - 1) + + fun snapToTile(v: Double): Double { + val iv = floor(v * (resolution - 1)) + return max(0.0, iv * tileSize) + } + + val x = snapToTile(cameraRect.x) + val y = snapToTile(cameraRect.y) + val w = (GridSettings.size - 1) * tileSize + val h = (GridSettings.size - 1) * tileSize + + if (x + w > cameraRect.right && y + h > cameraRect.bottom || level == lowestLevel) { + val snappedRect = + Rect(x - tileSize / 2.0, y - tileSize / 2.0, w + tileSize, h + tileSize) + val opacity = if (level != lowestLevel) { + (min(w / cameraRect.w, h / cameraRect.h) - 1.0).coerceIn(0.0, 1.0) + } else { + 1.0 + } + return CameraState( + cameraRect, + snappedRect, + getRelativeRect(cameraRect, snappedRect), + level, + opacity.toFloat() + ) + } + } + throw RuntimeException("Unreachable") +} diff --git a/app/src/main/java/me/tartikov/neuromap/CameraState.kt b/app/src/main/java/me/tartikov/neuromap/CameraState.kt new file mode 100644 index 0000000..8f7c937 --- /dev/null +++ b/app/src/main/java/me/tartikov/neuromap/CameraState.kt @@ -0,0 +1,24 @@ +package me.tartikov.neuromap + +import android.util.Size +import kotlin.math.ceil +import kotlin.math.min + +data class CameraState( + val rect: Rect, + val gridRect: Rect, + val screenSpaceGridRect: Rect, + val level: Int, + val levelOpacity: Float +) { + val activeGridSize: Size + get() { + val w = min(ceil(screenSpaceGridRect.right * GridSettings.size).toInt() + 1, GridSettings.size) + val h = min(ceil(screenSpaceGridRect.bottom * GridSettings.size).toInt() + 1, GridSettings.size) + return Size(w, h) + } + + companion object { + val DEFAULT = CameraState(Rect.WORLD, Rect.WORLD, Rect(0.0, 0.0, 1.0, 1.0), 0, 1.0f) + } +} \ No newline at end of file diff --git a/app/src/main/java/me/tartikov/neuromap/EncodingSettings.kt b/app/src/main/java/me/tartikov/neuromap/EncodingSettings.kt new file mode 100644 index 0000000..8aad206 --- /dev/null +++ b/app/src/main/java/me/tartikov/neuromap/EncodingSettings.kt @@ -0,0 +1,44 @@ +package me.tartikov.neuromap + +import kotlin.math.min + +object EncodingSettings { + const val levelCount = 12 + const val featurePerLevel = 4 + private const val baseResolution = 32 + private const val hashSize = 1 shl 22 + + val resolutions: IntArray = run { + var resolution = baseResolution + IntArray(levelCount) { + resolution.also { + resolution = (resolution - 1) * 2 + 1 + } + } + } + + val paramCounts: IntArray = resolutions.map { + if (it.toLong() * it.toLong() > Int.MAX_VALUE) { + return@map hashSize + } + min(nextMultiple(it * it, 8), hashSize) + }.toIntArray() + + val paramOffsets: IntArray = run { + var offset = 0 + IntArray(levelCount) { index -> + offset.also { + offset += paramCounts[index] + } + } + } +} + +private fun divRoundUp(value: Int, divisor: Int): Int { + return (value + divisor - 1) / divisor +} + +@Suppress("SameParameterValue") +private fun nextMultiple(value: Int, divisor: Int): Int { + return divRoundUp(value, divisor) * divisor +} diff --git a/app/src/main/java/me/tartikov/neuromap/FpsMeasurer.kt b/app/src/main/java/me/tartikov/neuromap/FpsMeasurer.kt new file mode 100644 index 0000000..1c18c79 --- /dev/null +++ b/app/src/main/java/me/tartikov/neuromap/FpsMeasurer.kt @@ -0,0 +1,24 @@ +package me.tartikov.neuromap + +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlin.math.roundToInt + +class FpsMeasurer { + private val _fps = MutableStateFlow(null) + val fps: StateFlow = _fps + + private var startTime = System.currentTimeMillis() + private var frames: Long = 0 + + fun frame() { + frames++ + } + + fun flush() { + val time = System.currentTimeMillis() + _fps.value = ((frames * 1000.0) / (time - startTime)).roundToInt() + startTime = System.currentTimeMillis() + frames = 0 + } +} \ No newline at end of file diff --git a/app/src/main/java/me/tartikov/neuromap/GLUtils.kt b/app/src/main/java/me/tartikov/neuromap/GLUtils.kt new file mode 100644 index 0000000..f0c65c1 --- /dev/null +++ b/app/src/main/java/me/tartikov/neuromap/GLUtils.kt @@ -0,0 +1,57 @@ +package me.tartikov.neuromap + +import android.opengl.GLES20.* +import android.opengl.GLU.gluErrorString + +fun checkGlError() { + val error = glGetError() + if (error != GL_NO_ERROR) { + throw RuntimeException("OpenGL error: ${gluErrorString(error)}") + } +} + +private fun loadShaderFromAsset(type: Int, fileName: String): Int { + val shader = glCreateShader(type) + glShaderSource(shader, loadStringFromAsset(fileName)) + glCompileShader(shader) + + val status = IntArray(1) + glGetShaderiv(shader, GL_COMPILE_STATUS, status, 0) + if (status[0] != GL_TRUE) { + throw RuntimeException("Failed to compile '$fileName':\n${glGetShaderInfoLog(shader)}") + } + + checkGlError() + return shader +} + +fun loadProgramFromAssets(vsName: String, fsName: String): Int { + val vs = loadShaderFromAsset(GL_VERTEX_SHADER, vsName) + val fs = loadShaderFromAsset(GL_FRAGMENT_SHADER,fsName) + + val program = glCreateProgram().also { + glAttachShader(it, vs) + glAttachShader(it, fs) + glLinkProgram(it) + } + + val status = IntArray(1) + glGetProgramiv(program, GL_LINK_STATUS, status, 0) + if (status[0] != GL_TRUE) { + throw RuntimeException("Failed to link '$vsName', '$fsName':\n${glGetProgramInfoLog(program)}") + } + + checkGlError() + return program +} + +fun getGlInt(name: Int): Int { + val values = IntArray(1) + glGetIntegerv(name, values, 0) + checkGlError() + return values[0] +} + +fun glUniformRect(location: Int, rect: Rect) = rect.apply { + glUniform4f(location, x.toFloat(), y.toFloat(), w.toFloat(), h.toFloat()) +} \ No newline at end of file diff --git a/app/src/main/java/me/tartikov/neuromap/GridSettings.kt b/app/src/main/java/me/tartikov/neuromap/GridSettings.kt new file mode 100644 index 0000000..9768960 --- /dev/null +++ b/app/src/main/java/me/tartikov/neuromap/GridSettings.kt @@ -0,0 +1,5 @@ +package me.tartikov.neuromap + +object GridSettings { + const val size = 512 +} diff --git a/app/src/main/java/me/tartikov/neuromap/GridShader.kt b/app/src/main/java/me/tartikov/neuromap/GridShader.kt new file mode 100644 index 0000000..02ddc2e --- /dev/null +++ b/app/src/main/java/me/tartikov/neuromap/GridShader.kt @@ -0,0 +1,49 @@ +package me.tartikov.neuromap + +import android.opengl.GLES20.* + +class GridShader { + private val program = loadProgramFromAssets("grid.vs", "grid.fs") + private val positionAttribute = glGetAttribLocation(program, "a_position") + private val encodingTexture = TextureBuffer(loadShortBufferFromAsset("encoding.data").also { + val expectedSize = EncodingSettings.paramCounts.sum() * EncodingSettings.featurePerLevel + check(it.capacity() == expectedSize) { + "Invalid encoding data size. Actual: ${it.capacity()}, expected: $expectedSize" + } + }) + private val gridRectUniform = glGetUniformLocation(program, "u_gridRect") + private val levelUniform = glGetUniformLocation(program, "u_level") + private val levelOpacityUniform = glGetUniformLocation(program, "u_levelOpacity") + + init { + val weights = loadFloatBufferFromAsset("network.data") + val weightsUniform = glGetUniformLocation(program, "u_networkWeights") + val encodingTextureUniform = glGetUniformLocation(program, "u_encodingTexture") + val encodingResolutionsUniform = glGetUniformLocation(program, "u_encodingResolutions") + val encodingParamCountsUniform = glGetUniformLocation(program, "u_encodingParamCounts") + val encodingParamOffsetsUniform = glGetUniformLocation(program, "u_encodingParamOffsets") + + glUseProgram(program) + glUniform4fv(weightsUniform, NetworkSettings.weightCount0 / 4, weights) + glUniform1iv(encodingResolutionsUniform, EncodingSettings.resolutions.size, EncodingSettings.resolutions, 0) + glUniform1iv(encodingParamCountsUniform, EncodingSettings.paramCounts.size, EncodingSettings.paramCounts, 0) + glUniform1iv(encodingParamOffsetsUniform, EncodingSettings.paramOffsets.size, EncodingSettings.paramOffsets, 0) + glUniform1i(encodingTextureUniform, 0) + glUseProgram(0) + checkGlError() + } + + fun draw(quad: Quad, cameraState: CameraState) { + cameraState.activeGridSize.let { + glEnable(GL_SCISSOR_TEST) + glScissor(0, 0, it.width, it.height) + } + glUseProgram(program) + glUniformRect(gridRectUniform, cameraState.gridRect) + glUniform1i(levelUniform, cameraState.level) + glUniform1f(levelOpacityUniform, cameraState.levelOpacity) + encodingTexture.bind() + quad.draw(positionAttribute) + glDisable(GL_SCISSOR_TEST) + } +} diff --git a/app/src/main/java/me/tartikov/neuromap/Log.kt b/app/src/main/java/me/tartikov/neuromap/Log.kt new file mode 100644 index 0000000..ead8214 --- /dev/null +++ b/app/src/main/java/me/tartikov/neuromap/Log.kt @@ -0,0 +1,3 @@ +package me.tartikov.neuromap + +const val LOG_TAG: String = "NeuroMap" \ No newline at end of file diff --git a/app/src/main/java/me/tartikov/neuromap/MainActivity.kt b/app/src/main/java/me/tartikov/neuromap/MainActivity.kt new file mode 100644 index 0000000..e4eec14 --- /dev/null +++ b/app/src/main/java/me/tartikov/neuromap/MainActivity.kt @@ -0,0 +1,11 @@ +package me.tartikov.neuromap + +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle + +class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + } +} diff --git a/app/src/main/java/me/tartikov/neuromap/MapRenderer.kt b/app/src/main/java/me/tartikov/neuromap/MapRenderer.kt new file mode 100644 index 0000000..3c641e4 --- /dev/null +++ b/app/src/main/java/me/tartikov/neuromap/MapRenderer.kt @@ -0,0 +1,82 @@ +package me.tartikov.neuromap + +import android.opengl.GLES20.* +import android.opengl.GLSurfaceView +import android.util.Size +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import java.lang.Exception +import javax.microedition.khronos.egl.EGLConfig +import javax.microedition.khronos.opengles.GL10 + +class MapRenderer( + mainCoroutineScope: CoroutineScope, + private val runOnRenderThread: (() -> Unit) -> Unit +) : GLSurfaceView.Renderer { + + private lateinit var grid: RenderTarget + private lateinit var quad: Quad + private lateinit var gridShader: GridShader + private lateinit var mapShader: MapShader + private var viewportSize = Size(0, 0) + + private val _error = MutableStateFlow(null) + val error: StateFlow = _error + + private val fpsMeasurer = FpsMeasurer() + val fps: StateFlow = fpsMeasurer.fps + + private var cameraState = CameraState.DEFAULT + + init { + mainCoroutineScope.launch { + while (true) { + delay(2000) + runOnRenderThread { + fpsMeasurer.flush() + } + } + } + } + + override fun onSurfaceCreated(unused: GL10, config: EGLConfig) { + glClearColor(0.0f, 0.0f, 0.0f, 1.0f) + try { + grid = RenderTarget(Size(GridSettings.size, GridSettings.size), 2) + quad = Quad() + gridShader = GridShader() + mapShader = MapShader() + } catch (e: Exception) { + _error.value = e + } + } + + override fun onDrawFrame(unused: GL10) { + if (_error.value != null) { + return + } + if (viewportSize.width == 0 || viewportSize.height == 0) { + return + } + + grid.drawInto { + gridShader.draw(quad, cameraState) + } + + glViewport(0, 0, viewportSize.width, viewportSize.height) + mapShader.draw(quad, grid, cameraState) + + fpsMeasurer.frame() + } + + override fun onSurfaceChanged(unused: GL10, width: Int, height: Int) { + viewportSize = Size(width, height) + } + + fun setCameraState(value: CameraState) { + cameraState = value + } +} diff --git a/app/src/main/java/me/tartikov/neuromap/MapShader.kt b/app/src/main/java/me/tartikov/neuromap/MapShader.kt new file mode 100644 index 0000000..62b11e1 --- /dev/null +++ b/app/src/main/java/me/tartikov/neuromap/MapShader.kt @@ -0,0 +1,32 @@ +package me.tartikov.neuromap + +import android.opengl.GLES20.* + +class MapShader { + private val program = loadProgramFromAssets("map.vs", "map.fs") + private val positionAttribute = glGetAttribLocation(program, "a_position") + private val screenSpaceGridRectUniform = glGetUniformLocation(program, "u_screenSpaceGridRect") + + init { + val gridTexturesUniform = glGetUniformLocation(program, "u_gridTextures") + val gridTextureSlots = IntArray(2) { it } + val weights = loadFloatBufferFromAsset("network.data").apply { + position(NetworkSettings.weightCount0) + } + val weightsUniform = glGetUniformLocation(program, "u_networkWeights") + + glUseProgram(program) + glUniform1iv(gridTexturesUniform, gridTextureSlots.size, gridTextureSlots, 0) + glUniform4fv(weightsUniform, NetworkSettings.weightCount1 / 4, weights) + glUseProgram(0) + checkGlError() + } + + fun draw(quad: Quad, grid: RenderTarget, cameraState: CameraState) { + glUseProgram(program) + grid.bindTextures() + glUniformRect(screenSpaceGridRectUniform, cameraState.screenSpaceGridRect) + quad.draw(positionAttribute) + grid.unbindTextures() + } +} diff --git a/app/src/main/java/me/tartikov/neuromap/MapView.kt b/app/src/main/java/me/tartikov/neuromap/MapView.kt new file mode 100644 index 0000000..25f04a6 --- /dev/null +++ b/app/src/main/java/me/tartikov/neuromap/MapView.kt @@ -0,0 +1,94 @@ +package me.tartikov.neuromap + +import android.annotation.SuppressLint +import android.content.Context +import android.opengl.GLSurfaceView +import android.text.method.ScrollingMovementMethod +import android.util.AttributeSet +import android.util.Log +import android.view.MotionEvent +import android.widget.FrameLayout +import android.widget.TextView +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch + +class MapView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyle: Int = 0 +) : FrameLayout(context, attrs, defStyle) { + + private val coroutineScope = MainScope() + private val glView: GLSurfaceView + private val fpsView: TextView + private val errorView: TextView + private val renderer: MapRenderer + private val cameraController: CameraController = CameraController(this) + + init { + inflate(context, R.layout.map_view, this) + + glView = findViewById(R.id.glView).apply { + setEGLContextClientVersion(3) + setEGLConfigChooser(8 , 8, 8, 0, 0, 0) + } + fpsView = findViewById(R.id.fpsView) + errorView = findViewById(R.id.errorView).apply { + movementMethod = ScrollingMovementMethod() + } + + renderer = MapRenderer(coroutineScope, ::runOnRenderThread).also { + glView.setRenderer(it) + } + + showErrors() + showFps() + initCameraController() + } + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent): Boolean { + return cameraController.onTouchEvent(event) || super.onTouchEvent(event) + } + + private fun showErrors() { + coroutineScope.launch { + renderer.error.collect { + if (it != null) { + Log.w(LOG_TAG, "Rendering error", it) + errorView.text = it.stackTraceToString() + errorView.visibility = VISIBLE + } + errorView.visibility = if (it != null) VISIBLE else GONE + } + } + } + + private fun showFps() { + coroutineScope.launch { + renderer.fps.collect { + fpsView.text = it?.toString() ?: "" + } + } + } + + private fun initCameraController() { + coroutineScope.launch { + cameraController.cameraState.collect { + runOnRenderThread { + renderer.setCameraState(it) + } + } + } + } + + private fun runOnRenderThread(action: () -> Unit) { + glView.queueEvent(action) + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + coroutineScope.cancel() + } +} diff --git a/app/src/main/java/me/tartikov/neuromap/NetworkSettings.kt b/app/src/main/java/me/tartikov/neuromap/NetworkSettings.kt new file mode 100644 index 0000000..0dff3b0 --- /dev/null +++ b/app/src/main/java/me/tartikov/neuromap/NetworkSettings.kt @@ -0,0 +1,10 @@ +package me.tartikov.neuromap + +object NetworkSettings { + private const val inputSize = EncodingSettings.levelCount * EncodingSettings.featurePerLevel + private const val hiddenSize = 8 + private const val outputSize = 4 + + const val weightCount0 = inputSize * hiddenSize + const val weightCount1 = hiddenSize * outputSize +} diff --git a/app/src/main/java/me/tartikov/neuromap/Quad.kt b/app/src/main/java/me/tartikov/neuromap/Quad.kt new file mode 100644 index 0000000..26fbfb2 --- /dev/null +++ b/app/src/main/java/me/tartikov/neuromap/Quad.kt @@ -0,0 +1,57 @@ +package me.tartikov.neuromap + +import android.opengl.GLES20.* +import java.nio.ByteBuffer +import java.nio.ByteOrder +import java.nio.FloatBuffer +import java.nio.ShortBuffer + +class Quad { + private val vertexBuffer: FloatBuffer + private val indexBuffer: ShortBuffer + + fun draw(positionAttribute: Int) { + glEnableVertexAttribArray(positionAttribute) + glVertexAttribPointer( + positionAttribute, COORD_COUNT, + GL_FLOAT, false, + COORD_COUNT * Float.SIZE_BYTES, vertexBuffer + ) + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, indexBuffer) + glDisableVertexAttribArray(positionAttribute) + } + + init { + val vertices = floatArrayOf( + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f + ) + vertexBuffer = ByteBuffer.allocateDirect( + vertices.size * Float.SIZE_BYTES + ).apply { + order(ByteOrder.nativeOrder()) + }.asFloatBuffer().apply { + put(vertices) + position(0) + } + + val indexes = shortArrayOf( + 0, 1, 2, + 0, 2, 3 + ) + indexBuffer = ByteBuffer.allocateDirect( + indexes.size * Short.SIZE_BYTES + ).apply { + order(ByteOrder.nativeOrder()) + }.asShortBuffer().apply { + put(indexes) + position(0) + } + } + + companion object { + private const val COORD_COUNT = 2 + } +} \ No newline at end of file diff --git a/app/src/main/java/me/tartikov/neuromap/Rect.kt b/app/src/main/java/me/tartikov/neuromap/Rect.kt new file mode 100644 index 0000000..f047e2f --- /dev/null +++ b/app/src/main/java/me/tartikov/neuromap/Rect.kt @@ -0,0 +1,10 @@ +package me.tartikov.neuromap + +data class Rect(val x: Double, val y: Double, val w: Double, val h: Double) { + companion object { + val WORLD = Rect(0.0, 0.0, 1.0, 1.0) + } + + val right get() = x + w + val bottom get() = y + h +} \ No newline at end of file diff --git a/app/src/main/java/me/tartikov/neuromap/RenderTarget.kt b/app/src/main/java/me/tartikov/neuromap/RenderTarget.kt new file mode 100644 index 0000000..eba0e42 --- /dev/null +++ b/app/src/main/java/me/tartikov/neuromap/RenderTarget.kt @@ -0,0 +1,69 @@ +package me.tartikov.neuromap + +import android.opengl.GLES20.* +import android.opengl.GLES30 +import android.opengl.GLES30.glDrawBuffers +import android.util.Size + + +class RenderTarget(private val size: Size, textureCount: Int = 1) { + private val fbo: Int = run { + val result = IntArray(1) + glGenFramebuffers(1, result, 0) + result[0] + } + private val textures = IntArray(textureCount) + + init { + glBindFramebuffer(GL_FRAMEBUFFER, fbo) + glGenTextures(textureCount, textures, 0) + textures.forEachIndexed { index, it -> + glBindTexture(GL_TEXTURE_2D, it) + glTexImage2D(GL_TEXTURE_2D, 0, GLES30.GL_RGBA32F, size.width, size.height, 0, GL_RGBA, GL_FLOAT, null) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) + glBindTexture(GL_TEXTURE_2D, 0) + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + index, GL_TEXTURE_2D, it, 0) + } + + val attachments = IntArray(textureCount) { + GL_COLOR_ATTACHMENT0 + it + } + glDrawBuffers(textureCount, attachments, 0) + + if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + throw RuntimeException("Failed to create RenderTarget") + } + glBindFramebuffer(GL_FRAMEBUFFER, 0) + checkGlError() + } + + fun drawInto(action: () -> Unit) { + glBindFramebuffer(GL_FRAMEBUFFER, fbo) + glViewport(0, 0, size.width, size.height) + action() + glBindFramebuffer(GL_FRAMEBUFFER, 0) + checkGlError() + } + + fun bindTextures(slot: Int = 0) { + textures.forEachIndexed { index, it -> + glActiveTexture(GL_TEXTURE0 + slot + index) + glBindTexture(GL_TEXTURE_2D, it) + } + glActiveTexture(GL_TEXTURE0) + checkGlError() + } + + fun unbindTextures(slot: Int = 0) { + repeat(textures.size) { + glActiveTexture(GL_TEXTURE0 + slot + it) + glBindTexture(GL_TEXTURE_2D, 0) + + } + glActiveTexture(GL_TEXTURE0) + checkGlError() + } +} diff --git a/app/src/main/java/me/tartikov/neuromap/TextureBuffer.kt b/app/src/main/java/me/tartikov/neuromap/TextureBuffer.kt new file mode 100644 index 0000000..061bdc2 --- /dev/null +++ b/app/src/main/java/me/tartikov/neuromap/TextureBuffer.kt @@ -0,0 +1,44 @@ +package me.tartikov.neuromap + +import android.opengl.GLES32.* +import java.nio.ShortBuffer + +class TextureBuffer(data: ShortBuffer) { + private val texture: Int + + init { + check(getGlInt(GL_MAX_TEXTURE_BUFFER_SIZE) > 0) { + "Texture buffer is not supported" + } + + val buffer = run { + val buffers = IntArray(1) + glGenBuffers(1, buffers, 0) + checkGlError() + buffers[0] + } + + glBindBuffer(GL_TEXTURE_BUFFER, buffer) + glBufferData(GL_TEXTURE_BUFFER, data.capacity() * Short.SIZE_BYTES, data, GL_STATIC_DRAW) + glBindBuffer(GL_TEXTURE_BUFFER, 0) + checkGlError() + + texture = run { + val textures = IntArray(1) + glGenTextures(1, textures, 0) + checkGlError() + textures[0] + } + + glActiveTexture(GL_TEXTURE0) + glBindTexture(GL_TEXTURE_BUFFER, texture) + glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA16F, buffer) + glBindTexture(GL_TEXTURE_BUFFER, 0) + checkGlError() + } + + fun bind(slot: Int = 0) { + glActiveTexture(GL_TEXTURE0 + slot) + glBindTexture(GL_TEXTURE_BUFFER, texture) + } +} diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..289f110 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,10 @@ + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..690bf14 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..49092cf --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/app/src/main/res/layout/map_view.xml b/app/src/main/res/layout/map_view.xml new file mode 100644 index 0000000..88fb3ef --- /dev/null +++ b/app/src/main/res/layout/map_view.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..94b44bda48e76089143ea63ba371fd14b57e1cb5 GIT binary patch literal 3319 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 BYpDPL literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..1182d6f2382cb49c160f2e832363404464c6e9a6 GIT binary patch literal 2084 zcmV+<2;29GP)ap z1dsziqcN!H=a3cMGiq7bBt2K7xo?K937*=;DapTgdjVmxA!F`Gk}BY4 zx_7#@0j?rVfb+y3;5?TBSMhif6%C~^1sfYS`}j1#Yys6>ClK+bMwk)!nkL4d0Cgsc zBD8BpNrKwm+ou6$3V2}uL#XXOg;#PNh+VCb)}vp5Qt>pD@~4P=xQG0~hauO$uC`}C z-o8y05K%A&3#t~PqO}#NrC(uPNiVFYW&AN)Bdr%;@4NzM`yt5H^R>}0iiA?P97Fp780*6amTg8?Y#+PaWmkmO1dF zL~X=>wL`+zauGL?fJZk;$T}h8%GDN64iLnaog`weCzOspOX2hg9;J z{_g^!3dZ51=1k1r?NCWhO%aiOQpU*T#sm&t;xHmNE%Rc6LerAuF!Y(Z>oSbb?fl*UeCJE;L(4>Dxv101GN_=lr%Z;6eY3G zK2SUX3T+5`ebtjtgcJOf{%>?`oY59KIpp-=yQOf)V>Pk zd^W8<)4c94p+x?NxdLXDK8>Q&y$D(AA)4^Egv@Fgduv2QzIlBC9PR&7tv z3!rDj)*RKpsRG9SXCihre~e_CLrr8pyu3&=l2qiGmHC+>6?YPz&v2l@FauP`QONKz z>f%|j#ttN~bY?U5{5MneZ>j)B9G#-d+CRm^sdRiyb)dXiL2j*#xV4%9F7PHLJ78;+ z(bdyDAV8sVLAq27=h5fXjcZ`^X)|Mg?AN<5Qv^g7j73JvZp?g78}-}hB=&p;kGhO; zYdoR#vv)=GedR9Z=r}1~vcY-yai7?C9h-xlR7P-_?%kIu0vHQF`x8~3l6nH`C;Uo| zgjH5gCg(6BWRN;{WkKtHb zDdf+S;N;qe&H}u7by7ZXJ8tjo(*UytFqj0tflIgNp}<~)T>CNFkWz8K^+EM_{@n2S z20BCMk6ZLbNPfN%72 z_pbFq+>OqFsnE~B;?ePXu10hJX)tQZs0bk^xXi4Q{9k;m@IfyKm-lcdC$0+-`AmyoPB%1_n(EnjD%U9`-+3@9(j~ogupb O0000v_MEfyudZhiu9W-19u|lgZ4Td%pX= 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 M07*qoM6N<$f{p0%b^rhX literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..fc1e8e9a1e04b6ccfe297cd368cb0a34b981275b GIT binary patch literal 7435 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 RHBKxnO;sJ0dL^rf{{kXsI=%n^ literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..263c6b52ad37d00ff7b77cae35c918a09bc5d943 GIT binary patch literal 10351 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 + + #1AB420 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..eda1826 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + NeuroMap + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..0c9422e --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..2b16134 --- /dev/null +++ b/build.gradle @@ -0,0 +1,17 @@ +buildscript { + ext { + kotlin_version = '1.6.10' + } + repositories { + google() + mavenCentral() + } + dependencies { + classpath 'com.android.tools.build:gradle:7.1.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..cd0519b --- /dev/null +++ b/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f GIT binary patch literal 59203 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

K7UpUK$>l@ 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 E2O(~oYXATM literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..6d613af --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sat Mar 05 23:33:16 NOVT 2022 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..ac1b06f --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..645dfb8134ebb4391dc8911badd7cd4e7739d147 GIT binary patch literal 198398 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ⅇQXK?2l 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�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 B3u^!X literal 0 HcmV?d00001 diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..7d40306 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,16 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} +rootProject.name = "NeuroMap" +include ':app' diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..4973ed4 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.18) +project(NeuroMapTools + LANGUAGES CXX CUDA +) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") + +include("cmake/cuda.cmake") + +add_subdirectory(thirdparty) +add_subdirectory(tiles_to_image) +add_subdirectory(trainer) +add_subdirectory(inference) diff --git a/tools/cmake/cuda.cmake b/tools/cmake/cuda.cmake new file mode 100644 index 0000000..0516a06 --- /dev/null +++ b/tools/cmake/cuda.cmake @@ -0,0 +1,11 @@ +set(CMAKE_CUDA_STANDARD 14) +set(CMAKE_CUDA_STANDARD_REQUIRED ON) +set(CMAKE_CUDA_EXTENSIONS OFF) + +list(APPEND CUDA_NVCC_FLAGS + "-Xcompiler=-mf16c" + "-Xcompiler=-Wno-float-conversion" + "-Xcompiler=-fno-strict-aliasing" + "--extended-lambda" + "--expt-relaxed-constexpr" +) diff --git a/tools/inference/CMakeLists.txt b/tools/inference/CMakeLists.txt new file mode 100644 index 0000000..5b5945d --- /dev/null +++ b/tools/inference/CMakeLists.txt @@ -0,0 +1,13 @@ +set(TARGET inference) + +add_executable(${TARGET} + main.cpp + hf.cpp + hf.h +) + +target_include_directories(${TARGET} PRIVATE + "${CMAKE_SOURCE_DIR}/thirdparty/tiny-cuda-nn/dependencies" +) + +target_link_libraries(${TARGET} cxxopts stbi) \ No newline at end of file diff --git a/tools/inference/hf.cpp b/tools/inference/hf.cpp new file mode 100644 index 0000000..b897bb2 --- /dev/null +++ b/tools/inference/hf.cpp @@ -0,0 +1,152 @@ +#include "hf.h" +#include +#include + +// From http://half.sourceforge.net/ +float hf2float(uint16_t value) +{ + static_assert(std::numeric_limits::is_iec559, "half to float conversion needs IEEE 754 conformant 'float' type"); + static_assert(sizeof(uint32_t)==sizeof(float), "half to float conversion needs unsigned integer type of exactly the size of a 'float'"); + + static const uint32_t mantissa_table[2048] = { + 0x00000000, 0x33800000, 0x34000000, 0x34400000, 0x34800000, 0x34A00000, 0x34C00000, 0x34E00000, 0x35000000, 0x35100000, 0x35200000, 0x35300000, 0x35400000, 0x35500000, 0x35600000, 0x35700000, + 0x35800000, 0x35880000, 0x35900000, 0x35980000, 0x35A00000, 0x35A80000, 0x35B00000, 0x35B80000, 0x35C00000, 0x35C80000, 0x35D00000, 0x35D80000, 0x35E00000, 0x35E80000, 0x35F00000, 0x35F80000, + 0x36000000, 0x36040000, 0x36080000, 0x360C0000, 0x36100000, 0x36140000, 0x36180000, 0x361C0000, 0x36200000, 0x36240000, 0x36280000, 0x362C0000, 0x36300000, 0x36340000, 0x36380000, 0x363C0000, + 0x36400000, 0x36440000, 0x36480000, 0x364C0000, 0x36500000, 0x36540000, 0x36580000, 0x365C0000, 0x36600000, 0x36640000, 0x36680000, 0x366C0000, 0x36700000, 0x36740000, 0x36780000, 0x367C0000, + 0x36800000, 0x36820000, 0x36840000, 0x36860000, 0x36880000, 0x368A0000, 0x368C0000, 0x368E0000, 0x36900000, 0x36920000, 0x36940000, 0x36960000, 0x36980000, 0x369A0000, 0x369C0000, 0x369E0000, + 0x36A00000, 0x36A20000, 0x36A40000, 0x36A60000, 0x36A80000, 0x36AA0000, 0x36AC0000, 0x36AE0000, 0x36B00000, 0x36B20000, 0x36B40000, 0x36B60000, 0x36B80000, 0x36BA0000, 0x36BC0000, 0x36BE0000, + 0x36C00000, 0x36C20000, 0x36C40000, 0x36C60000, 0x36C80000, 0x36CA0000, 0x36CC0000, 0x36CE0000, 0x36D00000, 0x36D20000, 0x36D40000, 0x36D60000, 0x36D80000, 0x36DA0000, 0x36DC0000, 0x36DE0000, + 0x36E00000, 0x36E20000, 0x36E40000, 0x36E60000, 0x36E80000, 0x36EA0000, 0x36EC0000, 0x36EE0000, 0x36F00000, 0x36F20000, 0x36F40000, 0x36F60000, 0x36F80000, 0x36FA0000, 0x36FC0000, 0x36FE0000, + 0x37000000, 0x37010000, 0x37020000, 0x37030000, 0x37040000, 0x37050000, 0x37060000, 0x37070000, 0x37080000, 0x37090000, 0x370A0000, 0x370B0000, 0x370C0000, 0x370D0000, 0x370E0000, 0x370F0000, + 0x37100000, 0x37110000, 0x37120000, 0x37130000, 0x37140000, 0x37150000, 0x37160000, 0x37170000, 0x37180000, 0x37190000, 0x371A0000, 0x371B0000, 0x371C0000, 0x371D0000, 0x371E0000, 0x371F0000, + 0x37200000, 0x37210000, 0x37220000, 0x37230000, 0x37240000, 0x37250000, 0x37260000, 0x37270000, 0x37280000, 0x37290000, 0x372A0000, 0x372B0000, 0x372C0000, 0x372D0000, 0x372E0000, 0x372F0000, + 0x37300000, 0x37310000, 0x37320000, 0x37330000, 0x37340000, 0x37350000, 0x37360000, 0x37370000, 0x37380000, 0x37390000, 0x373A0000, 0x373B0000, 0x373C0000, 0x373D0000, 0x373E0000, 0x373F0000, + 0x37400000, 0x37410000, 0x37420000, 0x37430000, 0x37440000, 0x37450000, 0x37460000, 0x37470000, 0x37480000, 0x37490000, 0x374A0000, 0x374B0000, 0x374C0000, 0x374D0000, 0x374E0000, 0x374F0000, + 0x37500000, 0x37510000, 0x37520000, 0x37530000, 0x37540000, 0x37550000, 0x37560000, 0x37570000, 0x37580000, 0x37590000, 0x375A0000, 0x375B0000, 0x375C0000, 0x375D0000, 0x375E0000, 0x375F0000, + 0x37600000, 0x37610000, 0x37620000, 0x37630000, 0x37640000, 0x37650000, 0x37660000, 0x37670000, 0x37680000, 0x37690000, 0x376A0000, 0x376B0000, 0x376C0000, 0x376D0000, 0x376E0000, 0x376F0000, + 0x37700000, 0x37710000, 0x37720000, 0x37730000, 0x37740000, 0x37750000, 0x37760000, 0x37770000, 0x37780000, 0x37790000, 0x377A0000, 0x377B0000, 0x377C0000, 0x377D0000, 0x377E0000, 0x377F0000, + 0x37800000, 0x37808000, 0x37810000, 0x37818000, 0x37820000, 0x37828000, 0x37830000, 0x37838000, 0x37840000, 0x37848000, 0x37850000, 0x37858000, 0x37860000, 0x37868000, 0x37870000, 0x37878000, + 0x37880000, 0x37888000, 0x37890000, 0x37898000, 0x378A0000, 0x378A8000, 0x378B0000, 0x378B8000, 0x378C0000, 0x378C8000, 0x378D0000, 0x378D8000, 0x378E0000, 0x378E8000, 0x378F0000, 0x378F8000, + 0x37900000, 0x37908000, 0x37910000, 0x37918000, 0x37920000, 0x37928000, 0x37930000, 0x37938000, 0x37940000, 0x37948000, 0x37950000, 0x37958000, 0x37960000, 0x37968000, 0x37970000, 0x37978000, + 0x37980000, 0x37988000, 0x37990000, 0x37998000, 0x379A0000, 0x379A8000, 0x379B0000, 0x379B8000, 0x379C0000, 0x379C8000, 0x379D0000, 0x379D8000, 0x379E0000, 0x379E8000, 0x379F0000, 0x379F8000, + 0x37A00000, 0x37A08000, 0x37A10000, 0x37A18000, 0x37A20000, 0x37A28000, 0x37A30000, 0x37A38000, 0x37A40000, 0x37A48000, 0x37A50000, 0x37A58000, 0x37A60000, 0x37A68000, 0x37A70000, 0x37A78000, + 0x37A80000, 0x37A88000, 0x37A90000, 0x37A98000, 0x37AA0000, 0x37AA8000, 0x37AB0000, 0x37AB8000, 0x37AC0000, 0x37AC8000, 0x37AD0000, 0x37AD8000, 0x37AE0000, 0x37AE8000, 0x37AF0000, 0x37AF8000, + 0x37B00000, 0x37B08000, 0x37B10000, 0x37B18000, 0x37B20000, 0x37B28000, 0x37B30000, 0x37B38000, 0x37B40000, 0x37B48000, 0x37B50000, 0x37B58000, 0x37B60000, 0x37B68000, 0x37B70000, 0x37B78000, + 0x37B80000, 0x37B88000, 0x37B90000, 0x37B98000, 0x37BA0000, 0x37BA8000, 0x37BB0000, 0x37BB8000, 0x37BC0000, 0x37BC8000, 0x37BD0000, 0x37BD8000, 0x37BE0000, 0x37BE8000, 0x37BF0000, 0x37BF8000, + 0x37C00000, 0x37C08000, 0x37C10000, 0x37C18000, 0x37C20000, 0x37C28000, 0x37C30000, 0x37C38000, 0x37C40000, 0x37C48000, 0x37C50000, 0x37C58000, 0x37C60000, 0x37C68000, 0x37C70000, 0x37C78000, + 0x37C80000, 0x37C88000, 0x37C90000, 0x37C98000, 0x37CA0000, 0x37CA8000, 0x37CB0000, 0x37CB8000, 0x37CC0000, 0x37CC8000, 0x37CD0000, 0x37CD8000, 0x37CE0000, 0x37CE8000, 0x37CF0000, 0x37CF8000, + 0x37D00000, 0x37D08000, 0x37D10000, 0x37D18000, 0x37D20000, 0x37D28000, 0x37D30000, 0x37D38000, 0x37D40000, 0x37D48000, 0x37D50000, 0x37D58000, 0x37D60000, 0x37D68000, 0x37D70000, 0x37D78000, + 0x37D80000, 0x37D88000, 0x37D90000, 0x37D98000, 0x37DA0000, 0x37DA8000, 0x37DB0000, 0x37DB8000, 0x37DC0000, 0x37DC8000, 0x37DD0000, 0x37DD8000, 0x37DE0000, 0x37DE8000, 0x37DF0000, 0x37DF8000, + 0x37E00000, 0x37E08000, 0x37E10000, 0x37E18000, 0x37E20000, 0x37E28000, 0x37E30000, 0x37E38000, 0x37E40000, 0x37E48000, 0x37E50000, 0x37E58000, 0x37E60000, 0x37E68000, 0x37E70000, 0x37E78000, + 0x37E80000, 0x37E88000, 0x37E90000, 0x37E98000, 0x37EA0000, 0x37EA8000, 0x37EB0000, 0x37EB8000, 0x37EC0000, 0x37EC8000, 0x37ED0000, 0x37ED8000, 0x37EE0000, 0x37EE8000, 0x37EF0000, 0x37EF8000, + 0x37F00000, 0x37F08000, 0x37F10000, 0x37F18000, 0x37F20000, 0x37F28000, 0x37F30000, 0x37F38000, 0x37F40000, 0x37F48000, 0x37F50000, 0x37F58000, 0x37F60000, 0x37F68000, 0x37F70000, 0x37F78000, + 0x37F80000, 0x37F88000, 0x37F90000, 0x37F98000, 0x37FA0000, 0x37FA8000, 0x37FB0000, 0x37FB8000, 0x37FC0000, 0x37FC8000, 0x37FD0000, 0x37FD8000, 0x37FE0000, 0x37FE8000, 0x37FF0000, 0x37FF8000, + 0x38000000, 0x38004000, 0x38008000, 0x3800C000, 0x38010000, 0x38014000, 0x38018000, 0x3801C000, 0x38020000, 0x38024000, 0x38028000, 0x3802C000, 0x38030000, 0x38034000, 0x38038000, 0x3803C000, + 0x38040000, 0x38044000, 0x38048000, 0x3804C000, 0x38050000, 0x38054000, 0x38058000, 0x3805C000, 0x38060000, 0x38064000, 0x38068000, 0x3806C000, 0x38070000, 0x38074000, 0x38078000, 0x3807C000, + 0x38080000, 0x38084000, 0x38088000, 0x3808C000, 0x38090000, 0x38094000, 0x38098000, 0x3809C000, 0x380A0000, 0x380A4000, 0x380A8000, 0x380AC000, 0x380B0000, 0x380B4000, 0x380B8000, 0x380BC000, + 0x380C0000, 0x380C4000, 0x380C8000, 0x380CC000, 0x380D0000, 0x380D4000, 0x380D8000, 0x380DC000, 0x380E0000, 0x380E4000, 0x380E8000, 0x380EC000, 0x380F0000, 0x380F4000, 0x380F8000, 0x380FC000, + 0x38100000, 0x38104000, 0x38108000, 0x3810C000, 0x38110000, 0x38114000, 0x38118000, 0x3811C000, 0x38120000, 0x38124000, 0x38128000, 0x3812C000, 0x38130000, 0x38134000, 0x38138000, 0x3813C000, + 0x38140000, 0x38144000, 0x38148000, 0x3814C000, 0x38150000, 0x38154000, 0x38158000, 0x3815C000, 0x38160000, 0x38164000, 0x38168000, 0x3816C000, 0x38170000, 0x38174000, 0x38178000, 0x3817C000, + 0x38180000, 0x38184000, 0x38188000, 0x3818C000, 0x38190000, 0x38194000, 0x38198000, 0x3819C000, 0x381A0000, 0x381A4000, 0x381A8000, 0x381AC000, 0x381B0000, 0x381B4000, 0x381B8000, 0x381BC000, + 0x381C0000, 0x381C4000, 0x381C8000, 0x381CC000, 0x381D0000, 0x381D4000, 0x381D8000, 0x381DC000, 0x381E0000, 0x381E4000, 0x381E8000, 0x381EC000, 0x381F0000, 0x381F4000, 0x381F8000, 0x381FC000, + 0x38200000, 0x38204000, 0x38208000, 0x3820C000, 0x38210000, 0x38214000, 0x38218000, 0x3821C000, 0x38220000, 0x38224000, 0x38228000, 0x3822C000, 0x38230000, 0x38234000, 0x38238000, 0x3823C000, + 0x38240000, 0x38244000, 0x38248000, 0x3824C000, 0x38250000, 0x38254000, 0x38258000, 0x3825C000, 0x38260000, 0x38264000, 0x38268000, 0x3826C000, 0x38270000, 0x38274000, 0x38278000, 0x3827C000, + 0x38280000, 0x38284000, 0x38288000, 0x3828C000, 0x38290000, 0x38294000, 0x38298000, 0x3829C000, 0x382A0000, 0x382A4000, 0x382A8000, 0x382AC000, 0x382B0000, 0x382B4000, 0x382B8000, 0x382BC000, + 0x382C0000, 0x382C4000, 0x382C8000, 0x382CC000, 0x382D0000, 0x382D4000, 0x382D8000, 0x382DC000, 0x382E0000, 0x382E4000, 0x382E8000, 0x382EC000, 0x382F0000, 0x382F4000, 0x382F8000, 0x382FC000, + 0x38300000, 0x38304000, 0x38308000, 0x3830C000, 0x38310000, 0x38314000, 0x38318000, 0x3831C000, 0x38320000, 0x38324000, 0x38328000, 0x3832C000, 0x38330000, 0x38334000, 0x38338000, 0x3833C000, + 0x38340000, 0x38344000, 0x38348000, 0x3834C000, 0x38350000, 0x38354000, 0x38358000, 0x3835C000, 0x38360000, 0x38364000, 0x38368000, 0x3836C000, 0x38370000, 0x38374000, 0x38378000, 0x3837C000, + 0x38380000, 0x38384000, 0x38388000, 0x3838C000, 0x38390000, 0x38394000, 0x38398000, 0x3839C000, 0x383A0000, 0x383A4000, 0x383A8000, 0x383AC000, 0x383B0000, 0x383B4000, 0x383B8000, 0x383BC000, + 0x383C0000, 0x383C4000, 0x383C8000, 0x383CC000, 0x383D0000, 0x383D4000, 0x383D8000, 0x383DC000, 0x383E0000, 0x383E4000, 0x383E8000, 0x383EC000, 0x383F0000, 0x383F4000, 0x383F8000, 0x383FC000, + 0x38400000, 0x38404000, 0x38408000, 0x3840C000, 0x38410000, 0x38414000, 0x38418000, 0x3841C000, 0x38420000, 0x38424000, 0x38428000, 0x3842C000, 0x38430000, 0x38434000, 0x38438000, 0x3843C000, + 0x38440000, 0x38444000, 0x38448000, 0x3844C000, 0x38450000, 0x38454000, 0x38458000, 0x3845C000, 0x38460000, 0x38464000, 0x38468000, 0x3846C000, 0x38470000, 0x38474000, 0x38478000, 0x3847C000, + 0x38480000, 0x38484000, 0x38488000, 0x3848C000, 0x38490000, 0x38494000, 0x38498000, 0x3849C000, 0x384A0000, 0x384A4000, 0x384A8000, 0x384AC000, 0x384B0000, 0x384B4000, 0x384B8000, 0x384BC000, + 0x384C0000, 0x384C4000, 0x384C8000, 0x384CC000, 0x384D0000, 0x384D4000, 0x384D8000, 0x384DC000, 0x384E0000, 0x384E4000, 0x384E8000, 0x384EC000, 0x384F0000, 0x384F4000, 0x384F8000, 0x384FC000, + 0x38500000, 0x38504000, 0x38508000, 0x3850C000, 0x38510000, 0x38514000, 0x38518000, 0x3851C000, 0x38520000, 0x38524000, 0x38528000, 0x3852C000, 0x38530000, 0x38534000, 0x38538000, 0x3853C000, + 0x38540000, 0x38544000, 0x38548000, 0x3854C000, 0x38550000, 0x38554000, 0x38558000, 0x3855C000, 0x38560000, 0x38564000, 0x38568000, 0x3856C000, 0x38570000, 0x38574000, 0x38578000, 0x3857C000, + 0x38580000, 0x38584000, 0x38588000, 0x3858C000, 0x38590000, 0x38594000, 0x38598000, 0x3859C000, 0x385A0000, 0x385A4000, 0x385A8000, 0x385AC000, 0x385B0000, 0x385B4000, 0x385B8000, 0x385BC000, + 0x385C0000, 0x385C4000, 0x385C8000, 0x385CC000, 0x385D0000, 0x385D4000, 0x385D8000, 0x385DC000, 0x385E0000, 0x385E4000, 0x385E8000, 0x385EC000, 0x385F0000, 0x385F4000, 0x385F8000, 0x385FC000, + 0x38600000, 0x38604000, 0x38608000, 0x3860C000, 0x38610000, 0x38614000, 0x38618000, 0x3861C000, 0x38620000, 0x38624000, 0x38628000, 0x3862C000, 0x38630000, 0x38634000, 0x38638000, 0x3863C000, + 0x38640000, 0x38644000, 0x38648000, 0x3864C000, 0x38650000, 0x38654000, 0x38658000, 0x3865C000, 0x38660000, 0x38664000, 0x38668000, 0x3866C000, 0x38670000, 0x38674000, 0x38678000, 0x3867C000, + 0x38680000, 0x38684000, 0x38688000, 0x3868C000, 0x38690000, 0x38694000, 0x38698000, 0x3869C000, 0x386A0000, 0x386A4000, 0x386A8000, 0x386AC000, 0x386B0000, 0x386B4000, 0x386B8000, 0x386BC000, + 0x386C0000, 0x386C4000, 0x386C8000, 0x386CC000, 0x386D0000, 0x386D4000, 0x386D8000, 0x386DC000, 0x386E0000, 0x386E4000, 0x386E8000, 0x386EC000, 0x386F0000, 0x386F4000, 0x386F8000, 0x386FC000, + 0x38700000, 0x38704000, 0x38708000, 0x3870C000, 0x38710000, 0x38714000, 0x38718000, 0x3871C000, 0x38720000, 0x38724000, 0x38728000, 0x3872C000, 0x38730000, 0x38734000, 0x38738000, 0x3873C000, + 0x38740000, 0x38744000, 0x38748000, 0x3874C000, 0x38750000, 0x38754000, 0x38758000, 0x3875C000, 0x38760000, 0x38764000, 0x38768000, 0x3876C000, 0x38770000, 0x38774000, 0x38778000, 0x3877C000, + 0x38780000, 0x38784000, 0x38788000, 0x3878C000, 0x38790000, 0x38794000, 0x38798000, 0x3879C000, 0x387A0000, 0x387A4000, 0x387A8000, 0x387AC000, 0x387B0000, 0x387B4000, 0x387B8000, 0x387BC000, + 0x387C0000, 0x387C4000, 0x387C8000, 0x387CC000, 0x387D0000, 0x387D4000, 0x387D8000, 0x387DC000, 0x387E0000, 0x387E4000, 0x387E8000, 0x387EC000, 0x387F0000, 0x387F4000, 0x387F8000, 0x387FC000, + 0x38000000, 0x38002000, 0x38004000, 0x38006000, 0x38008000, 0x3800A000, 0x3800C000, 0x3800E000, 0x38010000, 0x38012000, 0x38014000, 0x38016000, 0x38018000, 0x3801A000, 0x3801C000, 0x3801E000, + 0x38020000, 0x38022000, 0x38024000, 0x38026000, 0x38028000, 0x3802A000, 0x3802C000, 0x3802E000, 0x38030000, 0x38032000, 0x38034000, 0x38036000, 0x38038000, 0x3803A000, 0x3803C000, 0x3803E000, + 0x38040000, 0x38042000, 0x38044000, 0x38046000, 0x38048000, 0x3804A000, 0x3804C000, 0x3804E000, 0x38050000, 0x38052000, 0x38054000, 0x38056000, 0x38058000, 0x3805A000, 0x3805C000, 0x3805E000, + 0x38060000, 0x38062000, 0x38064000, 0x38066000, 0x38068000, 0x3806A000, 0x3806C000, 0x3806E000, 0x38070000, 0x38072000, 0x38074000, 0x38076000, 0x38078000, 0x3807A000, 0x3807C000, 0x3807E000, + 0x38080000, 0x38082000, 0x38084000, 0x38086000, 0x38088000, 0x3808A000, 0x3808C000, 0x3808E000, 0x38090000, 0x38092000, 0x38094000, 0x38096000, 0x38098000, 0x3809A000, 0x3809C000, 0x3809E000, + 0x380A0000, 0x380A2000, 0x380A4000, 0x380A6000, 0x380A8000, 0x380AA000, 0x380AC000, 0x380AE000, 0x380B0000, 0x380B2000, 0x380B4000, 0x380B6000, 0x380B8000, 0x380BA000, 0x380BC000, 0x380BE000, + 0x380C0000, 0x380C2000, 0x380C4000, 0x380C6000, 0x380C8000, 0x380CA000, 0x380CC000, 0x380CE000, 0x380D0000, 0x380D2000, 0x380D4000, 0x380D6000, 0x380D8000, 0x380DA000, 0x380DC000, 0x380DE000, + 0x380E0000, 0x380E2000, 0x380E4000, 0x380E6000, 0x380E8000, 0x380EA000, 0x380EC000, 0x380EE000, 0x380F0000, 0x380F2000, 0x380F4000, 0x380F6000, 0x380F8000, 0x380FA000, 0x380FC000, 0x380FE000, + 0x38100000, 0x38102000, 0x38104000, 0x38106000, 0x38108000, 0x3810A000, 0x3810C000, 0x3810E000, 0x38110000, 0x38112000, 0x38114000, 0x38116000, 0x38118000, 0x3811A000, 0x3811C000, 0x3811E000, + 0x38120000, 0x38122000, 0x38124000, 0x38126000, 0x38128000, 0x3812A000, 0x3812C000, 0x3812E000, 0x38130000, 0x38132000, 0x38134000, 0x38136000, 0x38138000, 0x3813A000, 0x3813C000, 0x3813E000, + 0x38140000, 0x38142000, 0x38144000, 0x38146000, 0x38148000, 0x3814A000, 0x3814C000, 0x3814E000, 0x38150000, 0x38152000, 0x38154000, 0x38156000, 0x38158000, 0x3815A000, 0x3815C000, 0x3815E000, + 0x38160000, 0x38162000, 0x38164000, 0x38166000, 0x38168000, 0x3816A000, 0x3816C000, 0x3816E000, 0x38170000, 0x38172000, 0x38174000, 0x38176000, 0x38178000, 0x3817A000, 0x3817C000, 0x3817E000, + 0x38180000, 0x38182000, 0x38184000, 0x38186000, 0x38188000, 0x3818A000, 0x3818C000, 0x3818E000, 0x38190000, 0x38192000, 0x38194000, 0x38196000, 0x38198000, 0x3819A000, 0x3819C000, 0x3819E000, + 0x381A0000, 0x381A2000, 0x381A4000, 0x381A6000, 0x381A8000, 0x381AA000, 0x381AC000, 0x381AE000, 0x381B0000, 0x381B2000, 0x381B4000, 0x381B6000, 0x381B8000, 0x381BA000, 0x381BC000, 0x381BE000, + 0x381C0000, 0x381C2000, 0x381C4000, 0x381C6000, 0x381C8000, 0x381CA000, 0x381CC000, 0x381CE000, 0x381D0000, 0x381D2000, 0x381D4000, 0x381D6000, 0x381D8000, 0x381DA000, 0x381DC000, 0x381DE000, + 0x381E0000, 0x381E2000, 0x381E4000, 0x381E6000, 0x381E8000, 0x381EA000, 0x381EC000, 0x381EE000, 0x381F0000, 0x381F2000, 0x381F4000, 0x381F6000, 0x381F8000, 0x381FA000, 0x381FC000, 0x381FE000, + 0x38200000, 0x38202000, 0x38204000, 0x38206000, 0x38208000, 0x3820A000, 0x3820C000, 0x3820E000, 0x38210000, 0x38212000, 0x38214000, 0x38216000, 0x38218000, 0x3821A000, 0x3821C000, 0x3821E000, + 0x38220000, 0x38222000, 0x38224000, 0x38226000, 0x38228000, 0x3822A000, 0x3822C000, 0x3822E000, 0x38230000, 0x38232000, 0x38234000, 0x38236000, 0x38238000, 0x3823A000, 0x3823C000, 0x3823E000, + 0x38240000, 0x38242000, 0x38244000, 0x38246000, 0x38248000, 0x3824A000, 0x3824C000, 0x3824E000, 0x38250000, 0x38252000, 0x38254000, 0x38256000, 0x38258000, 0x3825A000, 0x3825C000, 0x3825E000, + 0x38260000, 0x38262000, 0x38264000, 0x38266000, 0x38268000, 0x3826A000, 0x3826C000, 0x3826E000, 0x38270000, 0x38272000, 0x38274000, 0x38276000, 0x38278000, 0x3827A000, 0x3827C000, 0x3827E000, + 0x38280000, 0x38282000, 0x38284000, 0x38286000, 0x38288000, 0x3828A000, 0x3828C000, 0x3828E000, 0x38290000, 0x38292000, 0x38294000, 0x38296000, 0x38298000, 0x3829A000, 0x3829C000, 0x3829E000, + 0x382A0000, 0x382A2000, 0x382A4000, 0x382A6000, 0x382A8000, 0x382AA000, 0x382AC000, 0x382AE000, 0x382B0000, 0x382B2000, 0x382B4000, 0x382B6000, 0x382B8000, 0x382BA000, 0x382BC000, 0x382BE000, + 0x382C0000, 0x382C2000, 0x382C4000, 0x382C6000, 0x382C8000, 0x382CA000, 0x382CC000, 0x382CE000, 0x382D0000, 0x382D2000, 0x382D4000, 0x382D6000, 0x382D8000, 0x382DA000, 0x382DC000, 0x382DE000, + 0x382E0000, 0x382E2000, 0x382E4000, 0x382E6000, 0x382E8000, 0x382EA000, 0x382EC000, 0x382EE000, 0x382F0000, 0x382F2000, 0x382F4000, 0x382F6000, 0x382F8000, 0x382FA000, 0x382FC000, 0x382FE000, + 0x38300000, 0x38302000, 0x38304000, 0x38306000, 0x38308000, 0x3830A000, 0x3830C000, 0x3830E000, 0x38310000, 0x38312000, 0x38314000, 0x38316000, 0x38318000, 0x3831A000, 0x3831C000, 0x3831E000, + 0x38320000, 0x38322000, 0x38324000, 0x38326000, 0x38328000, 0x3832A000, 0x3832C000, 0x3832E000, 0x38330000, 0x38332000, 0x38334000, 0x38336000, 0x38338000, 0x3833A000, 0x3833C000, 0x3833E000, + 0x38340000, 0x38342000, 0x38344000, 0x38346000, 0x38348000, 0x3834A000, 0x3834C000, 0x3834E000, 0x38350000, 0x38352000, 0x38354000, 0x38356000, 0x38358000, 0x3835A000, 0x3835C000, 0x3835E000, + 0x38360000, 0x38362000, 0x38364000, 0x38366000, 0x38368000, 0x3836A000, 0x3836C000, 0x3836E000, 0x38370000, 0x38372000, 0x38374000, 0x38376000, 0x38378000, 0x3837A000, 0x3837C000, 0x3837E000, + 0x38380000, 0x38382000, 0x38384000, 0x38386000, 0x38388000, 0x3838A000, 0x3838C000, 0x3838E000, 0x38390000, 0x38392000, 0x38394000, 0x38396000, 0x38398000, 0x3839A000, 0x3839C000, 0x3839E000, + 0x383A0000, 0x383A2000, 0x383A4000, 0x383A6000, 0x383A8000, 0x383AA000, 0x383AC000, 0x383AE000, 0x383B0000, 0x383B2000, 0x383B4000, 0x383B6000, 0x383B8000, 0x383BA000, 0x383BC000, 0x383BE000, + 0x383C0000, 0x383C2000, 0x383C4000, 0x383C6000, 0x383C8000, 0x383CA000, 0x383CC000, 0x383CE000, 0x383D0000, 0x383D2000, 0x383D4000, 0x383D6000, 0x383D8000, 0x383DA000, 0x383DC000, 0x383DE000, + 0x383E0000, 0x383E2000, 0x383E4000, 0x383E6000, 0x383E8000, 0x383EA000, 0x383EC000, 0x383EE000, 0x383F0000, 0x383F2000, 0x383F4000, 0x383F6000, 0x383F8000, 0x383FA000, 0x383FC000, 0x383FE000, + 0x38400000, 0x38402000, 0x38404000, 0x38406000, 0x38408000, 0x3840A000, 0x3840C000, 0x3840E000, 0x38410000, 0x38412000, 0x38414000, 0x38416000, 0x38418000, 0x3841A000, 0x3841C000, 0x3841E000, + 0x38420000, 0x38422000, 0x38424000, 0x38426000, 0x38428000, 0x3842A000, 0x3842C000, 0x3842E000, 0x38430000, 0x38432000, 0x38434000, 0x38436000, 0x38438000, 0x3843A000, 0x3843C000, 0x3843E000, + 0x38440000, 0x38442000, 0x38444000, 0x38446000, 0x38448000, 0x3844A000, 0x3844C000, 0x3844E000, 0x38450000, 0x38452000, 0x38454000, 0x38456000, 0x38458000, 0x3845A000, 0x3845C000, 0x3845E000, + 0x38460000, 0x38462000, 0x38464000, 0x38466000, 0x38468000, 0x3846A000, 0x3846C000, 0x3846E000, 0x38470000, 0x38472000, 0x38474000, 0x38476000, 0x38478000, 0x3847A000, 0x3847C000, 0x3847E000, + 0x38480000, 0x38482000, 0x38484000, 0x38486000, 0x38488000, 0x3848A000, 0x3848C000, 0x3848E000, 0x38490000, 0x38492000, 0x38494000, 0x38496000, 0x38498000, 0x3849A000, 0x3849C000, 0x3849E000, + 0x384A0000, 0x384A2000, 0x384A4000, 0x384A6000, 0x384A8000, 0x384AA000, 0x384AC000, 0x384AE000, 0x384B0000, 0x384B2000, 0x384B4000, 0x384B6000, 0x384B8000, 0x384BA000, 0x384BC000, 0x384BE000, + 0x384C0000, 0x384C2000, 0x384C4000, 0x384C6000, 0x384C8000, 0x384CA000, 0x384CC000, 0x384CE000, 0x384D0000, 0x384D2000, 0x384D4000, 0x384D6000, 0x384D8000, 0x384DA000, 0x384DC000, 0x384DE000, + 0x384E0000, 0x384E2000, 0x384E4000, 0x384E6000, 0x384E8000, 0x384EA000, 0x384EC000, 0x384EE000, 0x384F0000, 0x384F2000, 0x384F4000, 0x384F6000, 0x384F8000, 0x384FA000, 0x384FC000, 0x384FE000, + 0x38500000, 0x38502000, 0x38504000, 0x38506000, 0x38508000, 0x3850A000, 0x3850C000, 0x3850E000, 0x38510000, 0x38512000, 0x38514000, 0x38516000, 0x38518000, 0x3851A000, 0x3851C000, 0x3851E000, + 0x38520000, 0x38522000, 0x38524000, 0x38526000, 0x38528000, 0x3852A000, 0x3852C000, 0x3852E000, 0x38530000, 0x38532000, 0x38534000, 0x38536000, 0x38538000, 0x3853A000, 0x3853C000, 0x3853E000, + 0x38540000, 0x38542000, 0x38544000, 0x38546000, 0x38548000, 0x3854A000, 0x3854C000, 0x3854E000, 0x38550000, 0x38552000, 0x38554000, 0x38556000, 0x38558000, 0x3855A000, 0x3855C000, 0x3855E000, + 0x38560000, 0x38562000, 0x38564000, 0x38566000, 0x38568000, 0x3856A000, 0x3856C000, 0x3856E000, 0x38570000, 0x38572000, 0x38574000, 0x38576000, 0x38578000, 0x3857A000, 0x3857C000, 0x3857E000, + 0x38580000, 0x38582000, 0x38584000, 0x38586000, 0x38588000, 0x3858A000, 0x3858C000, 0x3858E000, 0x38590000, 0x38592000, 0x38594000, 0x38596000, 0x38598000, 0x3859A000, 0x3859C000, 0x3859E000, + 0x385A0000, 0x385A2000, 0x385A4000, 0x385A6000, 0x385A8000, 0x385AA000, 0x385AC000, 0x385AE000, 0x385B0000, 0x385B2000, 0x385B4000, 0x385B6000, 0x385B8000, 0x385BA000, 0x385BC000, 0x385BE000, + 0x385C0000, 0x385C2000, 0x385C4000, 0x385C6000, 0x385C8000, 0x385CA000, 0x385CC000, 0x385CE000, 0x385D0000, 0x385D2000, 0x385D4000, 0x385D6000, 0x385D8000, 0x385DA000, 0x385DC000, 0x385DE000, + 0x385E0000, 0x385E2000, 0x385E4000, 0x385E6000, 0x385E8000, 0x385EA000, 0x385EC000, 0x385EE000, 0x385F0000, 0x385F2000, 0x385F4000, 0x385F6000, 0x385F8000, 0x385FA000, 0x385FC000, 0x385FE000, + 0x38600000, 0x38602000, 0x38604000, 0x38606000, 0x38608000, 0x3860A000, 0x3860C000, 0x3860E000, 0x38610000, 0x38612000, 0x38614000, 0x38616000, 0x38618000, 0x3861A000, 0x3861C000, 0x3861E000, + 0x38620000, 0x38622000, 0x38624000, 0x38626000, 0x38628000, 0x3862A000, 0x3862C000, 0x3862E000, 0x38630000, 0x38632000, 0x38634000, 0x38636000, 0x38638000, 0x3863A000, 0x3863C000, 0x3863E000, + 0x38640000, 0x38642000, 0x38644000, 0x38646000, 0x38648000, 0x3864A000, 0x3864C000, 0x3864E000, 0x38650000, 0x38652000, 0x38654000, 0x38656000, 0x38658000, 0x3865A000, 0x3865C000, 0x3865E000, + 0x38660000, 0x38662000, 0x38664000, 0x38666000, 0x38668000, 0x3866A000, 0x3866C000, 0x3866E000, 0x38670000, 0x38672000, 0x38674000, 0x38676000, 0x38678000, 0x3867A000, 0x3867C000, 0x3867E000, + 0x38680000, 0x38682000, 0x38684000, 0x38686000, 0x38688000, 0x3868A000, 0x3868C000, 0x3868E000, 0x38690000, 0x38692000, 0x38694000, 0x38696000, 0x38698000, 0x3869A000, 0x3869C000, 0x3869E000, + 0x386A0000, 0x386A2000, 0x386A4000, 0x386A6000, 0x386A8000, 0x386AA000, 0x386AC000, 0x386AE000, 0x386B0000, 0x386B2000, 0x386B4000, 0x386B6000, 0x386B8000, 0x386BA000, 0x386BC000, 0x386BE000, + 0x386C0000, 0x386C2000, 0x386C4000, 0x386C6000, 0x386C8000, 0x386CA000, 0x386CC000, 0x386CE000, 0x386D0000, 0x386D2000, 0x386D4000, 0x386D6000, 0x386D8000, 0x386DA000, 0x386DC000, 0x386DE000, + 0x386E0000, 0x386E2000, 0x386E4000, 0x386E6000, 0x386E8000, 0x386EA000, 0x386EC000, 0x386EE000, 0x386F0000, 0x386F2000, 0x386F4000, 0x386F6000, 0x386F8000, 0x386FA000, 0x386FC000, 0x386FE000, + 0x38700000, 0x38702000, 0x38704000, 0x38706000, 0x38708000, 0x3870A000, 0x3870C000, 0x3870E000, 0x38710000, 0x38712000, 0x38714000, 0x38716000, 0x38718000, 0x3871A000, 0x3871C000, 0x3871E000, + 0x38720000, 0x38722000, 0x38724000, 0x38726000, 0x38728000, 0x3872A000, 0x3872C000, 0x3872E000, 0x38730000, 0x38732000, 0x38734000, 0x38736000, 0x38738000, 0x3873A000, 0x3873C000, 0x3873E000, + 0x38740000, 0x38742000, 0x38744000, 0x38746000, 0x38748000, 0x3874A000, 0x3874C000, 0x3874E000, 0x38750000, 0x38752000, 0x38754000, 0x38756000, 0x38758000, 0x3875A000, 0x3875C000, 0x3875E000, + 0x38760000, 0x38762000, 0x38764000, 0x38766000, 0x38768000, 0x3876A000, 0x3876C000, 0x3876E000, 0x38770000, 0x38772000, 0x38774000, 0x38776000, 0x38778000, 0x3877A000, 0x3877C000, 0x3877E000, + 0x38780000, 0x38782000, 0x38784000, 0x38786000, 0x38788000, 0x3878A000, 0x3878C000, 0x3878E000, 0x38790000, 0x38792000, 0x38794000, 0x38796000, 0x38798000, 0x3879A000, 0x3879C000, 0x3879E000, + 0x387A0000, 0x387A2000, 0x387A4000, 0x387A6000, 0x387A8000, 0x387AA000, 0x387AC000, 0x387AE000, 0x387B0000, 0x387B2000, 0x387B4000, 0x387B6000, 0x387B8000, 0x387BA000, 0x387BC000, 0x387BE000, + 0x387C0000, 0x387C2000, 0x387C4000, 0x387C6000, 0x387C8000, 0x387CA000, 0x387CC000, 0x387CE000, 0x387D0000, 0x387D2000, 0x387D4000, 0x387D6000, 0x387D8000, 0x387DA000, 0x387DC000, 0x387DE000, + 0x387E0000, 0x387E2000, 0x387E4000, 0x387E6000, 0x387E8000, 0x387EA000, 0x387EC000, 0x387EE000, 0x387F0000, 0x387F2000, 0x387F4000, 0x387F6000, 0x387F8000, 0x387FA000, 0x387FC000, 0x387FE000 }; + static const uint32_t exponent_table[64] = { + 0x00000000, 0x00800000, 0x01000000, 0x01800000, 0x02000000, 0x02800000, 0x03000000, 0x03800000, 0x04000000, 0x04800000, 0x05000000, 0x05800000, 0x06000000, 0x06800000, 0x07000000, 0x07800000, + 0x08000000, 0x08800000, 0x09000000, 0x09800000, 0x0A000000, 0x0A800000, 0x0B000000, 0x0B800000, 0x0C000000, 0x0C800000, 0x0D000000, 0x0D800000, 0x0E000000, 0x0E800000, 0x0F000000, 0x47800000, + 0x80000000, 0x80800000, 0x81000000, 0x81800000, 0x82000000, 0x82800000, 0x83000000, 0x83800000, 0x84000000, 0x84800000, 0x85000000, 0x85800000, 0x86000000, 0x86800000, 0x87000000, 0x87800000, + 0x88000000, 0x88800000, 0x89000000, 0x89800000, 0x8A000000, 0x8A800000, 0x8B000000, 0x8B800000, 0x8C000000, 0x8C800000, 0x8D000000, 0x8D800000, 0x8E000000, 0x8E800000, 0x8F000000, 0xC7800000 }; + static const unsigned short offset_table[64] = { + 0, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, + 0, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024, 1024 }; + uint32_t bits = mantissa_table[offset_table[value>>10]+(value&0x3FF)] + exponent_table[value>>10]; + float out; + std::memcpy(&out, &bits, sizeof(float)); + return out; +} diff --git a/tools/inference/hf.h b/tools/inference/hf.h new file mode 100644 index 0000000..69657c4 --- /dev/null +++ b/tools/inference/hf.h @@ -0,0 +1,4 @@ +#pragma once +#include + +float hf2float(uint16_t value); diff --git a/tools/inference/main.cpp b/tools/inference/main.cpp new file mode 100644 index 0000000..0883f9d --- /dev/null +++ b/tools/inference/main.cpp @@ -0,0 +1,503 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "hf.h" + +using json = nlohmann::json; + +std::vector load_floats(const std::string & filename) +{ + std::ifstream f(filename, std::ios_base::binary); + f.seekg(0, std::ios::end); + const size_t size = f.tellg(); + f.seekg(0, std::ios::beg); + + std::vector result(size / sizeof(float)); + f.read(reinterpret_cast(result.data()), size); + return result; +} + +std::vector load_hp_floats(const std::string & filename) +{ + std::ifstream f(filename, std::ios_base::binary); + f.seekg(0, std::ios::end); + const size_t size = f.tellg(); + f.seekg(0, std::ios::beg); + + std::vector params(size / sizeof(uint16_t)); + f.read(reinterpret_cast(params.data()), size); + + std::vector result(params.size()); + std::transform(params.begin(), params.end(), result.begin(), hf2float); + return result; +} + +struct Config +{ + struct Encoding + { + unsigned int n_levels; + unsigned int n_features_per_level; + unsigned int log2_hashmap_size; + unsigned int base_resolution; + } encoding; + + struct Network + { + unsigned int n_neurons; + unsigned int n_hidden_layers; + std::string activation; + std::string output_activation; + } network; +}; + +Config load_config(const std::string & filename) +{ + std::ifstream f(filename); + const auto json = json::parse(f); + json::object_t encoding = json["encoding"]; + json::object_t network = json["network"]; + return Config { + .encoding = { + .n_levels = encoding["n_levels"], + .n_features_per_level = encoding["n_features_per_level"], + .log2_hashmap_size = encoding["log2_hashmap_size"], + .base_resolution = encoding["base_resolution"] + }, + .network = { + .n_neurons = network["n_neurons"], + .n_hidden_layers = network["n_hidden_layers"], + .activation = network["activation"], + .output_activation = network["output_activation"] + } + }; +} + +struct Point +{ + float x; + float y; +}; + +struct Rect +{ + float left; + float top; + float right; + float bottom; + + float width() const + { + return right - left; + } + + float height() const + { + return bottom - top; + } +}; + +struct Color +{ + float r; + float g; + float b; +}; + +struct Image +{ + unsigned int width; + unsigned int height; + std::vector pixels; +}; + +Color quantize(const Color & c) +{ + constexpr Color r = {1.0f, 0.0f, 0.0f}; + constexpr Color g = {0.0f, 1.0f, 0.0f}; + constexpr Color b = {0.0f, 0.0f, 1.0f}; + if (c.r > c.g) + { + return c.r > c.b ? r : b; + } + else + { + return c.g > c.b ? g : b; + } +} + +float dot_product(const float * a, const float * b, const size_t n) +{ + float result = 0.f; + for (size_t i = 0; i < n; ++i) + { + result += a[i] * b[i]; + } + return result; +} + +void linear_transform(const std::vector & input, const float * weights, std::vector & output) +{ + const auto m = input.size(); + for (size_t i = 0, n = output.size(); i < n; ++i) + { + output[i] = dot_product(input.data(), weights + i * m, m); + } +} + +void relu(std::vector & values) +{ + for (auto & v : values) + { + v = std::max(0.f, v); + } +} + +void sigmoid(std::vector & values) +{ + for (auto & v : values) + { + v = 1.0f / (1.0f + exp(-v)); + } +} + +void sine(std::vector & values) +{ + for (auto & v : values) + { + v = std::sin(v); + } +} + +void tanh(std::vector & values) +{ + for (auto & v : values) + { + v = std::tanh(v); + } +} + +void none(std::vector &) +{ +} + +using Activation = void (*)(std::vector &); + +Activation get_activation(const std::string & name) +{ + if (name == "ReLU") + { + return relu; + } + else if (name == "Sigmoid") + { + return sigmoid; + } + else if (name == "Sine") + { + return sine; + } + else if (name == "Tanh") + { + return tanh; + } + else if (name == "None") + { + return none; + } + + assert(false); + return nullptr; +} + +template +T div_round_up(T val, T divisor) +{ + return (val + divisor - 1) / divisor; +} + +template +T next_multiple(T val, T divisor) +{ + return div_round_up(val, divisor) * divisor; +} + +class Encoding +{ +public: + class Layer + { + public: + Layer(unsigned int resolution, unsigned int feature_count, bool use_hash, std::vector params) + : resolution_(resolution) + , feature_count_(feature_count) + , params_(std::move(params)) + , output_(feature_count, 0.f) + , use_hash_(use_hash) + { + } + + const std::vector & apply(const Point & p) const + { + const float x = p.x * (resolution_ - 1); + const float y = p.y * (resolution_ - 1); + const unsigned int ix = static_cast(x); + const unsigned int iy = static_cast(y); + const float kx = x - ix; + const float ky = y - iy; + for (unsigned int i = 0; i < feature_count_; ++i) + { + const float v00 = param(ix, iy, i); + const float v10 = param(ix + 1, iy, i); + const float v01 = param(ix, iy + 1, i); + const float v11 = param(ix + 1, iy + 1, i); + const float v_0 = v00 + (v10 - v00) * kx; + const float v_1 = v01 + (v11 - v01) * kx; + output_[i] = v_0 + (v_1 - v_0) * ky; + } + return output_; + } + + uint32_t hash(uint32_t x, uint32_t y) const + { + if (use_hash_) + { + return (y * 2654435761) ^ x; + } + else + { + return y * resolution_ + x; + } + } + + float param(unsigned int x, unsigned int y, unsigned int i) const + { + const size_t index = (hash(x, y) * feature_count_ + i); + return params_[index % params_.size()]; + } + + private: + const unsigned int resolution_; + const unsigned int feature_count_; + const std::vector params_; + const bool use_hash_; + mutable std::vector output_; + }; + + Encoding(const Config::Encoding & config, const std::vector & params) + : feature_count_(config.n_features_per_level) + , output_(config.n_levels * config.n_features_per_level, 1.f) + { + size_t params_offset = 0; + auto resolution = config.base_resolution; + const auto hash_size = size_t(1) << config.log2_hashmap_size; + layers_.reserve(config.n_levels); + for (unsigned int i = 0; i < config.n_levels; ++i) + { + auto use_hash = false; + auto params_count = static_cast(next_multiple(resolution * resolution, 8u)); + if (params_count > hash_size) + { + use_hash = true; + params_count = hash_size; + } + params_count *= config.n_features_per_level; + + assert(params.size() >= params_offset + params_count); + std::vector layer_params(params.begin() + params_offset, params.begin() + params_offset + params_count); + layers_.push_back(Layer(resolution, config.n_features_per_level, use_hash, std::move(layer_params))); + params_offset += params_count; + resolution = (resolution - 1) * 2 + 1; + } + } + + unsigned int output_size() const + { + return output_.size(); + } + + const std::vector & apply(const Point & p, float level) const + { + unsigned int offset = 0; + unsigned int index = 0; + for (const auto & layer : layers_) + { + const auto mask = level - index + 1.f; + if (mask <= 0.f) + { + std::fill(output_.begin() + offset, output_.begin() + offset + feature_count_, 0.f); + } + else + { + const auto & output = layer.apply(p); + std::copy(output.begin(), output.end(), output_.begin() + offset); + if (mask < 1.f) + { + for (auto it = output_.begin() + offset; it != output_.begin() + offset + feature_count_; ++it) + { + *it *= mask; + } + } + } + offset += feature_count_; + index++; + } + return output_; + } + +private: + const unsigned int feature_count_; + std::vector layers_; + mutable std::vector output_; +}; + +class Network +{ +public: + Network(const Config::Network & config, std::vector weights, unsigned int input_size) + : activation_(get_activation(config.activation)) + , output_activation_(get_activation(config.output_activation)) + , weights_(std::move(weights)) + { + values_.reserve(config.n_hidden_layers + 2); + values_.emplace_back(input_size, 0.f); + for (unsigned int i = 0; i < config.n_hidden_layers; i++) + { + values_.emplace_back(config.n_neurons, 0.f); + } + values_.emplace_back(4, 0.f); + + size_t expected_weight_count = 0; + for (size_t i = 0; i < values_.size() - 1; i++) + { + expected_weight_count += values_[i].size() * values_[i + 1].size(); + } + assert(expected_weight_count == weights_.size()); + } + + size_t weight_count() const + { + return weights_.size(); + } + + Color apply(const std::vector & input) const + { + assert(input.size() == values_.front().size()); + std::copy(input.begin(), input.end(), values_.front().begin()); + + size_t weights_offset = 0; + for (size_t i = 0; i < values_.size() - 1; i++) + { + const auto & input = values_[i]; + auto & output = values_[i + 1]; + linear_transform(input, weights_.data() + weights_offset, output); + if (i != values_.size() - 2) + { + activation_(output); + } + else + { + output_activation_(output); + } + weights_offset += input.size() * output.size(); + } + + const auto & output = values_.back(); + return Color{output[0], output[1], output[2]}; + } + +private: + const Activation activation_; + const Activation output_activation_; + std::vector weights_; + mutable std::vector> values_; +}; + +void save_image(const Image & image, const std::string & filename) +{ + const auto to_byte = [](const float v) { + return static_cast(std::max(0.f, std::min(v, 1.0f)) * 255.f + 0.5f); + }; + std::vector pixels; + pixels.reserve(image.pixels.size() * 3); + for (const auto & color : image.pixels) + { + pixels.push_back(to_byte(color.r)); + pixels.push_back(to_byte(color.g)); + pixels.push_back(to_byte(color.b)); + } + stbi_write_png(filename.c_str(), image.width, image.height, 3, pixels.data(), image.width * 3); +} + +Image generate_image(const Encoding & encoding, const Network & network, const Rect & rect, unsigned int resolution, float level) +{ + const auto width = rect.width() >= rect.height() ? resolution : static_cast(resolution * rect.width() / rect.height()); + const auto height = rect.height() >= rect.width() ? resolution : static_cast(resolution * rect.height() / rect.width()); + Image image {width, height, std::vector(width * height)}; + for (unsigned int iy = 0; iy < height; ++iy) + { + const auto y = (iy + 0.5f) / height * rect.height() + rect.top; + for (unsigned int ix = 0; ix < width; ++ix) + { + const auto x = (ix + 0.5f) / width * rect.width() + rect.left; + const auto & encoded = encoding.apply({x, y}, level); + const auto color = quantize(network.apply(encoded)); + image.pixels[iy * width + ix] = color; + } + } + return image; +} + +Rect get_rect(const cxxopts::ParseResult & opt) +{ + const auto rect = Rect { + opt["left"].as(), + opt["top"].as(), + opt["right"].as(), + opt["bottom"].as() + }; + if (rect.left > rect.right || rect.top > rect.bottom) + { + throw std::runtime_error("Invalid rect"); + } + return rect; +} + +int main(int argc, const char ** argv) +{ + cxxopts::Options options_parser("inference"); + options_parser.add_options() + ("config", "", cxxopts::value()) + ("encoding", "", cxxopts::value()->default_value("encoding.data")) + ("network", "", cxxopts::value()->default_value("network.data")) + ("left", "", cxxopts::value()->default_value("0.0")) + ("top", "", cxxopts::value()->default_value("0.0")) + ("right", "", cxxopts::value()->default_value("1.0")) + ("bottom", "", cxxopts::value()->default_value("1.0")) + ("resolution", "", cxxopts::value()->default_value("2000")) + ("level", "", cxxopts::value()->default_value("1000.0")) + ("output", "", cxxopts::value()->default_value("inference.png")); + const auto opt = options_parser.parse(argc, argv); + + const auto config = load_config(opt["config"].as()); + auto encoding_params = load_hp_floats(opt["encoding"].as()); + auto network_params = load_floats(opt["network"].as()); + const auto resolution = opt["resolution"].as(); + const auto level = opt["level"].as(); + const auto output = opt["output"].as(); + + const auto encoded_size = config.encoding.n_levels * config.encoding.n_features_per_level; + const Encoding encoding(config.encoding, std::move(encoding_params)); + const Network network(config.network, std::move(network_params), encoded_size); + + const auto rect = get_rect(opt); + const auto image = generate_image(encoding, network, rect, resolution, level); + save_image(image, output); +} diff --git a/tools/thirdparty/CMakeLists.txt b/tools/thirdparty/CMakeLists.txt new file mode 100644 index 0000000..481e053 --- /dev/null +++ b/tools/thirdparty/CMakeLists.txt @@ -0,0 +1,15 @@ +set(CXXOPTS_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) +set(CXXOPTS_BUILD_TESTS OFF CACHE BOOL "" FORCE) +set(CXXOPTS_ENABLE_INSTALL OFF CACHE BOOL "" FORCE) +add_subdirectory(cxxopts) +add_subdirectory(stbi) + +SET(TCNN_BUILD_BENCHMARK OFF CACHE BOOL "" FORCE) +SET(TCNN_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) +add_subdirectory(tiny-cuda-nn) +target_include_directories(tiny-cuda-nn PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/tiny-cuda-nn/include" + "${CMAKE_CURRENT_SOURCE_DIR}/tiny-cuda-nn/dependencies" +) +target_compile_definitions(tiny-cuda-nn PUBLIC ${TCNN_DEFINITIONS}) +set(CMAKE_CUDA_ARCHITECTURES ${CMAKE_CUDA_ARCHITECTURES} PARENT_SCOPE) diff --git a/tools/thirdparty/cxxopts b/tools/thirdparty/cxxopts new file mode 160000 index 0000000..779c429 --- /dev/null +++ b/tools/thirdparty/cxxopts @@ -0,0 +1 @@ +Subproject commit 779c429b0ec63cc6d5e28b159b044cb8e12ad3a3 diff --git a/tools/thirdparty/stbi/CMakeLists.txt b/tools/thirdparty/stbi/CMakeLists.txt new file mode 100644 index 0000000..a4b9b9e --- /dev/null +++ b/tools/thirdparty/stbi/CMakeLists.txt @@ -0,0 +1,5 @@ +set(TARGET stbi) +add_library(${TARGET} STATIC src/stbi.cpp) +target_include_directories(${TARGET} PUBLIC + "${CMAKE_CURRENT_SOURCE_DIR}/include" +) diff --git a/tools/thirdparty/stbi/include/stb_image.h b/tools/thirdparty/stbi/include/stb_image.h new file mode 100644 index 0000000..d60371b --- /dev/null +++ b/tools/thirdparty/stbi/include/stb_image.h @@ -0,0 +1,7897 @@ +/* stb_image - v2.27 - public domain image loader - http://nothings.org/stb + no warranty implied; use at your own risk + + Do this: + #define STB_IMAGE_IMPLEMENTATION + before you include this file in *one* C or C++ file to create the implementation. + + // i.e. it should look like this: + #include ... + #include ... + #include ... + #define STB_IMAGE_IMPLEMENTATION + #include "stb_image.h" + + You can #define STBI_ASSERT(x) before the #include to avoid using assert.h. + And #define STBI_MALLOC, STBI_REALLOC, and STBI_FREE to avoid using malloc,realloc,free + + + QUICK NOTES: + Primarily of interest to game developers and other people who can + avoid problematic images and only need the trivial interface + + JPEG baseline & progressive (12 bpc/arithmetic not supported, same as stock IJG lib) + PNG 1/2/4/8/16-bit-per-channel + + TGA (not sure what subset, if a subset) + BMP non-1bpp, non-RLE + PSD (composited view only, no extra channels, 8/16 bit-per-channel) + + GIF (*comp always reports as 4-channel) + HDR (radiance rgbE format) + PIC (Softimage PIC) + PNM (PPM and PGM binary only) + + Animated GIF still needs a proper API, but here's one way to do it: + http://gist.github.com/urraka/685d9a6340b26b830d49 + + - decode from memory or through FILE (define STBI_NO_STDIO to remove code) + - decode from arbitrary I/O callbacks + - SIMD acceleration on x86/x64 (SSE2) and ARM (NEON) + + Full documentation under "DOCUMENTATION" below. + + +LICENSE + + See end of file for license information. + +RECENT REVISION HISTORY: + + 2.27 (2021-07-11) document stbi_info better, 16-bit PNM support, bug fixes + 2.26 (2020-07-13) many minor fixes + 2.25 (2020-02-02) fix warnings + 2.24 (2020-02-02) fix warnings; thread-local failure_reason and flip_vertically + 2.23 (2019-08-11) fix clang static analysis warning + 2.22 (2019-03-04) gif fixes, fix warnings + 2.21 (2019-02-25) fix typo in comment + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) bugfix, 1-bit BMP, 16-bitness query, fix warnings + 2.16 (2017-07-23) all functions have 16-bit variants; optimizations; bugfixes + 2.15 (2017-03-18) fix png-1,2,4; all Imagenet JPGs; no runtime SSE detection on GCC + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-12-04) experimental 16-bit API, only for PNG so far; fixes + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) 16-bit PNGS; enable SSE2 in non-gcc x64 + RGB-format JPEG; remove white matting in PSD; + allocate large structures on the stack; + correct channel count for PNG & BMP + 2.10 (2016-01-22) avoid warning introduced in 2.09 + 2.09 (2016-01-16) 16-bit TGA; comments in PNM files; STBI_REALLOC_SIZED + + See end of file for full revision history. + + + ============================ Contributors ========================= + + Image formats Extensions, features + Sean Barrett (jpeg, png, bmp) Jetro Lauha (stbi_info) + Nicolas Schulz (hdr, psd) Martin "SpartanJ" Golini (stbi_info) + Jonathan Dummer (tga) James "moose2000" Brown (iPhone PNG) + Jean-Marc Lienher (gif) Ben "Disch" Wenger (io callbacks) + Tom Seddon (pic) Omar Cornut (1/2/4-bit PNG) + Thatcher Ulrich (psd) Nicolas Guillemot (vertical flip) + Ken Miller (pgm, ppm) Richard Mitton (16-bit PSD) + github:urraka (animated gif) Junggon Kim (PNM comments) + Christopher Forseth (animated gif) Daniel Gibson (16-bit TGA) + socks-the-fox (16-bit PNG) + Jeremy Sawicki (handle all ImageNet JPGs) + Optimizations & bugfixes Mikhail Morozov (1-bit BMP) + Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query) + Arseny Kapoulkine Simon Breuss (16-bit PNM) + John-Mark Allen + Carmelo J Fdez-Aguera + + Bug & warning fixes + Marc LeBlanc David Woo Guillaume George Martins Mozeiko + Christpher Lloyd Jerry Jansson Joseph Thomson Blazej Dariusz Roszkowski + Phil Jordan Dave Moore Roy Eltham + Hayaki Saito Nathan Reed Won Chun + Luke Graham Johan Duparc Nick Verigakis the Horde3D community + Thomas Ruf Ronny Chevalier github:rlyeh + Janez Zemva John Bartholomew Michal Cichon github:romigrou + Jonathan Blow Ken Hamada Tero Hanninen github:svdijk + Eugene Golushkov Laurent Gomila Cort Stratton github:snagar + Aruelien Pocheville Sergio Gonzalez Thibault Reuille github:Zelex + Cass Everitt Ryamond Barbiero github:grim210 + Paul Du Bois Engin Manap Aldo Culquicondor github:sammyhw + Philipp Wiesemann Dale Weiler Oriol Ferrer Mesia github:phprus + Josh Tobin Matthew Gregan github:poppolopoppo + Julian Raschke Gregory Mullen Christian Floisand github:darealshinji + Baldur Karlsson Kevin Schmidt JR Smith github:Michaelangel007 + Brad Weinberger Matvey Cherevko github:mosra + Luca Sas Alexander Veselov Zack Middleton [reserved] + Ryan C. Gordon [reserved] [reserved] + DO NOT ADD YOUR NAME HERE + + Jacko Dirks + + To add your name to the credits, pick a random blank space in the middle and fill it. + 80% of merge conflicts on stb PRs are due to people adding their name at the end + of the credits. +*/ + +#ifndef STBI_INCLUDE_STB_IMAGE_H +#define STBI_INCLUDE_STB_IMAGE_H + +// DOCUMENTATION +// +// Limitations: +// - no 12-bit-per-channel JPEG +// - no JPEGs with arithmetic coding +// - GIF always returns *comp=4 +// +// Basic usage (see HDR discussion below for HDR usage): +// int x,y,n; +// unsigned char *data = stbi_load(filename, &x, &y, &n, 0); +// // ... process data if not NULL ... +// // ... x = width, y = height, n = # 8-bit components per pixel ... +// // ... replace '0' with '1'..'4' to force that many components per pixel +// // ... but 'n' will always be the number that it would have been if you said 0 +// stbi_image_free(data) +// +// Standard parameters: +// int *x -- outputs image width in pixels +// int *y -- outputs image height in pixels +// int *channels_in_file -- outputs # of image components in image file +// int desired_channels -- if non-zero, # of image components requested in result +// +// The return value from an image loader is an 'unsigned char *' which points +// to the pixel data, or NULL on an allocation failure or if the image is +// corrupt or invalid. The pixel data consists of *y scanlines of *x pixels, +// with each pixel consisting of N interleaved 8-bit components; the first +// pixel pointed to is top-left-most in the image. There is no padding between +// image scanlines or between pixels, regardless of format. The number of +// components N is 'desired_channels' if desired_channels is non-zero, or +// *channels_in_file otherwise. If desired_channels is non-zero, +// *channels_in_file has the number of components that _would_ have been +// output otherwise. E.g. if you set desired_channels to 4, you will always +// get RGBA output, but you can check *channels_in_file to see if it's trivially +// opaque because e.g. there were only 3 channels in the source image. +// +// An output image with N components has the following components interleaved +// in this order in each pixel: +// +// N=#comp components +// 1 grey +// 2 grey, alpha +// 3 red, green, blue +// 4 red, green, blue, alpha +// +// If image loading fails for any reason, the return value will be NULL, +// and *x, *y, *channels_in_file will be unchanged. The function +// stbi_failure_reason() can be queried for an extremely brief, end-user +// unfriendly explanation of why the load failed. Define STBI_NO_FAILURE_STRINGS +// to avoid compiling these strings at all, and STBI_FAILURE_USERMSG to get slightly +// more user-friendly ones. +// +// Paletted PNG, BMP, GIF, and PIC images are automatically depalettized. +// +// To query the width, height and component count of an image without having to +// decode the full file, you can use the stbi_info family of functions: +// +// int x,y,n,ok; +// ok = stbi_info(filename, &x, &y, &n); +// // returns ok=1 and sets x, y, n if image is a supported format, +// // 0 otherwise. +// +// Note that stb_image pervasively uses ints in its public API for sizes, +// including sizes of memory buffers. This is now part of the API and thus +// hard to change without causing breakage. As a result, the various image +// loaders all have certain limits on image size; these differ somewhat +// by format but generally boil down to either just under 2GB or just under +// 1GB. When the decoded image would be larger than this, stb_image decoding +// will fail. +// +// Additionally, stb_image will reject image files that have any of their +// dimensions set to a larger value than the configurable STBI_MAX_DIMENSIONS, +// which defaults to 2**24 = 16777216 pixels. Due to the above memory limit, +// the only way to have an image with such dimensions load correctly +// is for it to have a rather extreme aspect ratio. Either way, the +// assumption here is that such larger images are likely to be malformed +// or malicious. If you do need to load an image with individual dimensions +// larger than that, and it still fits in the overall size limit, you can +// #define STBI_MAX_DIMENSIONS on your own to be something larger. +// +// =========================================================================== +// +// UNICODE: +// +// If compiling for Windows and you wish to use Unicode filenames, compile +// with +// #define STBI_WINDOWS_UTF8 +// and pass utf8-encoded filenames. Call stbi_convert_wchar_to_utf8 to convert +// Windows wchar_t filenames to utf8. +// +// =========================================================================== +// +// Philosophy +// +// stb libraries are designed with the following priorities: +// +// 1. easy to use +// 2. easy to maintain +// 3. good performance +// +// Sometimes I let "good performance" creep up in priority over "easy to maintain", +// and for best performance I may provide less-easy-to-use APIs that give higher +// performance, in addition to the easy-to-use ones. Nevertheless, it's important +// to keep in mind that from the standpoint of you, a client of this library, +// all you care about is #1 and #3, and stb libraries DO NOT emphasize #3 above all. +// +// Some secondary priorities arise directly from the first two, some of which +// provide more explicit reasons why performance can't be emphasized. +// +// - Portable ("ease of use") +// - Small source code footprint ("easy to maintain") +// - No dependencies ("ease of use") +// +// =========================================================================== +// +// I/O callbacks +// +// I/O callbacks allow you to read from arbitrary sources, like packaged +// files or some other source. Data read from callbacks are processed +// through a small internal buffer (currently 128 bytes) to try to reduce +// overhead. +// +// The three functions you must define are "read" (reads some bytes of data), +// "skip" (skips some bytes of data), "eof" (reports if the stream is at the end). +// +// =========================================================================== +// +// SIMD support +// +// The JPEG decoder will try to automatically use SIMD kernels on x86 when +// supported by the compiler. For ARM Neon support, you must explicitly +// request it. +// +// (The old do-it-yourself SIMD API is no longer supported in the current +// code.) +// +// On x86, SSE2 will automatically be used when available based on a run-time +// test; if not, the generic C versions are used as a fall-back. On ARM targets, +// the typical path is to have separate builds for NEON and non-NEON devices +// (at least this is true for iOS and Android). Therefore, the NEON support is +// toggled by a build flag: define STBI_NEON to get NEON loops. +// +// If for some reason you do not want to use any of SIMD code, or if +// you have issues compiling it, you can disable it entirely by +// defining STBI_NO_SIMD. +// +// =========================================================================== +// +// HDR image support (disable by defining STBI_NO_HDR) +// +// stb_image supports loading HDR images in general, and currently the Radiance +// .HDR file format specifically. You can still load any file through the existing +// interface; if you attempt to load an HDR file, it will be automatically remapped +// to LDR, assuming gamma 2.2 and an arbitrary scale factor defaulting to 1; +// both of these constants can be reconfigured through this interface: +// +// stbi_hdr_to_ldr_gamma(2.2f); +// stbi_hdr_to_ldr_scale(1.0f); +// +// (note, do not use _inverse_ constants; stbi_image will invert them +// appropriately). +// +// Additionally, there is a new, parallel interface for loading files as +// (linear) floats to preserve the full dynamic range: +// +// float *data = stbi_loadf(filename, &x, &y, &n, 0); +// +// If you load LDR images through this interface, those images will +// be promoted to floating point values, run through the inverse of +// constants corresponding to the above: +// +// stbi_ldr_to_hdr_scale(1.0f); +// stbi_ldr_to_hdr_gamma(2.2f); +// +// Finally, given a filename (or an open file or memory block--see header +// file for details) containing image data, you can query for the "most +// appropriate" interface to use (that is, whether the image is HDR or +// not), using: +// +// stbi_is_hdr(char *filename); +// +// =========================================================================== +// +// iPhone PNG support: +// +// We optionally support converting iPhone-formatted PNGs (which store +// premultiplied BGRA) back to RGB, even though they're internally encoded +// differently. To enable this conversion, call +// stbi_convert_iphone_png_to_rgb(1). +// +// Call stbi_set_unpremultiply_on_load(1) as well to force a divide per +// pixel to remove any premultiplied alpha *only* if the image file explicitly +// says there's premultiplied data (currently only happens in iPhone images, +// and only if iPhone convert-to-rgb processing is on). +// +// =========================================================================== +// +// ADDITIONAL CONFIGURATION +// +// - You can suppress implementation of any of the decoders to reduce +// your code footprint by #defining one or more of the following +// symbols before creating the implementation. +// +// STBI_NO_JPEG +// STBI_NO_PNG +// STBI_NO_BMP +// STBI_NO_PSD +// STBI_NO_TGA +// STBI_NO_GIF +// STBI_NO_HDR +// STBI_NO_PIC +// STBI_NO_PNM (.ppm and .pgm) +// +// - You can request *only* certain decoders and suppress all other ones +// (this will be more forward-compatible, as addition of new decoders +// doesn't require you to disable them explicitly): +// +// STBI_ONLY_JPEG +// STBI_ONLY_PNG +// STBI_ONLY_BMP +// STBI_ONLY_PSD +// STBI_ONLY_TGA +// STBI_ONLY_GIF +// STBI_ONLY_HDR +// STBI_ONLY_PIC +// STBI_ONLY_PNM (.ppm and .pgm) +// +// - If you use STBI_NO_PNG (or _ONLY_ without PNG), and you still +// want the zlib decoder to be available, #define STBI_SUPPORT_ZLIB +// +// - If you define STBI_MAX_DIMENSIONS, stb_image will reject images greater +// than that size (in either width or height) without further processing. +// This is to let programs in the wild set an upper bound to prevent +// denial-of-service attacks on untrusted data, as one could generate a +// valid image of gigantic dimensions and force stb_image to allocate a +// huge block of memory and spend disproportionate time decoding it. By +// default this is set to (1 << 24), which is 16777216, but that's still +// very big. + +#ifndef STBI_NO_STDIO +#include +#endif // STBI_NO_STDIO + +#define STBI_VERSION 1 + +enum +{ + STBI_default = 0, // only used for desired_channels + + STBI_grey = 1, + STBI_grey_alpha = 2, + STBI_rgb = 3, + STBI_rgb_alpha = 4 +}; + +#include +typedef unsigned char stbi_uc; +typedef unsigned short stbi_us; + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef STBIDEF +#ifdef STB_IMAGE_STATIC +#define STBIDEF static +#else +#define STBIDEF extern +#endif +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// PRIMARY API - works on images of any type +// + +// +// load image by filename, open file, or memory buffer +// + +typedef struct +{ + int (*read) (void *user,char *data,int size); // fill 'data' with 'size' bytes. return number of bytes actually read + void (*skip) (void *user,int n); // skip the next 'n' bytes, or 'unget' the last -n bytes if negative + int (*eof) (void *user); // returns nonzero if we are at end of file/data +} stbi_io_callbacks; + +//////////////////////////////////// +// +// 8-bits-per-channel interface +// + +STBIDEF stbi_uc *stbi_load_from_memory (stbi_uc const *buffer, int len , int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk , void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_uc *stbi_load (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_uc *stbi_load_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +// for stbi_load_from_file, file pointer is left pointing immediately after image +#endif + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +#endif + +#ifdef STBI_WINDOWS_UTF8 +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif + +//////////////////////////////////// +// +// 16-bits-per-channel interface +// + +STBIDEF stbi_us *stbi_load_16_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + +#ifndef STBI_NO_STDIO +STBIDEF stbi_us *stbi_load_16 (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); +STBIDEF stbi_us *stbi_load_from_file_16(FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); +#endif + +//////////////////////////////////// +// +// float-per-channel interface +// +#ifndef STBI_NO_LINEAR + STBIDEF float *stbi_loadf_from_memory (stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_callbacks (stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels); + + #ifndef STBI_NO_STDIO + STBIDEF float *stbi_loadf (char const *filename, int *x, int *y, int *channels_in_file, int desired_channels); + STBIDEF float *stbi_loadf_from_file (FILE *f, int *x, int *y, int *channels_in_file, int desired_channels); + #endif +#endif + +#ifndef STBI_NO_HDR + STBIDEF void stbi_hdr_to_ldr_gamma(float gamma); + STBIDEF void stbi_hdr_to_ldr_scale(float scale); +#endif // STBI_NO_HDR + +#ifndef STBI_NO_LINEAR + STBIDEF void stbi_ldr_to_hdr_gamma(float gamma); + STBIDEF void stbi_ldr_to_hdr_scale(float scale); +#endif // STBI_NO_LINEAR + +// stbi_is_hdr is always defined, but always returns false if STBI_NO_HDR +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user); +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len); +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename); +STBIDEF int stbi_is_hdr_from_file(FILE *f); +#endif // STBI_NO_STDIO + + +// get a VERY brief reason for failure +// on most compilers (and ALL modern mainstream compilers) this is threadsafe +STBIDEF const char *stbi_failure_reason (void); + +// free the loaded image -- this is just free() +STBIDEF void stbi_image_free (void *retval_from_stbi_load); + +// get image dimensions & components without fully decoding +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len); +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *clbk, void *user); + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info (char const *filename, int *x, int *y, int *comp); +STBIDEF int stbi_info_from_file (FILE *f, int *x, int *y, int *comp); +STBIDEF int stbi_is_16_bit (char const *filename); +STBIDEF int stbi_is_16_bit_from_file(FILE *f); +#endif + + + +// for image formats that explicitly notate that they have premultiplied alpha, +// we just return the colors as stored in the file. set this flag to force +// unpremultiplication. results are undefined if the unpremultiply overflow. +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply); + +// indicate whether we should process iphone images back to canonical format, +// or just pass them through "as-is" +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert); + +// flip the image vertically, so the first pixel in the output array is the bottom left +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip); + +// as above, but only applies to images loaded on the thread that calls the function +// this function is only available if your compiler supports thread-local variables; +// calling it will fail to link if your compiler doesn't +STBIDEF void stbi_set_unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply); +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert); +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip); + +// ZLIB client - used by PNG, available for other purposes + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen); +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header); +STBIDEF char *stbi_zlib_decode_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + +STBIDEF char *stbi_zlib_decode_noheader_malloc(const char *buffer, int len, int *outlen); +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen); + + +#ifdef __cplusplus +} +#endif + +// +// +//// end header file ///////////////////////////////////////////////////// +#endif // STBI_INCLUDE_STB_IMAGE_H + +#ifdef STB_IMAGE_IMPLEMENTATION + +#if defined(STBI_ONLY_JPEG) || defined(STBI_ONLY_PNG) || defined(STBI_ONLY_BMP) \ + || defined(STBI_ONLY_TGA) || defined(STBI_ONLY_GIF) || defined(STBI_ONLY_PSD) \ + || defined(STBI_ONLY_HDR) || defined(STBI_ONLY_PIC) || defined(STBI_ONLY_PNM) \ + || defined(STBI_ONLY_ZLIB) + #ifndef STBI_ONLY_JPEG + #define STBI_NO_JPEG + #endif + #ifndef STBI_ONLY_PNG + #define STBI_NO_PNG + #endif + #ifndef STBI_ONLY_BMP + #define STBI_NO_BMP + #endif + #ifndef STBI_ONLY_PSD + #define STBI_NO_PSD + #endif + #ifndef STBI_ONLY_TGA + #define STBI_NO_TGA + #endif + #ifndef STBI_ONLY_GIF + #define STBI_NO_GIF + #endif + #ifndef STBI_ONLY_HDR + #define STBI_NO_HDR + #endif + #ifndef STBI_ONLY_PIC + #define STBI_NO_PIC + #endif + #ifndef STBI_ONLY_PNM + #define STBI_NO_PNM + #endif +#endif + +#if defined(STBI_NO_PNG) && !defined(STBI_SUPPORT_ZLIB) && !defined(STBI_NO_ZLIB) +#define STBI_NO_ZLIB +#endif + + +#include +#include // ptrdiff_t on osx +#include +#include +#include + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) +#include // ldexp, pow +#endif + +#ifndef STBI_NO_STDIO +#include +#endif + +#ifndef STBI_ASSERT +#include +#define STBI_ASSERT(x) assert(x) +#endif + +#ifdef __cplusplus +#define STBI_EXTERN extern "C" +#else +#define STBI_EXTERN extern +#endif + + +#ifndef _MSC_VER + #ifdef __cplusplus + #define stbi_inline inline + #else + #define stbi_inline + #endif +#else + #define stbi_inline __forceinline +#endif + +#ifndef STBI_NO_THREAD_LOCALS + #if defined(__cplusplus) && __cplusplus >= 201103L + #define STBI_THREAD_LOCAL thread_local + #elif defined(__GNUC__) && __GNUC__ < 5 + #define STBI_THREAD_LOCAL __thread + #elif defined(_MSC_VER) + #define STBI_THREAD_LOCAL __declspec(thread) + #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_THREADS__) + #define STBI_THREAD_LOCAL _Thread_local + #endif + + #ifndef STBI_THREAD_LOCAL + #if defined(__GNUC__) + #define STBI_THREAD_LOCAL __thread + #endif + #endif +#endif + +#ifdef _MSC_VER +typedef unsigned short stbi__uint16; +typedef signed short stbi__int16; +typedef unsigned int stbi__uint32; +typedef signed int stbi__int32; +#else +#include +typedef uint16_t stbi__uint16; +typedef int16_t stbi__int16; +typedef uint32_t stbi__uint32; +typedef int32_t stbi__int32; +#endif + +// should produce compiler error if size is wrong +typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1]; + +#ifdef _MSC_VER +#define STBI_NOTUSED(v) (void)(v) +#else +#define STBI_NOTUSED(v) (void)sizeof(v) +#endif + +#ifdef _MSC_VER +#define STBI_HAS_LROTL +#endif + +#ifdef STBI_HAS_LROTL + #define stbi_lrot(x,y) _lrotl(x,y) +#else + #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (-(y) & 31))) +#endif + +#if defined(STBI_MALLOC) && defined(STBI_FREE) && (defined(STBI_REALLOC) || defined(STBI_REALLOC_SIZED)) +// ok +#elif !defined(STBI_MALLOC) && !defined(STBI_FREE) && !defined(STBI_REALLOC) && !defined(STBI_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBI_MALLOC, STBI_FREE, and STBI_REALLOC (or STBI_REALLOC_SIZED)." +#endif + +#ifndef STBI_MALLOC +#define STBI_MALLOC(sz) malloc(sz) +#define STBI_REALLOC(p,newsz) realloc(p,newsz) +#define STBI_FREE(p) free(p) +#endif + +#ifndef STBI_REALLOC_SIZED +#define STBI_REALLOC_SIZED(p,oldsz,newsz) STBI_REALLOC(p,newsz) +#endif + +// x86/x64 detection +#if defined(__x86_64__) || defined(_M_X64) +#define STBI__X64_TARGET +#elif defined(__i386) || defined(_M_IX86) +#define STBI__X86_TARGET +#endif + +#if defined(__GNUC__) && defined(STBI__X86_TARGET) && !defined(__SSE2__) && !defined(STBI_NO_SIMD) +// gcc doesn't support sse2 intrinsics unless you compile with -msse2, +// which in turn means it gets to use SSE2 everywhere. This is unfortunate, +// but previous attempts to provide the SSE2 functions with runtime +// detection caused numerous issues. The way architecture extensions are +// exposed in GCC/Clang is, sadly, not really suited for one-file libs. +// New behavior: if compiled with -msse2, we use SSE2 without any +// detection; if not, we don't use it at all. +#define STBI_NO_SIMD +#endif + +#if defined(__MINGW32__) && defined(STBI__X86_TARGET) && !defined(STBI_MINGW_ENABLE_SSE2) && !defined(STBI_NO_SIMD) +// Note that __MINGW32__ doesn't actually mean 32-bit, so we have to avoid STBI__X64_TARGET +// +// 32-bit MinGW wants ESP to be 16-byte aligned, but this is not in the +// Windows ABI and VC++ as well as Windows DLLs don't maintain that invariant. +// As a result, enabling SSE2 on 32-bit MinGW is dangerous when not +// simultaneously enabling "-mstackrealign". +// +// See https://github.com/nothings/stb/issues/81 for more information. +// +// So default to no SSE2 on 32-bit MinGW. If you've read this far and added +// -mstackrealign to your build settings, feel free to #define STBI_MINGW_ENABLE_SSE2. +#define STBI_NO_SIMD +#endif + +#if !defined(STBI_NO_SIMD) && (defined(STBI__X86_TARGET) || defined(STBI__X64_TARGET)) +#define STBI_SSE2 +#include + +#ifdef _MSC_VER + +#if _MSC_VER >= 1400 // not VC6 +#include // __cpuid +static int stbi__cpuid3(void) +{ + int info[4]; + __cpuid(info,1); + return info[3]; +} +#else +static int stbi__cpuid3(void) +{ + int res; + __asm { + mov eax,1 + cpuid + mov res,edx + } + return res; +} +#endif + +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + int info3 = stbi__cpuid3(); + return ((info3 >> 26) & 1) != 0; +} +#endif + +#else // assume GCC-style if not VC++ +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) + +#if !defined(STBI_NO_JPEG) && defined(STBI_SSE2) +static int stbi__sse2_available(void) +{ + // If we're even attempting to compile this on GCC/Clang, that means + // -msse2 is on, which means the compiler is allowed to use SSE2 + // instructions at will, and so are we. + return 1; +} +#endif + +#endif +#endif + +// ARM NEON +#if defined(STBI_NO_SIMD) && defined(STBI_NEON) +#undef STBI_NEON +#endif + +#ifdef STBI_NEON +#include +#ifdef _MSC_VER +#define STBI_SIMD_ALIGN(type, name) __declspec(align(16)) type name +#else +#define STBI_SIMD_ALIGN(type, name) type name __attribute__((aligned(16))) +#endif +#endif + +#ifndef STBI_SIMD_ALIGN +#define STBI_SIMD_ALIGN(type, name) type name +#endif + +#ifndef STBI_MAX_DIMENSIONS +#define STBI_MAX_DIMENSIONS (1 << 24) +#endif + +/////////////////////////////////////////////// +// +// stbi__context struct and start_xxx functions + +// stbi__context structure is our basic context used by all images, so it +// contains all the IO context, plus some basic image information +typedef struct +{ + stbi__uint32 img_x, img_y; + int img_n, img_out_n; + + stbi_io_callbacks io; + void *io_user_data; + + int read_from_callbacks; + int buflen; + stbi_uc buffer_start[128]; + int callback_already_read; + + stbi_uc *img_buffer, *img_buffer_end; + stbi_uc *img_buffer_original, *img_buffer_original_end; +} stbi__context; + + +static void stbi__refill_buffer(stbi__context *s); + +// initialize a memory-decode context +static void stbi__start_mem(stbi__context *s, stbi_uc const *buffer, int len) +{ + s->io.read = NULL; + s->read_from_callbacks = 0; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = (stbi_uc *) buffer; + s->img_buffer_end = s->img_buffer_original_end = (stbi_uc *) buffer+len; +} + +// initialize a callback-based context +static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *user) +{ + s->io = *c; + s->io_user_data = user; + s->buflen = sizeof(s->buffer_start); + s->read_from_callbacks = 1; + s->callback_already_read = 0; + s->img_buffer = s->img_buffer_original = s->buffer_start; + stbi__refill_buffer(s); + s->img_buffer_original_end = s->img_buffer_end; +} + +#ifndef STBI_NO_STDIO + +static int stbi__stdio_read(void *user, char *data, int size) +{ + return (int) fread(data,1,size,(FILE*) user); +} + +static void stbi__stdio_skip(void *user, int n) +{ + int ch; + fseek((FILE*) user, n, SEEK_CUR); + ch = fgetc((FILE*) user); /* have to read a byte to reset feof()'s flag */ + if (ch != EOF) { + ungetc(ch, (FILE *) user); /* push byte back onto stream if valid. */ + } +} + +static int stbi__stdio_eof(void *user) +{ + return feof((FILE*) user) || ferror((FILE *) user); +} + +static stbi_io_callbacks stbi__stdio_callbacks = +{ + stbi__stdio_read, + stbi__stdio_skip, + stbi__stdio_eof, +}; + +static void stbi__start_file(stbi__context *s, FILE *f) +{ + stbi__start_callbacks(s, &stbi__stdio_callbacks, (void *) f); +} + +//static void stop_file(stbi__context *s) { } + +#endif // !STBI_NO_STDIO + +static void stbi__rewind(stbi__context *s) +{ + // conceptually rewind SHOULD rewind to the beginning of the stream, + // but we just rewind to the beginning of the initial buffer, because + // we only use it after doing 'test', which only ever looks at at most 92 bytes + s->img_buffer = s->img_buffer_original; + s->img_buffer_end = s->img_buffer_original_end; +} + +enum +{ + STBI_ORDER_RGB, + STBI_ORDER_BGR +}; + +typedef struct +{ + int bits_per_channel; + int num_channels; + int channel_order; +} stbi__result_info; + +#ifndef STBI_NO_JPEG +static int stbi__jpeg_test(stbi__context *s); +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNG +static int stbi__png_test(stbi__context *s); +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__png_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_BMP +static int stbi__bmp_test(stbi__context *s); +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_TGA +static int stbi__tga_test(stbi__context *s); +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s); +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc); +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__psd_is16(stbi__context *s); +#endif + +#ifndef STBI_NO_HDR +static int stbi__hdr_test(stbi__context *s); +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_test(stbi__context *s); +static void *stbi__pic_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_GIF +static int stbi__gif_test(stbi__context *s); +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp); +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp); +#endif + +#ifndef STBI_NO_PNM +static int stbi__pnm_test(stbi__context *s); +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri); +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp); +static int stbi__pnm_is16(stbi__context *s); +#endif + +static +#ifdef STBI_THREAD_LOCAL +STBI_THREAD_LOCAL +#endif +const char *stbi__g_failure_reason; + +STBIDEF const char *stbi_failure_reason(void) +{ + return stbi__g_failure_reason; +} + +#ifndef STBI_NO_FAILURE_STRINGS +static int stbi__err(const char *str) +{ + stbi__g_failure_reason = str; + return 0; +} +#endif + +static void *stbi__malloc(size_t size) +{ + return STBI_MALLOC(size); +} + +// stb_image uses ints pervasively, including for offset calculations. +// therefore the largest decoded image size we can support with the +// current code, even on 64-bit targets, is INT_MAX. this is not a +// significant limitation for the intended use case. +// +// we do, however, need to make sure our size calculations don't +// overflow. hence a few helper functions for size calculations that +// multiply integers together, making sure that they're non-negative +// and no overflow occurs. + +// return 1 if the sum is valid, 0 on overflow. +// negative terms are considered invalid. +static int stbi__addsizes_valid(int a, int b) +{ + if (b < 0) return 0; + // now 0 <= b <= INT_MAX, hence also + // 0 <= INT_MAX - b <= INTMAX. + // And "a + b <= INT_MAX" (which might overflow) is the + // same as a <= INT_MAX - b (no overflow) + return a <= INT_MAX - b; +} + +// returns 1 if the product is valid, 0 on overflow. +// negative factors are considered invalid. +static int stbi__mul2sizes_valid(int a, int b) +{ + if (a < 0 || b < 0) return 0; + if (b == 0) return 1; // mul-by-0 is always safe + // portable way to check for no overflows in a*b + return a <= INT_MAX/b; +} + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// returns 1 if "a*b + add" has no negative terms/factors and doesn't overflow +static int stbi__mad2sizes_valid(int a, int b, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__addsizes_valid(a*b, add); +} +#endif + +// returns 1 if "a*b*c + add" has no negative terms/factors and doesn't overflow +static int stbi__mad3sizes_valid(int a, int b, int c, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__addsizes_valid(a*b*c, add); +} + +// returns 1 if "a*b*c*d + add" has no negative terms/factors and doesn't overflow +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static int stbi__mad4sizes_valid(int a, int b, int c, int d, int add) +{ + return stbi__mul2sizes_valid(a, b) && stbi__mul2sizes_valid(a*b, c) && + stbi__mul2sizes_valid(a*b*c, d) && stbi__addsizes_valid(a*b*c*d, add); +} +#endif + +#if !defined(STBI_NO_JPEG) || !defined(STBI_NO_PNG) || !defined(STBI_NO_TGA) || !defined(STBI_NO_HDR) +// mallocs with size overflow checking +static void *stbi__malloc_mad2(int a, int b, int add) +{ + if (!stbi__mad2sizes_valid(a, b, add)) return NULL; + return stbi__malloc(a*b + add); +} +#endif + +static void *stbi__malloc_mad3(int a, int b, int c, int add) +{ + if (!stbi__mad3sizes_valid(a, b, c, add)) return NULL; + return stbi__malloc(a*b*c + add); +} + +#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR) || !defined(STBI_NO_PNM) +static void *stbi__malloc_mad4(int a, int b, int c, int d, int add) +{ + if (!stbi__mad4sizes_valid(a, b, c, d, add)) return NULL; + return stbi__malloc(a*b*c*d + add); +} +#endif + +// stbi__err - error +// stbi__errpf - error returning pointer to float +// stbi__errpuc - error returning pointer to unsigned char + +#ifdef STBI_NO_FAILURE_STRINGS + #define stbi__err(x,y) 0 +#elif defined(STBI_FAILURE_USERMSG) + #define stbi__err(x,y) stbi__err(y) +#else + #define stbi__err(x,y) stbi__err(x) +#endif + +#define stbi__errpf(x,y) ((float *)(size_t) (stbi__err(x,y)?NULL:NULL)) +#define stbi__errpuc(x,y) ((unsigned char *)(size_t) (stbi__err(x,y)?NULL:NULL)) + +STBIDEF void stbi_image_free(void *retval_from_stbi_load) +{ + STBI_FREE(retval_from_stbi_load); +} + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp); +#endif + +#ifndef STBI_NO_HDR +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp); +#endif + +static int stbi__vertically_flip_on_load_global = 0; + +STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_global = flag_true_if_should_flip; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__vertically_flip_on_load stbi__vertically_flip_on_load_global +#else +static STBI_THREAD_LOCAL int stbi__vertically_flip_on_load_local, stbi__vertically_flip_on_load_set; + +STBIDEF void stbi_set_flip_vertically_on_load_thread(int flag_true_if_should_flip) +{ + stbi__vertically_flip_on_load_local = flag_true_if_should_flip; + stbi__vertically_flip_on_load_set = 1; +} + +#define stbi__vertically_flip_on_load (stbi__vertically_flip_on_load_set \ + ? stbi__vertically_flip_on_load_local \ + : stbi__vertically_flip_on_load_global) +#endif // STBI_THREAD_LOCAL + +static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + memset(ri, 0, sizeof(*ri)); // make sure it's initialized if we add new fields + ri->bits_per_channel = 8; // default is 8 so most paths don't have to be changed + ri->channel_order = STBI_ORDER_RGB; // all current input & output are this, but this is here so we can add BGR order + ri->num_channels = 0; + + // test the formats with a very explicit header first (at least a FOURCC + // or distinctive magic number first) + #ifndef STBI_NO_PNG + if (stbi__png_test(s)) return stbi__png_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_BMP + if (stbi__bmp_test(s)) return stbi__bmp_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_GIF + if (stbi__gif_test(s)) return stbi__gif_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PSD + if (stbi__psd_test(s)) return stbi__psd_load(s,x,y,comp,req_comp, ri, bpc); + #else + STBI_NOTUSED(bpc); + #endif + #ifndef STBI_NO_PIC + if (stbi__pic_test(s)) return stbi__pic_load(s,x,y,comp,req_comp, ri); + #endif + + // then the formats that can end up attempting to load with just 1 or 2 + // bytes matching expectations; these are prone to false positives, so + // try them later + #ifndef STBI_NO_JPEG + if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri); + #endif + #ifndef STBI_NO_PNM + if (stbi__pnm_test(s)) return stbi__pnm_load(s,x,y,comp,req_comp, ri); + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + float *hdr = stbi__hdr_load(s, x,y,comp,req_comp, ri); + return stbi__hdr_to_ldr(hdr, *x, *y, req_comp ? req_comp : *comp); + } + #endif + + #ifndef STBI_NO_TGA + // test tga last because it's a crappy test! + if (stbi__tga_test(s)) + return stbi__tga_load(s,x,y,comp,req_comp, ri); + #endif + + return stbi__errpuc("unknown image type", "Image not of any known type, or corrupt"); +} + +static stbi_uc *stbi__convert_16_to_8(stbi__uint16 *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi_uc *reduced; + + reduced = (stbi_uc *) stbi__malloc(img_len); + if (reduced == NULL) return stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + reduced[i] = (stbi_uc)((orig[i] >> 8) & 0xFF); // top half of each byte is sufficient approx of 16->8 bit scaling + + STBI_FREE(orig); + return reduced; +} + +static stbi__uint16 *stbi__convert_8_to_16(stbi_uc *orig, int w, int h, int channels) +{ + int i; + int img_len = w * h * channels; + stbi__uint16 *enlarged; + + enlarged = (stbi__uint16 *) stbi__malloc(img_len*2); + if (enlarged == NULL) return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + + for (i = 0; i < img_len; ++i) + enlarged[i] = (stbi__uint16)((orig[i] << 8) + orig[i]); // replicate to high and low byte, maps 0->0, 255->0xffff + + STBI_FREE(orig); + return enlarged; +} + +static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel) +{ + int row; + size_t bytes_per_row = (size_t)w * bytes_per_pixel; + stbi_uc temp[2048]; + stbi_uc *bytes = (stbi_uc *)image; + + for (row = 0; row < (h>>1); row++) { + stbi_uc *row0 = bytes + row*bytes_per_row; + stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row; + // swap row0 with row1 + size_t bytes_left = bytes_per_row; + while (bytes_left) { + size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp); + memcpy(temp, row0, bytes_copy); + memcpy(row0, row1, bytes_copy); + memcpy(row1, temp, bytes_copy); + row0 += bytes_copy; + row1 += bytes_copy; + bytes_left -= bytes_copy; + } + } +} + +#ifndef STBI_NO_GIF +static void stbi__vertical_flip_slices(void *image, int w, int h, int z, int bytes_per_pixel) +{ + int slice; + int slice_size = w * h * bytes_per_pixel; + + stbi_uc *bytes = (stbi_uc *)image; + for (slice = 0; slice < z; ++slice) { + stbi__vertical_flip(bytes, w, h, bytes_per_pixel); + bytes += slice_size; + } +} +#endif + +static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 8); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 8) { + result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 8; + } + + // @TODO: move stbi__convert_format to here + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi_uc)); + } + + return (unsigned char *) result; +} + +static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + stbi__result_info ri; + void *result = stbi__load_main(s, x, y, comp, req_comp, &ri, 16); + + if (result == NULL) + return NULL; + + // it is the responsibility of the loaders to make sure we get either 8 or 16 bit. + STBI_ASSERT(ri.bits_per_channel == 8 || ri.bits_per_channel == 16); + + if (ri.bits_per_channel != 16) { + result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp); + ri.bits_per_channel = 16; + } + + // @TODO: move stbi__convert_format16 to here + // @TODO: special case RGB-to-Y (and RGBA-to-YA) for 8-bit-to-16-bit case to keep more precision + + if (stbi__vertically_flip_on_load) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(stbi__uint16)); + } + + return (stbi__uint16 *) result; +} + +#if !defined(STBI_NO_HDR) && !defined(STBI_NO_LINEAR) +static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, int req_comp) +{ + if (stbi__vertically_flip_on_load && result != NULL) { + int channels = req_comp ? req_comp : *comp; + stbi__vertical_flip(result, *x, *y, channels * sizeof(float)); + } +} +#endif + +#ifndef STBI_NO_STDIO + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBI_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBI_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); +#endif + +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) +STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbi__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + + +STBIDEF stbi_uc *stbi_load(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + unsigned char *result; + if (!f) return stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF stbi_uc *stbi_load_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi__uint16 *stbi_load_from_file_16(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__uint16 *result; + stbi__context s; + stbi__start_file(&s,f); + result = stbi__load_and_postprocess_16bit(&s,x,y,comp,req_comp); + if (result) { + // need to 'unget' all the characters in the IO buffer + fseek(f, - (int) (s.img_buffer_end - s.img_buffer), SEEK_CUR); + } + return result; +} + +STBIDEF stbi_us *stbi_load_16(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + stbi__uint16 *result; + if (!f) return (stbi_us *) stbi__errpuc("can't fopen", "Unable to open file"); + result = stbi_load_from_file_16(f,x,y,comp,req_comp); + fclose(f); + return result; +} + + +#endif //!STBI_NO_STDIO + +STBIDEF stbi_us *stbi_load_16_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_us *stbi_load_16_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *channels_in_file, int desired_channels) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *)clbk, user); + return stbi__load_and_postprocess_16bit(&s,x,y,channels_in_file,desired_channels); +} + +STBIDEF stbi_uc *stbi_load_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +STBIDEF stbi_uc *stbi_load_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__load_and_postprocess_8bit(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_GIF +STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + unsigned char *result; + stbi__context s; + stbi__start_mem(&s,buffer,len); + + result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); + if (stbi__vertically_flip_on_load) { + stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); + } + + return result; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__loadf_main(stbi__context *s, int *x, int *y, int *comp, int req_comp) +{ + unsigned char *data; + #ifndef STBI_NO_HDR + if (stbi__hdr_test(s)) { + stbi__result_info ri; + float *hdr_data = stbi__hdr_load(s,x,y,comp,req_comp, &ri); + if (hdr_data) + stbi__float_postprocess(hdr_data,x,y,comp,req_comp); + return hdr_data; + } + #endif + data = stbi__load_and_postprocess_8bit(s, x, y, comp, req_comp); + if (data) + return stbi__ldr_to_hdr(data, *x, *y, req_comp ? req_comp : *comp); + return stbi__errpf("unknown image type", "Image not of any known type, or corrupt"); +} + +STBIDEF float *stbi_loadf_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +STBIDEF float *stbi_loadf_from_callbacks(stbi_io_callbacks const *clbk, void *user, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} + +#ifndef STBI_NO_STDIO +STBIDEF float *stbi_loadf(char const *filename, int *x, int *y, int *comp, int req_comp) +{ + float *result; + FILE *f = stbi__fopen(filename, "rb"); + if (!f) return stbi__errpf("can't fopen", "Unable to open file"); + result = stbi_loadf_from_file(f,x,y,comp,req_comp); + fclose(f); + return result; +} + +STBIDEF float *stbi_loadf_from_file(FILE *f, int *x, int *y, int *comp, int req_comp) +{ + stbi__context s; + stbi__start_file(&s,f); + return stbi__loadf_main(&s,x,y,comp,req_comp); +} +#endif // !STBI_NO_STDIO + +#endif // !STBI_NO_LINEAR + +// these is-hdr-or-not is defined independent of whether STBI_NO_LINEAR is +// defined, for API simplicity; if STBI_NO_LINEAR is defined, it always +// reports false! + +STBIDEF int stbi_is_hdr_from_memory(stbi_uc const *buffer, int len) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(buffer); + STBI_NOTUSED(len); + return 0; + #endif +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_is_hdr (char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result=0; + if (f) { + result = stbi_is_hdr_from_file(f); + fclose(f); + } + return result; +} + +STBIDEF int stbi_is_hdr_from_file(FILE *f) +{ + #ifndef STBI_NO_HDR + long pos = ftell(f); + int res; + stbi__context s; + stbi__start_file(&s,f); + res = stbi__hdr_test(&s); + fseek(f, pos, SEEK_SET); + return res; + #else + STBI_NOTUSED(f); + return 0; + #endif +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_is_hdr_from_callbacks(stbi_io_callbacks const *clbk, void *user) +{ + #ifndef STBI_NO_HDR + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) clbk, user); + return stbi__hdr_test(&s); + #else + STBI_NOTUSED(clbk); + STBI_NOTUSED(user); + return 0; + #endif +} + +#ifndef STBI_NO_LINEAR +static float stbi__l2h_gamma=2.2f, stbi__l2h_scale=1.0f; + +STBIDEF void stbi_ldr_to_hdr_gamma(float gamma) { stbi__l2h_gamma = gamma; } +STBIDEF void stbi_ldr_to_hdr_scale(float scale) { stbi__l2h_scale = scale; } +#endif + +static float stbi__h2l_gamma_i=1.0f/2.2f, stbi__h2l_scale_i=1.0f; + +STBIDEF void stbi_hdr_to_ldr_gamma(float gamma) { stbi__h2l_gamma_i = 1/gamma; } +STBIDEF void stbi_hdr_to_ldr_scale(float scale) { stbi__h2l_scale_i = 1/scale; } + + +////////////////////////////////////////////////////////////////////////////// +// +// Common code used by all image loaders +// + +enum +{ + STBI__SCAN_load=0, + STBI__SCAN_type, + STBI__SCAN_header +}; + +static void stbi__refill_buffer(stbi__context *s) +{ + int n = (s->io.read)(s->io_user_data,(char*)s->buffer_start,s->buflen); + s->callback_already_read += (int) (s->img_buffer - s->img_buffer_original); + if (n == 0) { + // at end of file, treat same as if from memory, but need to handle case + // where s->img_buffer isn't pointing to safe memory, e.g. 0-byte file + s->read_from_callbacks = 0; + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start+1; + *s->img_buffer = 0; + } else { + s->img_buffer = s->buffer_start; + s->img_buffer_end = s->buffer_start + n; + } +} + +stbi_inline static stbi_uc stbi__get8(stbi__context *s) +{ + if (s->img_buffer < s->img_buffer_end) + return *s->img_buffer++; + if (s->read_from_callbacks) { + stbi__refill_buffer(s); + return *s->img_buffer++; + } + return 0; +} + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_HDR) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +stbi_inline static int stbi__at_eof(stbi__context *s) +{ + if (s->io.read) { + if (!(s->io.eof)(s->io_user_data)) return 0; + // if feof() is true, check if buffer = end + // special case: we've only got the special 0 character at the end + if (s->read_from_callbacks == 0) return 1; + } + + return s->img_buffer >= s->img_buffer_end; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) +// nothing +#else +static void stbi__skip(stbi__context *s, int n) +{ + if (n == 0) return; // already there! + if (n < 0) { + s->img_buffer = s->img_buffer_end; + return; + } + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + s->img_buffer = s->img_buffer_end; + (s->io.skip)(s->io_user_data, n - blen); + return; + } + } + s->img_buffer += n; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_TGA) && defined(STBI_NO_HDR) && defined(STBI_NO_PNM) +// nothing +#else +static int stbi__getn(stbi__context *s, stbi_uc *buffer, int n) +{ + if (s->io.read) { + int blen = (int) (s->img_buffer_end - s->img_buffer); + if (blen < n) { + int res, count; + + memcpy(buffer, s->img_buffer, blen); + + count = (s->io.read)(s->io_user_data, (char*) buffer + blen, n - blen); + res = (count == (n-blen)); + s->img_buffer = s->img_buffer_end; + return res; + } + } + + if (s->img_buffer+n <= s->img_buffer_end) { + memcpy(buffer, s->img_buffer, n); + s->img_buffer += n; + return 1; + } else + return 0; +} +#endif + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static int stbi__get16be(stbi__context *s) +{ + int z = stbi__get8(s); + return (z << 8) + stbi__get8(s); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) && defined(STBI_NO_PIC) +// nothing +#else +static stbi__uint32 stbi__get32be(stbi__context *s) +{ + stbi__uint32 z = stbi__get16be(s); + return (z << 16) + stbi__get16be(s); +} +#endif + +#if defined(STBI_NO_BMP) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) +// nothing +#else +static int stbi__get16le(stbi__context *s) +{ + int z = stbi__get8(s); + return z + (stbi__get8(s) << 8); +} +#endif + +#ifndef STBI_NO_BMP +static stbi__uint32 stbi__get32le(stbi__context *s) +{ + stbi__uint32 z = stbi__get16le(s); + z += (stbi__uint32)stbi__get16le(s) << 16; + return z; +} +#endif + +#define STBI__BYTECAST(x) ((stbi_uc) ((x) & 255)) // truncate int to byte without warnings + +#if defined(STBI_NO_JPEG) && defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +////////////////////////////////////////////////////////////////////////////// +// +// generic converter from built-in img_n to req_comp +// individual types do this automatically as much as possible (e.g. jpeg +// does all cases internally since it needs to colorspace convert anyway, +// and it never has alpha, so very few cases ). png can automatically +// interleave an alpha=255 channel, but falls back to this for other cases +// +// assume data buffer is malloced, so malloc a new one and free that one +// only failure mode is malloc failing + +static stbi_uc stbi__compute_y(int r, int g, int b) +{ + return (stbi_uc) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_BMP) && defined(STBI_NO_PSD) && defined(STBI_NO_TGA) && defined(STBI_NO_GIF) && defined(STBI_NO_PIC) && defined(STBI_NO_PNM) +// nothing +#else +static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + unsigned char *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (unsigned char *) stbi__malloc_mad3(req_comp, x, y, 0); + if (good == NULL) { + STBI_FREE(data); + return stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + unsigned char *src = data + j * x * img_n ; + unsigned char *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=255; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=255; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=255; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = 255; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 stbi__compute_y_16(int r, int g, int b) +{ + return (stbi__uint16) (((r*77) + (g*150) + (29*b)) >> 8); +} +#endif + +#if defined(STBI_NO_PNG) && defined(STBI_NO_PSD) +// nothing +#else +static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int req_comp, unsigned int x, unsigned int y) +{ + int i,j; + stbi__uint16 *good; + + if (req_comp == img_n) return data; + STBI_ASSERT(req_comp >= 1 && req_comp <= 4); + + good = (stbi__uint16 *) stbi__malloc(req_comp * x * y * 2); + if (good == NULL) { + STBI_FREE(data); + return (stbi__uint16 *) stbi__errpuc("outofmem", "Out of memory"); + } + + for (j=0; j < (int) y; ++j) { + stbi__uint16 *src = data + j * x * img_n ; + stbi__uint16 *dest = good + j * x * req_comp; + + #define STBI__COMBO(a,b) ((a)*8+(b)) + #define STBI__CASE(a,b) case STBI__COMBO(a,b): for(i=x-1; i >= 0; --i, src += a, dest += b) + // convert source image with img_n components to one with req_comp components; + // avoid switch per pixel, so use switch per scanline and massive macros + switch (STBI__COMBO(img_n, req_comp)) { + STBI__CASE(1,2) { dest[0]=src[0]; dest[1]=0xffff; } break; + STBI__CASE(1,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(1,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=0xffff; } break; + STBI__CASE(2,1) { dest[0]=src[0]; } break; + STBI__CASE(2,3) { dest[0]=dest[1]=dest[2]=src[0]; } break; + STBI__CASE(2,4) { dest[0]=dest[1]=dest[2]=src[0]; dest[3]=src[1]; } break; + STBI__CASE(3,4) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2];dest[3]=0xffff; } break; + STBI__CASE(3,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(3,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = 0xffff; } break; + STBI__CASE(4,1) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); } break; + STBI__CASE(4,2) { dest[0]=stbi__compute_y_16(src[0],src[1],src[2]); dest[1] = src[3]; } break; + STBI__CASE(4,3) { dest[0]=src[0];dest[1]=src[1];dest[2]=src[2]; } break; + default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion"); + } + #undef STBI__CASE + } + + STBI_FREE(data); + return good; +} +#endif + +#ifndef STBI_NO_LINEAR +static float *stbi__ldr_to_hdr(stbi_uc *data, int x, int y, int comp) +{ + int i,k,n; + float *output; + if (!data) return NULL; + output = (float *) stbi__malloc_mad4(x, y, comp, sizeof(float), 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpf("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + output[i*comp + k] = (float) (pow(data[i*comp+k]/255.0f, stbi__l2h_gamma) * stbi__l2h_scale); + } + } + if (n < comp) { + for (i=0; i < x*y; ++i) { + output[i*comp + n] = data[i*comp + n]/255.0f; + } + } + STBI_FREE(data); + return output; +} +#endif + +#ifndef STBI_NO_HDR +#define stbi__float2int(x) ((int) (x)) +static stbi_uc *stbi__hdr_to_ldr(float *data, int x, int y, int comp) +{ + int i,k,n; + stbi_uc *output; + if (!data) return NULL; + output = (stbi_uc *) stbi__malloc_mad3(x, y, comp, 0); + if (output == NULL) { STBI_FREE(data); return stbi__errpuc("outofmem", "Out of memory"); } + // compute number of non-alpha components + if (comp & 1) n = comp; else n = comp-1; + for (i=0; i < x*y; ++i) { + for (k=0; k < n; ++k) { + float z = (float) pow(data[i*comp+k]*stbi__h2l_scale_i, stbi__h2l_gamma_i) * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + if (k < comp) { + float z = data[i*comp+k] * 255 + 0.5f; + if (z < 0) z = 0; + if (z > 255) z = 255; + output[i*comp + k] = (stbi_uc) stbi__float2int(z); + } + } + STBI_FREE(data); + return output; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// "baseline" JPEG/JFIF decoder +// +// simple implementation +// - doesn't support delayed output of y-dimension +// - simple interface (only one output format: 8-bit interleaved RGB) +// - doesn't try to recover corrupt jpegs +// - doesn't allow partial loading, loading multiple at once +// - still fast on x86 (copying globals into locals doesn't help x86) +// - allocates lots of intermediate memory (full size of all components) +// - non-interleaved case requires this anyway +// - allows good upsampling (see next) +// high-quality +// - upsampled channels are bilinearly interpolated, even across blocks +// - quality integer IDCT derived from IJG's 'slow' +// performance +// - fast huffman; reasonable integer IDCT +// - some SIMD kernels for common paths on targets with SSE2/NEON +// - uses a lot of intermediate memory, could cache poorly + +#ifndef STBI_NO_JPEG + +// huffman decoding acceleration +#define FAST_BITS 9 // larger handles more cases; smaller stomps less cache + +typedef struct +{ + stbi_uc fast[1 << FAST_BITS]; + // weirdly, repacking this into AoS is a 10% speed loss, instead of a win + stbi__uint16 code[256]; + stbi_uc values[256]; + stbi_uc size[257]; + unsigned int maxcode[18]; + int delta[17]; // old 'firstsymbol' - old 'firstcode' +} stbi__huffman; + +typedef struct +{ + stbi__context *s; + stbi__huffman huff_dc[4]; + stbi__huffman huff_ac[4]; + stbi__uint16 dequant[4][64]; + stbi__int16 fast_ac[4][1 << FAST_BITS]; + +// sizes for components, interleaved MCUs + int img_h_max, img_v_max; + int img_mcu_x, img_mcu_y; + int img_mcu_w, img_mcu_h; + +// definition of jpeg image component + struct + { + int id; + int h,v; + int tq; + int hd,ha; + int dc_pred; + + int x,y,w2,h2; + stbi_uc *data; + void *raw_data, *raw_coeff; + stbi_uc *linebuf; + short *coeff; // progressive only + int coeff_w, coeff_h; // number of 8x8 coefficient blocks + } img_comp[4]; + + stbi__uint32 code_buffer; // jpeg entropy-coded buffer + int code_bits; // number of valid bits + unsigned char marker; // marker seen while filling entropy buffer + int nomore; // flag if we saw a marker so must stop + + int progressive; + int spec_start; + int spec_end; + int succ_high; + int succ_low; + int eob_run; + int jfif; + int app14_color_transform; // Adobe APP14 tag + int rgb; + + int scan_n, order[4]; + int restart_interval, todo; + +// kernels + void (*idct_block_kernel)(stbi_uc *out, int out_stride, short data[64]); + void (*YCbCr_to_RGB_kernel)(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step); + stbi_uc *(*resample_row_hv_2_kernel)(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs); +} stbi__jpeg; + +static int stbi__build_huffman(stbi__huffman *h, int *count) +{ + int i,j,k=0; + unsigned int code; + // build size list for each symbol (from JPEG spec) + for (i=0; i < 16; ++i) + for (j=0; j < count[i]; ++j) + h->size[k++] = (stbi_uc) (i+1); + h->size[k] = 0; + + // compute actual symbols (from jpeg spec) + code = 0; + k = 0; + for(j=1; j <= 16; ++j) { + // compute delta to add to code to compute symbol id + h->delta[j] = k - code; + if (h->size[k] == j) { + while (h->size[k] == j) + h->code[k++] = (stbi__uint16) (code++); + if (code-1 >= (1u << j)) return stbi__err("bad code lengths","Corrupt JPEG"); + } + // compute largest code + 1 for this size, preshifted as needed later + h->maxcode[j] = code << (16-j); + code <<= 1; + } + h->maxcode[j] = 0xffffffff; + + // build non-spec acceleration table; 255 is flag for not-accelerated + memset(h->fast, 255, 1 << FAST_BITS); + for (i=0; i < k; ++i) { + int s = h->size[i]; + if (s <= FAST_BITS) { + int c = h->code[i] << (FAST_BITS-s); + int m = 1 << (FAST_BITS-s); + for (j=0; j < m; ++j) { + h->fast[c+j] = (stbi_uc) i; + } + } + } + return 1; +} + +// build a table that decodes both magnitude and value of small ACs in +// one go. +static void stbi__build_fast_ac(stbi__int16 *fast_ac, stbi__huffman *h) +{ + int i; + for (i=0; i < (1 << FAST_BITS); ++i) { + stbi_uc fast = h->fast[i]; + fast_ac[i] = 0; + if (fast < 255) { + int rs = h->values[fast]; + int run = (rs >> 4) & 15; + int magbits = rs & 15; + int len = h->size[fast]; + + if (magbits && len + magbits <= FAST_BITS) { + // magnitude code followed by receive_extend code + int k = ((i << len) & ((1 << FAST_BITS) - 1)) >> (FAST_BITS - magbits); + int m = 1 << (magbits - 1); + if (k < m) k += (~0U << magbits) + 1; + // if the result is small enough, we can fit it in fast_ac table + if (k >= -128 && k <= 127) + fast_ac[i] = (stbi__int16) ((k * 256) + (run * 16) + (len + magbits)); + } + } + } +} + +static void stbi__grow_buffer_unsafe(stbi__jpeg *j) +{ + do { + unsigned int b = j->nomore ? 0 : stbi__get8(j->s); + if (b == 0xff) { + int c = stbi__get8(j->s); + while (c == 0xff) c = stbi__get8(j->s); // consume fill bytes + if (c != 0) { + j->marker = (unsigned char) c; + j->nomore = 1; + return; + } + } + j->code_buffer |= b << (24 - j->code_bits); + j->code_bits += 8; + } while (j->code_bits <= 24); +} + +// (1 << n) - 1 +static const stbi__uint32 stbi__bmask[17]={0,1,3,7,15,31,63,127,255,511,1023,2047,4095,8191,16383,32767,65535}; + +// decode a jpeg huffman value from the bitstream +stbi_inline static int stbi__jpeg_huff_decode(stbi__jpeg *j, stbi__huffman *h) +{ + unsigned int temp; + int c,k; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + // look at the top FAST_BITS and determine what symbol ID it is, + // if the code is <= FAST_BITS + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + k = h->fast[c]; + if (k < 255) { + int s = h->size[k]; + if (s > j->code_bits) + return -1; + j->code_buffer <<= s; + j->code_bits -= s; + return h->values[k]; + } + + // naive test is to shift the code_buffer down so k bits are + // valid, then test against maxcode. To speed this up, we've + // preshifted maxcode left so that it has (16-k) 0s at the + // end; in other words, regardless of the number of bits, it + // wants to be compared against something shifted to have 16; + // that way we don't need to shift inside the loop. + temp = j->code_buffer >> 16; + for (k=FAST_BITS+1 ; ; ++k) + if (temp < h->maxcode[k]) + break; + if (k == 17) { + // error! code not found + j->code_bits -= 16; + return -1; + } + + if (k > j->code_bits) + return -1; + + // convert the huffman code to the symbol id + c = ((j->code_buffer >> (32 - k)) & stbi__bmask[k]) + h->delta[k]; + STBI_ASSERT((((j->code_buffer) >> (32 - h->size[c])) & stbi__bmask[h->size[c]]) == h->code[c]); + + // convert the id to a symbol + j->code_bits -= k; + j->code_buffer <<= k; + return h->values[c]; +} + +// bias[n] = (-1<code_bits < n) stbi__grow_buffer_unsafe(j); + + sgn = j->code_buffer >> 31; // sign bit always in MSB; 0 if MSB clear (positive), 1 if MSB set (negative) + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k + (stbi__jbias[n] & (sgn - 1)); +} + +// get some unsigned bits +stbi_inline static int stbi__jpeg_get_bits(stbi__jpeg *j, int n) +{ + unsigned int k; + if (j->code_bits < n) stbi__grow_buffer_unsafe(j); + k = stbi_lrot(j->code_buffer, n); + j->code_buffer = k & ~stbi__bmask[n]; + k &= stbi__bmask[n]; + j->code_bits -= n; + return k; +} + +stbi_inline static int stbi__jpeg_get_bit(stbi__jpeg *j) +{ + unsigned int k; + if (j->code_bits < 1) stbi__grow_buffer_unsafe(j); + k = j->code_buffer; + j->code_buffer <<= 1; + --j->code_bits; + return k & 0x80000000; +} + +// given a value that's at position X in the zigzag stream, +// where does it appear in the 8x8 matrix coded as row-major? +static const stbi_uc stbi__jpeg_dezigzag[64+15] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, + // let corrupt input sample past end + 63, 63, 63, 63, 63, 63, 63, 63, + 63, 63, 63, 63, 63, 63, 63 +}; + +// decode one 64-entry block-- +static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman *hdc, stbi__huffman *hac, stbi__int16 *fac, int b, stbi__uint16 *dequant) +{ + int diff,dc,k; + int t; + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("bad huffman code","Corrupt JPEG"); + + // 0 all the ac values now so we can do it 32-bits at a time + memset(data,0,64*sizeof(data[0])); + + diff = t ? stbi__extend_receive(j, t) : 0; + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * dequant[0]); + + // decode AC components, see JPEG spec + k = 1; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * dequant[zig]); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (rs != 0xf0) break; // end block + k += 16; + } else { + k += r; + // decode into unzigzag'd location + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * dequant[zig]); + } + } + } while (k < 64); + return 1; +} + +static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__huffman *hdc, int b) +{ + int diff,dc; + int t; + if (j->spec_end != 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + + if (j->succ_high == 0) { + // first scan for DC coefficient, must be first + memset(data,0,64*sizeof(data[0])); // 0 all the ac values now + t = stbi__jpeg_huff_decode(j, hdc); + if (t < 0 || t > 15) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + diff = t ? stbi__extend_receive(j, t) : 0; + + dc = j->img_comp[b].dc_pred + diff; + j->img_comp[b].dc_pred = dc; + data[0] = (short) (dc * (1 << j->succ_low)); + } else { + // refinement scan for DC coefficient + if (stbi__jpeg_get_bit(j)) + data[0] += (short) (1 << j->succ_low); + } + return 1; +} + +// @OPTIMIZE: store non-zigzagged during the decode passes, +// and only de-zigzag when dequantizing +static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__huffman *hac, stbi__int16 *fac) +{ + int k; + if (j->spec_start == 0) return stbi__err("can't merge dc and ac", "Corrupt JPEG"); + + if (j->succ_high == 0) { + int shift = j->succ_low; + + if (j->eob_run) { + --j->eob_run; + return 1; + } + + k = j->spec_start; + do { + unsigned int zig; + int c,r,s; + if (j->code_bits < 16) stbi__grow_buffer_unsafe(j); + c = (j->code_buffer >> (32 - FAST_BITS)) & ((1 << FAST_BITS)-1); + r = fac[c]; + if (r) { // fast-AC path + k += (r >> 4) & 15; // run + s = r & 15; // combined length + j->code_buffer <<= s; + j->code_bits -= s; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) ((r >> 8) * (1 << shift)); + } else { + int rs = stbi__jpeg_huff_decode(j, hac); + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r); + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + --j->eob_run; + break; + } + k += 16; + } else { + k += r; + zig = stbi__jpeg_dezigzag[k++]; + data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift)); + } + } + } while (k <= j->spec_end); + } else { + // refinement scan for these AC coefficients + + short bit = (short) (1 << j->succ_low); + + if (j->eob_run) { + --j->eob_run; + for (k = j->spec_start; k <= j->spec_end; ++k) { + short *p = &data[stbi__jpeg_dezigzag[k]]; + if (*p != 0) + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } + } else { + k = j->spec_start; + do { + int r,s; + int rs = stbi__jpeg_huff_decode(j, hac); // @OPTIMIZE see if we can use the fast path here, advance-by-r is so slow, eh + if (rs < 0) return stbi__err("bad huffman code","Corrupt JPEG"); + s = rs & 15; + r = rs >> 4; + if (s == 0) { + if (r < 15) { + j->eob_run = (1 << r) - 1; + if (r) + j->eob_run += stbi__jpeg_get_bits(j, r); + r = 64; // force end of block + } else { + // r=15 s=0 should write 16 0s, so we just do + // a run of 15 0s and then write s (which is 0), + // so we don't have to do anything special here + } + } else { + if (s != 1) return stbi__err("bad huffman code", "Corrupt JPEG"); + // sign bit + if (stbi__jpeg_get_bit(j)) + s = bit; + else + s = -bit; + } + + // advance by r + while (k <= j->spec_end) { + short *p = &data[stbi__jpeg_dezigzag[k++]]; + if (*p != 0) { + if (stbi__jpeg_get_bit(j)) + if ((*p & bit)==0) { + if (*p > 0) + *p += bit; + else + *p -= bit; + } + } else { + if (r == 0) { + *p = (short) s; + break; + } + --r; + } + } + } while (k <= j->spec_end); + } + } + return 1; +} + +// take a -128..127 value and stbi__clamp it and convert to 0..255 +stbi_inline static stbi_uc stbi__clamp(int x) +{ + // trick to use a single test to catch both cases + if ((unsigned int) x > 255) { + if (x < 0) return 0; + if (x > 255) return 255; + } + return (stbi_uc) x; +} + +#define stbi__f2f(x) ((int) (((x) * 4096 + 0.5))) +#define stbi__fsh(x) ((x) * 4096) + +// derived from jidctint -- DCT_ISLOW +#define STBI__IDCT_1D(s0,s1,s2,s3,s4,s5,s6,s7) \ + int t0,t1,t2,t3,p1,p2,p3,p4,p5,x0,x1,x2,x3; \ + p2 = s2; \ + p3 = s6; \ + p1 = (p2+p3) * stbi__f2f(0.5411961f); \ + t2 = p1 + p3*stbi__f2f(-1.847759065f); \ + t3 = p1 + p2*stbi__f2f( 0.765366865f); \ + p2 = s0; \ + p3 = s4; \ + t0 = stbi__fsh(p2+p3); \ + t1 = stbi__fsh(p2-p3); \ + x0 = t0+t3; \ + x3 = t0-t3; \ + x1 = t1+t2; \ + x2 = t1-t2; \ + t0 = s7; \ + t1 = s5; \ + t2 = s3; \ + t3 = s1; \ + p3 = t0+t2; \ + p4 = t1+t3; \ + p1 = t0+t3; \ + p2 = t1+t2; \ + p5 = (p3+p4)*stbi__f2f( 1.175875602f); \ + t0 = t0*stbi__f2f( 0.298631336f); \ + t1 = t1*stbi__f2f( 2.053119869f); \ + t2 = t2*stbi__f2f( 3.072711026f); \ + t3 = t3*stbi__f2f( 1.501321110f); \ + p1 = p5 + p1*stbi__f2f(-0.899976223f); \ + p2 = p5 + p2*stbi__f2f(-2.562915447f); \ + p3 = p3*stbi__f2f(-1.961570560f); \ + p4 = p4*stbi__f2f(-0.390180644f); \ + t3 += p1+p4; \ + t2 += p2+p3; \ + t1 += p2+p4; \ + t0 += p1+p3; + +static void stbi__idct_block(stbi_uc *out, int out_stride, short data[64]) +{ + int i,val[64],*v=val; + stbi_uc *o; + short *d = data; + + // columns + for (i=0; i < 8; ++i,++d, ++v) { + // if all zeroes, shortcut -- this avoids dequantizing 0s and IDCTing + if (d[ 8]==0 && d[16]==0 && d[24]==0 && d[32]==0 + && d[40]==0 && d[48]==0 && d[56]==0) { + // no shortcut 0 seconds + // (1|2|3|4|5|6|7)==0 0 seconds + // all separate -0.047 seconds + // 1 && 2|3 && 4|5 && 6|7: -0.047 seconds + int dcterm = d[0]*4; + v[0] = v[8] = v[16] = v[24] = v[32] = v[40] = v[48] = v[56] = dcterm; + } else { + STBI__IDCT_1D(d[ 0],d[ 8],d[16],d[24],d[32],d[40],d[48],d[56]) + // constants scaled things up by 1<<12; let's bring them back + // down, but keep 2 extra bits of precision + x0 += 512; x1 += 512; x2 += 512; x3 += 512; + v[ 0] = (x0+t3) >> 10; + v[56] = (x0-t3) >> 10; + v[ 8] = (x1+t2) >> 10; + v[48] = (x1-t2) >> 10; + v[16] = (x2+t1) >> 10; + v[40] = (x2-t1) >> 10; + v[24] = (x3+t0) >> 10; + v[32] = (x3-t0) >> 10; + } + } + + for (i=0, v=val, o=out; i < 8; ++i,v+=8,o+=out_stride) { + // no fast case since the first 1D IDCT spread components out + STBI__IDCT_1D(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]) + // constants scaled things up by 1<<12, plus we had 1<<2 from first + // loop, plus horizontal and vertical each scale by sqrt(8) so together + // we've got an extra 1<<3, so 1<<17 total we need to remove. + // so we want to round that, which means adding 0.5 * 1<<17, + // aka 65536. Also, we'll end up with -128 to 127 that we want + // to encode as 0..255 by adding 128, so we'll add that before the shift + x0 += 65536 + (128<<17); + x1 += 65536 + (128<<17); + x2 += 65536 + (128<<17); + x3 += 65536 + (128<<17); + // tried computing the shifts into temps, or'ing the temps to see + // if any were out of range, but that was slower + o[0] = stbi__clamp((x0+t3) >> 17); + o[7] = stbi__clamp((x0-t3) >> 17); + o[1] = stbi__clamp((x1+t2) >> 17); + o[6] = stbi__clamp((x1-t2) >> 17); + o[2] = stbi__clamp((x2+t1) >> 17); + o[5] = stbi__clamp((x2-t1) >> 17); + o[3] = stbi__clamp((x3+t0) >> 17); + o[4] = stbi__clamp((x3-t0) >> 17); + } +} + +#ifdef STBI_SSE2 +// sse2 integer IDCT. not the fastest possible implementation but it +// produces bit-identical results to the generic C version so it's +// fully "transparent". +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + // This is constructed to match our regular (generic) integer IDCT exactly. + __m128i row0, row1, row2, row3, row4, row5, row6, row7; + __m128i tmp; + + // dot product constant: even elems=x, odd elems=y + #define dct_const(x,y) _mm_setr_epi16((x),(y),(x),(y),(x),(y),(x),(y)) + + // out(0) = c0[even]*x + c0[odd]*y (c0, x, y 16-bit, out 32-bit) + // out(1) = c1[even]*x + c1[odd]*y + #define dct_rot(out0,out1, x,y,c0,c1) \ + __m128i c0##lo = _mm_unpacklo_epi16((x),(y)); \ + __m128i c0##hi = _mm_unpackhi_epi16((x),(y)); \ + __m128i out0##_l = _mm_madd_epi16(c0##lo, c0); \ + __m128i out0##_h = _mm_madd_epi16(c0##hi, c0); \ + __m128i out1##_l = _mm_madd_epi16(c0##lo, c1); \ + __m128i out1##_h = _mm_madd_epi16(c0##hi, c1) + + // out = in << 12 (in 16-bit, out 32-bit) + #define dct_widen(out, in) \ + __m128i out##_l = _mm_srai_epi32(_mm_unpacklo_epi16(_mm_setzero_si128(), (in)), 4); \ + __m128i out##_h = _mm_srai_epi32(_mm_unpackhi_epi16(_mm_setzero_si128(), (in)), 4) + + // wide add + #define dct_wadd(out, a, b) \ + __m128i out##_l = _mm_add_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_add_epi32(a##_h, b##_h) + + // wide sub + #define dct_wsub(out, a, b) \ + __m128i out##_l = _mm_sub_epi32(a##_l, b##_l); \ + __m128i out##_h = _mm_sub_epi32(a##_h, b##_h) + + // butterfly a/b, add bias, then shift by "s" and pack + #define dct_bfly32o(out0, out1, a,b,bias,s) \ + { \ + __m128i abiased_l = _mm_add_epi32(a##_l, bias); \ + __m128i abiased_h = _mm_add_epi32(a##_h, bias); \ + dct_wadd(sum, abiased, b); \ + dct_wsub(dif, abiased, b); \ + out0 = _mm_packs_epi32(_mm_srai_epi32(sum_l, s), _mm_srai_epi32(sum_h, s)); \ + out1 = _mm_packs_epi32(_mm_srai_epi32(dif_l, s), _mm_srai_epi32(dif_h, s)); \ + } + + // 8-bit interleave step (for transposes) + #define dct_interleave8(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi8(a, b); \ + b = _mm_unpackhi_epi8(tmp, b) + + // 16-bit interleave step (for transposes) + #define dct_interleave16(a, b) \ + tmp = a; \ + a = _mm_unpacklo_epi16(a, b); \ + b = _mm_unpackhi_epi16(tmp, b) + + #define dct_pass(bias,shift) \ + { \ + /* even part */ \ + dct_rot(t2e,t3e, row2,row6, rot0_0,rot0_1); \ + __m128i sum04 = _mm_add_epi16(row0, row4); \ + __m128i dif04 = _mm_sub_epi16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + dct_rot(y0o,y2o, row7,row3, rot2_0,rot2_1); \ + dct_rot(y1o,y3o, row5,row1, rot3_0,rot3_1); \ + __m128i sum17 = _mm_add_epi16(row1, row7); \ + __m128i sum35 = _mm_add_epi16(row3, row5); \ + dct_rot(y4o,y5o, sum17,sum35, rot1_0,rot1_1); \ + dct_wadd(x4, y0o, y4o); \ + dct_wadd(x5, y1o, y5o); \ + dct_wadd(x6, y2o, y5o); \ + dct_wadd(x7, y3o, y4o); \ + dct_bfly32o(row0,row7, x0,x7,bias,shift); \ + dct_bfly32o(row1,row6, x1,x6,bias,shift); \ + dct_bfly32o(row2,row5, x2,x5,bias,shift); \ + dct_bfly32o(row3,row4, x3,x4,bias,shift); \ + } + + __m128i rot0_0 = dct_const(stbi__f2f(0.5411961f), stbi__f2f(0.5411961f) + stbi__f2f(-1.847759065f)); + __m128i rot0_1 = dct_const(stbi__f2f(0.5411961f) + stbi__f2f( 0.765366865f), stbi__f2f(0.5411961f)); + __m128i rot1_0 = dct_const(stbi__f2f(1.175875602f) + stbi__f2f(-0.899976223f), stbi__f2f(1.175875602f)); + __m128i rot1_1 = dct_const(stbi__f2f(1.175875602f), stbi__f2f(1.175875602f) + stbi__f2f(-2.562915447f)); + __m128i rot2_0 = dct_const(stbi__f2f(-1.961570560f) + stbi__f2f( 0.298631336f), stbi__f2f(-1.961570560f)); + __m128i rot2_1 = dct_const(stbi__f2f(-1.961570560f), stbi__f2f(-1.961570560f) + stbi__f2f( 3.072711026f)); + __m128i rot3_0 = dct_const(stbi__f2f(-0.390180644f) + stbi__f2f( 2.053119869f), stbi__f2f(-0.390180644f)); + __m128i rot3_1 = dct_const(stbi__f2f(-0.390180644f), stbi__f2f(-0.390180644f) + stbi__f2f( 1.501321110f)); + + // rounding biases in column/row passes, see stbi__idct_block for explanation. + __m128i bias_0 = _mm_set1_epi32(512); + __m128i bias_1 = _mm_set1_epi32(65536 + (128<<17)); + + // load + row0 = _mm_load_si128((const __m128i *) (data + 0*8)); + row1 = _mm_load_si128((const __m128i *) (data + 1*8)); + row2 = _mm_load_si128((const __m128i *) (data + 2*8)); + row3 = _mm_load_si128((const __m128i *) (data + 3*8)); + row4 = _mm_load_si128((const __m128i *) (data + 4*8)); + row5 = _mm_load_si128((const __m128i *) (data + 5*8)); + row6 = _mm_load_si128((const __m128i *) (data + 6*8)); + row7 = _mm_load_si128((const __m128i *) (data + 7*8)); + + // column pass + dct_pass(bias_0, 10); + + { + // 16bit 8x8 transpose pass 1 + dct_interleave16(row0, row4); + dct_interleave16(row1, row5); + dct_interleave16(row2, row6); + dct_interleave16(row3, row7); + + // transpose pass 2 + dct_interleave16(row0, row2); + dct_interleave16(row1, row3); + dct_interleave16(row4, row6); + dct_interleave16(row5, row7); + + // transpose pass 3 + dct_interleave16(row0, row1); + dct_interleave16(row2, row3); + dct_interleave16(row4, row5); + dct_interleave16(row6, row7); + } + + // row pass + dct_pass(bias_1, 17); + + { + // pack + __m128i p0 = _mm_packus_epi16(row0, row1); // a0a1a2a3...a7b0b1b2b3...b7 + __m128i p1 = _mm_packus_epi16(row2, row3); + __m128i p2 = _mm_packus_epi16(row4, row5); + __m128i p3 = _mm_packus_epi16(row6, row7); + + // 8bit 8x8 transpose pass 1 + dct_interleave8(p0, p2); // a0e0a1e1... + dct_interleave8(p1, p3); // c0g0c1g1... + + // transpose pass 2 + dct_interleave8(p0, p1); // a0c0e0g0... + dct_interleave8(p2, p3); // b0d0f0h0... + + // transpose pass 3 + dct_interleave8(p0, p2); // a0b0c0d0... + dct_interleave8(p1, p3); // a4b4c4d4... + + // store + _mm_storel_epi64((__m128i *) out, p0); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p0, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p2); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p2, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p1); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p1, 0x4e)); out += out_stride; + _mm_storel_epi64((__m128i *) out, p3); out += out_stride; + _mm_storel_epi64((__m128i *) out, _mm_shuffle_epi32(p3, 0x4e)); + } + +#undef dct_const +#undef dct_rot +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_interleave8 +#undef dct_interleave16 +#undef dct_pass +} + +#endif // STBI_SSE2 + +#ifdef STBI_NEON + +// NEON integer IDCT. should produce bit-identical +// results to the generic C version. +static void stbi__idct_simd(stbi_uc *out, int out_stride, short data[64]) +{ + int16x8_t row0, row1, row2, row3, row4, row5, row6, row7; + + int16x4_t rot0_0 = vdup_n_s16(stbi__f2f(0.5411961f)); + int16x4_t rot0_1 = vdup_n_s16(stbi__f2f(-1.847759065f)); + int16x4_t rot0_2 = vdup_n_s16(stbi__f2f( 0.765366865f)); + int16x4_t rot1_0 = vdup_n_s16(stbi__f2f( 1.175875602f)); + int16x4_t rot1_1 = vdup_n_s16(stbi__f2f(-0.899976223f)); + int16x4_t rot1_2 = vdup_n_s16(stbi__f2f(-2.562915447f)); + int16x4_t rot2_0 = vdup_n_s16(stbi__f2f(-1.961570560f)); + int16x4_t rot2_1 = vdup_n_s16(stbi__f2f(-0.390180644f)); + int16x4_t rot3_0 = vdup_n_s16(stbi__f2f( 0.298631336f)); + int16x4_t rot3_1 = vdup_n_s16(stbi__f2f( 2.053119869f)); + int16x4_t rot3_2 = vdup_n_s16(stbi__f2f( 3.072711026f)); + int16x4_t rot3_3 = vdup_n_s16(stbi__f2f( 1.501321110f)); + +#define dct_long_mul(out, inq, coeff) \ + int32x4_t out##_l = vmull_s16(vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmull_s16(vget_high_s16(inq), coeff) + +#define dct_long_mac(out, acc, inq, coeff) \ + int32x4_t out##_l = vmlal_s16(acc##_l, vget_low_s16(inq), coeff); \ + int32x4_t out##_h = vmlal_s16(acc##_h, vget_high_s16(inq), coeff) + +#define dct_widen(out, inq) \ + int32x4_t out##_l = vshll_n_s16(vget_low_s16(inq), 12); \ + int32x4_t out##_h = vshll_n_s16(vget_high_s16(inq), 12) + +// wide add +#define dct_wadd(out, a, b) \ + int32x4_t out##_l = vaddq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vaddq_s32(a##_h, b##_h) + +// wide sub +#define dct_wsub(out, a, b) \ + int32x4_t out##_l = vsubq_s32(a##_l, b##_l); \ + int32x4_t out##_h = vsubq_s32(a##_h, b##_h) + +// butterfly a/b, then shift using "shiftop" by "s" and pack +#define dct_bfly32o(out0,out1, a,b,shiftop,s) \ + { \ + dct_wadd(sum, a, b); \ + dct_wsub(dif, a, b); \ + out0 = vcombine_s16(shiftop(sum_l, s), shiftop(sum_h, s)); \ + out1 = vcombine_s16(shiftop(dif_l, s), shiftop(dif_h, s)); \ + } + +#define dct_pass(shiftop, shift) \ + { \ + /* even part */ \ + int16x8_t sum26 = vaddq_s16(row2, row6); \ + dct_long_mul(p1e, sum26, rot0_0); \ + dct_long_mac(t2e, p1e, row6, rot0_1); \ + dct_long_mac(t3e, p1e, row2, rot0_2); \ + int16x8_t sum04 = vaddq_s16(row0, row4); \ + int16x8_t dif04 = vsubq_s16(row0, row4); \ + dct_widen(t0e, sum04); \ + dct_widen(t1e, dif04); \ + dct_wadd(x0, t0e, t3e); \ + dct_wsub(x3, t0e, t3e); \ + dct_wadd(x1, t1e, t2e); \ + dct_wsub(x2, t1e, t2e); \ + /* odd part */ \ + int16x8_t sum15 = vaddq_s16(row1, row5); \ + int16x8_t sum17 = vaddq_s16(row1, row7); \ + int16x8_t sum35 = vaddq_s16(row3, row5); \ + int16x8_t sum37 = vaddq_s16(row3, row7); \ + int16x8_t sumodd = vaddq_s16(sum17, sum35); \ + dct_long_mul(p5o, sumodd, rot1_0); \ + dct_long_mac(p1o, p5o, sum17, rot1_1); \ + dct_long_mac(p2o, p5o, sum35, rot1_2); \ + dct_long_mul(p3o, sum37, rot2_0); \ + dct_long_mul(p4o, sum15, rot2_1); \ + dct_wadd(sump13o, p1o, p3o); \ + dct_wadd(sump24o, p2o, p4o); \ + dct_wadd(sump23o, p2o, p3o); \ + dct_wadd(sump14o, p1o, p4o); \ + dct_long_mac(x4, sump13o, row7, rot3_0); \ + dct_long_mac(x5, sump24o, row5, rot3_1); \ + dct_long_mac(x6, sump23o, row3, rot3_2); \ + dct_long_mac(x7, sump14o, row1, rot3_3); \ + dct_bfly32o(row0,row7, x0,x7,shiftop,shift); \ + dct_bfly32o(row1,row6, x1,x6,shiftop,shift); \ + dct_bfly32o(row2,row5, x2,x5,shiftop,shift); \ + dct_bfly32o(row3,row4, x3,x4,shiftop,shift); \ + } + + // load + row0 = vld1q_s16(data + 0*8); + row1 = vld1q_s16(data + 1*8); + row2 = vld1q_s16(data + 2*8); + row3 = vld1q_s16(data + 3*8); + row4 = vld1q_s16(data + 4*8); + row5 = vld1q_s16(data + 5*8); + row6 = vld1q_s16(data + 6*8); + row7 = vld1q_s16(data + 7*8); + + // add DC bias + row0 = vaddq_s16(row0, vsetq_lane_s16(1024, vdupq_n_s16(0), 0)); + + // column pass + dct_pass(vrshrn_n_s32, 10); + + // 16bit 8x8 transpose + { +// these three map to a single VTRN.16, VTRN.32, and VSWP, respectively. +// whether compilers actually get this is another story, sadly. +#define dct_trn16(x, y) { int16x8x2_t t = vtrnq_s16(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn32(x, y) { int32x4x2_t t = vtrnq_s32(vreinterpretq_s32_s16(x), vreinterpretq_s32_s16(y)); x = vreinterpretq_s16_s32(t.val[0]); y = vreinterpretq_s16_s32(t.val[1]); } +#define dct_trn64(x, y) { int16x8_t x0 = x; int16x8_t y0 = y; x = vcombine_s16(vget_low_s16(x0), vget_low_s16(y0)); y = vcombine_s16(vget_high_s16(x0), vget_high_s16(y0)); } + + // pass 1 + dct_trn16(row0, row1); // a0b0a2b2a4b4a6b6 + dct_trn16(row2, row3); + dct_trn16(row4, row5); + dct_trn16(row6, row7); + + // pass 2 + dct_trn32(row0, row2); // a0b0c0d0a4b4c4d4 + dct_trn32(row1, row3); + dct_trn32(row4, row6); + dct_trn32(row5, row7); + + // pass 3 + dct_trn64(row0, row4); // a0b0c0d0e0f0g0h0 + dct_trn64(row1, row5); + dct_trn64(row2, row6); + dct_trn64(row3, row7); + +#undef dct_trn16 +#undef dct_trn32 +#undef dct_trn64 + } + + // row pass + // vrshrn_n_s32 only supports shifts up to 16, we need + // 17. so do a non-rounding shift of 16 first then follow + // up with a rounding shift by 1. + dct_pass(vshrn_n_s32, 16); + + { + // pack and round + uint8x8_t p0 = vqrshrun_n_s16(row0, 1); + uint8x8_t p1 = vqrshrun_n_s16(row1, 1); + uint8x8_t p2 = vqrshrun_n_s16(row2, 1); + uint8x8_t p3 = vqrshrun_n_s16(row3, 1); + uint8x8_t p4 = vqrshrun_n_s16(row4, 1); + uint8x8_t p5 = vqrshrun_n_s16(row5, 1); + uint8x8_t p6 = vqrshrun_n_s16(row6, 1); + uint8x8_t p7 = vqrshrun_n_s16(row7, 1); + + // again, these can translate into one instruction, but often don't. +#define dct_trn8_8(x, y) { uint8x8x2_t t = vtrn_u8(x, y); x = t.val[0]; y = t.val[1]; } +#define dct_trn8_16(x, y) { uint16x4x2_t t = vtrn_u16(vreinterpret_u16_u8(x), vreinterpret_u16_u8(y)); x = vreinterpret_u8_u16(t.val[0]); y = vreinterpret_u8_u16(t.val[1]); } +#define dct_trn8_32(x, y) { uint32x2x2_t t = vtrn_u32(vreinterpret_u32_u8(x), vreinterpret_u32_u8(y)); x = vreinterpret_u8_u32(t.val[0]); y = vreinterpret_u8_u32(t.val[1]); } + + // sadly can't use interleaved stores here since we only write + // 8 bytes to each scan line! + + // 8x8 8-bit transpose pass 1 + dct_trn8_8(p0, p1); + dct_trn8_8(p2, p3); + dct_trn8_8(p4, p5); + dct_trn8_8(p6, p7); + + // pass 2 + dct_trn8_16(p0, p2); + dct_trn8_16(p1, p3); + dct_trn8_16(p4, p6); + dct_trn8_16(p5, p7); + + // pass 3 + dct_trn8_32(p0, p4); + dct_trn8_32(p1, p5); + dct_trn8_32(p2, p6); + dct_trn8_32(p3, p7); + + // store + vst1_u8(out, p0); out += out_stride; + vst1_u8(out, p1); out += out_stride; + vst1_u8(out, p2); out += out_stride; + vst1_u8(out, p3); out += out_stride; + vst1_u8(out, p4); out += out_stride; + vst1_u8(out, p5); out += out_stride; + vst1_u8(out, p6); out += out_stride; + vst1_u8(out, p7); + +#undef dct_trn8_8 +#undef dct_trn8_16 +#undef dct_trn8_32 + } + +#undef dct_long_mul +#undef dct_long_mac +#undef dct_widen +#undef dct_wadd +#undef dct_wsub +#undef dct_bfly32o +#undef dct_pass +} + +#endif // STBI_NEON + +#define STBI__MARKER_none 0xff +// if there's a pending marker from the entropy stream, return that +// otherwise, fetch from the stream and get a marker. if there's no +// marker, return 0xff, which is never a valid marker value +static stbi_uc stbi__get_marker(stbi__jpeg *j) +{ + stbi_uc x; + if (j->marker != STBI__MARKER_none) { x = j->marker; j->marker = STBI__MARKER_none; return x; } + x = stbi__get8(j->s); + if (x != 0xff) return STBI__MARKER_none; + while (x == 0xff) + x = stbi__get8(j->s); // consume repeated 0xff fill bytes + return x; +} + +// in each scan, we'll have scan_n components, and the order +// of the components is specified by order[] +#define STBI__RESTART(x) ((x) >= 0xd0 && (x) <= 0xd7) + +// after a restart interval, stbi__jpeg_reset the entropy decoder and +// the dc prediction +static void stbi__jpeg_reset(stbi__jpeg *j) +{ + j->code_bits = 0; + j->code_buffer = 0; + j->nomore = 0; + j->img_comp[0].dc_pred = j->img_comp[1].dc_pred = j->img_comp[2].dc_pred = j->img_comp[3].dc_pred = 0; + j->marker = STBI__MARKER_none; + j->todo = j->restart_interval ? j->restart_interval : 0x7fffffff; + j->eob_run = 0; + // no more than 1<<31 MCUs if no restart_interal? that's plenty safe, + // since we don't even allow 1<<30 pixels +} + +static int stbi__parse_entropy_coded_data(stbi__jpeg *z) +{ + stbi__jpeg_reset(z); + if (!z->progressive) { + if (z->scan_n == 1) { + int i,j; + STBI_SIMD_ALIGN(short, data[64]); + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + // if it's NOT a restart, then just bail, so we get corrupt data + // rather than no data + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + STBI_SIMD_ALIGN(short, data[64]); + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x)*8; + int y2 = (j*z->img_comp[n].v + y)*8; + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block(z, data, z->huff_dc+z->img_comp[n].hd, z->huff_ac+ha, z->fast_ac[ha], n, z->dequant[z->img_comp[n].tq])) return 0; + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*y2+x2, z->img_comp[n].w2, data); + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } else { + if (z->scan_n == 1) { + int i,j; + int n = z->order[0]; + // non-interleaved data, we just need to process one block at a time, + // in trivial scanline order + // number of blocks to do just depends on how many actual "pixels" this + // component has, independent of interleaved MCU blocking and such + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + if (z->spec_start == 0) { + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } else { + int ha = z->img_comp[n].ha; + if (!stbi__jpeg_decode_block_prog_ac(z, data, &z->huff_ac[ha], z->fast_ac[ha])) + return 0; + } + // every data block is an MCU, so countdown the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } else { // interleaved + int i,j,k,x,y; + for (j=0; j < z->img_mcu_y; ++j) { + for (i=0; i < z->img_mcu_x; ++i) { + // scan an interleaved mcu... process scan_n components in order + for (k=0; k < z->scan_n; ++k) { + int n = z->order[k]; + // scan out an mcu's worth of this component; that's just determined + // by the basic H and V specified for the component + for (y=0; y < z->img_comp[n].v; ++y) { + for (x=0; x < z->img_comp[n].h; ++x) { + int x2 = (i*z->img_comp[n].h + x); + int y2 = (j*z->img_comp[n].v + y); + short *data = z->img_comp[n].coeff + 64 * (x2 + y2 * z->img_comp[n].coeff_w); + if (!stbi__jpeg_decode_block_prog_dc(z, data, &z->huff_dc[z->img_comp[n].hd], n)) + return 0; + } + } + } + // after all interleaved components, that's an interleaved MCU, + // so now count down the restart interval + if (--z->todo <= 0) { + if (z->code_bits < 24) stbi__grow_buffer_unsafe(z); + if (!STBI__RESTART(z->marker)) return 1; + stbi__jpeg_reset(z); + } + } + } + return 1; + } + } +} + +static void stbi__jpeg_dequantize(short *data, stbi__uint16 *dequant) +{ + int i; + for (i=0; i < 64; ++i) + data[i] *= dequant[i]; +} + +static void stbi__jpeg_finish(stbi__jpeg *z) +{ + if (z->progressive) { + // dequantize and idct the data + int i,j,n; + for (n=0; n < z->s->img_n; ++n) { + int w = (z->img_comp[n].x+7) >> 3; + int h = (z->img_comp[n].y+7) >> 3; + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) { + short *data = z->img_comp[n].coeff + 64 * (i + j * z->img_comp[n].coeff_w); + stbi__jpeg_dequantize(data, z->dequant[z->img_comp[n].tq]); + z->idct_block_kernel(z->img_comp[n].data+z->img_comp[n].w2*j*8+i*8, z->img_comp[n].w2, data); + } + } + } + } +} + +static int stbi__process_marker(stbi__jpeg *z, int m) +{ + int L; + switch (m) { + case STBI__MARKER_none: // no marker found + return stbi__err("expected marker","Corrupt JPEG"); + + case 0xDD: // DRI - specify restart interval + if (stbi__get16be(z->s) != 4) return stbi__err("bad DRI len","Corrupt JPEG"); + z->restart_interval = stbi__get16be(z->s); + return 1; + + case 0xDB: // DQT - define quantization table + L = stbi__get16be(z->s)-2; + while (L > 0) { + int q = stbi__get8(z->s); + int p = q >> 4, sixteen = (p != 0); + int t = q & 15,i; + if (p != 0 && p != 1) return stbi__err("bad DQT type","Corrupt JPEG"); + if (t > 3) return stbi__err("bad DQT table","Corrupt JPEG"); + + for (i=0; i < 64; ++i) + z->dequant[t][stbi__jpeg_dezigzag[i]] = (stbi__uint16)(sixteen ? stbi__get16be(z->s) : stbi__get8(z->s)); + L -= (sixteen ? 129 : 65); + } + return L==0; + + case 0xC4: // DHT - define huffman table + L = stbi__get16be(z->s)-2; + while (L > 0) { + stbi_uc *v; + int sizes[16],i,n=0; + int q = stbi__get8(z->s); + int tc = q >> 4; + int th = q & 15; + if (tc > 1 || th > 3) return stbi__err("bad DHT header","Corrupt JPEG"); + for (i=0; i < 16; ++i) { + sizes[i] = stbi__get8(z->s); + n += sizes[i]; + } + L -= 17; + if (tc == 0) { + if (!stbi__build_huffman(z->huff_dc+th, sizes)) return 0; + v = z->huff_dc[th].values; + } else { + if (!stbi__build_huffman(z->huff_ac+th, sizes)) return 0; + v = z->huff_ac[th].values; + } + for (i=0; i < n; ++i) + v[i] = stbi__get8(z->s); + if (tc != 0) + stbi__build_fast_ac(z->fast_ac[th], z->huff_ac + th); + L -= n; + } + return L==0; + } + + // check for comment block or APP blocks + if ((m >= 0xE0 && m <= 0xEF) || m == 0xFE) { + L = stbi__get16be(z->s); + if (L < 2) { + if (m == 0xFE) + return stbi__err("bad COM len","Corrupt JPEG"); + else + return stbi__err("bad APP len","Corrupt JPEG"); + } + L -= 2; + + if (m == 0xE0 && L >= 5) { // JFIF APP0 segment + static const unsigned char tag[5] = {'J','F','I','F','\0'}; + int ok = 1; + int i; + for (i=0; i < 5; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 5; + if (ok) + z->jfif = 1; + } else if (m == 0xEE && L >= 12) { // Adobe APP14 segment + static const unsigned char tag[6] = {'A','d','o','b','e','\0'}; + int ok = 1; + int i; + for (i=0; i < 6; ++i) + if (stbi__get8(z->s) != tag[i]) + ok = 0; + L -= 6; + if (ok) { + stbi__get8(z->s); // version + stbi__get16be(z->s); // flags0 + stbi__get16be(z->s); // flags1 + z->app14_color_transform = stbi__get8(z->s); // color transform + L -= 6; + } + } + + stbi__skip(z->s, L); + return 1; + } + + return stbi__err("unknown marker","Corrupt JPEG"); +} + +// after we see SOS +static int stbi__process_scan_header(stbi__jpeg *z) +{ + int i; + int Ls = stbi__get16be(z->s); + z->scan_n = stbi__get8(z->s); + if (z->scan_n < 1 || z->scan_n > 4 || z->scan_n > (int) z->s->img_n) return stbi__err("bad SOS component count","Corrupt JPEG"); + if (Ls != 6+2*z->scan_n) return stbi__err("bad SOS len","Corrupt JPEG"); + for (i=0; i < z->scan_n; ++i) { + int id = stbi__get8(z->s), which; + int q = stbi__get8(z->s); + for (which = 0; which < z->s->img_n; ++which) + if (z->img_comp[which].id == id) + break; + if (which == z->s->img_n) return 0; // no match + z->img_comp[which].hd = q >> 4; if (z->img_comp[which].hd > 3) return stbi__err("bad DC huff","Corrupt JPEG"); + z->img_comp[which].ha = q & 15; if (z->img_comp[which].ha > 3) return stbi__err("bad AC huff","Corrupt JPEG"); + z->order[i] = which; + } + + { + int aa; + z->spec_start = stbi__get8(z->s); + z->spec_end = stbi__get8(z->s); // should be 63, but might be 0 + aa = stbi__get8(z->s); + z->succ_high = (aa >> 4); + z->succ_low = (aa & 15); + if (z->progressive) { + if (z->spec_start > 63 || z->spec_end > 63 || z->spec_start > z->spec_end || z->succ_high > 13 || z->succ_low > 13) + return stbi__err("bad SOS", "Corrupt JPEG"); + } else { + if (z->spec_start != 0) return stbi__err("bad SOS","Corrupt JPEG"); + if (z->succ_high != 0 || z->succ_low != 0) return stbi__err("bad SOS","Corrupt JPEG"); + z->spec_end = 63; + } + } + + return 1; +} + +static int stbi__free_jpeg_components(stbi__jpeg *z, int ncomp, int why) +{ + int i; + for (i=0; i < ncomp; ++i) { + if (z->img_comp[i].raw_data) { + STBI_FREE(z->img_comp[i].raw_data); + z->img_comp[i].raw_data = NULL; + z->img_comp[i].data = NULL; + } + if (z->img_comp[i].raw_coeff) { + STBI_FREE(z->img_comp[i].raw_coeff); + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].coeff = 0; + } + if (z->img_comp[i].linebuf) { + STBI_FREE(z->img_comp[i].linebuf); + z->img_comp[i].linebuf = NULL; + } + } + return why; +} + +static int stbi__process_frame_header(stbi__jpeg *z, int scan) +{ + stbi__context *s = z->s; + int Lf,p,i,q, h_max=1,v_max=1,c; + Lf = stbi__get16be(s); if (Lf < 11) return stbi__err("bad SOF len","Corrupt JPEG"); // JPEG + p = stbi__get8(s); if (p != 8) return stbi__err("only 8-bit","JPEG format not supported: 8-bit only"); // JPEG baseline + s->img_y = stbi__get16be(s); if (s->img_y == 0) return stbi__err("no header height", "JPEG format not supported: delayed height"); // Legal, but we don't handle it--but neither does IJG + s->img_x = stbi__get16be(s); if (s->img_x == 0) return stbi__err("0 width","Corrupt JPEG"); // JPEG requires + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + c = stbi__get8(s); + if (c != 3 && c != 1 && c != 4) return stbi__err("bad component count","Corrupt JPEG"); + s->img_n = c; + for (i=0; i < c; ++i) { + z->img_comp[i].data = NULL; + z->img_comp[i].linebuf = NULL; + } + + if (Lf != 8+3*s->img_n) return stbi__err("bad SOF len","Corrupt JPEG"); + + z->rgb = 0; + for (i=0; i < s->img_n; ++i) { + static const unsigned char rgb[3] = { 'R', 'G', 'B' }; + z->img_comp[i].id = stbi__get8(s); + if (s->img_n == 3 && z->img_comp[i].id == rgb[i]) + ++z->rgb; + q = stbi__get8(s); + z->img_comp[i].h = (q >> 4); if (!z->img_comp[i].h || z->img_comp[i].h > 4) return stbi__err("bad H","Corrupt JPEG"); + z->img_comp[i].v = q & 15; if (!z->img_comp[i].v || z->img_comp[i].v > 4) return stbi__err("bad V","Corrupt JPEG"); + z->img_comp[i].tq = stbi__get8(s); if (z->img_comp[i].tq > 3) return stbi__err("bad TQ","Corrupt JPEG"); + } + + if (scan != STBI__SCAN_load) return 1; + + if (!stbi__mad3sizes_valid(s->img_x, s->img_y, s->img_n, 0)) return stbi__err("too large", "Image too large to decode"); + + for (i=0; i < s->img_n; ++i) { + if (z->img_comp[i].h > h_max) h_max = z->img_comp[i].h; + if (z->img_comp[i].v > v_max) v_max = z->img_comp[i].v; + } + + // check that plane subsampling factors are integer ratios; our resamplers can't deal with fractional ratios + // and I've never seen a non-corrupted JPEG file actually use them + for (i=0; i < s->img_n; ++i) { + if (h_max % z->img_comp[i].h != 0) return stbi__err("bad H","Corrupt JPEG"); + if (v_max % z->img_comp[i].v != 0) return stbi__err("bad V","Corrupt JPEG"); + } + + // compute interleaved mcu info + z->img_h_max = h_max; + z->img_v_max = v_max; + z->img_mcu_w = h_max * 8; + z->img_mcu_h = v_max * 8; + // these sizes can't be more than 17 bits + z->img_mcu_x = (s->img_x + z->img_mcu_w-1) / z->img_mcu_w; + z->img_mcu_y = (s->img_y + z->img_mcu_h-1) / z->img_mcu_h; + + for (i=0; i < s->img_n; ++i) { + // number of effective pixels (e.g. for non-interleaved MCU) + z->img_comp[i].x = (s->img_x * z->img_comp[i].h + h_max-1) / h_max; + z->img_comp[i].y = (s->img_y * z->img_comp[i].v + v_max-1) / v_max; + // to simplify generation, we'll allocate enough memory to decode + // the bogus oversized data from using interleaved MCUs and their + // big blocks (e.g. a 16x16 iMCU on an image of width 33); we won't + // discard the extra data until colorspace conversion + // + // img_mcu_x, img_mcu_y: <=17 bits; comp[i].h and .v are <=4 (checked earlier) + // so these muls can't overflow with 32-bit ints (which we require) + z->img_comp[i].w2 = z->img_mcu_x * z->img_comp[i].h * 8; + z->img_comp[i].h2 = z->img_mcu_y * z->img_comp[i].v * 8; + z->img_comp[i].coeff = 0; + z->img_comp[i].raw_coeff = 0; + z->img_comp[i].linebuf = NULL; + z->img_comp[i].raw_data = stbi__malloc_mad2(z->img_comp[i].w2, z->img_comp[i].h2, 15); + if (z->img_comp[i].raw_data == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + // align blocks for idct using mmx/sse + z->img_comp[i].data = (stbi_uc*) (((size_t) z->img_comp[i].raw_data + 15) & ~15); + if (z->progressive) { + // w2, h2 are multiples of 8 (see above) + z->img_comp[i].coeff_w = z->img_comp[i].w2 / 8; + z->img_comp[i].coeff_h = z->img_comp[i].h2 / 8; + z->img_comp[i].raw_coeff = stbi__malloc_mad3(z->img_comp[i].w2, z->img_comp[i].h2, sizeof(short), 15); + if (z->img_comp[i].raw_coeff == NULL) + return stbi__free_jpeg_components(z, i+1, stbi__err("outofmem", "Out of memory")); + z->img_comp[i].coeff = (short*) (((size_t) z->img_comp[i].raw_coeff + 15) & ~15); + } + } + + return 1; +} + +// use comparisons since in some cases we handle more than one case (e.g. SOF) +#define stbi__DNL(x) ((x) == 0xdc) +#define stbi__SOI(x) ((x) == 0xd8) +#define stbi__EOI(x) ((x) == 0xd9) +#define stbi__SOF(x) ((x) == 0xc0 || (x) == 0xc1 || (x) == 0xc2) +#define stbi__SOS(x) ((x) == 0xda) + +#define stbi__SOF_progressive(x) ((x) == 0xc2) + +static int stbi__decode_jpeg_header(stbi__jpeg *z, int scan) +{ + int m; + z->jfif = 0; + z->app14_color_transform = -1; // valid values are 0,1,2 + z->marker = STBI__MARKER_none; // initialize cached marker to empty + m = stbi__get_marker(z); + if (!stbi__SOI(m)) return stbi__err("no SOI","Corrupt JPEG"); + if (scan == STBI__SCAN_type) return 1; + m = stbi__get_marker(z); + while (!stbi__SOF(m)) { + if (!stbi__process_marker(z,m)) return 0; + m = stbi__get_marker(z); + while (m == STBI__MARKER_none) { + // some files have extra padding after their blocks, so ok, we'll scan + if (stbi__at_eof(z->s)) return stbi__err("no SOF", "Corrupt JPEG"); + m = stbi__get_marker(z); + } + } + z->progressive = stbi__SOF_progressive(m); + if (!stbi__process_frame_header(z, scan)) return 0; + return 1; +} + +// decode image to YCbCr format +static int stbi__decode_jpeg_image(stbi__jpeg *j) +{ + int m; + for (m = 0; m < 4; m++) { + j->img_comp[m].raw_data = NULL; + j->img_comp[m].raw_coeff = NULL; + } + j->restart_interval = 0; + if (!stbi__decode_jpeg_header(j, STBI__SCAN_load)) return 0; + m = stbi__get_marker(j); + while (!stbi__EOI(m)) { + if (stbi__SOS(m)) { + if (!stbi__process_scan_header(j)) return 0; + if (!stbi__parse_entropy_coded_data(j)) return 0; + if (j->marker == STBI__MARKER_none ) { + // handle 0s at the end of image data from IP Kamera 9060 + while (!stbi__at_eof(j->s)) { + int x = stbi__get8(j->s); + if (x == 255) { + j->marker = stbi__get8(j->s); + break; + } + } + // if we reach eof without hitting a marker, stbi__get_marker() below will fail and we'll eventually return 0 + } + } else if (stbi__DNL(m)) { + int Ld = stbi__get16be(j->s); + stbi__uint32 NL = stbi__get16be(j->s); + if (Ld != 4) return stbi__err("bad DNL len", "Corrupt JPEG"); + if (NL != j->s->img_y) return stbi__err("bad DNL height", "Corrupt JPEG"); + } else { + if (!stbi__process_marker(j, m)) return 0; + } + m = stbi__get_marker(j); + } + if (j->progressive) + stbi__jpeg_finish(j); + return 1; +} + +// static jfif-centered resampling (across block boundaries) + +typedef stbi_uc *(*resample_row_func)(stbi_uc *out, stbi_uc *in0, stbi_uc *in1, + int w, int hs); + +#define stbi__div4(x) ((stbi_uc) ((x) >> 2)) + +static stbi_uc *resample_row_1(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + STBI_NOTUSED(out); + STBI_NOTUSED(in_far); + STBI_NOTUSED(w); + STBI_NOTUSED(hs); + return in_near; +} + +static stbi_uc* stbi__resample_row_v_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples vertically for every one in input + int i; + STBI_NOTUSED(hs); + for (i=0; i < w; ++i) + out[i] = stbi__div4(3*in_near[i] + in_far[i] + 2); + return out; +} + +static stbi_uc* stbi__resample_row_h_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate two samples horizontally for every one in input + int i; + stbi_uc *input = in_near; + + if (w == 1) { + // if only one sample, can't do any interpolation + out[0] = out[1] = input[0]; + return out; + } + + out[0] = input[0]; + out[1] = stbi__div4(input[0]*3 + input[1] + 2); + for (i=1; i < w-1; ++i) { + int n = 3*input[i]+2; + out[i*2+0] = stbi__div4(n+input[i-1]); + out[i*2+1] = stbi__div4(n+input[i+1]); + } + out[i*2+0] = stbi__div4(input[w-2]*3 + input[w-1] + 2); + out[i*2+1] = input[w-1]; + + STBI_NOTUSED(in_far); + STBI_NOTUSED(hs); + + return out; +} + +#define stbi__div16(x) ((stbi_uc) ((x) >> 4)) + +static stbi_uc *stbi__resample_row_hv_2(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i,t0,t1; + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + out[0] = stbi__div4(t1+2); + for (i=1; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static stbi_uc *stbi__resample_row_hv_2_simd(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // need to generate 2x2 samples for every one in input + int i=0,t0,t1; + + if (w == 1) { + out[0] = out[1] = stbi__div4(3*in_near[0] + in_far[0] + 2); + return out; + } + + t1 = 3*in_near[0] + in_far[0]; + // process groups of 8 pixels for as long as we can. + // note we can't handle the last pixel in a row in this loop + // because we need to handle the filter boundary conditions. + for (; i < ((w-1) & ~7); i += 8) { +#if defined(STBI_SSE2) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + __m128i zero = _mm_setzero_si128(); + __m128i farb = _mm_loadl_epi64((__m128i *) (in_far + i)); + __m128i nearb = _mm_loadl_epi64((__m128i *) (in_near + i)); + __m128i farw = _mm_unpacklo_epi8(farb, zero); + __m128i nearw = _mm_unpacklo_epi8(nearb, zero); + __m128i diff = _mm_sub_epi16(farw, nearw); + __m128i nears = _mm_slli_epi16(nearw, 2); + __m128i curr = _mm_add_epi16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + __m128i prv0 = _mm_slli_si128(curr, 2); + __m128i nxt0 = _mm_srli_si128(curr, 2); + __m128i prev = _mm_insert_epi16(prv0, t1, 0); + __m128i next = _mm_insert_epi16(nxt0, 3*in_near[i+8] + in_far[i+8], 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + __m128i bias = _mm_set1_epi16(8); + __m128i curs = _mm_slli_epi16(curr, 2); + __m128i prvd = _mm_sub_epi16(prev, curr); + __m128i nxtd = _mm_sub_epi16(next, curr); + __m128i curb = _mm_add_epi16(curs, bias); + __m128i even = _mm_add_epi16(prvd, curb); + __m128i odd = _mm_add_epi16(nxtd, curb); + + // interleave even and odd pixels, then undo scaling. + __m128i int0 = _mm_unpacklo_epi16(even, odd); + __m128i int1 = _mm_unpackhi_epi16(even, odd); + __m128i de0 = _mm_srli_epi16(int0, 4); + __m128i de1 = _mm_srli_epi16(int1, 4); + + // pack and write output + __m128i outv = _mm_packus_epi16(de0, de1); + _mm_storeu_si128((__m128i *) (out + i*2), outv); +#elif defined(STBI_NEON) + // load and perform the vertical filtering pass + // this uses 3*x + y = 4*x + (y - x) + uint8x8_t farb = vld1_u8(in_far + i); + uint8x8_t nearb = vld1_u8(in_near + i); + int16x8_t diff = vreinterpretq_s16_u16(vsubl_u8(farb, nearb)); + int16x8_t nears = vreinterpretq_s16_u16(vshll_n_u8(nearb, 2)); + int16x8_t curr = vaddq_s16(nears, diff); // current row + + // horizontal filter works the same based on shifted vers of current + // row. "prev" is current row shifted right by 1 pixel; we need to + // insert the previous pixel value (from t1). + // "next" is current row shifted left by 1 pixel, with first pixel + // of next block of 8 pixels added in. + int16x8_t prv0 = vextq_s16(curr, curr, 7); + int16x8_t nxt0 = vextq_s16(curr, curr, 1); + int16x8_t prev = vsetq_lane_s16(t1, prv0, 0); + int16x8_t next = vsetq_lane_s16(3*in_near[i+8] + in_far[i+8], nxt0, 7); + + // horizontal filter, polyphase implementation since it's convenient: + // even pixels = 3*cur + prev = cur*4 + (prev - cur) + // odd pixels = 3*cur + next = cur*4 + (next - cur) + // note the shared term. + int16x8_t curs = vshlq_n_s16(curr, 2); + int16x8_t prvd = vsubq_s16(prev, curr); + int16x8_t nxtd = vsubq_s16(next, curr); + int16x8_t even = vaddq_s16(curs, prvd); + int16x8_t odd = vaddq_s16(curs, nxtd); + + // undo scaling and round, then store with even/odd phases interleaved + uint8x8x2_t o; + o.val[0] = vqrshrun_n_s16(even, 4); + o.val[1] = vqrshrun_n_s16(odd, 4); + vst2_u8(out + i*2, o); +#endif + + // "previous" value for next iter + t1 = 3*in_near[i+7] + in_far[i+7]; + } + + t0 = t1; + t1 = 3*in_near[i] + in_far[i]; + out[i*2] = stbi__div16(3*t1 + t0 + 8); + + for (++i; i < w; ++i) { + t0 = t1; + t1 = 3*in_near[i]+in_far[i]; + out[i*2-1] = stbi__div16(3*t0 + t1 + 8); + out[i*2 ] = stbi__div16(3*t1 + t0 + 8); + } + out[w*2-1] = stbi__div4(t1+2); + + STBI_NOTUSED(hs); + + return out; +} +#endif + +static stbi_uc *stbi__resample_row_generic(stbi_uc *out, stbi_uc *in_near, stbi_uc *in_far, int w, int hs) +{ + // resample with nearest-neighbor + int i,j; + STBI_NOTUSED(in_far); + for (i=0; i < w; ++i) + for (j=0; j < hs; ++j) + out[i*hs+j] = in_near[i]; + return out; +} + +// this is a reduced-precision calculation of YCbCr-to-RGB introduced +// to make sure the code produces the same results in both SIMD and scalar +#define stbi__float2fixed(x) (((int) ((x) * 4096.0f + 0.5f)) << 8) +static void stbi__YCbCr_to_RGB_row(stbi_uc *out, const stbi_uc *y, const stbi_uc *pcb, const stbi_uc *pcr, int count, int step) +{ + int i; + for (i=0; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + (cr*-stbi__float2fixed(0.71414f)) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} + +#if defined(STBI_SSE2) || defined(STBI_NEON) +static void stbi__YCbCr_to_RGB_simd(stbi_uc *out, stbi_uc const *y, stbi_uc const *pcb, stbi_uc const *pcr, int count, int step) +{ + int i = 0; + +#ifdef STBI_SSE2 + // step == 3 is pretty ugly on the final interleave, and i'm not convinced + // it's useful in practice (you wouldn't use it for textures, for example). + // so just accelerate step == 4 case. + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + __m128i signflip = _mm_set1_epi8(-0x80); + __m128i cr_const0 = _mm_set1_epi16( (short) ( 1.40200f*4096.0f+0.5f)); + __m128i cr_const1 = _mm_set1_epi16( - (short) ( 0.71414f*4096.0f+0.5f)); + __m128i cb_const0 = _mm_set1_epi16( - (short) ( 0.34414f*4096.0f+0.5f)); + __m128i cb_const1 = _mm_set1_epi16( (short) ( 1.77200f*4096.0f+0.5f)); + __m128i y_bias = _mm_set1_epi8((char) (unsigned char) 128); + __m128i xw = _mm_set1_epi16(255); // alpha channel + + for (; i+7 < count; i += 8) { + // load + __m128i y_bytes = _mm_loadl_epi64((__m128i *) (y+i)); + __m128i cr_bytes = _mm_loadl_epi64((__m128i *) (pcr+i)); + __m128i cb_bytes = _mm_loadl_epi64((__m128i *) (pcb+i)); + __m128i cr_biased = _mm_xor_si128(cr_bytes, signflip); // -128 + __m128i cb_biased = _mm_xor_si128(cb_bytes, signflip); // -128 + + // unpack to short (and left-shift cr, cb by 8) + __m128i yw = _mm_unpacklo_epi8(y_bias, y_bytes); + __m128i crw = _mm_unpacklo_epi8(_mm_setzero_si128(), cr_biased); + __m128i cbw = _mm_unpacklo_epi8(_mm_setzero_si128(), cb_biased); + + // color transform + __m128i yws = _mm_srli_epi16(yw, 4); + __m128i cr0 = _mm_mulhi_epi16(cr_const0, crw); + __m128i cb0 = _mm_mulhi_epi16(cb_const0, cbw); + __m128i cb1 = _mm_mulhi_epi16(cbw, cb_const1); + __m128i cr1 = _mm_mulhi_epi16(crw, cr_const1); + __m128i rws = _mm_add_epi16(cr0, yws); + __m128i gwt = _mm_add_epi16(cb0, yws); + __m128i bws = _mm_add_epi16(yws, cb1); + __m128i gws = _mm_add_epi16(gwt, cr1); + + // descale + __m128i rw = _mm_srai_epi16(rws, 4); + __m128i bw = _mm_srai_epi16(bws, 4); + __m128i gw = _mm_srai_epi16(gws, 4); + + // back to byte, set up for transpose + __m128i brb = _mm_packus_epi16(rw, bw); + __m128i gxb = _mm_packus_epi16(gw, xw); + + // transpose to interleave channels + __m128i t0 = _mm_unpacklo_epi8(brb, gxb); + __m128i t1 = _mm_unpackhi_epi8(brb, gxb); + __m128i o0 = _mm_unpacklo_epi16(t0, t1); + __m128i o1 = _mm_unpackhi_epi16(t0, t1); + + // store + _mm_storeu_si128((__m128i *) (out + 0), o0); + _mm_storeu_si128((__m128i *) (out + 16), o1); + out += 32; + } + } +#endif + +#ifdef STBI_NEON + // in this version, step=3 support would be easy to add. but is there demand? + if (step == 4) { + // this is a fairly straightforward implementation and not super-optimized. + uint8x8_t signflip = vdup_n_u8(0x80); + int16x8_t cr_const0 = vdupq_n_s16( (short) ( 1.40200f*4096.0f+0.5f)); + int16x8_t cr_const1 = vdupq_n_s16( - (short) ( 0.71414f*4096.0f+0.5f)); + int16x8_t cb_const0 = vdupq_n_s16( - (short) ( 0.34414f*4096.0f+0.5f)); + int16x8_t cb_const1 = vdupq_n_s16( (short) ( 1.77200f*4096.0f+0.5f)); + + for (; i+7 < count; i += 8) { + // load + uint8x8_t y_bytes = vld1_u8(y + i); + uint8x8_t cr_bytes = vld1_u8(pcr + i); + uint8x8_t cb_bytes = vld1_u8(pcb + i); + int8x8_t cr_biased = vreinterpret_s8_u8(vsub_u8(cr_bytes, signflip)); + int8x8_t cb_biased = vreinterpret_s8_u8(vsub_u8(cb_bytes, signflip)); + + // expand to s16 + int16x8_t yws = vreinterpretq_s16_u16(vshll_n_u8(y_bytes, 4)); + int16x8_t crw = vshll_n_s8(cr_biased, 7); + int16x8_t cbw = vshll_n_s8(cb_biased, 7); + + // color transform + int16x8_t cr0 = vqdmulhq_s16(crw, cr_const0); + int16x8_t cb0 = vqdmulhq_s16(cbw, cb_const0); + int16x8_t cr1 = vqdmulhq_s16(crw, cr_const1); + int16x8_t cb1 = vqdmulhq_s16(cbw, cb_const1); + int16x8_t rws = vaddq_s16(yws, cr0); + int16x8_t gws = vaddq_s16(vaddq_s16(yws, cb0), cr1); + int16x8_t bws = vaddq_s16(yws, cb1); + + // undo scaling, round, convert to byte + uint8x8x4_t o; + o.val[0] = vqrshrun_n_s16(rws, 4); + o.val[1] = vqrshrun_n_s16(gws, 4); + o.val[2] = vqrshrun_n_s16(bws, 4); + o.val[3] = vdup_n_u8(255); + + // store, interleaving r/g/b/a + vst4_u8(out, o); + out += 8*4; + } + } +#endif + + for (; i < count; ++i) { + int y_fixed = (y[i] << 20) + (1<<19); // rounding + int r,g,b; + int cr = pcr[i] - 128; + int cb = pcb[i] - 128; + r = y_fixed + cr* stbi__float2fixed(1.40200f); + g = y_fixed + cr*-stbi__float2fixed(0.71414f) + ((cb*-stbi__float2fixed(0.34414f)) & 0xffff0000); + b = y_fixed + cb* stbi__float2fixed(1.77200f); + r >>= 20; + g >>= 20; + b >>= 20; + if ((unsigned) r > 255) { if (r < 0) r = 0; else r = 255; } + if ((unsigned) g > 255) { if (g < 0) g = 0; else g = 255; } + if ((unsigned) b > 255) { if (b < 0) b = 0; else b = 255; } + out[0] = (stbi_uc)r; + out[1] = (stbi_uc)g; + out[2] = (stbi_uc)b; + out[3] = 255; + out += step; + } +} +#endif + +// set up the kernels +static void stbi__setup_jpeg(stbi__jpeg *j) +{ + j->idct_block_kernel = stbi__idct_block; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_row; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2; + +#ifdef STBI_SSE2 + if (stbi__sse2_available()) { + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; + } +#endif + +#ifdef STBI_NEON + j->idct_block_kernel = stbi__idct_simd; + j->YCbCr_to_RGB_kernel = stbi__YCbCr_to_RGB_simd; + j->resample_row_hv_2_kernel = stbi__resample_row_hv_2_simd; +#endif +} + +// clean up the temporary component buffers +static void stbi__cleanup_jpeg(stbi__jpeg *j) +{ + stbi__free_jpeg_components(j, j->s->img_n, 0); +} + +typedef struct +{ + resample_row_func resample; + stbi_uc *line0,*line1; + int hs,vs; // expansion factor in each axis + int w_lores; // horizontal pixels pre-expansion + int ystep; // how far through vertical expansion we are + int ypos; // which pre-expansion row we're on +} stbi__resample; + +// fast 0..255 * 0..255 => 0..255 rounded multiplication +static stbi_uc stbi__blinn_8x8(stbi_uc x, stbi_uc y) +{ + unsigned int t = x*y + 128; + return (stbi_uc) ((t + (t >>8)) >> 8); +} + +static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp, int req_comp) +{ + int n, decode_n, is_rgb; + z->s->img_n = 0; // make stbi__cleanup_jpeg safe + + // validate req_comp + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + + // load a jpeg image from whichever source, but leave in YCbCr format + if (!stbi__decode_jpeg_image(z)) { stbi__cleanup_jpeg(z); return NULL; } + + // determine actual number of components to generate + n = req_comp ? req_comp : z->s->img_n >= 3 ? 3 : 1; + + is_rgb = z->s->img_n == 3 && (z->rgb == 3 || (z->app14_color_transform == 0 && !z->jfif)); + + if (z->s->img_n == 3 && n < 3 && !is_rgb) + decode_n = 1; + else + decode_n = z->s->img_n; + + // nothing to do if no components requested; check this now to avoid + // accessing uninitialized coutput[0] later + if (decode_n <= 0) { stbi__cleanup_jpeg(z); return NULL; } + + // resample and color-convert + { + int k; + unsigned int i,j; + stbi_uc *output; + stbi_uc *coutput[4] = { NULL, NULL, NULL, NULL }; + + stbi__resample res_comp[4]; + + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + + // allocate line buffer big enough for upsampling off the edges + // with upsample factor of 4 + z->img_comp[k].linebuf = (stbi_uc *) stbi__malloc(z->s->img_x + 3); + if (!z->img_comp[k].linebuf) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + r->hs = z->img_h_max / z->img_comp[k].h; + r->vs = z->img_v_max / z->img_comp[k].v; + r->ystep = r->vs >> 1; + r->w_lores = (z->s->img_x + r->hs-1) / r->hs; + r->ypos = 0; + r->line0 = r->line1 = z->img_comp[k].data; + + if (r->hs == 1 && r->vs == 1) r->resample = resample_row_1; + else if (r->hs == 1 && r->vs == 2) r->resample = stbi__resample_row_v_2; + else if (r->hs == 2 && r->vs == 1) r->resample = stbi__resample_row_h_2; + else if (r->hs == 2 && r->vs == 2) r->resample = z->resample_row_hv_2_kernel; + else r->resample = stbi__resample_row_generic; + } + + // can't error after this so, this is safe + output = (stbi_uc *) stbi__malloc_mad3(n, z->s->img_x, z->s->img_y, 1); + if (!output) { stbi__cleanup_jpeg(z); return stbi__errpuc("outofmem", "Out of memory"); } + + // now go ahead and resample + for (j=0; j < z->s->img_y; ++j) { + stbi_uc *out = output + n * z->s->img_x * j; + for (k=0; k < decode_n; ++k) { + stbi__resample *r = &res_comp[k]; + int y_bot = r->ystep >= (r->vs >> 1); + coutput[k] = r->resample(z->img_comp[k].linebuf, + y_bot ? r->line1 : r->line0, + y_bot ? r->line0 : r->line1, + r->w_lores, r->hs); + if (++r->ystep >= r->vs) { + r->ystep = 0; + r->line0 = r->line1; + if (++r->ypos < z->img_comp[k].y) + r->line1 += z->img_comp[k].w2; + } + } + if (n >= 3) { + stbi_uc *y = coutput[0]; + if (z->s->img_n == 3) { + if (is_rgb) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = y[i]; + out[1] = coutput[1][i]; + out[2] = coutput[2][i]; + out[3] = 255; + out += n; + } + } else { + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else if (z->s->img_n == 4) { + if (z->app14_color_transform == 0) { // CMYK + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(coutput[0][i], m); + out[1] = stbi__blinn_8x8(coutput[1][i], m); + out[2] = stbi__blinn_8x8(coutput[2][i], m); + out[3] = 255; + out += n; + } + } else if (z->app14_color_transform == 2) { // YCCK + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + out[0] = stbi__blinn_8x8(255 - out[0], m); + out[1] = stbi__blinn_8x8(255 - out[1], m); + out[2] = stbi__blinn_8x8(255 - out[2], m); + out += n; + } + } else { // YCbCr + alpha? Ignore the fourth channel for now + z->YCbCr_to_RGB_kernel(out, y, coutput[1], coutput[2], z->s->img_x, n); + } + } else + for (i=0; i < z->s->img_x; ++i) { + out[0] = out[1] = out[2] = y[i]; + out[3] = 255; // not used if n==3 + out += n; + } + } else { + if (is_rgb) { + if (n == 1) + for (i=0; i < z->s->img_x; ++i) + *out++ = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + else { + for (i=0; i < z->s->img_x; ++i, out += 2) { + out[0] = stbi__compute_y(coutput[0][i], coutput[1][i], coutput[2][i]); + out[1] = 255; + } + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 0) { + for (i=0; i < z->s->img_x; ++i) { + stbi_uc m = coutput[3][i]; + stbi_uc r = stbi__blinn_8x8(coutput[0][i], m); + stbi_uc g = stbi__blinn_8x8(coutput[1][i], m); + stbi_uc b = stbi__blinn_8x8(coutput[2][i], m); + out[0] = stbi__compute_y(r, g, b); + out[1] = 255; + out += n; + } + } else if (z->s->img_n == 4 && z->app14_color_transform == 2) { + for (i=0; i < z->s->img_x; ++i) { + out[0] = stbi__blinn_8x8(255 - coutput[0][i], coutput[3][i]); + out[1] = 255; + out += n; + } + } else { + stbi_uc *y = coutput[0]; + if (n == 1) + for (i=0; i < z->s->img_x; ++i) out[i] = y[i]; + else + for (i=0; i < z->s->img_x; ++i) { *out++ = y[i]; *out++ = 255; } + } + } + } + stbi__cleanup_jpeg(z); + *out_x = z->s->img_x; + *out_y = z->s->img_y; + if (comp) *comp = z->s->img_n >= 3 ? 3 : 1; // report original components, not output + return output; + } +} + +static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + unsigned char* result; + stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__errpuc("outofmem", "Out of memory"); + STBI_NOTUSED(ri); + j->s = s; + stbi__setup_jpeg(j); + result = load_jpeg_image(j, x,y,comp,req_comp); + STBI_FREE(j); + return result; +} + +static int stbi__jpeg_test(stbi__context *s) +{ + int r; + stbi__jpeg* j = (stbi__jpeg*)stbi__malloc(sizeof(stbi__jpeg)); + if (!j) return stbi__err("outofmem", "Out of memory"); + j->s = s; + stbi__setup_jpeg(j); + r = stbi__decode_jpeg_header(j, STBI__SCAN_type); + stbi__rewind(s); + STBI_FREE(j); + return r; +} + +static int stbi__jpeg_info_raw(stbi__jpeg *j, int *x, int *y, int *comp) +{ + if (!stbi__decode_jpeg_header(j, STBI__SCAN_header)) { + stbi__rewind( j->s ); + return 0; + } + if (x) *x = j->s->img_x; + if (y) *y = j->s->img_y; + if (comp) *comp = j->s->img_n >= 3 ? 3 : 1; + return 1; +} + +static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp) +{ + int result; + stbi__jpeg* j = (stbi__jpeg*) (stbi__malloc(sizeof(stbi__jpeg))); + if (!j) return stbi__err("outofmem", "Out of memory"); + j->s = s; + result = stbi__jpeg_info_raw(j, x, y, comp); + STBI_FREE(j); + return result; +} +#endif + +// public domain zlib decode v0.2 Sean Barrett 2006-11-18 +// simple implementation +// - all input must be provided in an upfront buffer +// - all output is written to a single output buffer (can malloc/realloc) +// performance +// - fast huffman + +#ifndef STBI_NO_ZLIB + +// fast-way is faster to check than jpeg huffman, but slow way is slower +#define STBI__ZFAST_BITS 9 // accelerate all cases in default tables +#define STBI__ZFAST_MASK ((1 << STBI__ZFAST_BITS) - 1) +#define STBI__ZNSYMS 288 // number of symbols in literal/length alphabet + +// zlib-style huffman encoding +// (jpegs packs from left, zlib from right, so can't share code) +typedef struct +{ + stbi__uint16 fast[1 << STBI__ZFAST_BITS]; + stbi__uint16 firstcode[16]; + int maxcode[17]; + stbi__uint16 firstsymbol[16]; + stbi_uc size[STBI__ZNSYMS]; + stbi__uint16 value[STBI__ZNSYMS]; +} stbi__zhuffman; + +stbi_inline static int stbi__bitreverse16(int n) +{ + n = ((n & 0xAAAA) >> 1) | ((n & 0x5555) << 1); + n = ((n & 0xCCCC) >> 2) | ((n & 0x3333) << 2); + n = ((n & 0xF0F0) >> 4) | ((n & 0x0F0F) << 4); + n = ((n & 0xFF00) >> 8) | ((n & 0x00FF) << 8); + return n; +} + +stbi_inline static int stbi__bit_reverse(int v, int bits) +{ + STBI_ASSERT(bits <= 16); + // to bit reverse n bits, reverse 16 and shift + // e.g. 11 bits, bit reverse and shift away 5 + return stbi__bitreverse16(v) >> (16-bits); +} + +static int stbi__zbuild_huffman(stbi__zhuffman *z, const stbi_uc *sizelist, int num) +{ + int i,k=0; + int code, next_code[16], sizes[17]; + + // DEFLATE spec for generating codes + memset(sizes, 0, sizeof(sizes)); + memset(z->fast, 0, sizeof(z->fast)); + for (i=0; i < num; ++i) + ++sizes[sizelist[i]]; + sizes[0] = 0; + for (i=1; i < 16; ++i) + if (sizes[i] > (1 << i)) + return stbi__err("bad sizes", "Corrupt PNG"); + code = 0; + for (i=1; i < 16; ++i) { + next_code[i] = code; + z->firstcode[i] = (stbi__uint16) code; + z->firstsymbol[i] = (stbi__uint16) k; + code = (code + sizes[i]); + if (sizes[i]) + if (code-1 >= (1 << i)) return stbi__err("bad codelengths","Corrupt PNG"); + z->maxcode[i] = code << (16-i); // preshift for inner loop + code <<= 1; + k += sizes[i]; + } + z->maxcode[16] = 0x10000; // sentinel + for (i=0; i < num; ++i) { + int s = sizelist[i]; + if (s) { + int c = next_code[s] - z->firstcode[s] + z->firstsymbol[s]; + stbi__uint16 fastv = (stbi__uint16) ((s << 9) | i); + z->size [c] = (stbi_uc ) s; + z->value[c] = (stbi__uint16) i; + if (s <= STBI__ZFAST_BITS) { + int j = stbi__bit_reverse(next_code[s],s); + while (j < (1 << STBI__ZFAST_BITS)) { + z->fast[j] = fastv; + j += (1 << s); + } + } + ++next_code[s]; + } + } + return 1; +} + +// zlib-from-memory implementation for PNG reading +// because PNG allows splitting the zlib stream arbitrarily, +// and it's annoying structurally to have PNG call ZLIB call PNG, +// we require PNG read all the IDATs and combine them into a single +// memory buffer + +typedef struct +{ + stbi_uc *zbuffer, *zbuffer_end; + int num_bits; + stbi__uint32 code_buffer; + + char *zout; + char *zout_start; + char *zout_end; + int z_expandable; + + stbi__zhuffman z_length, z_distance; +} stbi__zbuf; + +stbi_inline static int stbi__zeof(stbi__zbuf *z) +{ + return (z->zbuffer >= z->zbuffer_end); +} + +stbi_inline static stbi_uc stbi__zget8(stbi__zbuf *z) +{ + return stbi__zeof(z) ? 0 : *z->zbuffer++; +} + +static void stbi__fill_bits(stbi__zbuf *z) +{ + do { + if (z->code_buffer >= (1U << z->num_bits)) { + z->zbuffer = z->zbuffer_end; /* treat this as EOF so we fail. */ + return; + } + z->code_buffer |= (unsigned int) stbi__zget8(z) << z->num_bits; + z->num_bits += 8; + } while (z->num_bits <= 24); +} + +stbi_inline static unsigned int stbi__zreceive(stbi__zbuf *z, int n) +{ + unsigned int k; + if (z->num_bits < n) stbi__fill_bits(z); + k = z->code_buffer & ((1 << n) - 1); + z->code_buffer >>= n; + z->num_bits -= n; + return k; +} + +static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s,k; + // not resolved by fast table, so compute it the slow way + // use jpeg approach, which requires MSbits at top + k = stbi__bit_reverse(a->code_buffer, 16); + for (s=STBI__ZFAST_BITS+1; ; ++s) + if (k < z->maxcode[s]) + break; + if (s >= 16) return -1; // invalid code! + // code size is s, so: + b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s]; + if (b >= STBI__ZNSYMS) return -1; // some data was corrupt somewhere! + if (z->size[b] != s) return -1; // was originally an assert, but report failure instead. + a->code_buffer >>= s; + a->num_bits -= s; + return z->value[b]; +} + +stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z) +{ + int b,s; + if (a->num_bits < 16) { + if (stbi__zeof(a)) { + return -1; /* report error for unexpected end of data. */ + } + stbi__fill_bits(a); + } + b = z->fast[a->code_buffer & STBI__ZFAST_MASK]; + if (b) { + s = b >> 9; + a->code_buffer >>= s; + a->num_bits -= s; + return b & 511; + } + return stbi__zhuffman_decode_slowpath(a, z); +} + +static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes +{ + char *q; + unsigned int cur, limit, old_limit; + z->zout = zout; + if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG"); + cur = (unsigned int) (z->zout - z->zout_start); + limit = old_limit = (unsigned) (z->zout_end - z->zout_start); + if (UINT_MAX - cur < (unsigned) n) return stbi__err("outofmem", "Out of memory"); + while (cur + n > limit) { + if(limit > UINT_MAX / 2) return stbi__err("outofmem", "Out of memory"); + limit *= 2; + } + q = (char *) STBI_REALLOC_SIZED(z->zout_start, old_limit, limit); + STBI_NOTUSED(old_limit); + if (q == NULL) return stbi__err("outofmem", "Out of memory"); + z->zout_start = q; + z->zout = q + cur; + z->zout_end = q + limit; + return 1; +} + +static const int stbi__zlength_base[31] = { + 3,4,5,6,7,8,9,10,11,13, + 15,17,19,23,27,31,35,43,51,59, + 67,83,99,115,131,163,195,227,258,0,0 }; + +static const int stbi__zlength_extra[31]= +{ 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; + +static const int stbi__zdist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, +257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; + +static const int stbi__zdist_extra[32] = +{ 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; + +static int stbi__parse_huffman_block(stbi__zbuf *a) +{ + char *zout = a->zout; + for(;;) { + int z = stbi__zhuffman_decode(a, &a->z_length); + if (z < 256) { + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); // error in huffman codes + if (zout >= a->zout_end) { + if (!stbi__zexpand(a, zout, 1)) return 0; + zout = a->zout; + } + *zout++ = (char) z; + } else { + stbi_uc *p; + int len,dist; + if (z == 256) { + a->zout = zout; + return 1; + } + z -= 257; + len = stbi__zlength_base[z]; + if (stbi__zlength_extra[z]) len += stbi__zreceive(a, stbi__zlength_extra[z]); + z = stbi__zhuffman_decode(a, &a->z_distance); + if (z < 0) return stbi__err("bad huffman code","Corrupt PNG"); + dist = stbi__zdist_base[z]; + if (stbi__zdist_extra[z]) dist += stbi__zreceive(a, stbi__zdist_extra[z]); + if (zout - a->zout_start < dist) return stbi__err("bad dist","Corrupt PNG"); + if (zout + len > a->zout_end) { + if (!stbi__zexpand(a, zout, len)) return 0; + zout = a->zout; + } + p = (stbi_uc *) (zout - dist); + if (dist == 1) { // run of one byte; common in images. + stbi_uc v = *p; + if (len) { do *zout++ = v; while (--len); } + } else { + if (len) { do *zout++ = *p++; while (--len); } + } + } + } +} + +static int stbi__compute_huffman_codes(stbi__zbuf *a) +{ + static const stbi_uc length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; + stbi__zhuffman z_codelength; + stbi_uc lencodes[286+32+137];//padding for maximum single op + stbi_uc codelength_sizes[19]; + int i,n; + + int hlit = stbi__zreceive(a,5) + 257; + int hdist = stbi__zreceive(a,5) + 1; + int hclen = stbi__zreceive(a,4) + 4; + int ntot = hlit + hdist; + + memset(codelength_sizes, 0, sizeof(codelength_sizes)); + for (i=0; i < hclen; ++i) { + int s = stbi__zreceive(a,3); + codelength_sizes[length_dezigzag[i]] = (stbi_uc) s; + } + if (!stbi__zbuild_huffman(&z_codelength, codelength_sizes, 19)) return 0; + + n = 0; + while (n < ntot) { + int c = stbi__zhuffman_decode(a, &z_codelength); + if (c < 0 || c >= 19) return stbi__err("bad codelengths", "Corrupt PNG"); + if (c < 16) + lencodes[n++] = (stbi_uc) c; + else { + stbi_uc fill = 0; + if (c == 16) { + c = stbi__zreceive(a,2)+3; + if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG"); + fill = lencodes[n-1]; + } else if (c == 17) { + c = stbi__zreceive(a,3)+3; + } else if (c == 18) { + c = stbi__zreceive(a,7)+11; + } else { + return stbi__err("bad codelengths", "Corrupt PNG"); + } + if (ntot - n < c) return stbi__err("bad codelengths", "Corrupt PNG"); + memset(lencodes+n, fill, c); + n += c; + } + } + if (n != ntot) return stbi__err("bad codelengths","Corrupt PNG"); + if (!stbi__zbuild_huffman(&a->z_length, lencodes, hlit)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, lencodes+hlit, hdist)) return 0; + return 1; +} + +static int stbi__parse_uncompressed_block(stbi__zbuf *a) +{ + stbi_uc header[4]; + int len,nlen,k; + if (a->num_bits & 7) + stbi__zreceive(a, a->num_bits & 7); // discard + // drain the bit-packed data into header + k = 0; + while (a->num_bits > 0) { + header[k++] = (stbi_uc) (a->code_buffer & 255); // suppress MSVC run-time check + a->code_buffer >>= 8; + a->num_bits -= 8; + } + if (a->num_bits < 0) return stbi__err("zlib corrupt","Corrupt PNG"); + // now fill header the normal way + while (k < 4) + header[k++] = stbi__zget8(a); + len = header[1] * 256 + header[0]; + nlen = header[3] * 256 + header[2]; + if (nlen != (len ^ 0xffff)) return stbi__err("zlib corrupt","Corrupt PNG"); + if (a->zbuffer + len > a->zbuffer_end) return stbi__err("read past buffer","Corrupt PNG"); + if (a->zout + len > a->zout_end) + if (!stbi__zexpand(a, a->zout, len)) return 0; + memcpy(a->zout, a->zbuffer, len); + a->zbuffer += len; + a->zout += len; + return 1; +} + +static int stbi__parse_zlib_header(stbi__zbuf *a) +{ + int cmf = stbi__zget8(a); + int cm = cmf & 15; + /* int cinfo = cmf >> 4; */ + int flg = stbi__zget8(a); + if (stbi__zeof(a)) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if ((cmf*256+flg) % 31 != 0) return stbi__err("bad zlib header","Corrupt PNG"); // zlib spec + if (flg & 32) return stbi__err("no preset dict","Corrupt PNG"); // preset dictionary not allowed in png + if (cm != 8) return stbi__err("bad compression","Corrupt PNG"); // DEFLATE required for png + // window = 1 << (8 + cinfo)... but who cares, we fully buffer output + return 1; +} + +static const stbi_uc stbi__zdefault_length[STBI__ZNSYMS] = +{ + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, + 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,8,8,8,8,8,8,8,8 +}; +static const stbi_uc stbi__zdefault_distance[32] = +{ + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5 +}; +/* +Init algorithm: +{ + int i; // use <= to match clearly with spec + for (i=0; i <= 143; ++i) stbi__zdefault_length[i] = 8; + for ( ; i <= 255; ++i) stbi__zdefault_length[i] = 9; + for ( ; i <= 279; ++i) stbi__zdefault_length[i] = 7; + for ( ; i <= 287; ++i) stbi__zdefault_length[i] = 8; + + for (i=0; i <= 31; ++i) stbi__zdefault_distance[i] = 5; +} +*/ + +static int stbi__parse_zlib(stbi__zbuf *a, int parse_header) +{ + int final, type; + if (parse_header) + if (!stbi__parse_zlib_header(a)) return 0; + a->num_bits = 0; + a->code_buffer = 0; + do { + final = stbi__zreceive(a,1); + type = stbi__zreceive(a,2); + if (type == 0) { + if (!stbi__parse_uncompressed_block(a)) return 0; + } else if (type == 3) { + return 0; + } else { + if (type == 1) { + // use fixed code lengths + if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , STBI__ZNSYMS)) return 0; + if (!stbi__zbuild_huffman(&a->z_distance, stbi__zdefault_distance, 32)) return 0; + } else { + if (!stbi__compute_huffman_codes(a)) return 0; + } + if (!stbi__parse_huffman_block(a)) return 0; + } + } while (!final); + return 1; +} + +static int stbi__do_zlib(stbi__zbuf *a, char *obuf, int olen, int exp, int parse_header) +{ + a->zout_start = obuf; + a->zout = obuf; + a->zout_end = obuf + olen; + a->z_expandable = exp; + + return stbi__parse_zlib(a, parse_header); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize(const char *buffer, int len, int initial_size, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, 1)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF char *stbi_zlib_decode_malloc(char const *buffer, int len, int *outlen) +{ + return stbi_zlib_decode_malloc_guesssize(buffer, len, 16384, outlen); +} + +STBIDEF char *stbi_zlib_decode_malloc_guesssize_headerflag(const char *buffer, int len, int initial_size, int *outlen, int parse_header) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(initial_size); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer + len; + if (stbi__do_zlib(&a, p, initial_size, 1, parse_header)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_buffer(char *obuffer, int olen, char const *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 1)) + return (int) (a.zout - a.zout_start); + else + return -1; +} + +STBIDEF char *stbi_zlib_decode_noheader_malloc(char const *buffer, int len, int *outlen) +{ + stbi__zbuf a; + char *p = (char *) stbi__malloc(16384); + if (p == NULL) return NULL; + a.zbuffer = (stbi_uc *) buffer; + a.zbuffer_end = (stbi_uc *) buffer+len; + if (stbi__do_zlib(&a, p, 16384, 1, 0)) { + if (outlen) *outlen = (int) (a.zout - a.zout_start); + return a.zout_start; + } else { + STBI_FREE(a.zout_start); + return NULL; + } +} + +STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const char *ibuffer, int ilen) +{ + stbi__zbuf a; + a.zbuffer = (stbi_uc *) ibuffer; + a.zbuffer_end = (stbi_uc *) ibuffer + ilen; + if (stbi__do_zlib(&a, obuffer, olen, 0, 0)) + return (int) (a.zout - a.zout_start); + else + return -1; +} +#endif + +// public domain "baseline" PNG decoder v0.10 Sean Barrett 2006-11-18 +// simple implementation +// - only 8-bit samples +// - no CRC checking +// - allocates lots of intermediate memory +// - avoids problem of streaming data between subsystems +// - avoids explicit window management +// performance +// - uses stb_zlib, a PD zlib implementation with fast huffman decoding + +#ifndef STBI_NO_PNG +typedef struct +{ + stbi__uint32 length; + stbi__uint32 type; +} stbi__pngchunk; + +static stbi__pngchunk stbi__get_chunk_header(stbi__context *s) +{ + stbi__pngchunk c; + c.length = stbi__get32be(s); + c.type = stbi__get32be(s); + return c; +} + +static int stbi__check_png_header(stbi__context *s) +{ + static const stbi_uc png_sig[8] = { 137,80,78,71,13,10,26,10 }; + int i; + for (i=0; i < 8; ++i) + if (stbi__get8(s) != png_sig[i]) return stbi__err("bad png sig","Not a PNG"); + return 1; +} + +typedef struct +{ + stbi__context *s; + stbi_uc *idata, *expanded, *out; + int depth; +} stbi__png; + + +enum { + STBI__F_none=0, + STBI__F_sub=1, + STBI__F_up=2, + STBI__F_avg=3, + STBI__F_paeth=4, + // synthetic filters used for first scanline to avoid needing a dummy row of 0s + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static stbi_uc first_row_filter[5] = +{ + STBI__F_none, + STBI__F_sub, + STBI__F_none, + STBI__F_avg_first, + STBI__F_paeth_first +}; + +static int stbi__paeth(int a, int b, int c) +{ + int p = a + b - c; + int pa = abs(p-a); + int pb = abs(p-b); + int pc = abs(p-c); + if (pa <= pb && pa <= pc) return a; + if (pb <= pc) return b; + return c; +} + +static const stbi_uc stbi__depth_scale_table[9] = { 0, 0xff, 0x55, 0, 0x11, 0,0,0, 0x01 }; + +// create the png data from post-deflated data +static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 raw_len, int out_n, stbi__uint32 x, stbi__uint32 y, int depth, int color) +{ + int bytes = (depth == 16? 2 : 1); + stbi__context *s = a->s; + stbi__uint32 i,j,stride = x*out_n*bytes; + stbi__uint32 img_len, img_width_bytes; + int k; + int img_n = s->img_n; // copy it into a local for later + + int output_bytes = out_n*bytes; + int filter_bytes = img_n*bytes; + int width = x; + + STBI_ASSERT(out_n == s->img_n || out_n == s->img_n+1); + a->out = (stbi_uc *) stbi__malloc_mad3(x, y, output_bytes, 0); // extra bytes to write off the end into + if (!a->out) return stbi__err("outofmem", "Out of memory"); + + if (!stbi__mad3sizes_valid(img_n, x, depth, 7)) return stbi__err("too large", "Corrupt PNG"); + img_width_bytes = (((img_n * x * depth) + 7) >> 3); + img_len = (img_width_bytes + 1) * y; + + // we used to check for exact match between raw_len and img_len on non-interlaced PNGs, + // but issue #276 reported a PNG in the wild that had extra data at the end (all zeros), + // so just check for raw_len < img_len always. + if (raw_len < img_len) return stbi__err("not enough pixels","Corrupt PNG"); + + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *prior; + int filter = *raw++; + + if (filter > 4) + return stbi__err("invalid filter","Corrupt PNG"); + + if (depth < 8) { + if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG"); + cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place + filter_bytes = 1; + width = img_width_bytes; + } + prior = cur - stride; // bugfix: need to compute this after 'cur +=' computation above + + // if first row, use special filter that doesn't sample previous row + if (j == 0) filter = first_row_filter[filter]; + + // handle first byte explicitly + for (k=0; k < filter_bytes; ++k) { + switch (filter) { + case STBI__F_none : cur[k] = raw[k]; break; + case STBI__F_sub : cur[k] = raw[k]; break; + case STBI__F_up : cur[k] = STBI__BYTECAST(raw[k] + prior[k]); break; + case STBI__F_avg : cur[k] = STBI__BYTECAST(raw[k] + (prior[k]>>1)); break; + case STBI__F_paeth : cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(0,prior[k],0)); break; + case STBI__F_avg_first : cur[k] = raw[k]; break; + case STBI__F_paeth_first: cur[k] = raw[k]; break; + } + } + + if (depth == 8) { + if (img_n != out_n) + cur[img_n] = 255; // first pixel + raw += img_n; + cur += out_n; + prior += out_n; + } else if (depth == 16) { + if (img_n != out_n) { + cur[filter_bytes] = 255; // first pixel top byte + cur[filter_bytes+1] = 255; // first pixel bottom byte + } + raw += filter_bytes; + cur += output_bytes; + prior += output_bytes; + } else { + raw += 1; + cur += 1; + prior += 1; + } + + // this is a little gross, so that we don't switch per-pixel or per-component + if (depth < 8 || img_n == out_n) { + int nk = (width - 1)*filter_bytes; + #define STBI__CASE(f) \ + case f: \ + for (k=0; k < nk; ++k) + switch (filter) { + // "none" filter turns into a memcpy here; make that explicit. + case STBI__F_none: memcpy(cur, raw, nk); break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k-filter_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k-filter_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],prior[k],prior[k-filter_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k-filter_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k-filter_bytes],0,0)); } break; + } + #undef STBI__CASE + raw += nk; + } else { + STBI_ASSERT(img_n+1 == out_n); + #define STBI__CASE(f) \ + case f: \ + for (i=x-1; i >= 1; --i, cur[filter_bytes]=255,raw+=filter_bytes,cur+=output_bytes,prior+=output_bytes) \ + for (k=0; k < filter_bytes; ++k) + switch (filter) { + STBI__CASE(STBI__F_none) { cur[k] = raw[k]; } break; + STBI__CASE(STBI__F_sub) { cur[k] = STBI__BYTECAST(raw[k] + cur[k- output_bytes]); } break; + STBI__CASE(STBI__F_up) { cur[k] = STBI__BYTECAST(raw[k] + prior[k]); } break; + STBI__CASE(STBI__F_avg) { cur[k] = STBI__BYTECAST(raw[k] + ((prior[k] + cur[k- output_bytes])>>1)); } break; + STBI__CASE(STBI__F_paeth) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],prior[k],prior[k- output_bytes])); } break; + STBI__CASE(STBI__F_avg_first) { cur[k] = STBI__BYTECAST(raw[k] + (cur[k- output_bytes] >> 1)); } break; + STBI__CASE(STBI__F_paeth_first) { cur[k] = STBI__BYTECAST(raw[k] + stbi__paeth(cur[k- output_bytes],0,0)); } break; + } + #undef STBI__CASE + + // the loop above sets the high byte of the pixels' alpha, but for + // 16 bit png files we also need the low byte set. we'll do that here. + if (depth == 16) { + cur = a->out + stride*j; // start at the beginning of the row again + for (i=0; i < x; ++i,cur+=output_bytes) { + cur[filter_bytes+1] = 255; + } + } + } + } + + // we make a separate pass to expand bits to pixels; for performance, + // this could run two scanlines behind the above code, so it won't + // intefere with filtering but will still be in the cache. + if (depth < 8) { + for (j=0; j < y; ++j) { + stbi_uc *cur = a->out + stride*j; + stbi_uc *in = a->out + stride*j + x*out_n - img_width_bytes; + // unpack 1/2/4-bit into a 8-bit buffer. allows us to keep the common 8-bit path optimal at minimal cost for 1/2/4-bit + // png guarante byte alignment, if width is not multiple of 8/4/2 we'll decode dummy trailing data that will be skipped in the later loop + stbi_uc scale = (color == 0) ? stbi__depth_scale_table[depth] : 1; // scale grayscale values to 0..255 range + + // note that the final byte might overshoot and write more data than desired. + // we can allocate enough data that this never writes out of memory, but it + // could also overwrite the next scanline. can it overwrite non-empty data + // on the next scanline? yes, consider 1-pixel-wide scanlines with 1-bit-per-pixel. + // so we need to explicitly clamp the final ones + + if (depth == 4) { + for (k=x*img_n; k >= 2; k-=2, ++in) { + *cur++ = scale * ((*in >> 4) ); + *cur++ = scale * ((*in ) & 0x0f); + } + if (k > 0) *cur++ = scale * ((*in >> 4) ); + } else if (depth == 2) { + for (k=x*img_n; k >= 4; k-=4, ++in) { + *cur++ = scale * ((*in >> 6) ); + *cur++ = scale * ((*in >> 4) & 0x03); + *cur++ = scale * ((*in >> 2) & 0x03); + *cur++ = scale * ((*in ) & 0x03); + } + if (k > 0) *cur++ = scale * ((*in >> 6) ); + if (k > 1) *cur++ = scale * ((*in >> 4) & 0x03); + if (k > 2) *cur++ = scale * ((*in >> 2) & 0x03); + } else if (depth == 1) { + for (k=x*img_n; k >= 8; k-=8, ++in) { + *cur++ = scale * ((*in >> 7) ); + *cur++ = scale * ((*in >> 6) & 0x01); + *cur++ = scale * ((*in >> 5) & 0x01); + *cur++ = scale * ((*in >> 4) & 0x01); + *cur++ = scale * ((*in >> 3) & 0x01); + *cur++ = scale * ((*in >> 2) & 0x01); + *cur++ = scale * ((*in >> 1) & 0x01); + *cur++ = scale * ((*in ) & 0x01); + } + if (k > 0) *cur++ = scale * ((*in >> 7) ); + if (k > 1) *cur++ = scale * ((*in >> 6) & 0x01); + if (k > 2) *cur++ = scale * ((*in >> 5) & 0x01); + if (k > 3) *cur++ = scale * ((*in >> 4) & 0x01); + if (k > 4) *cur++ = scale * ((*in >> 3) & 0x01); + if (k > 5) *cur++ = scale * ((*in >> 2) & 0x01); + if (k > 6) *cur++ = scale * ((*in >> 1) & 0x01); + } + if (img_n != out_n) { + int q; + // insert alpha = 255 + cur = a->out + stride*j; + if (img_n == 1) { + for (q=x-1; q >= 0; --q) { + cur[q*2+1] = 255; + cur[q*2+0] = cur[q]; + } + } else { + STBI_ASSERT(img_n == 3); + for (q=x-1; q >= 0; --q) { + cur[q*4+3] = 255; + cur[q*4+2] = cur[q*3+2]; + cur[q*4+1] = cur[q*3+1]; + cur[q*4+0] = cur[q*3+0]; + } + } + } + } + } else if (depth == 16) { + // force the image data from big-endian to platform-native. + // this is done in a separate pass due to the decoding relying + // on the data being untouched, but could probably be done + // per-line during decode if care is taken. + stbi_uc *cur = a->out; + stbi__uint16 *cur16 = (stbi__uint16*)cur; + + for(i=0; i < x*y*out_n; ++i,cur16++,cur+=2) { + *cur16 = (cur[0] << 8) | cur[1]; + } + } + + return 1; +} + +static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint32 image_data_len, int out_n, int depth, int color, int interlaced) +{ + int bytes = (depth == 16 ? 2 : 1); + int out_bytes = out_n * bytes; + stbi_uc *final; + int p; + if (!interlaced) + return stbi__create_png_image_raw(a, image_data, image_data_len, out_n, a->s->img_x, a->s->img_y, depth, color); + + // de-interlacing + final = (stbi_uc *) stbi__malloc_mad3(a->s->img_x, a->s->img_y, out_bytes, 0); + if (!final) return stbi__err("outofmem", "Out of memory"); + for (p=0; p < 7; ++p) { + int xorig[] = { 0,4,0,2,0,1,0 }; + int yorig[] = { 0,0,4,0,2,0,1 }; + int xspc[] = { 8,8,4,4,2,2,1 }; + int yspc[] = { 8,8,8,4,4,2,2 }; + int i,j,x,y; + // pass1_x[4] = 0, pass1_x[5] = 1, pass1_x[12] = 1 + x = (a->s->img_x - xorig[p] + xspc[p]-1) / xspc[p]; + y = (a->s->img_y - yorig[p] + yspc[p]-1) / yspc[p]; + if (x && y) { + stbi__uint32 img_len = ((((a->s->img_n * x * depth) + 7) >> 3) + 1) * y; + if (!stbi__create_png_image_raw(a, image_data, image_data_len, out_n, x, y, depth, color)) { + STBI_FREE(final); + return 0; + } + for (j=0; j < y; ++j) { + for (i=0; i < x; ++i) { + int out_y = j*yspc[p]+yorig[p]; + int out_x = i*xspc[p]+xorig[p]; + memcpy(final + out_y*a->s->img_x*out_bytes + out_x*out_bytes, + a->out + (j*x+i)*out_bytes, out_bytes); + } + } + STBI_FREE(a->out); + image_data += img_len; + image_data_len -= img_len; + } + } + a->out = final; + + return 1; +} + +static int stbi__compute_transparency(stbi__png *z, stbi_uc tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + // compute color-based transparency, assuming we've + // already got 255 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i=0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 255); + p += 2; + } + } else { + for (i=0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__compute_transparency16(stbi__png *z, stbi__uint16 tc[3], int out_n) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi__uint16 *p = (stbi__uint16*) z->out; + + // compute color-based transparency, assuming we've + // already got 65535 as the alpha value in the output + STBI_ASSERT(out_n == 2 || out_n == 4); + + if (out_n == 2) { + for (i = 0; i < pixel_count; ++i) { + p[1] = (p[0] == tc[0] ? 0 : 65535); + p += 2; + } + } else { + for (i = 0; i < pixel_count; ++i) { + if (p[0] == tc[0] && p[1] == tc[1] && p[2] == tc[2]) + p[3] = 0; + p += 4; + } + } + return 1; +} + +static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int pal_img_n) +{ + stbi__uint32 i, pixel_count = a->s->img_x * a->s->img_y; + stbi_uc *p, *temp_out, *orig = a->out; + + p = (stbi_uc *) stbi__malloc_mad2(pixel_count, pal_img_n, 0); + if (p == NULL) return stbi__err("outofmem", "Out of memory"); + + // between here and free(out) below, exitting would leak + temp_out = p; + + if (pal_img_n == 3) { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p += 3; + } + } else { + for (i=0; i < pixel_count; ++i) { + int n = orig[i]*4; + p[0] = palette[n ]; + p[1] = palette[n+1]; + p[2] = palette[n+2]; + p[3] = palette[n+3]; + p += 4; + } + } + STBI_FREE(a->out); + a->out = temp_out; + + STBI_NOTUSED(len); + + return 1; +} + +static int stbi__unpremultiply_on_load_global = 0; +static int stbi__de_iphone_flag_global = 0; + +STBIDEF void stbi_set_unpremultiply_on_load(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_global = flag_true_if_should_unpremultiply; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_global = flag_true_if_should_convert; +} + +#ifndef STBI_THREAD_LOCAL +#define stbi__unpremultiply_on_load stbi__unpremultiply_on_load_global +#define stbi__de_iphone_flag stbi__de_iphone_flag_global +#else +static STBI_THREAD_LOCAL int stbi__unpremultiply_on_load_local, stbi__unpremultiply_on_load_set; +static STBI_THREAD_LOCAL int stbi__de_iphone_flag_local, stbi__de_iphone_flag_set; + +STBIDEF void stbi__unpremultiply_on_load_thread(int flag_true_if_should_unpremultiply) +{ + stbi__unpremultiply_on_load_local = flag_true_if_should_unpremultiply; + stbi__unpremultiply_on_load_set = 1; +} + +STBIDEF void stbi_convert_iphone_png_to_rgb_thread(int flag_true_if_should_convert) +{ + stbi__de_iphone_flag_local = flag_true_if_should_convert; + stbi__de_iphone_flag_set = 1; +} + +#define stbi__unpremultiply_on_load (stbi__unpremultiply_on_load_set \ + ? stbi__unpremultiply_on_load_local \ + : stbi__unpremultiply_on_load_global) +#define stbi__de_iphone_flag (stbi__de_iphone_flag_set \ + ? stbi__de_iphone_flag_local \ + : stbi__de_iphone_flag_global) +#endif // STBI_THREAD_LOCAL + +static void stbi__de_iphone(stbi__png *z) +{ + stbi__context *s = z->s; + stbi__uint32 i, pixel_count = s->img_x * s->img_y; + stbi_uc *p = z->out; + + if (s->img_out_n == 3) { // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 3; + } + } else { + STBI_ASSERT(s->img_out_n == 4); + if (stbi__unpremultiply_on_load) { + // convert bgr to rgb and unpremultiply + for (i=0; i < pixel_count; ++i) { + stbi_uc a = p[3]; + stbi_uc t = p[0]; + if (a) { + stbi_uc half = a / 2; + p[0] = (p[2] * 255 + half) / a; + p[1] = (p[1] * 255 + half) / a; + p[2] = ( t * 255 + half) / a; + } else { + p[0] = p[2]; + p[2] = t; + } + p += 4; + } + } else { + // convert bgr to rgb + for (i=0; i < pixel_count; ++i) { + stbi_uc t = p[0]; + p[0] = p[2]; + p[2] = t; + p += 4; + } + } + } +} + +#define STBI__PNG_TYPE(a,b,c,d) (((unsigned) (a) << 24) + ((unsigned) (b) << 16) + ((unsigned) (c) << 8) + (unsigned) (d)) + +static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp) +{ + stbi_uc palette[1024], pal_img_n=0; + stbi_uc has_trans=0, tc[3]={0}; + stbi__uint16 tc16[3]; + stbi__uint32 ioff=0, idata_limit=0, i, pal_len=0; + int first=1,k,interlace=0, color=0, is_iphone=0; + stbi__context *s = z->s; + + z->expanded = NULL; + z->idata = NULL; + z->out = NULL; + + if (!stbi__check_png_header(s)) return 0; + + if (scan == STBI__SCAN_type) return 1; + + for (;;) { + stbi__pngchunk c = stbi__get_chunk_header(s); + switch (c.type) { + case STBI__PNG_TYPE('C','g','B','I'): + is_iphone = 1; + stbi__skip(s, c.length); + break; + case STBI__PNG_TYPE('I','H','D','R'): { + int comp,filter; + if (!first) return stbi__err("multiple IHDR","Corrupt PNG"); + first = 0; + if (c.length != 13) return stbi__err("bad IHDR len","Corrupt PNG"); + s->img_x = stbi__get32be(s); + s->img_y = stbi__get32be(s); + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + z->depth = stbi__get8(s); if (z->depth != 1 && z->depth != 2 && z->depth != 4 && z->depth != 8 && z->depth != 16) return stbi__err("1/2/4/8/16-bit only","PNG not supported: 1/2/4/8/16-bit only"); + color = stbi__get8(s); if (color > 6) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3 && z->depth == 16) return stbi__err("bad ctype","Corrupt PNG"); + if (color == 3) pal_img_n = 3; else if (color & 1) return stbi__err("bad ctype","Corrupt PNG"); + comp = stbi__get8(s); if (comp) return stbi__err("bad comp method","Corrupt PNG"); + filter= stbi__get8(s); if (filter) return stbi__err("bad filter method","Corrupt PNG"); + interlace = stbi__get8(s); if (interlace>1) return stbi__err("bad interlace method","Corrupt PNG"); + if (!s->img_x || !s->img_y) return stbi__err("0-pixel image","Corrupt PNG"); + if (!pal_img_n) { + s->img_n = (color & 2 ? 3 : 1) + (color & 4 ? 1 : 0); + if ((1 << 30) / s->img_x / s->img_n < s->img_y) return stbi__err("too large", "Image too large to decode"); + if (scan == STBI__SCAN_header) return 1; + } else { + // if paletted, then pal_n is our final components, and + // img_n is # components to decompress/filter. + s->img_n = 1; + if ((1 << 30) / s->img_x / 4 < s->img_y) return stbi__err("too large","Corrupt PNG"); + // if SCAN_header, have to scan to see if we have a tRNS + } + break; + } + + case STBI__PNG_TYPE('P','L','T','E'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (c.length > 256*3) return stbi__err("invalid PLTE","Corrupt PNG"); + pal_len = c.length / 3; + if (pal_len * 3 != c.length) return stbi__err("invalid PLTE","Corrupt PNG"); + for (i=0; i < pal_len; ++i) { + palette[i*4+0] = stbi__get8(s); + palette[i*4+1] = stbi__get8(s); + palette[i*4+2] = stbi__get8(s); + palette[i*4+3] = 255; + } + break; + } + + case STBI__PNG_TYPE('t','R','N','S'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (z->idata) return stbi__err("tRNS after IDAT","Corrupt PNG"); + if (pal_img_n) { + if (scan == STBI__SCAN_header) { s->img_n = 4; return 1; } + if (pal_len == 0) return stbi__err("tRNS before PLTE","Corrupt PNG"); + if (c.length > pal_len) return stbi__err("bad tRNS len","Corrupt PNG"); + pal_img_n = 4; + for (i=0; i < c.length; ++i) + palette[i*4+3] = stbi__get8(s); + } else { + if (!(s->img_n & 1)) return stbi__err("tRNS with alpha","Corrupt PNG"); + if (c.length != (stbi__uint32) s->img_n*2) return stbi__err("bad tRNS len","Corrupt PNG"); + has_trans = 1; + if (z->depth == 16) { + for (k = 0; k < s->img_n; ++k) tc16[k] = (stbi__uint16)stbi__get16be(s); // copy the values as-is + } else { + for (k = 0; k < s->img_n; ++k) tc[k] = (stbi_uc)(stbi__get16be(s) & 255) * stbi__depth_scale_table[z->depth]; // non 8-bit images will be larger + } + } + break; + } + + case STBI__PNG_TYPE('I','D','A','T'): { + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (pal_img_n && !pal_len) return stbi__err("no PLTE","Corrupt PNG"); + if (scan == STBI__SCAN_header) { s->img_n = pal_img_n; return 1; } + if ((int)(ioff + c.length) < (int)ioff) return 0; + if (ioff + c.length > idata_limit) { + stbi__uint32 idata_limit_old = idata_limit; + stbi_uc *p; + if (idata_limit == 0) idata_limit = c.length > 4096 ? c.length : 4096; + while (ioff + c.length > idata_limit) + idata_limit *= 2; + STBI_NOTUSED(idata_limit_old); + p = (stbi_uc *) STBI_REALLOC_SIZED(z->idata, idata_limit_old, idata_limit); if (p == NULL) return stbi__err("outofmem", "Out of memory"); + z->idata = p; + } + if (!stbi__getn(s, z->idata+ioff,c.length)) return stbi__err("outofdata","Corrupt PNG"); + ioff += c.length; + break; + } + + case STBI__PNG_TYPE('I','E','N','D'): { + stbi__uint32 raw_len, bpl; + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if (scan != STBI__SCAN_load) return 1; + if (z->idata == NULL) return stbi__err("no IDAT","Corrupt PNG"); + // initial guess for decoded data size to avoid unnecessary reallocs + bpl = (s->img_x * z->depth + 7) / 8; // bytes per line, per component + raw_len = bpl * s->img_y * s->img_n /* pixels */ + s->img_y /* filter mode per row */; + z->expanded = (stbi_uc *) stbi_zlib_decode_malloc_guesssize_headerflag((char *) z->idata, ioff, raw_len, (int *) &raw_len, !is_iphone); + if (z->expanded == NULL) return 0; // zlib should set error + STBI_FREE(z->idata); z->idata = NULL; + if ((req_comp == s->img_n+1 && req_comp != 3 && !pal_img_n) || has_trans) + s->img_out_n = s->img_n+1; + else + s->img_out_n = s->img_n; + if (!stbi__create_png_image(z, z->expanded, raw_len, s->img_out_n, z->depth, color, interlace)) return 0; + if (has_trans) { + if (z->depth == 16) { + if (!stbi__compute_transparency16(z, tc16, s->img_out_n)) return 0; + } else { + if (!stbi__compute_transparency(z, tc, s->img_out_n)) return 0; + } + } + if (is_iphone && stbi__de_iphone_flag && s->img_out_n > 2) + stbi__de_iphone(z); + if (pal_img_n) { + // pal_img_n == 3 or 4 + s->img_n = pal_img_n; // record the actual colors we had + s->img_out_n = pal_img_n; + if (req_comp >= 3) s->img_out_n = req_comp; + if (!stbi__expand_png_palette(z, palette, pal_len, s->img_out_n)) + return 0; + } else if (has_trans) { + // non-paletted image with tRNS -> source image has (constant) alpha + ++s->img_n; + } + STBI_FREE(z->expanded); z->expanded = NULL; + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + return 1; + } + + default: + // if critical, fail + if (first) return stbi__err("first not IHDR", "Corrupt PNG"); + if ((c.type & (1 << 29)) == 0) { + #ifndef STBI_NO_FAILURE_STRINGS + // not threadsafe + static char invalid_chunk[] = "XXXX PNG chunk not known"; + invalid_chunk[0] = STBI__BYTECAST(c.type >> 24); + invalid_chunk[1] = STBI__BYTECAST(c.type >> 16); + invalid_chunk[2] = STBI__BYTECAST(c.type >> 8); + invalid_chunk[3] = STBI__BYTECAST(c.type >> 0); + #endif + return stbi__err(invalid_chunk, "PNG not supported: unknown PNG chunk type"); + } + stbi__skip(s, c.length); + break; + } + // end of PNG chunk, read and skip CRC + stbi__get32be(s); + } +} + +static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, stbi__result_info *ri) +{ + void *result=NULL; + if (req_comp < 0 || req_comp > 4) return stbi__errpuc("bad req_comp", "Internal error"); + if (stbi__parse_png_file(p, STBI__SCAN_load, req_comp)) { + if (p->depth <= 8) + ri->bits_per_channel = 8; + else if (p->depth == 16) + ri->bits_per_channel = 16; + else + return stbi__errpuc("bad bits_per_channel", "PNG not supported: unsupported color depth"); + result = p->out; + p->out = NULL; + if (req_comp && req_comp != p->s->img_out_n) { + if (ri->bits_per_channel == 8) + result = stbi__convert_format((unsigned char *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + else + result = stbi__convert_format16((stbi__uint16 *) result, p->s->img_out_n, req_comp, p->s->img_x, p->s->img_y); + p->s->img_out_n = req_comp; + if (result == NULL) return result; + } + *x = p->s->img_x; + *y = p->s->img_y; + if (n) *n = p->s->img_n; + } + STBI_FREE(p->out); p->out = NULL; + STBI_FREE(p->expanded); p->expanded = NULL; + STBI_FREE(p->idata); p->idata = NULL; + + return result; +} + +static void *stbi__png_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi__png p; + p.s = s; + return stbi__do_png(&p, x,y,comp,req_comp, ri); +} + +static int stbi__png_test(stbi__context *s) +{ + int r; + r = stbi__check_png_header(s); + stbi__rewind(s); + return r; +} + +static int stbi__png_info_raw(stbi__png *p, int *x, int *y, int *comp) +{ + if (!stbi__parse_png_file(p, STBI__SCAN_header, 0)) { + stbi__rewind( p->s ); + return 0; + } + if (x) *x = p->s->img_x; + if (y) *y = p->s->img_y; + if (comp) *comp = p->s->img_n; + return 1; +} + +static int stbi__png_info(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__png p; + p.s = s; + return stbi__png_info_raw(&p, x, y, comp); +} + +static int stbi__png_is16(stbi__context *s) +{ + stbi__png p; + p.s = s; + if (!stbi__png_info_raw(&p, NULL, NULL, NULL)) + return 0; + if (p.depth != 16) { + stbi__rewind(p.s); + return 0; + } + return 1; +} +#endif + +// Microsoft/Windows BMP image + +#ifndef STBI_NO_BMP +static int stbi__bmp_test_raw(stbi__context *s) +{ + int r; + int sz; + if (stbi__get8(s) != 'B') return 0; + if (stbi__get8(s) != 'M') return 0; + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + stbi__get32le(s); // discard data offset + sz = stbi__get32le(s); + r = (sz == 12 || sz == 40 || sz == 56 || sz == 108 || sz == 124); + return r; +} + +static int stbi__bmp_test(stbi__context *s) +{ + int r = stbi__bmp_test_raw(s); + stbi__rewind(s); + return r; +} + + +// returns 0..31 for the highest set bit +static int stbi__high_bit(unsigned int z) +{ + int n=0; + if (z == 0) return -1; + if (z >= 0x10000) { n += 16; z >>= 16; } + if (z >= 0x00100) { n += 8; z >>= 8; } + if (z >= 0x00010) { n += 4; z >>= 4; } + if (z >= 0x00004) { n += 2; z >>= 2; } + if (z >= 0x00002) { n += 1;/* >>= 1;*/ } + return n; +} + +static int stbi__bitcount(unsigned int a) +{ + a = (a & 0x55555555) + ((a >> 1) & 0x55555555); // max 2 + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); // max 4 + a = (a + (a >> 4)) & 0x0f0f0f0f; // max 8 per 4, now 8 bits + a = (a + (a >> 8)); // max 16 per 8 bits + a = (a + (a >> 16)); // max 32 per 8 bits + return a & 0xff; +} + +// extract an arbitrarily-aligned N-bit value (N=bits) +// from v, and then make it 8-bits long and fractionally +// extend it to full full range. +static int stbi__shiftsigned(unsigned int v, int shift, int bits) +{ + static unsigned int mul_table[9] = { + 0, + 0xff/*0b11111111*/, 0x55/*0b01010101*/, 0x49/*0b01001001*/, 0x11/*0b00010001*/, + 0x21/*0b00100001*/, 0x41/*0b01000001*/, 0x81/*0b10000001*/, 0x01/*0b00000001*/, + }; + static unsigned int shift_table[9] = { + 0, 0,0,1,0,2,4,6,0, + }; + if (shift < 0) + v <<= -shift; + else + v >>= shift; + STBI_ASSERT(v < 256); + v >>= (8-bits); + STBI_ASSERT(bits >= 0 && bits <= 8); + return (int) ((unsigned) v * mul_table[bits]) >> shift_table[bits]; +} + +typedef struct +{ + int bpp, offset, hsz; + unsigned int mr,mg,mb,ma, all_a; + int extra_read; +} stbi__bmp_data; + +static int stbi__bmp_set_mask_defaults(stbi__bmp_data *info, int compress) +{ + // BI_BITFIELDS specifies masks explicitly, don't override + if (compress == 3) + return 1; + + if (compress == 0) { + if (info->bpp == 16) { + info->mr = 31u << 10; + info->mg = 31u << 5; + info->mb = 31u << 0; + } else if (info->bpp == 32) { + info->mr = 0xffu << 16; + info->mg = 0xffu << 8; + info->mb = 0xffu << 0; + info->ma = 0xffu << 24; + info->all_a = 0; // if all_a is 0 at end, then we loaded alpha channel but it was all 0 + } else { + // otherwise, use defaults, which is all-0 + info->mr = info->mg = info->mb = info->ma = 0; + } + return 1; + } + return 0; // error +} + +static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info) +{ + int hsz; + if (stbi__get8(s) != 'B' || stbi__get8(s) != 'M') return stbi__errpuc("not BMP", "Corrupt BMP"); + stbi__get32le(s); // discard filesize + stbi__get16le(s); // discard reserved + stbi__get16le(s); // discard reserved + info->offset = stbi__get32le(s); + info->hsz = hsz = stbi__get32le(s); + info->mr = info->mg = info->mb = info->ma = 0; + info->extra_read = 14; + + if (info->offset < 0) return stbi__errpuc("bad BMP", "bad BMP"); + + if (hsz != 12 && hsz != 40 && hsz != 56 && hsz != 108 && hsz != 124) return stbi__errpuc("unknown BMP", "BMP type not supported: unknown"); + if (hsz == 12) { + s->img_x = stbi__get16le(s); + s->img_y = stbi__get16le(s); + } else { + s->img_x = stbi__get32le(s); + s->img_y = stbi__get32le(s); + } + if (stbi__get16le(s) != 1) return stbi__errpuc("bad BMP", "bad BMP"); + info->bpp = stbi__get16le(s); + if (hsz != 12) { + int compress = stbi__get32le(s); + if (compress == 1 || compress == 2) return stbi__errpuc("BMP RLE", "BMP type not supported: RLE"); + if (compress >= 4) return stbi__errpuc("BMP JPEG/PNG", "BMP type not supported: unsupported compression"); // this includes PNG/JPEG modes + if (compress == 3 && info->bpp != 16 && info->bpp != 32) return stbi__errpuc("bad BMP", "bad BMP"); // bitfields requires 16 or 32 bits/pixel + stbi__get32le(s); // discard sizeof + stbi__get32le(s); // discard hres + stbi__get32le(s); // discard vres + stbi__get32le(s); // discard colorsused + stbi__get32le(s); // discard max important + if (hsz == 40 || hsz == 56) { + if (hsz == 56) { + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + stbi__get32le(s); + } + if (info->bpp == 16 || info->bpp == 32) { + if (compress == 0) { + stbi__bmp_set_mask_defaults(info, compress); + } else if (compress == 3) { + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->extra_read += 12; + // not documented, but generated by photoshop and handled by mspaint + if (info->mr == info->mg && info->mg == info->mb) { + // ?!?!? + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else + return stbi__errpuc("bad BMP", "bad BMP"); + } + } else { + // V4/V5 header + int i; + if (hsz != 108 && hsz != 124) + return stbi__errpuc("bad BMP", "bad BMP"); + info->mr = stbi__get32le(s); + info->mg = stbi__get32le(s); + info->mb = stbi__get32le(s); + info->ma = stbi__get32le(s); + if (compress != 3) // override mr/mg/mb unless in BI_BITFIELDS mode, as per docs + stbi__bmp_set_mask_defaults(info, compress); + stbi__get32le(s); // discard color space + for (i=0; i < 12; ++i) + stbi__get32le(s); // discard color space parameters + if (hsz == 124) { + stbi__get32le(s); // discard rendering intent + stbi__get32le(s); // discard offset of profile data + stbi__get32le(s); // discard size of profile data + stbi__get32le(s); // discard reserved + } + } + } + return (void *) 1; +} + + +static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + unsigned int mr=0,mg=0,mb=0,ma=0, all_a; + stbi_uc pal[256][4]; + int psize=0,i,j,width; + int flip_vertically, pad, target; + stbi__bmp_data info; + STBI_NOTUSED(ri); + + info.all_a = 255; + if (stbi__bmp_parse_header(s, &info) == NULL) + return NULL; // error code already set + + flip_vertically = ((int) s->img_y) > 0; + s->img_y = abs((int) s->img_y); + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + mr = info.mr; + mg = info.mg; + mb = info.mb; + ma = info.ma; + all_a = info.all_a; + + if (info.hsz == 12) { + if (info.bpp < 24) + psize = (info.offset - info.extra_read - 24) / 3; + } else { + if (info.bpp < 16) + psize = (info.offset - info.extra_read - info.hsz) >> 2; + } + if (psize == 0) { + if (info.offset != s->callback_already_read + (s->img_buffer - s->img_buffer_original)) { + return stbi__errpuc("bad offset", "Corrupt BMP"); + } + } + + if (info.bpp == 24 && ma == 0xff000000) + s->img_n = 3; + else + s->img_n = ma ? 4 : 3; + if (req_comp && req_comp >= 3) // we can directly decode 3 or 4 + target = req_comp; + else + target = s->img_n; // if they want monochrome, we'll post-convert + + // sanity-check size + if (!stbi__mad3sizes_valid(target, s->img_x, s->img_y, 0)) + return stbi__errpuc("too large", "Corrupt BMP"); + + out = (stbi_uc *) stbi__malloc_mad3(target, s->img_x, s->img_y, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + if (info.bpp < 16) { + int z=0; + if (psize == 0 || psize > 256) { STBI_FREE(out); return stbi__errpuc("invalid", "Corrupt BMP"); } + for (i=0; i < psize; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + if (info.hsz != 12) stbi__get8(s); + pal[i][3] = 255; + } + stbi__skip(s, info.offset - info.extra_read - info.hsz - psize * (info.hsz == 12 ? 3 : 4)); + if (info.bpp == 1) width = (s->img_x + 7) >> 3; + else if (info.bpp == 4) width = (s->img_x + 1) >> 1; + else if (info.bpp == 8) width = s->img_x; + else { STBI_FREE(out); return stbi__errpuc("bad bpp", "Corrupt BMP"); } + pad = (-width)&3; + if (info.bpp == 1) { + for (j=0; j < (int) s->img_y; ++j) { + int bit_offset = 7, v = stbi__get8(s); + for (i=0; i < (int) s->img_x; ++i) { + int color = (v>>bit_offset)&0x1; + out[z++] = pal[color][0]; + out[z++] = pal[color][1]; + out[z++] = pal[color][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + if((--bit_offset) < 0) { + bit_offset = 7; + v = stbi__get8(s); + } + } + stbi__skip(s, pad); + } + } else { + for (j=0; j < (int) s->img_y; ++j) { + for (i=0; i < (int) s->img_x; i += 2) { + int v=stbi__get8(s),v2=0; + if (info.bpp == 4) { + v2 = v & 15; + v >>= 4; + } + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + if (i+1 == (int) s->img_x) break; + v = (info.bpp == 8) ? stbi__get8(s) : v2; + out[z++] = pal[v][0]; + out[z++] = pal[v][1]; + out[z++] = pal[v][2]; + if (target == 4) out[z++] = 255; + } + stbi__skip(s, pad); + } + } + } else { + int rshift=0,gshift=0,bshift=0,ashift=0,rcount=0,gcount=0,bcount=0,acount=0; + int z = 0; + int easy=0; + stbi__skip(s, info.offset - info.extra_read - info.hsz); + if (info.bpp == 24) width = 3 * s->img_x; + else if (info.bpp == 16) width = 2*s->img_x; + else /* bpp = 32 and pad = 0 */ width=0; + pad = (-width) & 3; + if (info.bpp == 24) { + easy = 1; + } else if (info.bpp == 32) { + if (mb == 0xff && mg == 0xff00 && mr == 0x00ff0000 && ma == 0xff000000) + easy = 2; + } + if (!easy) { + if (!mr || !mg || !mb) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + // right shift amt to put high bit in position #7 + rshift = stbi__high_bit(mr)-7; rcount = stbi__bitcount(mr); + gshift = stbi__high_bit(mg)-7; gcount = stbi__bitcount(mg); + bshift = stbi__high_bit(mb)-7; bcount = stbi__bitcount(mb); + ashift = stbi__high_bit(ma)-7; acount = stbi__bitcount(ma); + if (rcount > 8 || gcount > 8 || bcount > 8 || acount > 8) { STBI_FREE(out); return stbi__errpuc("bad masks", "Corrupt BMP"); } + } + for (j=0; j < (int) s->img_y; ++j) { + if (easy) { + for (i=0; i < (int) s->img_x; ++i) { + unsigned char a; + out[z+2] = stbi__get8(s); + out[z+1] = stbi__get8(s); + out[z+0] = stbi__get8(s); + z += 3; + a = (easy == 2 ? stbi__get8(s) : 255); + all_a |= a; + if (target == 4) out[z++] = a; + } + } else { + int bpp = info.bpp; + for (i=0; i < (int) s->img_x; ++i) { + stbi__uint32 v = (bpp == 16 ? (stbi__uint32) stbi__get16le(s) : stbi__get32le(s)); + unsigned int a; + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mr, rshift, rcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mg, gshift, gcount)); + out[z++] = STBI__BYTECAST(stbi__shiftsigned(v & mb, bshift, bcount)); + a = (ma ? stbi__shiftsigned(v & ma, ashift, acount) : 255); + all_a |= a; + if (target == 4) out[z++] = STBI__BYTECAST(a); + } + } + stbi__skip(s, pad); + } + } + + // if alpha channel is all 0s, replace with all 255s + if (target == 4 && all_a == 0) + for (i=4*s->img_x*s->img_y-1; i >= 0; i -= 4) + out[i] = 255; + + if (flip_vertically) { + stbi_uc t; + for (j=0; j < (int) s->img_y>>1; ++j) { + stbi_uc *p1 = out + j *s->img_x*target; + stbi_uc *p2 = out + (s->img_y-1-j)*s->img_x*target; + for (i=0; i < (int) s->img_x*target; ++i) { + t = p1[i]; p1[i] = p2[i]; p2[i] = t; + } + } + } + + if (req_comp && req_comp != target) { + out = stbi__convert_format(out, target, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + return out; +} +#endif + +// Targa Truevision - TGA +// by Jonathan Dummer +#ifndef STBI_NO_TGA +// returns STBI_rgb or whatever, 0 on error +static int stbi__tga_get_comp(int bits_per_pixel, int is_grey, int* is_rgb16) +{ + // only RGB or RGBA (incl. 16bit) or grey allowed + if (is_rgb16) *is_rgb16 = 0; + switch(bits_per_pixel) { + case 8: return STBI_grey; + case 16: if(is_grey) return STBI_grey_alpha; + // fallthrough + case 15: if(is_rgb16) *is_rgb16 = 1; + return STBI_rgb; + case 24: // fallthrough + case 32: return bits_per_pixel/8; + default: return 0; + } +} + +static int stbi__tga_info(stbi__context *s, int *x, int *y, int *comp) +{ + int tga_w, tga_h, tga_comp, tga_image_type, tga_bits_per_pixel, tga_colormap_bpp; + int sz, tga_colormap_type; + stbi__get8(s); // discard Offset + tga_colormap_type = stbi__get8(s); // colormap type + if( tga_colormap_type > 1 ) { + stbi__rewind(s); + return 0; // only RGB or indexed allowed + } + tga_image_type = stbi__get8(s); // image type + if ( tga_colormap_type == 1 ) { // colormapped (paletted) image + if (tga_image_type != 1 && tga_image_type != 9) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) { + stbi__rewind(s); + return 0; + } + stbi__skip(s,4); // skip image x and y origin + tga_colormap_bpp = sz; + } else { // "normal" image w/o colormap - only RGB or grey allowed, +/- RLE + if ( (tga_image_type != 2) && (tga_image_type != 3) && (tga_image_type != 10) && (tga_image_type != 11) ) { + stbi__rewind(s); + return 0; // only RGB or grey allowed, +/- RLE + } + stbi__skip(s,9); // skip colormap specification and image x/y origin + tga_colormap_bpp = 0; + } + tga_w = stbi__get16le(s); + if( tga_w < 1 ) { + stbi__rewind(s); + return 0; // test width + } + tga_h = stbi__get16le(s); + if( tga_h < 1 ) { + stbi__rewind(s); + return 0; // test height + } + tga_bits_per_pixel = stbi__get8(s); // bits per pixel + stbi__get8(s); // ignore alpha bits + if (tga_colormap_bpp != 0) { + if((tga_bits_per_pixel != 8) && (tga_bits_per_pixel != 16)) { + // when using a colormap, tga_bits_per_pixel is the size of the indexes + // I don't think anything but 8 or 16bit indexes makes sense + stbi__rewind(s); + return 0; + } + tga_comp = stbi__tga_get_comp(tga_colormap_bpp, 0, NULL); + } else { + tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3) || (tga_image_type == 11), NULL); + } + if(!tga_comp) { + stbi__rewind(s); + return 0; + } + if (x) *x = tga_w; + if (y) *y = tga_h; + if (comp) *comp = tga_comp; + return 1; // seems to have passed everything +} + +static int stbi__tga_test(stbi__context *s) +{ + int res = 0; + int sz, tga_color_type; + stbi__get8(s); // discard Offset + tga_color_type = stbi__get8(s); // color type + if ( tga_color_type > 1 ) goto errorEnd; // only RGB or indexed allowed + sz = stbi__get8(s); // image type + if ( tga_color_type == 1 ) { // colormapped (paletted) image + if (sz != 1 && sz != 9) goto errorEnd; // colortype 1 demands image type 1 or 9 + stbi__skip(s,4); // skip index of first colormap entry and number of entries + sz = stbi__get8(s); // check bits per palette color entry + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + stbi__skip(s,4); // skip image x and y origin + } else { // "normal" image w/o colormap + if ( (sz != 2) && (sz != 3) && (sz != 10) && (sz != 11) ) goto errorEnd; // only RGB or grey allowed, +/- RLE + stbi__skip(s,9); // skip colormap specification and image x/y origin + } + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test width + if ( stbi__get16le(s) < 1 ) goto errorEnd; // test height + sz = stbi__get8(s); // bits per pixel + if ( (tga_color_type == 1) && (sz != 8) && (sz != 16) ) goto errorEnd; // for colormapped images, bpp is size of an index + if ( (sz != 8) && (sz != 15) && (sz != 16) && (sz != 24) && (sz != 32) ) goto errorEnd; + + res = 1; // if we got this far, everything's good and we can return 1 instead of 0 + +errorEnd: + stbi__rewind(s); + return res; +} + +// read 16bit value and convert to 24bit RGB +static void stbi__tga_read_rgb16(stbi__context *s, stbi_uc* out) +{ + stbi__uint16 px = (stbi__uint16)stbi__get16le(s); + stbi__uint16 fiveBitMask = 31; + // we have 3 channels with 5bits each + int r = (px >> 10) & fiveBitMask; + int g = (px >> 5) & fiveBitMask; + int b = px & fiveBitMask; + // Note that this saves the data in RGB(A) order, so it doesn't need to be swapped later + out[0] = (stbi_uc)((r * 255)/31); + out[1] = (stbi_uc)((g * 255)/31); + out[2] = (stbi_uc)((b * 255)/31); + + // some people claim that the most significant bit might be used for alpha + // (possibly if an alpha-bit is set in the "image descriptor byte") + // but that only made 16bit test images completely translucent.. + // so let's treat all 15 and 16bit TGAs as RGB with no alpha. +} + +static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + // read in the TGA header stuff + int tga_offset = stbi__get8(s); + int tga_indexed = stbi__get8(s); + int tga_image_type = stbi__get8(s); + int tga_is_RLE = 0; + int tga_palette_start = stbi__get16le(s); + int tga_palette_len = stbi__get16le(s); + int tga_palette_bits = stbi__get8(s); + int tga_x_origin = stbi__get16le(s); + int tga_y_origin = stbi__get16le(s); + int tga_width = stbi__get16le(s); + int tga_height = stbi__get16le(s); + int tga_bits_per_pixel = stbi__get8(s); + int tga_comp, tga_rgb16=0; + int tga_inverted = stbi__get8(s); + // int tga_alpha_bits = tga_inverted & 15; // the 4 lowest bits - unused (useless?) + // image data + unsigned char *tga_data; + unsigned char *tga_palette = NULL; + int i, j; + unsigned char raw_data[4] = {0}; + int RLE_count = 0; + int RLE_repeating = 0; + int read_next_pixel = 1; + STBI_NOTUSED(ri); + STBI_NOTUSED(tga_x_origin); // @TODO + STBI_NOTUSED(tga_y_origin); // @TODO + + if (tga_height > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (tga_width > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // do a tiny bit of precessing + if ( tga_image_type >= 8 ) + { + tga_image_type -= 8; + tga_is_RLE = 1; + } + tga_inverted = 1 - ((tga_inverted >> 5) & 1); + + // If I'm paletted, then I'll use the number of bits from the palette + if ( tga_indexed ) tga_comp = stbi__tga_get_comp(tga_palette_bits, 0, &tga_rgb16); + else tga_comp = stbi__tga_get_comp(tga_bits_per_pixel, (tga_image_type == 3), &tga_rgb16); + + if(!tga_comp) // shouldn't really happen, stbi__tga_test() should have ensured basic consistency + return stbi__errpuc("bad format", "Can't find out TGA pixelformat"); + + // tga info + *x = tga_width; + *y = tga_height; + if (comp) *comp = tga_comp; + + if (!stbi__mad3sizes_valid(tga_width, tga_height, tga_comp, 0)) + return stbi__errpuc("too large", "Corrupt TGA"); + + tga_data = (unsigned char*)stbi__malloc_mad3(tga_width, tga_height, tga_comp, 0); + if (!tga_data) return stbi__errpuc("outofmem", "Out of memory"); + + // skip to the data's starting position (offset usually = 0) + stbi__skip(s, tga_offset ); + + if ( !tga_indexed && !tga_is_RLE && !tga_rgb16 ) { + for (i=0; i < tga_height; ++i) { + int row = tga_inverted ? tga_height -i - 1 : i; + stbi_uc *tga_row = tga_data + row*tga_width*tga_comp; + stbi__getn(s, tga_row, tga_width * tga_comp); + } + } else { + // do I need to load a palette? + if ( tga_indexed) + { + if (tga_palette_len == 0) { /* you have to have at least one entry! */ + STBI_FREE(tga_data); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + + // any data to skip? (offset usually = 0) + stbi__skip(s, tga_palette_start ); + // load the palette + tga_palette = (unsigned char*)stbi__malloc_mad2(tga_palette_len, tga_comp, 0); + if (!tga_palette) { + STBI_FREE(tga_data); + return stbi__errpuc("outofmem", "Out of memory"); + } + if (tga_rgb16) { + stbi_uc *pal_entry = tga_palette; + STBI_ASSERT(tga_comp == STBI_rgb); + for (i=0; i < tga_palette_len; ++i) { + stbi__tga_read_rgb16(s, pal_entry); + pal_entry += tga_comp; + } + } else if (!stbi__getn(s, tga_palette, tga_palette_len * tga_comp)) { + STBI_FREE(tga_data); + STBI_FREE(tga_palette); + return stbi__errpuc("bad palette", "Corrupt TGA"); + } + } + // load the data + for (i=0; i < tga_width * tga_height; ++i) + { + // if I'm in RLE mode, do I need to get a RLE stbi__pngchunk? + if ( tga_is_RLE ) + { + if ( RLE_count == 0 ) + { + // yep, get the next byte as a RLE command + int RLE_cmd = stbi__get8(s); + RLE_count = 1 + (RLE_cmd & 127); + RLE_repeating = RLE_cmd >> 7; + read_next_pixel = 1; + } else if ( !RLE_repeating ) + { + read_next_pixel = 1; + } + } else + { + read_next_pixel = 1; + } + // OK, if I need to read a pixel, do it now + if ( read_next_pixel ) + { + // load however much data we did have + if ( tga_indexed ) + { + // read in index, then perform the lookup + int pal_idx = (tga_bits_per_pixel == 8) ? stbi__get8(s) : stbi__get16le(s); + if ( pal_idx >= tga_palette_len ) { + // invalid index + pal_idx = 0; + } + pal_idx *= tga_comp; + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = tga_palette[pal_idx+j]; + } + } else if(tga_rgb16) { + STBI_ASSERT(tga_comp == STBI_rgb); + stbi__tga_read_rgb16(s, raw_data); + } else { + // read in the data raw + for (j = 0; j < tga_comp; ++j) { + raw_data[j] = stbi__get8(s); + } + } + // clear the reading flag for the next pixel + read_next_pixel = 0; + } // end of reading a pixel + + // copy data + for (j = 0; j < tga_comp; ++j) + tga_data[i*tga_comp+j] = raw_data[j]; + + // in case we're in RLE mode, keep counting down + --RLE_count; + } + // do I need to invert the image? + if ( tga_inverted ) + { + for (j = 0; j*2 < tga_height; ++j) + { + int index1 = j * tga_width * tga_comp; + int index2 = (tga_height - 1 - j) * tga_width * tga_comp; + for (i = tga_width * tga_comp; i > 0; --i) + { + unsigned char temp = tga_data[index1]; + tga_data[index1] = tga_data[index2]; + tga_data[index2] = temp; + ++index1; + ++index2; + } + } + } + // clear my palette, if I had one + if ( tga_palette != NULL ) + { + STBI_FREE( tga_palette ); + } + } + + // swap RGB - if the source data was RGB16, it already is in the right order + if (tga_comp >= 3 && !tga_rgb16) + { + unsigned char* tga_pixel = tga_data; + for (i=0; i < tga_width * tga_height; ++i) + { + unsigned char temp = tga_pixel[0]; + tga_pixel[0] = tga_pixel[2]; + tga_pixel[2] = temp; + tga_pixel += tga_comp; + } + } + + // convert to target component count + if (req_comp && req_comp != tga_comp) + tga_data = stbi__convert_format(tga_data, tga_comp, req_comp, tga_width, tga_height); + + // the things I do to get rid of an error message, and yet keep + // Microsoft's C compilers happy... [8^( + tga_palette_start = tga_palette_len = tga_palette_bits = + tga_x_origin = tga_y_origin = 0; + STBI_NOTUSED(tga_palette_start); + // OK, done + return tga_data; +} +#endif + +// ************************************************************************************************* +// Photoshop PSD loader -- PD by Thatcher Ulrich, integration by Nicolas Schulz, tweaked by STB + +#ifndef STBI_NO_PSD +static int stbi__psd_test(stbi__context *s) +{ + int r = (stbi__get32be(s) == 0x38425053); + stbi__rewind(s); + return r; +} + +static int stbi__psd_decode_rle(stbi__context *s, stbi_uc *p, int pixelCount) +{ + int count, nleft, len; + + count = 0; + while ((nleft = pixelCount - count) > 0) { + len = stbi__get8(s); + if (len == 128) { + // No-op. + } else if (len < 128) { + // Copy next len+1 bytes literally. + len++; + if (len > nleft) return 0; // corrupt data + count += len; + while (len) { + *p = stbi__get8(s); + p += 4; + len--; + } + } else if (len > 128) { + stbi_uc val; + // Next -len+1 bytes in the dest are replicated from next source byte. + // (Interpret len as a negative 8-bit int.) + len = 257 - len; + if (len > nleft) return 0; // corrupt data + val = stbi__get8(s); + count += len; + while (len) { + *p = val; + p += 4; + len--; + } + } + } + + return 1; +} + +static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri, int bpc) +{ + int pixelCount; + int channelCount, compression; + int channel, i; + int bitdepth; + int w,h; + stbi_uc *out; + STBI_NOTUSED(ri); + + // Check identifier + if (stbi__get32be(s) != 0x38425053) // "8BPS" + return stbi__errpuc("not PSD", "Corrupt PSD image"); + + // Check file type version. + if (stbi__get16be(s) != 1) + return stbi__errpuc("wrong version", "Unsupported version of PSD image"); + + // Skip 6 reserved bytes. + stbi__skip(s, 6 ); + + // Read the number of channels (R, G, B, A, etc). + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) + return stbi__errpuc("wrong channel count", "Unsupported number of channels in PSD image"); + + // Read the rows and columns of the image. + h = stbi__get32be(s); + w = stbi__get32be(s); + + if (h > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (w > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + // Make sure the depth is 8 bits. + bitdepth = stbi__get16be(s); + if (bitdepth != 8 && bitdepth != 16) + return stbi__errpuc("unsupported bit depth", "PSD bit depth is not 8 or 16 bit"); + + // Make sure the color mode is RGB. + // Valid options are: + // 0: Bitmap + // 1: Grayscale + // 2: Indexed color + // 3: RGB color + // 4: CMYK color + // 7: Multichannel + // 8: Duotone + // 9: Lab color + if (stbi__get16be(s) != 3) + return stbi__errpuc("wrong color format", "PSD is not in RGB color format"); + + // Skip the Mode Data. (It's the palette for indexed color; other info for other modes.) + stbi__skip(s,stbi__get32be(s) ); + + // Skip the image resources. (resolution, pen tool paths, etc) + stbi__skip(s, stbi__get32be(s) ); + + // Skip the reserved data. + stbi__skip(s, stbi__get32be(s) ); + + // Find out if the data is compressed. + // Known values: + // 0: no compression + // 1: RLE compressed + compression = stbi__get16be(s); + if (compression > 1) + return stbi__errpuc("bad compression", "PSD has an unknown compression format"); + + // Check size + if (!stbi__mad3sizes_valid(4, w, h, 0)) + return stbi__errpuc("too large", "Corrupt PSD"); + + // Create the destination image. + + if (!compression && bitdepth == 16 && bpc == 16) { + out = (stbi_uc *) stbi__malloc_mad3(8, w, h, 0); + ri->bits_per_channel = 16; + } else + out = (stbi_uc *) stbi__malloc(4 * w*h); + + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + pixelCount = w*h; + + // Initialize the data to zero. + //memset( out, 0, pixelCount * 4 ); + + // Finally, the image data. + if (compression) { + // RLE as used by .PSD and .TIFF + // Loop until you get the number of unpacked bytes you are expecting: + // Read the next source byte into n. + // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally. + // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times. + // Else if n is 128, noop. + // Endloop + + // The RLE-compressed data is preceded by a 2-byte data count for each row in the data, + // which we're going to just skip. + stbi__skip(s, h * channelCount * 2 ); + + // Read the RLE data by channel. + for (channel = 0; channel < 4; channel++) { + stbi_uc *p; + + p = out+channel; + if (channel >= channelCount) { + // Fill this channel with default data. + for (i = 0; i < pixelCount; i++, p += 4) + *p = (channel == 3 ? 255 : 0); + } else { + // Read the RLE data. + if (!stbi__psd_decode_rle(s, p, pixelCount)) { + STBI_FREE(out); + return stbi__errpuc("corrupt", "bad RLE data"); + } + } + } + + } else { + // We're at the raw image data. It's each channel in order (Red, Green, Blue, Alpha, ...) + // where each channel consists of an 8-bit (or 16-bit) value for each pixel in the image. + + // Read the data by channel. + for (channel = 0; channel < 4; channel++) { + if (channel >= channelCount) { + // Fill this channel with default data. + if (bitdepth == 16 && bpc == 16) { + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + stbi__uint16 val = channel == 3 ? 65535 : 0; + for (i = 0; i < pixelCount; i++, q += 4) + *q = val; + } else { + stbi_uc *p = out+channel; + stbi_uc val = channel == 3 ? 255 : 0; + for (i = 0; i < pixelCount; i++, p += 4) + *p = val; + } + } else { + if (ri->bits_per_channel == 16) { // output bpc + stbi__uint16 *q = ((stbi__uint16 *) out) + channel; + for (i = 0; i < pixelCount; i++, q += 4) + *q = (stbi__uint16) stbi__get16be(s); + } else { + stbi_uc *p = out+channel; + if (bitdepth == 16) { // input bpc + for (i = 0; i < pixelCount; i++, p += 4) + *p = (stbi_uc) (stbi__get16be(s) >> 8); + } else { + for (i = 0; i < pixelCount; i++, p += 4) + *p = stbi__get8(s); + } + } + } + } + } + + // remove weird white matte from PSD + if (channelCount >= 4) { + if (ri->bits_per_channel == 16) { + for (i=0; i < w*h; ++i) { + stbi__uint16 *pixel = (stbi__uint16 *) out + 4*i; + if (pixel[3] != 0 && pixel[3] != 65535) { + float a = pixel[3] / 65535.0f; + float ra = 1.0f / a; + float inv_a = 65535.0f * (1 - ra); + pixel[0] = (stbi__uint16) (pixel[0]*ra + inv_a); + pixel[1] = (stbi__uint16) (pixel[1]*ra + inv_a); + pixel[2] = (stbi__uint16) (pixel[2]*ra + inv_a); + } + } + } else { + for (i=0; i < w*h; ++i) { + unsigned char *pixel = out + 4*i; + if (pixel[3] != 0 && pixel[3] != 255) { + float a = pixel[3] / 255.0f; + float ra = 1.0f / a; + float inv_a = 255.0f * (1 - ra); + pixel[0] = (unsigned char) (pixel[0]*ra + inv_a); + pixel[1] = (unsigned char) (pixel[1]*ra + inv_a); + pixel[2] = (unsigned char) (pixel[2]*ra + inv_a); + } + } + } + } + + // convert to desired output format + if (req_comp && req_comp != 4) { + if (ri->bits_per_channel == 16) + out = (stbi_uc *) stbi__convert_format16((stbi__uint16 *) out, 4, req_comp, w, h); + else + out = stbi__convert_format(out, 4, req_comp, w, h); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + + if (comp) *comp = 4; + *y = h; + *x = w; + + return out; +} +#endif + +// ************************************************************************************************* +// Softimage PIC loader +// by Tom Seddon +// +// See http://softimage.wiki.softimage.com/index.php/INFO:_PIC_file_format +// See http://ozviz.wasp.uwa.edu.au/~pbourke/dataformats/softimagepic/ + +#ifndef STBI_NO_PIC +static int stbi__pic_is4(stbi__context *s,const char *str) +{ + int i; + for (i=0; i<4; ++i) + if (stbi__get8(s) != (stbi_uc)str[i]) + return 0; + + return 1; +} + +static int stbi__pic_test_core(stbi__context *s) +{ + int i; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) + return 0; + + for(i=0;i<84;++i) + stbi__get8(s); + + if (!stbi__pic_is4(s,"PICT")) + return 0; + + return 1; +} + +typedef struct +{ + stbi_uc size,type,channel; +} stbi__pic_packet; + +static stbi_uc *stbi__readval(stbi__context *s, int channel, stbi_uc *dest) +{ + int mask=0x80, i; + + for (i=0; i<4; ++i, mask>>=1) { + if (channel & mask) { + if (stbi__at_eof(s)) return stbi__errpuc("bad file","PIC file too short"); + dest[i]=stbi__get8(s); + } + } + + return dest; +} + +static void stbi__copyval(int channel,stbi_uc *dest,const stbi_uc *src) +{ + int mask=0x80,i; + + for (i=0;i<4; ++i, mask>>=1) + if (channel&mask) + dest[i]=src[i]; +} + +static stbi_uc *stbi__pic_load_core(stbi__context *s,int width,int height,int *comp, stbi_uc *result) +{ + int act_comp=0,num_packets=0,y,chained; + stbi__pic_packet packets[10]; + + // this will (should...) cater for even some bizarre stuff like having data + // for the same channel in multiple packets. + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return stbi__errpuc("bad format","too many packets"); + + packet = &packets[num_packets++]; + + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + + act_comp |= packet->channel; + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (reading packets)"); + if (packet->size != 8) return stbi__errpuc("bad format","packet isn't 8bpp"); + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); // has alpha channel? + + for(y=0; ytype) { + default: + return stbi__errpuc("bad format","packet has bad compression type"); + + case 0: {//uncompressed + int x; + + for(x=0;xchannel,dest)) + return 0; + break; + } + + case 1://Pure RLE + { + int left=width, i; + + while (left>0) { + stbi_uc count,value[4]; + + count=stbi__get8(s); + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pure read count)"); + + if (count > left) + count = (stbi_uc) left; + + if (!stbi__readval(s,packet->channel,value)) return 0; + + for(i=0; ichannel,dest,value); + left -= count; + } + } + break; + + case 2: {//Mixed RLE + int left=width; + while (left>0) { + int count = stbi__get8(s), i; + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (mixed read count)"); + + if (count >= 128) { // Repeated + stbi_uc value[4]; + + if (count==128) + count = stbi__get16be(s); + else + count -= 127; + if (count > left) + return stbi__errpuc("bad file","scanline overrun"); + + if (!stbi__readval(s,packet->channel,value)) + return 0; + + for(i=0;ichannel,dest,value); + } else { // Raw + ++count; + if (count>left) return stbi__errpuc("bad file","scanline overrun"); + + for(i=0;ichannel,dest)) + return 0; + } + left-=count; + } + break; + } + } + } + } + + return result; +} + +static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_comp, stbi__result_info *ri) +{ + stbi_uc *result; + int i, x,y, internal_comp; + STBI_NOTUSED(ri); + + if (!comp) comp = &internal_comp; + + for (i=0; i<92; ++i) + stbi__get8(s); + + x = stbi__get16be(s); + y = stbi__get16be(s); + + if (y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + if (stbi__at_eof(s)) return stbi__errpuc("bad file","file too short (pic header)"); + if (!stbi__mad3sizes_valid(x, y, 4, 0)) return stbi__errpuc("too large", "PIC image too large to decode"); + + stbi__get32be(s); //skip `ratio' + stbi__get16be(s); //skip `fields' + stbi__get16be(s); //skip `pad' + + // intermediate buffer is RGBA + result = (stbi_uc *) stbi__malloc_mad3(x, y, 4, 0); + if (!result) return stbi__errpuc("outofmem", "Out of memory"); + memset(result, 0xff, x*y*4); + + if (!stbi__pic_load_core(s,x,y,comp, result)) { + STBI_FREE(result); + result=0; + } + *px = x; + *py = y; + if (req_comp == 0) req_comp = *comp; + result=stbi__convert_format(result,4,req_comp,x,y); + + return result; +} + +static int stbi__pic_test(stbi__context *s) +{ + int r = stbi__pic_test_core(s); + stbi__rewind(s); + return r; +} +#endif + +// ************************************************************************************************* +// GIF loader -- public domain by Jean-Marc Lienher -- simplified/shrunk by stb + +#ifndef STBI_NO_GIF +typedef struct +{ + stbi__int16 prefix; + stbi_uc first; + stbi_uc suffix; +} stbi__gif_lzw; + +typedef struct +{ + int w,h; + stbi_uc *out; // output buffer (always 4 components) + stbi_uc *background; // The current "background" as far as a gif is concerned + stbi_uc *history; + int flags, bgindex, ratio, transparent, eflags; + stbi_uc pal[256][4]; + stbi_uc lpal[256][4]; + stbi__gif_lzw codes[8192]; + stbi_uc *color_table; + int parse, step; + int lflags; + int start_x, start_y; + int max_x, max_y; + int cur_x, cur_y; + int line_size; + int delay; +} stbi__gif; + +static int stbi__gif_test_raw(stbi__context *s) +{ + int sz; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') return 0; + sz = stbi__get8(s); + if (sz != '9' && sz != '7') return 0; + if (stbi__get8(s) != 'a') return 0; + return 1; +} + +static int stbi__gif_test(stbi__context *s) +{ + int r = stbi__gif_test_raw(s); + stbi__rewind(s); + return r; +} + +static void stbi__gif_parse_colortable(stbi__context *s, stbi_uc pal[256][4], int num_entries, int transp) +{ + int i; + for (i=0; i < num_entries; ++i) { + pal[i][2] = stbi__get8(s); + pal[i][1] = stbi__get8(s); + pal[i][0] = stbi__get8(s); + pal[i][3] = transp == i ? 0 : 255; + } +} + +static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_info) +{ + stbi_uc version; + if (stbi__get8(s) != 'G' || stbi__get8(s) != 'I' || stbi__get8(s) != 'F' || stbi__get8(s) != '8') + return stbi__err("not GIF", "Corrupt GIF"); + + version = stbi__get8(s); + if (version != '7' && version != '9') return stbi__err("not GIF", "Corrupt GIF"); + if (stbi__get8(s) != 'a') return stbi__err("not GIF", "Corrupt GIF"); + + stbi__g_failure_reason = ""; + g->w = stbi__get16le(s); + g->h = stbi__get16le(s); + g->flags = stbi__get8(s); + g->bgindex = stbi__get8(s); + g->ratio = stbi__get8(s); + g->transparent = -1; + + if (g->w > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + if (g->h > STBI_MAX_DIMENSIONS) return stbi__err("too large","Very large image (corrupt?)"); + + if (comp != 0) *comp = 4; // can't actually tell whether it's 3 or 4 until we parse the comments + + if (is_info) return 1; + + if (g->flags & 0x80) + stbi__gif_parse_colortable(s,g->pal, 2 << (g->flags & 7), -1); + + return 1; +} + +static int stbi__gif_info_raw(stbi__context *s, int *x, int *y, int *comp) +{ + stbi__gif* g = (stbi__gif*) stbi__malloc(sizeof(stbi__gif)); + if (!g) return stbi__err("outofmem", "Out of memory"); + if (!stbi__gif_header(s, g, comp, 1)) { + STBI_FREE(g); + stbi__rewind( s ); + return 0; + } + if (x) *x = g->w; + if (y) *y = g->h; + STBI_FREE(g); + return 1; +} + +static void stbi__out_gif_code(stbi__gif *g, stbi__uint16 code) +{ + stbi_uc *p, *c; + int idx; + + // recurse to decode the prefixes, since the linked-list is backwards, + // and working backwards through an interleaved image would be nasty + if (g->codes[code].prefix >= 0) + stbi__out_gif_code(g, g->codes[code].prefix); + + if (g->cur_y >= g->max_y) return; + + idx = g->cur_x + g->cur_y; + p = &g->out[idx]; + g->history[idx / 4] = 1; + + c = &g->color_table[g->codes[code].suffix * 4]; + if (c[3] > 128) { // don't render transparent pixels; + p[0] = c[2]; + p[1] = c[1]; + p[2] = c[0]; + p[3] = c[3]; + } + g->cur_x += 4; + + if (g->cur_x >= g->max_x) { + g->cur_x = g->start_x; + g->cur_y += g->step; + + while (g->cur_y >= g->max_y && g->parse > 0) { + g->step = (1 << g->parse) * g->line_size; + g->cur_y = g->start_y + (g->step >> 1); + --g->parse; + } + } +} + +static stbi_uc *stbi__process_gif_raster(stbi__context *s, stbi__gif *g) +{ + stbi_uc lzw_cs; + stbi__int32 len, init_code; + stbi__uint32 first; + stbi__int32 codesize, codemask, avail, oldcode, bits, valid_bits, clear; + stbi__gif_lzw *p; + + lzw_cs = stbi__get8(s); + if (lzw_cs > 12) return NULL; + clear = 1 << lzw_cs; + first = 1; + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + bits = 0; + valid_bits = 0; + for (init_code = 0; init_code < clear; init_code++) { + g->codes[init_code].prefix = -1; + g->codes[init_code].first = (stbi_uc) init_code; + g->codes[init_code].suffix = (stbi_uc) init_code; + } + + // support no starting clear code + avail = clear+2; + oldcode = -1; + + len = 0; + for(;;) { + if (valid_bits < codesize) { + if (len == 0) { + len = stbi__get8(s); // start new block + if (len == 0) + return g->out; + } + --len; + bits |= (stbi__int32) stbi__get8(s) << valid_bits; + valid_bits += 8; + } else { + stbi__int32 code = bits & codemask; + bits >>= codesize; + valid_bits -= codesize; + // @OPTIMIZE: is there some way we can accelerate the non-clear path? + if (code == clear) { // clear code + codesize = lzw_cs + 1; + codemask = (1 << codesize) - 1; + avail = clear + 2; + oldcode = -1; + first = 0; + } else if (code == clear + 1) { // end of stream code + stbi__skip(s, len); + while ((len = stbi__get8(s)) > 0) + stbi__skip(s,len); + return g->out; + } else if (code <= avail) { + if (first) { + return stbi__errpuc("no clear code", "Corrupt GIF"); + } + + if (oldcode >= 0) { + p = &g->codes[avail++]; + if (avail > 8192) { + return stbi__errpuc("too many codes", "Corrupt GIF"); + } + + p->prefix = (stbi__int16) oldcode; + p->first = g->codes[oldcode].first; + p->suffix = (code == avail) ? p->first : g->codes[code].first; + } else if (code == avail) + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + + stbi__out_gif_code(g, (stbi__uint16) code); + + if ((avail & codemask) == 0 && avail <= 0x0FFF) { + codesize++; + codemask = (1 << codesize) - 1; + } + + oldcode = code; + } else { + return stbi__errpuc("illegal code in raster", "Corrupt GIF"); + } + } + } +} + +// this function is designed to support animated gifs, although stb_image doesn't support it +// two back is the image from two frames ago, used for a very specific disposal format +static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, int req_comp, stbi_uc *two_back) +{ + int dispose; + int first_frame; + int pi; + int pcount; + STBI_NOTUSED(req_comp); + + // on first frame, any non-written pixels get the background colour (non-transparent) + first_frame = 0; + if (g->out == 0) { + if (!stbi__gif_header(s, g, comp,0)) return 0; // stbi__g_failure_reason set by stbi__gif_header + if (!stbi__mad3sizes_valid(4, g->w, g->h, 0)) + return stbi__errpuc("too large", "GIF image is too large"); + pcount = g->w * g->h; + g->out = (stbi_uc *) stbi__malloc(4 * pcount); + g->background = (stbi_uc *) stbi__malloc(4 * pcount); + g->history = (stbi_uc *) stbi__malloc(pcount); + if (!g->out || !g->background || !g->history) + return stbi__errpuc("outofmem", "Out of memory"); + + // image is treated as "transparent" at the start - ie, nothing overwrites the current background; + // background colour is only used for pixels that are not rendered first frame, after that "background" + // color refers to the color that was there the previous frame. + memset(g->out, 0x00, 4 * pcount); + memset(g->background, 0x00, 4 * pcount); // state of the background (starts transparent) + memset(g->history, 0x00, pcount); // pixels that were affected previous frame + first_frame = 1; + } else { + // second frame - how do we dispose of the previous one? + dispose = (g->eflags & 0x1C) >> 2; + pcount = g->w * g->h; + + if ((dispose == 3) && (two_back == 0)) { + dispose = 2; // if I don't have an image to revert back to, default to the old background + } + + if (dispose == 3) { // use previous graphic + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &two_back[pi * 4], 4 ); + } + } + } else if (dispose == 2) { + // restore what was changed last frame to background before that frame; + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi]) { + memcpy( &g->out[pi * 4], &g->background[pi * 4], 4 ); + } + } + } else { + // This is a non-disposal case eithe way, so just + // leave the pixels as is, and they will become the new background + // 1: do not dispose + // 0: not specified. + } + + // background is what out is after the undoing of the previou frame; + memcpy( g->background, g->out, 4 * g->w * g->h ); + } + + // clear my history; + memset( g->history, 0x00, g->w * g->h ); // pixels that were affected previous frame + + for (;;) { + int tag = stbi__get8(s); + switch (tag) { + case 0x2C: /* Image Descriptor */ + { + stbi__int32 x, y, w, h; + stbi_uc *o; + + x = stbi__get16le(s); + y = stbi__get16le(s); + w = stbi__get16le(s); + h = stbi__get16le(s); + if (((x + w) > (g->w)) || ((y + h) > (g->h))) + return stbi__errpuc("bad Image Descriptor", "Corrupt GIF"); + + g->line_size = g->w * 4; + g->start_x = x * 4; + g->start_y = y * g->line_size; + g->max_x = g->start_x + w * 4; + g->max_y = g->start_y + h * g->line_size; + g->cur_x = g->start_x; + g->cur_y = g->start_y; + + // if the width of the specified rectangle is 0, that means + // we may not see *any* pixels or the image is malformed; + // to make sure this is caught, move the current y down to + // max_y (which is what out_gif_code checks). + if (w == 0) + g->cur_y = g->max_y; + + g->lflags = stbi__get8(s); + + if (g->lflags & 0x40) { + g->step = 8 * g->line_size; // first interlaced spacing + g->parse = 3; + } else { + g->step = g->line_size; + g->parse = 0; + } + + if (g->lflags & 0x80) { + stbi__gif_parse_colortable(s,g->lpal, 2 << (g->lflags & 7), g->eflags & 0x01 ? g->transparent : -1); + g->color_table = (stbi_uc *) g->lpal; + } else if (g->flags & 0x80) { + g->color_table = (stbi_uc *) g->pal; + } else + return stbi__errpuc("missing color table", "Corrupt GIF"); + + o = stbi__process_gif_raster(s, g); + if (!o) return NULL; + + // if this was the first frame, + pcount = g->w * g->h; + if (first_frame && (g->bgindex > 0)) { + // if first frame, any pixel not drawn to gets the background color + for (pi = 0; pi < pcount; ++pi) { + if (g->history[pi] == 0) { + g->pal[g->bgindex][3] = 255; // just in case it was made transparent, undo that; It will be reset next frame if need be; + memcpy( &g->out[pi * 4], &g->pal[g->bgindex], 4 ); + } + } + } + + return o; + } + + case 0x21: // Comment Extension. + { + int len; + int ext = stbi__get8(s); + if (ext == 0xF9) { // Graphic Control Extension. + len = stbi__get8(s); + if (len == 4) { + g->eflags = stbi__get8(s); + g->delay = 10 * stbi__get16le(s); // delay - 1/100th of a second, saving as 1/1000ths. + + // unset old transparent + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 255; + } + if (g->eflags & 0x01) { + g->transparent = stbi__get8(s); + if (g->transparent >= 0) { + g->pal[g->transparent][3] = 0; + } + } else { + // don't need transparent + stbi__skip(s, 1); + g->transparent = -1; + } + } else { + stbi__skip(s, len); + break; + } + } + while ((len = stbi__get8(s)) != 0) { + stbi__skip(s, len); + } + break; + } + + case 0x3B: // gif stream termination code + return (stbi_uc *) s; // using '1' causes warning on some compilers + + default: + return stbi__errpuc("unknown code", "Corrupt GIF"); + } + } +} + +static void *stbi__load_gif_main_outofmem(stbi__gif *g, stbi_uc *out, int **delays) +{ + STBI_FREE(g->out); + STBI_FREE(g->history); + STBI_FREE(g->background); + + if (out) STBI_FREE(out); + if (delays && *delays) STBI_FREE(*delays); + return stbi__errpuc("outofmem", "Out of memory"); +} + +static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp) +{ + if (stbi__gif_test(s)) { + int layers = 0; + stbi_uc *u = 0; + stbi_uc *out = 0; + stbi_uc *two_back = 0; + stbi__gif g; + int stride; + int out_size = 0; + int delays_size = 0; + + STBI_NOTUSED(out_size); + STBI_NOTUSED(delays_size); + + memset(&g, 0, sizeof(g)); + if (delays) { + *delays = 0; + } + + do { + u = stbi__gif_load_next(s, &g, comp, req_comp, two_back); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + + if (u) { + *x = g.w; + *y = g.h; + ++layers; + stride = g.w * g.h * 4; + + if (out) { + void *tmp = (stbi_uc*) STBI_REALLOC_SIZED( out, out_size, layers * stride ); + if (!tmp) + return stbi__load_gif_main_outofmem(&g, out, delays); + else { + out = (stbi_uc*) tmp; + out_size = layers * stride; + } + + if (delays) { + int *new_delays = (int*) STBI_REALLOC_SIZED( *delays, delays_size, sizeof(int) * layers ); + if (!new_delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + *delays = new_delays; + delays_size = layers * sizeof(int); + } + } else { + out = (stbi_uc*)stbi__malloc( layers * stride ); + if (!out) + return stbi__load_gif_main_outofmem(&g, out, delays); + out_size = layers * stride; + if (delays) { + *delays = (int*) stbi__malloc( layers * sizeof(int) ); + if (!*delays) + return stbi__load_gif_main_outofmem(&g, out, delays); + delays_size = layers * sizeof(int); + } + } + memcpy( out + ((layers - 1) * stride), u, stride ); + if (layers >= 2) { + two_back = out - 2 * stride; + } + + if (delays) { + (*delays)[layers - 1U] = g.delay; + } + } + } while (u != 0); + + // free temp buffer; + STBI_FREE(g.out); + STBI_FREE(g.history); + STBI_FREE(g.background); + + // do the final conversion after loading everything; + if (req_comp && req_comp != 4) + out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h); + + *z = layers; + return out; + } else { + return stbi__errpuc("not GIF", "Image was not as a gif type."); + } +} + +static void *stbi__gif_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *u = 0; + stbi__gif g; + memset(&g, 0, sizeof(g)); + STBI_NOTUSED(ri); + + u = stbi__gif_load_next(s, &g, comp, req_comp, 0); + if (u == (stbi_uc *) s) u = 0; // end of animated gif marker + if (u) { + *x = g.w; + *y = g.h; + + // moved conversion to after successful load so that the same + // can be done for multiple frames. + if (req_comp && req_comp != 4) + u = stbi__convert_format(u, 4, req_comp, g.w, g.h); + } else if (g.out) { + // if there was an error and we allocated an image buffer, free it! + STBI_FREE(g.out); + } + + // free buffers needed for multiple frame loading; + STBI_FREE(g.history); + STBI_FREE(g.background); + + return u; +} + +static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp) +{ + return stbi__gif_info_raw(s,x,y,comp); +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR loader +// originally by Nicolas Schulz +#ifndef STBI_NO_HDR +static int stbi__hdr_test_core(stbi__context *s, const char *signature) +{ + int i; + for (i=0; signature[i]; ++i) + if (stbi__get8(s) != signature[i]) + return 0; + stbi__rewind(s); + return 1; +} + +static int stbi__hdr_test(stbi__context* s) +{ + int r = stbi__hdr_test_core(s, "#?RADIANCE\n"); + stbi__rewind(s); + if(!r) { + r = stbi__hdr_test_core(s, "#?RGBE\n"); + stbi__rewind(s); + } + return r; +} + +#define STBI__HDR_BUFLEN 1024 +static char *stbi__hdr_gettoken(stbi__context *z, char *buffer) +{ + int len=0; + char c = '\0'; + + c = (char) stbi__get8(z); + + while (!stbi__at_eof(z) && c != '\n') { + buffer[len++] = c; + if (len == STBI__HDR_BUFLEN-1) { + // flush to end of line + while (!stbi__at_eof(z) && stbi__get8(z) != '\n') + ; + break; + } + c = (char) stbi__get8(z); + } + + buffer[len] = 0; + return buffer; +} + +static void stbi__hdr_convert(float *output, stbi_uc *input, int req_comp) +{ + if ( input[3] != 0 ) { + float f1; + // Exponent + f1 = (float) ldexp(1.0f, input[3] - (int)(128 + 8)); + if (req_comp <= 2) + output[0] = (input[0] + input[1] + input[2]) * f1 / 3; + else { + output[0] = input[0] * f1; + output[1] = input[1] * f1; + output[2] = input[2] * f1; + } + if (req_comp == 2) output[1] = 1; + if (req_comp == 4) output[3] = 1; + } else { + switch (req_comp) { + case 4: output[3] = 1; /* fallthrough */ + case 3: output[0] = output[1] = output[2] = 0; + break; + case 2: output[1] = 1; /* fallthrough */ + case 1: output[0] = 0; + break; + } + } +} + +static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int width, height; + stbi_uc *scanline; + float *hdr_data; + int len; + unsigned char count, value; + int i, j, k, c1,c2, z; + const char *headerToken; + STBI_NOTUSED(ri); + + // Check identifier + headerToken = stbi__hdr_gettoken(s,buffer); + if (strcmp(headerToken, "#?RADIANCE") != 0 && strcmp(headerToken, "#?RGBE") != 0) + return stbi__errpf("not HDR", "Corrupt HDR image"); + + // Parse header + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) return stbi__errpf("unsupported format", "Unsupported HDR format"); + + // Parse width and height + // can't use sscanf() if we're not using stdio! + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + height = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) return stbi__errpf("unsupported data layout", "Unsupported HDR format"); + token += 3; + width = (int) strtol(token, NULL, 10); + + if (height > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + if (width > STBI_MAX_DIMENSIONS) return stbi__errpf("too large","Very large image (corrupt?)"); + + *x = width; + *y = height; + + if (comp) *comp = 3; + if (req_comp == 0) req_comp = 3; + + if (!stbi__mad4sizes_valid(width, height, req_comp, sizeof(float), 0)) + return stbi__errpf("too large", "HDR image is too large"); + + // Read data + hdr_data = (float *) stbi__malloc_mad4(width, height, req_comp, sizeof(float), 0); + if (!hdr_data) + return stbi__errpf("outofmem", "Out of memory"); + + // Load image data + // image data is stored as some number of sca + if ( width < 8 || width >= 32768) { + // Read flat data + for (j=0; j < height; ++j) { + for (i=0; i < width; ++i) { + stbi_uc rgbe[4]; + main_decode_loop: + stbi__getn(s, rgbe, 4); + stbi__hdr_convert(hdr_data + j * width * req_comp + i * req_comp, rgbe, req_comp); + } + } + } else { + // Read RLE-encoded data + scanline = NULL; + + for (j = 0; j < height; ++j) { + c1 = stbi__get8(s); + c2 = stbi__get8(s); + len = stbi__get8(s); + if (c1 != 2 || c2 != 2 || (len & 0x80)) { + // not run-length encoded, so we have to actually use THIS data as a decoded + // pixel (note this can't be a valid pixel--one of RGB must be >= 128) + stbi_uc rgbe[4]; + rgbe[0] = (stbi_uc) c1; + rgbe[1] = (stbi_uc) c2; + rgbe[2] = (stbi_uc) len; + rgbe[3] = (stbi_uc) stbi__get8(s); + stbi__hdr_convert(hdr_data, rgbe, req_comp); + i = 1; + j = 0; + STBI_FREE(scanline); + goto main_decode_loop; // yes, this makes no sense + } + len <<= 8; + len |= stbi__get8(s); + if (len != width) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("invalid decoded scanline length", "corrupt HDR"); } + if (scanline == NULL) { + scanline = (stbi_uc *) stbi__malloc_mad2(width, 4, 0); + if (!scanline) { + STBI_FREE(hdr_data); + return stbi__errpf("outofmem", "Out of memory"); + } + } + + for (k = 0; k < 4; ++k) { + int nleft; + i = 0; + while ((nleft = width - i) > 0) { + count = stbi__get8(s); + if (count > 128) { + // Run + value = stbi__get8(s); + count -= 128; + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = value; + } else { + // Dump + if (count > nleft) { STBI_FREE(hdr_data); STBI_FREE(scanline); return stbi__errpf("corrupt", "bad RLE data in HDR"); } + for (z = 0; z < count; ++z) + scanline[i++ * 4 + k] = stbi__get8(s); + } + } + } + for (i=0; i < width; ++i) + stbi__hdr_convert(hdr_data+(j*width + i)*req_comp, scanline + i*4, req_comp); + } + if (scanline) + STBI_FREE(scanline); + } + + return hdr_data; +} + +static int stbi__hdr_info(stbi__context *s, int *x, int *y, int *comp) +{ + char buffer[STBI__HDR_BUFLEN]; + char *token; + int valid = 0; + int dummy; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (stbi__hdr_test(s) == 0) { + stbi__rewind( s ); + return 0; + } + + for(;;) { + token = stbi__hdr_gettoken(s,buffer); + if (token[0] == 0) break; + if (strcmp(token, "FORMAT=32-bit_rle_rgbe") == 0) valid = 1; + } + + if (!valid) { + stbi__rewind( s ); + return 0; + } + token = stbi__hdr_gettoken(s,buffer); + if (strncmp(token, "-Y ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *y = (int) strtol(token, &token, 10); + while (*token == ' ') ++token; + if (strncmp(token, "+X ", 3)) { + stbi__rewind( s ); + return 0; + } + token += 3; + *x = (int) strtol(token, NULL, 10); + *comp = 3; + return 1; +} +#endif // STBI_NO_HDR + +#ifndef STBI_NO_BMP +static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp) +{ + void *p; + stbi__bmp_data info; + + info.all_a = 255; + p = stbi__bmp_parse_header(s, &info); + if (p == NULL) { + stbi__rewind( s ); + return 0; + } + if (x) *x = s->img_x; + if (y) *y = s->img_y; + if (comp) { + if (info.bpp == 24 && info.ma == 0xff000000) + *comp = 3; + else + *comp = info.ma ? 4 : 3; + } + return 1; +} +#endif + +#ifndef STBI_NO_PSD +static int stbi__psd_info(stbi__context *s, int *x, int *y, int *comp) +{ + int channelCount, dummy, depth; + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + *y = stbi__get32be(s); + *x = stbi__get32be(s); + depth = stbi__get16be(s); + if (depth != 8 && depth != 16) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 3) { + stbi__rewind( s ); + return 0; + } + *comp = 4; + return 1; +} + +static int stbi__psd_is16(stbi__context *s) +{ + int channelCount, depth; + if (stbi__get32be(s) != 0x38425053) { + stbi__rewind( s ); + return 0; + } + if (stbi__get16be(s) != 1) { + stbi__rewind( s ); + return 0; + } + stbi__skip(s, 6); + channelCount = stbi__get16be(s); + if (channelCount < 0 || channelCount > 16) { + stbi__rewind( s ); + return 0; + } + STBI_NOTUSED(stbi__get32be(s)); + STBI_NOTUSED(stbi__get32be(s)); + depth = stbi__get16be(s); + if (depth != 16) { + stbi__rewind( s ); + return 0; + } + return 1; +} +#endif + +#ifndef STBI_NO_PIC +static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp) +{ + int act_comp=0,num_packets=0,chained,dummy; + stbi__pic_packet packets[10]; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + if (!stbi__pic_is4(s,"\x53\x80\xF6\x34")) { + stbi__rewind(s); + return 0; + } + + stbi__skip(s, 88); + + *x = stbi__get16be(s); + *y = stbi__get16be(s); + if (stbi__at_eof(s)) { + stbi__rewind( s); + return 0; + } + if ( (*x) != 0 && (1 << 28) / (*x) < (*y)) { + stbi__rewind( s ); + return 0; + } + + stbi__skip(s, 8); + + do { + stbi__pic_packet *packet; + + if (num_packets==sizeof(packets)/sizeof(packets[0])) + return 0; + + packet = &packets[num_packets++]; + chained = stbi__get8(s); + packet->size = stbi__get8(s); + packet->type = stbi__get8(s); + packet->channel = stbi__get8(s); + act_comp |= packet->channel; + + if (stbi__at_eof(s)) { + stbi__rewind( s ); + return 0; + } + if (packet->size != 8) { + stbi__rewind( s ); + return 0; + } + } while (chained); + + *comp = (act_comp & 0x10 ? 4 : 3); + + return 1; +} +#endif + +// ************************************************************************************************* +// Portable Gray Map and Portable Pixel Map loader +// by Ken Miller +// +// PGM: http://netpbm.sourceforge.net/doc/pgm.html +// PPM: http://netpbm.sourceforge.net/doc/ppm.html +// +// Known limitations: +// Does not support comments in the header section +// Does not support ASCII image data (formats P2 and P3) + +#ifndef STBI_NO_PNM + +static int stbi__pnm_test(stbi__context *s) +{ + char p, t; + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind( s ); + return 0; + } + return 1; +} + +static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req_comp, stbi__result_info *ri) +{ + stbi_uc *out; + STBI_NOTUSED(ri); + + ri->bits_per_channel = stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n); + if (ri->bits_per_channel == 0) + return 0; + + if (s->img_y > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + if (s->img_x > STBI_MAX_DIMENSIONS) return stbi__errpuc("too large","Very large image (corrupt?)"); + + *x = s->img_x; + *y = s->img_y; + if (comp) *comp = s->img_n; + + if (!stbi__mad4sizes_valid(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0)) + return stbi__errpuc("too large", "PNM too large"); + + out = (stbi_uc *) stbi__malloc_mad4(s->img_n, s->img_x, s->img_y, ri->bits_per_channel / 8, 0); + if (!out) return stbi__errpuc("outofmem", "Out of memory"); + stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8)); + + if (req_comp && req_comp != s->img_n) { + out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y); + if (out == NULL) return out; // stbi__convert_format frees input on failure + } + return out; +} + +static int stbi__pnm_isspace(char c) +{ + return c == ' ' || c == '\t' || c == '\n' || c == '\v' || c == '\f' || c == '\r'; +} + +static void stbi__pnm_skip_whitespace(stbi__context *s, char *c) +{ + for (;;) { + while (!stbi__at_eof(s) && stbi__pnm_isspace(*c)) + *c = (char) stbi__get8(s); + + if (stbi__at_eof(s) || *c != '#') + break; + + while (!stbi__at_eof(s) && *c != '\n' && *c != '\r' ) + *c = (char) stbi__get8(s); + } +} + +static int stbi__pnm_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +static int stbi__pnm_getinteger(stbi__context *s, char *c) +{ + int value = 0; + + while (!stbi__at_eof(s) && stbi__pnm_isdigit(*c)) { + value = value*10 + (*c - '0'); + *c = (char) stbi__get8(s); + } + + return value; +} + +static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp) +{ + int maxv, dummy; + char c, p, t; + + if (!x) x = &dummy; + if (!y) y = &dummy; + if (!comp) comp = &dummy; + + stbi__rewind(s); + + // Get identifier + p = (char) stbi__get8(s); + t = (char) stbi__get8(s); + if (p != 'P' || (t != '5' && t != '6')) { + stbi__rewind(s); + return 0; + } + + *comp = (t == '6') ? 3 : 1; // '5' is 1-component .pgm; '6' is 3-component .ppm + + c = (char) stbi__get8(s); + stbi__pnm_skip_whitespace(s, &c); + + *x = stbi__pnm_getinteger(s, &c); // read width + stbi__pnm_skip_whitespace(s, &c); + + *y = stbi__pnm_getinteger(s, &c); // read height + stbi__pnm_skip_whitespace(s, &c); + + maxv = stbi__pnm_getinteger(s, &c); // read max value + if (maxv > 65535) + return stbi__err("max value > 65535", "PPM image supports only 8-bit and 16-bit images"); + else if (maxv > 255) + return 16; + else + return 8; +} + +static int stbi__pnm_is16(stbi__context *s) +{ + if (stbi__pnm_info(s, NULL, NULL, NULL) == 16) + return 1; + return 0; +} +#endif + +static int stbi__info_main(stbi__context *s, int *x, int *y, int *comp) +{ + #ifndef STBI_NO_JPEG + if (stbi__jpeg_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNG + if (stbi__png_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_GIF + if (stbi__gif_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_BMP + if (stbi__bmp_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PIC + if (stbi__pic_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_info(s, x, y, comp)) return 1; + #endif + + #ifndef STBI_NO_HDR + if (stbi__hdr_info(s, x, y, comp)) return 1; + #endif + + // test tga last because it's a crappy test! + #ifndef STBI_NO_TGA + if (stbi__tga_info(s, x, y, comp)) + return 1; + #endif + return stbi__err("unknown image type", "Image not of any known type, or corrupt"); +} + +static int stbi__is_16_main(stbi__context *s) +{ + #ifndef STBI_NO_PNG + if (stbi__png_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PSD + if (stbi__psd_is16(s)) return 1; + #endif + + #ifndef STBI_NO_PNM + if (stbi__pnm_is16(s)) return 1; + #endif + return 0; +} + +#ifndef STBI_NO_STDIO +STBIDEF int stbi_info(char const *filename, int *x, int *y, int *comp) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_info_from_file(f, x, y, comp); + fclose(f); + return result; +} + +STBIDEF int stbi_info_from_file(FILE *f, int *x, int *y, int *comp) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__info_main(&s,x,y,comp); + fseek(f,pos,SEEK_SET); + return r; +} + +STBIDEF int stbi_is_16_bit(char const *filename) +{ + FILE *f = stbi__fopen(filename, "rb"); + int result; + if (!f) return stbi__err("can't fopen", "Unable to open file"); + result = stbi_is_16_bit_from_file(f); + fclose(f); + return result; +} + +STBIDEF int stbi_is_16_bit_from_file(FILE *f) +{ + int r; + stbi__context s; + long pos = ftell(f); + stbi__start_file(&s, f); + r = stbi__is_16_main(&s); + fseek(f,pos,SEEK_SET); + return r; +} +#endif // !STBI_NO_STDIO + +STBIDEF int stbi_info_from_memory(stbi_uc const *buffer, int len, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_info_from_callbacks(stbi_io_callbacks const *c, void *user, int *x, int *y, int *comp) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__info_main(&s,x,y,comp); +} + +STBIDEF int stbi_is_16_bit_from_memory(stbi_uc const *buffer, int len) +{ + stbi__context s; + stbi__start_mem(&s,buffer,len); + return stbi__is_16_main(&s); +} + +STBIDEF int stbi_is_16_bit_from_callbacks(stbi_io_callbacks const *c, void *user) +{ + stbi__context s; + stbi__start_callbacks(&s, (stbi_io_callbacks *) c, user); + return stbi__is_16_main(&s); +} + +#endif // STB_IMAGE_IMPLEMENTATION + +/* + revision history: + 2.20 (2019-02-07) support utf8 filenames in Windows; fix warnings and platform ifdefs + 2.19 (2018-02-11) fix warning + 2.18 (2018-01-30) fix warnings + 2.17 (2018-01-29) change sbti__shiftsigned to avoid clang -O2 bug + 1-bit BMP + *_is_16_bit api + avoid warnings + 2.16 (2017-07-23) all functions have 16-bit variants; + STBI_NO_STDIO works again; + compilation fixes; + fix rounding in unpremultiply; + optimize vertical flip; + disable raw_len validation; + documentation fixes + 2.15 (2017-03-18) fix png-1,2,4 bug; now all Imagenet JPGs decode; + warning fixes; disable run-time SSE detection on gcc; + uniform handling of optional "return" values; + thread-safe initialization of zlib tables + 2.14 (2017-03-03) remove deprecated STBI_JPEG_OLD; fixes for Imagenet JPGs + 2.13 (2016-11-29) add 16-bit API, only supported for PNG right now + 2.12 (2016-04-02) fix typo in 2.11 PSD fix that caused crashes + 2.11 (2016-04-02) allocate large structures on the stack + remove white matting for transparent PSD + fix reported channel count for PNG & BMP + re-enable SSE2 in non-gcc 64-bit + support RGB-formatted JPEG + read 16-bit PNGs (only as 8-bit) + 2.10 (2016-01-22) avoid warning introduced in 2.09 by STBI_REALLOC_SIZED + 2.09 (2016-01-16) allow comments in PNM files + 16-bit-per-pixel TGA (not bit-per-component) + info() for TGA could break due to .hdr handling + info() for BMP to shares code instead of sloppy parse + can use STBI_REALLOC_SIZED if allocator doesn't support realloc + code cleanup + 2.08 (2015-09-13) fix to 2.07 cleanup, reading RGB PSD as RGBA + 2.07 (2015-09-13) fix compiler warnings + partial animated GIF support + limited 16-bpc PSD support + #ifdef unused functions + bug with < 92 byte PIC,PNM,HDR,TGA + 2.06 (2015-04-19) fix bug where PSD returns wrong '*comp' value + 2.05 (2015-04-19) fix bug in progressive JPEG handling, fix warning + 2.04 (2015-04-15) try to re-enable SIMD on MinGW 64-bit + 2.03 (2015-04-12) extra corruption checking (mmozeiko) + stbi_set_flip_vertically_on_load (nguillemot) + fix NEON support; fix mingw support + 2.02 (2015-01-19) fix incorrect assert, fix warning + 2.01 (2015-01-17) fix various warnings; suppress SIMD on gcc 32-bit without -msse2 + 2.00b (2014-12-25) fix STBI_MALLOC in progressive JPEG + 2.00 (2014-12-25) optimize JPG, including x86 SSE2 & NEON SIMD (ryg) + progressive JPEG (stb) + PGM/PPM support (Ken Miller) + STBI_MALLOC,STBI_REALLOC,STBI_FREE + GIF bugfix -- seemingly never worked + STBI_NO_*, STBI_ONLY_* + 1.48 (2014-12-14) fix incorrectly-named assert() + 1.47 (2014-12-14) 1/2/4-bit PNG support, both direct and paletted (Omar Cornut & stb) + optimize PNG (ryg) + fix bug in interlaced PNG with user-specified channel count (stb) + 1.46 (2014-08-26) + fix broken tRNS chunk (colorkey-style transparency) in non-paletted PNG + 1.45 (2014-08-16) + fix MSVC-ARM internal compiler error by wrapping malloc + 1.44 (2014-08-07) + various warning fixes from Ronny Chevalier + 1.43 (2014-07-15) + fix MSVC-only compiler problem in code changed in 1.42 + 1.42 (2014-07-09) + don't define _CRT_SECURE_NO_WARNINGS (affects user code) + fixes to stbi__cleanup_jpeg path + added STBI_ASSERT to avoid requiring assert.h + 1.41 (2014-06-25) + fix search&replace from 1.36 that messed up comments/error messages + 1.40 (2014-06-22) + fix gcc struct-initialization warning + 1.39 (2014-06-15) + fix to TGA optimization when req_comp != number of components in TGA; + fix to GIF loading because BMP wasn't rewinding (whoops, no GIFs in my test suite) + add support for BMP version 5 (more ignored fields) + 1.38 (2014-06-06) + suppress MSVC warnings on integer casts truncating values + fix accidental rename of 'skip' field of I/O + 1.37 (2014-06-04) + remove duplicate typedef + 1.36 (2014-06-03) + convert to header file single-file library + if de-iphone isn't set, load iphone images color-swapped instead of returning NULL + 1.35 (2014-05-27) + various warnings + fix broken STBI_SIMD path + fix bug where stbi_load_from_file no longer left file pointer in correct place + fix broken non-easy path for 32-bit BMP (possibly never used) + TGA optimization by Arseny Kapoulkine + 1.34 (unknown) + use STBI_NOTUSED in stbi__resample_row_generic(), fix one more leak in tga failure case + 1.33 (2011-07-14) + make stbi_is_hdr work in STBI_NO_HDR (as specified), minor compiler-friendly improvements + 1.32 (2011-07-13) + support for "info" function for all supported filetypes (SpartanJ) + 1.31 (2011-06-20) + a few more leak fixes, bug in PNG handling (SpartanJ) + 1.30 (2011-06-11) + added ability to load files via callbacks to accomidate custom input streams (Ben Wenger) + removed deprecated format-specific test/load functions + removed support for installable file formats (stbi_loader) -- would have been broken for IO callbacks anyway + error cases in bmp and tga give messages and don't leak (Raymond Barbiero, grisha) + fix inefficiency in decoding 32-bit BMP (David Woo) + 1.29 (2010-08-16) + various warning fixes from Aurelien Pocheville + 1.28 (2010-08-01) + fix bug in GIF palette transparency (SpartanJ) + 1.27 (2010-08-01) + cast-to-stbi_uc to fix warnings + 1.26 (2010-07-24) + fix bug in file buffering for PNG reported by SpartanJ + 1.25 (2010-07-17) + refix trans_data warning (Won Chun) + 1.24 (2010-07-12) + perf improvements reading from files on platforms with lock-heavy fgetc() + minor perf improvements for jpeg + deprecated type-specific functions so we'll get feedback if they're needed + attempt to fix trans_data warning (Won Chun) + 1.23 fixed bug in iPhone support + 1.22 (2010-07-10) + removed image *writing* support + stbi_info support from Jetro Lauha + GIF support from Jean-Marc Lienher + iPhone PNG-extensions from James Brown + warning-fixes from Nicolas Schulz and Janez Zemva (i.stbi__err. Janez (U+017D)emva) + 1.21 fix use of 'stbi_uc' in header (reported by jon blow) + 1.20 added support for Softimage PIC, by Tom Seddon + 1.19 bug in interlaced PNG corruption check (found by ryg) + 1.18 (2008-08-02) + fix a threading bug (local mutable static) + 1.17 support interlaced PNG + 1.16 major bugfix - stbi__convert_format converted one too many pixels + 1.15 initialize some fields for thread safety + 1.14 fix threadsafe conversion bug + header-file-only version (#define STBI_HEADER_FILE_ONLY before including) + 1.13 threadsafe + 1.12 const qualifiers in the API + 1.11 Support installable IDCT, colorspace conversion routines + 1.10 Fixes for 64-bit (don't use "unsigned long") + optimized upsampling by Fabian "ryg" Giesen + 1.09 Fix format-conversion for PSD code (bad global variables!) + 1.08 Thatcher Ulrich's PSD code integrated by Nicolas Schulz + 1.07 attempt to fix C++ warning/errors again + 1.06 attempt to fix C++ warning/errors again + 1.05 fix TGA loading to return correct *comp and use good luminance calc + 1.04 default float alpha is 1, not 255; use 'void *' for stbi_image_free + 1.03 bugfixes to STBI_NO_STDIO, STBI_NO_HDR + 1.02 support for (subset of) HDR files, float interface for preferred access to them + 1.01 fix bug: possible bug in handling right-side up bmps... not sure + fix bug: the stbi__bmp_load() and stbi__tga_load() functions didn't work at all + 1.00 interface to zlib that skips zlib header + 0.99 correct handling of alpha in palette + 0.98 TGA loader by lonesock; dynamically add loaders (untested) + 0.97 jpeg errors on too large a file; also catch another malloc failure + 0.96 fix detection of invalid v value - particleman@mollyrocket forum + 0.95 during header scan, seek to markers in case of padding + 0.94 STBI_NO_STDIO to disable stdio usage; rename all #defines the same + 0.93 handle jpegtran output; verbose errors + 0.92 read 4,8,16,24,32-bit BMP files of several formats + 0.91 output 24-bit Windows 3.0 BMP files + 0.90 fix a few more warnings; bump version number to approach 1.0 + 0.61 bugfixes due to Marc LeBlanc, Christopher Lloyd + 0.60 fix compiling as c++ + 0.59 fix warnings: merge Dave Moore's -Wall fixes + 0.58 fix bug: zlib uncompressed mode len/nlen was wrong endian + 0.57 fix bug: jpg last huffman symbol before marker was >9 bits but less than 16 available + 0.56 fix bug: zlib uncompressed mode len vs. nlen + 0.55 fix bug: restart_interval not initialized to 0 + 0.54 allow NULL for 'int *comp' + 0.53 fix bug in png 3->4; speedup png decoding + 0.52 png handles req_comp=3,4 directly; minor cleanup; jpeg comments + 0.51 obey req_comp requests, 1-component jpegs return as 1-component, + on 'test' only check type, not whether we support this variant + 0.50 (2006-11-19) + first released version +*/ + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +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. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +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 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. +------------------------------------------------------------------------------ +*/ diff --git a/tools/thirdparty/stbi/include/stb_image_write.h b/tools/thirdparty/stbi/include/stb_image_write.h new file mode 100644 index 0000000..e4b32ed --- /dev/null +++ b/tools/thirdparty/stbi/include/stb_image_write.h @@ -0,0 +1,1724 @@ +/* stb_image_write - v1.16 - public domain - http://nothings.org/stb + writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 + no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio or a callback. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation; though providing a custom + zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. + This library is designed for source code compactness and simplicity, + not optimal image file size or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can #define STBIW_MEMMOVE() to replace memmove() + You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function + for PNG compression (instead of the builtin one), it must have the following signature: + unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); + The returned data will be freed with STBIW_FREE() (free() by default), + so it must be heap allocated with STBIW_MALLOC() (malloc() by default), + +UNICODE: + + If compiling for Windows and you wish to use Unicode filenames, compile + with + #define STBIW_WINDOWS_UTF8 + and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert + Windows wchar_t filenames to utf8. + +USAGE: + + There are five functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + + void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically + + There are also five equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can configure it with these global variables: + int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE + int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression + int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode + + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + PNG allows you to set the deflate compression level by setting the global + variable 'stbi_write_png_compression_level' (it defaults to 8). + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + + JPEG does ignore alpha channels in input data; quality is between 1 and 100. + Higher quality looks better but results in a bigger image. + JPEG baseline (no JPEG progressive). + +CREDITS: + + + Sean Barrett - PNG/BMP/TGA + Baldur Karlsson - HDR + Jean-Sebastien Guay - TGA monochrome + Tim Kelsey - misc enhancements + Alan Hickman - TGA RLE + Emmanuel Julien - initial file IO callback implementation + Jon Olick - original jo_jpeg.cpp code + Daniel Gibson - integrate JPEG, allow external zlib + Aarni Koskela - allow choosing PNG filter + + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + github:poppolopoppo + Patrick Boettcher + github:xeekworx + Cap Petschulat + Simon Rodriguez + Ivan Tikhonov + github:ignotion + Adam Schackart + Andrew Kensler + +LICENSE + + See end of file for license information. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#include + +// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' +#ifndef STBIWDEF +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#ifdef __cplusplus +#define STBIWDEF extern "C" +#else +#define STBIWDEF extern +#endif +#endif +#endif + +#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations +STBIWDEF int stbi_write_tga_with_rle; +STBIWDEF int stbi_write_png_compression_level; +STBIWDEF int stbi_write_force_png_filter; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); + +#ifdef STBIW_WINDOWS_UTF8 +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); +#endif +#endif + +typedef void stbi_write_func(void *context, void *data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + +STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif + #ifndef _CRT_NONSTDC_NO_DEPRECATE + #define _CRT_NONSTDC_NO_DEPRECATE + #endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi_write_png_compression_level = 8; +static int stbi_write_tga_with_rle = 1; +static int stbi_write_force_png_filter = -1; +#else +int stbi_write_png_compression_level = 8; +int stbi_write_tga_with_rle = 1; +int stbi_write_force_png_filter = -1; +#endif + +static int stbi__flip_vertically_on_write = 0; + +STBIWDEF void stbi_flip_vertically_on_write(int flag) +{ + stbi__flip_vertically_on_write = flag; +} + +typedef struct +{ + stbi_write_func *func; + void *context; + unsigned char buffer[64]; + int buf_used; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) +#ifdef __cplusplus +#define STBIW_EXTERN extern "C" +#else +#define STBIW_EXTERN extern +#endif +STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); +STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); + +STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); +} +#endif + +static FILE *stbiw__fopen(char const *filename, char const *mode) +{ + FILE *f; +#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) + return 0; + +#if defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f=0; +#else + f = fopen(filename, mode); +#endif + return f; +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = stbiw__fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context,&x,1); + break; } + case '2': { int x = va_arg(v,int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x>>8); + s->func(s->context,b,2); + break; } + case '4': { stbiw_uint32 x = va_arg(v,int); + unsigned char b[4]; + b[0]=STBIW_UCHAR(x); + b[1]=STBIW_UCHAR(x>>8); + b[2]=STBIW_UCHAR(x>>16); + b[3]=STBIW_UCHAR(x>>24); + s->func(s->context,b,4); + break; } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__write_flush(stbi__write_context *s) +{ + if (s->buf_used) { + s->func(s->context, &s->buffer, s->buf_used); + s->buf_used = 0; + } +} + +static void stbiw__putc(stbi__write_context *s, unsigned char c) +{ + s->func(s->context, &c, 1); +} + +static void stbiw__write1(stbi__write_context *s, unsigned char a) +{ + if ((size_t)s->buf_used + 1 > sizeof(s->buffer)) + stbiw__write_flush(s); + s->buffer[s->buf_used++] = a; +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) +{ + int n; + if ((size_t)s->buf_used + 3 > sizeof(s->buffer)) + stbiw__write_flush(s); + n = s->buf_used; + s->buf_used = n+3; + s->buffer[n+0] = a; + s->buffer[n+1] = b; + s->buffer[n+2] = c; +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) + stbiw__write1(s, d[comp - 1]); + + switch (comp) { + case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case + case 1: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + stbiw__write1(s, d[0]); // monochrome TGA + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k = 0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + stbiw__write1(s, d[comp - 1]); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) +{ + stbiw_uint32 zero = 0; + int i,j, j_end; + + if (y <= 0) + return; + + if (stbi__flip_vertically_on_write) + vdir *= -1; + + if (vdir < 0) { + j_end = -1; j = y-1; + } else { + j_end = y; j = 0; + } + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + unsigned char *d = (unsigned char *) data + (j*x+i)*comp; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + stbiw__write_flush(s); + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) +{ + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) +{ + if (comp != 4) { + // write RGB bitmap + int pad = (-x*3) & 3; + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header + } else { + // RGBA bitmaps need a v4 header + // use BI_BITFIELDS mode with 32bpp and alpha mask + // (straight BI_RGB with alpha mask doesn't work in most readers) + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *)data,1,0, + "11 4 22 4" "4 44 22 444444 4444 4 444 444 444 444", + 'B', 'M', 14+108+x*y*4, 0, 0, 14+108, // file header + 108, x,y, 1,32, 3,0,0,0,0,0, 0xff0000,0xff00,0xff,0xff000000u, 0, 0,0,0, 0,0,0, 0,0,0, 0,0,0); // bitmap V4 header + } +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif //!STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) +{ + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp-1 : comp; + int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); + } else { + int i,j,k; + int jend, jdir; + + stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); + + if (stbi__flip_vertically_on_write) { + j = 0; + jend = y; + jdir = 1; + } else { + j = y-1; + jend = -1; + jdir = -1; + } + for (; j != jend; j += jdir) { + unsigned char *row = (unsigned char *) data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + stbiw__write1(s, header); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + stbiw__write1(s, header); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + stbiw__write_flush(s); + } + return 1; +} + +STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void *) data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +#ifndef STBI_WRITE_NO_STDIO + +static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +{ + unsigned char lengthbyte = STBIW_UCHAR(length+128); + STBIW_ASSERT(length+128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +{ + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) +{ + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full output scanline. + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + int i, len; + char buffer[128]; + char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header)-1); + +#ifdef __STDC_LIB_EXT1__ + len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#else + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#endif + s->func(s->context, buffer, len); + + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); + STBIW_FREE(scratch); + return 1; + } +} + +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *) data); +} + +STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +#ifndef STBIW_ZLIB_COMPRESS +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() +#define stbiw__sbraw(a) ((int *) (void *) (a) - 2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) +#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbiw__zhash(unsigned char *data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) +#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +#endif // STBIW_ZLIB_COMPRESS + +STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ +#ifdef STBIW_ZLIB_COMPRESS + // user provided a zlib compress implementation, use that + return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); +#else // use builtin + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); + if (hash_table == NULL) + return NULL; + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1,1); // BFINAL = 1 + stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) { best=d; bestloc=hlist[j]; } + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__sbpush(hash_table[h],data+i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int) (data+i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbiw__zlib_huff(j+257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0,1); + + for (i=0; i < stbiw__ZHASH; ++i) + (void) stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + // store uncompressed instead if compression was worse + if (stbiw__sbn(out) > data_len + 2 + ((data_len+32766)/32767)*5) { + stbiw__sbn(out) = 2; // truncate to DEFLATE 32K window and FLEVEL = 1 + for (j = 0; j < data_len;) { + int blocklen = data_len - j; + if (blocklen > 32767) blocklen = 32767; + stbiw__sbpush(out, data_len - j == blocklen); // BFINAL = ?, BTYPE = 0 -- no compression + stbiw__sbpush(out, STBIW_UCHAR(blocklen)); // LEN + stbiw__sbpush(out, STBIW_UCHAR(blocklen >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(~blocklen)); // NLEN + stbiw__sbpush(out, STBIW_UCHAR(~blocklen >> 8)); + memcpy(out+stbiw__sbn(out), data+j, blocklen); + stbiw__sbn(out) += blocklen; + j += blocklen; + } + } + + { + // compute adler32 on input + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; } + s1 %= 65521; s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *) stbiw__sbraw(out); +#endif // STBIW_ZLIB_COMPRESS +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) +{ +#ifdef STBIW_CRC32 + return STBIW_CRC32(buffer, len); +#else + static unsigned int crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + unsigned int crc = ~0u; + int i; + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +#endif +} + +#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) +#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbiw__crc32(*data - len - 4, len+4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); + if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +// @OPTIMIZE: provide an option that always forces left-predict or paeth predict +static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) +{ + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = (y != 0) ? mapping : firstmap; + int i; + int type = mymap[filter_type]; + unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); + int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; + + if (type==0) { + memcpy(line_buffer, z, width*n); + return; + } + + // first loop isn't optimized since it's just one pixel + for (i = 0; i < n; ++i) { + switch (type) { + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + } + switch (type) { + case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; + case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; + case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; + case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; + case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; + } +} + +STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int force_filter = stbi_write_force_png_filter; + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char *out,*o, *filt, *zlib; + signed char *line_buffer; + int j,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + if (force_filter >= 5) { + force_filter = -1; + } + + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } + for (j=0; j < y; ++j) { + int filter_type; + if (force_filter > -1) { + filter_type = force_filter; + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); + } else { // Estimate the best filter by running through all of them: + int best_filter = 0, best_filter_val = 0x7fffffff, est, i; + for (filter_type = 0; filter_type < 5; filter_type++) { + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); + + // Estimate the entropy of the line using this filter; the less, the better. + est = 0; + for (i = 0; i < x*n; ++i) { + est += abs((signed char) line_buffer[i]); + } + if (est < best_filter_val) { + best_filter_val = est; + best_filter = filter_type; + } + } + if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); + filter_type = best_filter; + } + } + // when we get here, filter_type contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) filter_type; + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + STBIW_MEMMOVE(o,sig,8); o+= 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o,13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o,0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o,0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) +{ + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + + f = stbiw__fopen(filename, "wb"); + if (!f) { STBIW_FREE(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) +{ + int len; + unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + + +/* *************************************************************************** + * + * JPEG writer + * + * This is based on Jon Olick's jo_jpeg.cpp: + * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html + */ + +static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, + 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; + +static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { + int bitBuf = *bitBufP, bitCnt = *bitCntP; + bitCnt += bs[1]; + bitBuf |= bs[0] << (24 - bitCnt); + while(bitCnt >= 8) { + unsigned char c = (bitBuf >> 16) & 255; + stbiw__putc(s, c); + if(c == 255) { + stbiw__putc(s, 0); + } + bitBuf <<= 8; + bitCnt -= 8; + } + *bitBufP = bitBuf; + *bitCntP = bitCnt; +} + +static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { + float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; + float z1, z2, z3, z4, z5, z11, z13; + + float tmp0 = d0 + d7; + float tmp7 = d0 - d7; + float tmp1 = d1 + d6; + float tmp6 = d1 - d6; + float tmp2 = d2 + d5; + float tmp5 = d2 - d5; + float tmp3 = d3 + d4; + float tmp4 = d3 - d4; + + // Even part + float tmp10 = tmp0 + tmp3; // phase 2 + float tmp13 = tmp0 - tmp3; + float tmp11 = tmp1 + tmp2; + float tmp12 = tmp1 - tmp2; + + d0 = tmp10 + tmp11; // phase 3 + d4 = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * 0.707106781f; // c4 + d2 = tmp13 + z1; // phase 5 + d6 = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; // phase 2 + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = (tmp10 - tmp12) * 0.382683433f; // c6 + z2 = tmp10 * 0.541196100f + z5; // c2-c6 + z4 = tmp12 * 1.306562965f + z5; // c2+c6 + z3 = tmp11 * 0.707106781f; // c4 + + z11 = tmp7 + z3; // phase 5 + z13 = tmp7 - z3; + + *d5p = z13 + z2; // phase 6 + *d3p = z13 - z2; + *d1p = z11 + z4; + *d7p = z11 - z4; + + *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; +} + +static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { + int tmp1 = val < 0 ? -val : val; + val = val < 0 ? val-1 : val; + bits[1] = 1; + while(tmp1 >>= 1) { + ++bits[1]; + } + bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { + } + // end0pos = first element in reverse order !=0 + if(end0pos == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + return DU[0]; + } + for(i = 1; i <= end0pos; ++i) { + int startpos = i; + int nrzeroes; + unsigned short bits[2]; + for (; DU[i]==0 && i<=end0pos; ++i) { + } + nrzeroes = i-startpos; + if ( nrzeroes >= 16 ) { + int lng = nrzeroes>>4; + int nrmarker; + for (nrmarker=1; nrmarker <= lng; ++nrmarker) + stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); + nrzeroes &= 15; + } + stbiw__jpg_calcBits(DU[i], bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + if(end0pos != 63) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + } + return DU[0]; +} + +static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { + // Constants that don't pollute global namespace + static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; + static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; + static const unsigned char std_ac_luminance_values[] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; + static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; + static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; + static const unsigned char std_ac_chrominance_values[] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + // Huffman tables + static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; + static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; + static const unsigned short YAC_HT[256][2] = { + {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const unsigned short UVAC_HT[256][2] = { + {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, + 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; + static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; + static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, + 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; + + int row, col, i, k, subsample; + float fdtbl_Y[64], fdtbl_UV[64]; + unsigned char YTable[64], UVTable[64]; + + if(!data || !width || !height || comp > 4 || comp < 1) { + return 0; + } + + quality = quality ? quality : 90; + subsample = quality <= 90 ? 1 : 0; + quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + for(i = 0; i < 64; ++i) { + int uvti, yti = (YQT[i]*quality+50)/100; + YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); + uvti = (UVQT[i]*quality+50)/100; + UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); + } + + for(row = 0, k = 0; row < 8; ++row) { + for(col = 0; col < 8; ++col, ++k) { + fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + } + } + + // Write Headers + { + static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; + static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; + const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), + 3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; + s->func(s->context, (void*)head0, sizeof(head0)); + s->func(s->context, (void*)YTable, sizeof(YTable)); + stbiw__putc(s, 1); + s->func(s->context, UVTable, sizeof(UVTable)); + s->func(s->context, (void*)head1, sizeof(head1)); + s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); + s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); + stbiw__putc(s, 0x10); // HTYACinfo + s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); + s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); + stbiw__putc(s, 1); // HTUDCinfo + s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); + stbiw__putc(s, 0x11); // HTUACinfo + s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); + s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); + s->func(s->context, (void*)head2, sizeof(head2)); + } + + // Encode 8x8 macroblocks + { + static const unsigned short fillBits[] = {0x7F, 7}; + int DCY=0, DCU=0, DCV=0; + int bitBuf=0, bitCnt=0; + // comp == 2 is grey+alpha (alpha is ignored) + int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; + const unsigned char *dataR = (const unsigned char *)data; + const unsigned char *dataG = dataR + ofsG; + const unsigned char *dataB = dataR + ofsB; + int x, y, pos; + if(subsample) { + for(y = 0; y < height; y += 16) { + for(x = 0; x < width; x += 16) { + float Y[256], U[256], V[256]; + for(row = y, pos = 0; row < y+16; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+16; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + + // subsample U,V + { + float subU[64], subV[64]; + int yy, xx; + for(yy = 0, pos = 0; yy < 8; ++yy) { + for(xx = 0; xx < 8; ++xx, ++pos) { + int j = yy*32+xx*2; + subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f; + subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f; + } + } + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + } else { + for(y = 0; y < height; y += 8) { + for(x = 0; x < width; x += 8) { + float Y[64], U[64], V[64]; + for(row = y, pos = 0; row < y+8; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; + for(col = x; col < x+8; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width-1))*comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; + U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; + V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; + } + } + + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + + // Do the bit alignment of the EOI marker + stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); + } + + // EOI + stbiw__putc(s, 0xFF); + stbiw__putc(s, 0xD9); + + return 1; +} + +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); +} + + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +#endif + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.16 (2021-07-11) + make Deflate code emit uncompressed blocks when it would otherwise expand + support writing BMPs with alpha channel + 1.15 (2020-07-13) unknown + 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels + 1.13 + 1.12 + 1.11 (2019-08-11) + + 1.10 (2019-02-07) + support utf8 filenames in Windows; fix warnings and platform ifdefs + 1.09 (2018-02-11) + fix typo in zlib quality API, improve STB_I_W_STATIC in C++ + 1.08 (2018-01-29) + add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter + 1.07 (2017-07-24) + doc fix + 1.06 (2017-07-23) + writing JPEG (using Jon Olick's code) + 1.05 ??? + 1.04 (2017-03-03) + monochrome BMP expansion + 1.03 ??? + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +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. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +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 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. +------------------------------------------------------------------------------ +*/ diff --git a/tools/thirdparty/stbi/src/stbi.cpp b/tools/thirdparty/stbi/src/stbi.cpp new file mode 100644 index 0000000..e19519d --- /dev/null +++ b/tools/thirdparty/stbi/src/stbi.cpp @@ -0,0 +1,5 @@ +#define STB_IMAGE_IMPLEMENTATION +#define STB_IMAGE_WRITE_IMPLEMENTATION + +#include +#include \ No newline at end of file diff --git a/tools/thirdparty/tiny-cuda-nn b/tools/thirdparty/tiny-cuda-nn new file mode 160000 index 0000000..4ba6268 --- /dev/null +++ b/tools/thirdparty/tiny-cuda-nn @@ -0,0 +1 @@ +Subproject commit 4ba6268aecc51240c16cd04ad30020c03e9cbf09 diff --git a/tools/tiles_to_image/CMakeLists.txt b/tools/tiles_to_image/CMakeLists.txt new file mode 100644 index 0000000..4b13228 --- /dev/null +++ b/tools/tiles_to_image/CMakeLists.txt @@ -0,0 +1,3 @@ +set(TARGET tiles_to_image) +add_executable(${TARGET} src/main.cpp) +target_link_libraries(${TARGET} cxxopts stbi) diff --git a/tools/tiles_to_image/src/main.cpp b/tools/tiles_to_image/src/main.cpp new file mode 100644 index 0000000..f32e21f --- /dev/null +++ b/tools/tiles_to_image/src/main.cpp @@ -0,0 +1,222 @@ +#include +#include +#include +#include +#include + +#include +#include + +struct Rect +{ + double left; + double top; + double right; + double bottom; + + double width() const + { + return right - left; + } + + double height() const + { + return bottom - top; + } +}; + + +struct Color +{ + uint32_t value = 0xFF000000; +}; + + +Color to_color(uint8_t index) +{ + switch (index) + { + case 1: return Color{0xFF0000FF}; + case 2: return Color{0xFF00FF00}; + case 3: return Color{0xFFFF0000}; + } + return Color{0xFFFFFFFF}; +} + +struct Tile +{ + static constexpr uint32_t resolution = 32; + static constexpr uint32_t pixel_count = resolution * resolution; + static constexpr uint32_t data_size = pixel_count / 4; + + uint32_t x; + uint32_t y; + std::array data; + + Color get_color(double x, double y) const + { + const auto ix = static_cast(x * resolution); + const auto iy = static_cast(y * resolution); + const auto subindex = ix % 4; + const auto c = (data[resolution / 4 * (resolution - 1 - iy) + ix / 4] >> (subindex * 2)) & 3; + return to_color(c); + } +}; + +namespace std +{ + +template<> +struct hash>{ + size_t operator() (pair v) const + { + return std::hash()(static_cast(v.first) << 32 | v.second); + } +}; +} + +struct Map +{ + using PositionToIndexMap = std::unordered_map, uint32_t>; + + unsigned int resolution; + std::vector empty_tiles; + std::vector tiles; + PositionToIndexMap position_to_tile_index; + + Color get_color(double x, double y) const + { + const auto ix = static_cast(x * resolution); + const auto iy = static_cast(y * resolution); + const auto subindex = ix % 4; + const auto c = (empty_tiles[resolution / 4 * iy + ix / 4] >> (subindex * 2)) & 3; + if (c) + { + return to_color(c); + } + + const auto it = position_to_tile_index.find({ix, iy}); + if (it != position_to_tile_index.end()) + { + const auto & tile = tiles[it->second]; + return tile.get_color(x * resolution - ix, y * resolution - iy); + } + + return Color{0xFFFFFFFF}; + } +}; + +struct Image +{ + unsigned int width; + unsigned int height; + std::vector pixels; +}; + +template +std::vector read_file_content(const std::string & path) +{ + std::ifstream f(path, std::ios_base::binary); + f.seekg(0, std::ios::end); + const size_t size = f.tellg(); + f.seekg(0, std::ios::beg); + std::vector result(size / sizeof(T)); + f.read(reinterpret_cast(result.data()), size); + if (!f) + { + throw std::runtime_error("Failded to read file content"); + } + return result; +} + +Map load_map(const std::string & empty_tiles_path, const std::string & tiles_path) +{ + (void)tiles_path; + auto empty_tiles = read_file_content(empty_tiles_path); + const auto resolution = static_cast(std::sqrt(empty_tiles.size() * 4)); + auto tiles = read_file_content(tiles_path); + Map::PositionToIndexMap position_to_tile_index; + + uint32_t index = 0; + for (const auto & tile : tiles) + { + position_to_tile_index.insert({std::make_pair(tile.x, tile.y), index}); + ++index; + } + + return Map { + resolution, + std::move(empty_tiles), + std::move(tiles), + std::move(position_to_tile_index) + }; +} + +Image generate_image(const Map & map, const Rect & rect, unsigned int resolution) +{ + const auto width = rect.width() >= rect.height() ? resolution : static_cast(resolution * rect.width() / rect.height()); + const auto height = rect.height() >= rect.width() ? resolution : static_cast(resolution * rect.height() / rect.width()); + Image image {width, height, std::vector(width * height)}; + for (unsigned int iy = 0; iy < height; ++iy) + { + const auto y = (iy + 0.5) / height * rect.height() + rect.top; + for (unsigned int ix = 0; ix < width; ++ix) + { + const auto x = (ix + 0.5) / width * rect.width() + rect.left; + const auto color = map.get_color(x, y); + image.pixels[iy * width + ix] = color; + } + } + return image; +} + +void save_image(const Image & image, const std::string & path) +{ + const auto ok = stbi_write_png(path.c_str(), image.width, image.height, 4, image.pixels.data(), image.width * 4); + if (!ok) + { + throw std::runtime_error("Failed to save image"); + } +} + +Rect get_rect(const cxxopts::ParseResult & opt) +{ + const auto rect = Rect { + opt["left"].as(), + opt["top"].as(), + opt["right"].as(), + opt["bottom"].as() + }; + if (rect.left > rect.right || rect.top > rect.bottom) + { + throw std::runtime_error("Invalid rect"); + } + return rect; +} + +int main(int argc, const char ** argv) +{ + cxxopts::Options options_parser("tiles_to_image"); + options_parser.add_options() + ("empty_tiles", "", cxxopts::value()) + ("tiles", "", cxxopts::value()) + ("output", "", cxxopts::value()->default_value("tiles.png")) + ("left", "", cxxopts::value()->default_value("0.0")) + ("top", "", cxxopts::value()->default_value("0.0")) + ("right", "", cxxopts::value()->default_value("1.0")) + ("bottom", "", cxxopts::value()->default_value("1.0")) + ("resolution", "", cxxopts::value()->default_value("2000")); + + const auto opt = options_parser.parse(argc, argv); + const auto rect = get_rect(opt); + const auto resolution = opt["resolution"].as(); + const auto output = opt["output"].as(); + + const auto map = load_map( + opt["empty_tiles"].as(), + opt["tiles"].as() + ); + + const auto image = generate_image(map, rect, resolution); + save_image(image, output); +} diff --git a/tools/trainer/Accuracy.cu b/tools/trainer/Accuracy.cu new file mode 100644 index 0000000..85a4b87 --- /dev/null +++ b/tools/trainer/Accuracy.cu @@ -0,0 +1,72 @@ +#include + +#include +#include + +#include "Accuracy.h" +#include "CombinedTilesSampler.h" +#include "Model.h" + +namespace +{ + +__device__ inline int max_index(float v0, float v1, float v2) +{ + if (v0 > v1) + { + return v0 > v2 ? 0 : 2; + } + else + { + return v1 > v2 ? 1 : 2; + } +} + +__global__ void calculate_difference(uint32_t n_elements, float* __restrict__ a, float* __restrict__ b, float* __restrict__ result) +{ + uint32_t i = blockIdx.x * blockDim.x + threadIdx.x; + if (i >= n_elements) return; + + const uint32_t j = i * 3; + const auto a_max = max_index(a[j], a[j + 1], a[j + 2]); + const auto b_max = max_index(b[j], b[j + 1], b[j + 2]); + result[i] = a_max == b_max ? 1.0f : 0.0f; +} + +double calculate_accuracy(cudaStream_t stream, tcnn::default_rng_t & rng, CombinedTilesSampler & tiles_sampler, Model & model, float level) +{ + constexpr auto n_input_dims = 2; + constexpr auto n_output_dims = 3; + constexpr auto n_iter = 1000; + const auto batch_size = tiles_sampler.batch_size(); + + tcnn::GPUMatrix coords(n_input_dims, batch_size); + tcnn::GPUMatrix target(n_output_dims, batch_size); + tcnn::GPUMatrix prediction(n_output_dims, batch_size); + tcnn::GPUMemory difference(batch_size); + + model.set_max_level(level); + + double total_sum = 0.0; + for (int i = 0; i < n_iter; ++i) + { + tiles_sampler.sample(stream, rng, coords.data(), target.data()); + model.inference(stream, coords, prediction); + tcnn::linear_kernel(calculate_difference, 0, stream, batch_size, target.data(), prediction.data(), difference.data()); + const double sum = tcnn::reduce_sum(difference.data(), batch_size, stream); + total_sum += sum / batch_size; + } + return total_sum / n_iter; +} + +} + +void print_accuracy(cudaStream_t stream, tcnn::default_rng_t & rng, CombinedTilesSampler & tiles_sampler, Model & model) +{ + std::cout << "calculating accuracy..." << std::endl; + for (auto level = 2; level <= model.level_count(); ++level) + { + const auto accuracy = calculate_accuracy(stream, rng, tiles_sampler, model, level / static_cast(model.level_count())); + std::cout << "accuracy_" << level << "=" << std::setprecision(8) << accuracy * 100.0 << std::endl; + } +} diff --git a/tools/trainer/Accuracy.h b/tools/trainer/Accuracy.h new file mode 100644 index 0000000..41dc8a4 --- /dev/null +++ b/tools/trainer/Accuracy.h @@ -0,0 +1,11 @@ +#pragma once +#include + +class CombinedTilesSampler; +class Model; + +void print_accuracy( + cudaStream_t stream, + tcnn::default_rng_t & rng, + CombinedTilesSampler & tiles_sampler, + Model & model); diff --git a/tools/trainer/CMakeLists.txt b/tools/trainer/CMakeLists.txt new file mode 100644 index 0000000..e25e563 --- /dev/null +++ b/tools/trainer/CMakeLists.txt @@ -0,0 +1,21 @@ +set(TARGET trainer) +add_executable(${TARGET} + Accuracy.cu + Accuracy.h + CombinedTilesSampler.cu + CombinedTilesSampler.h + Common.h + DetailedTilesSampler.cu + DetailedTilesSampler.h + EmptyTilesSampler.cu + EmptyTilesSampler.h + HostMemory.h + Model.cu + Model.h + SoftmaxCrossEntropyLoss.h + ThreadSafeQueue.h + main.cu +) + +target_link_libraries(${TARGET} PRIVATE ${CUDA_LIBRARIES} tiny-cuda-nn cxxopts) +target_compile_options(${TARGET} PRIVATE $<$:${CUDA_NVCC_FLAGS}>) diff --git a/tools/trainer/CombinedTilesSampler.cu b/tools/trainer/CombinedTilesSampler.cu new file mode 100644 index 0000000..cda0f9f --- /dev/null +++ b/tools/trainer/CombinedTilesSampler.cu @@ -0,0 +1,24 @@ +#include "CombinedTilesSampler.h" + +CombinedTilesSampler::CombinedTilesSampler(const uint32_t batch_size, const std::string & empty_tiles_path, const std::string & tiles_path) + : batch_size_(batch_size) + , empty_tiles_sampler_(empty_tiles_path) + , detailed_tiles_sampler_(batch_size_ / 2, empty_tiles_sampler_.resolution(), tiles_path) +{ +} + +uint32_t CombinedTilesSampler::batch_size() const +{ + return batch_size_; +} + +void CombinedTilesSampler::sample( + cudaStream_t stream, + tcnn::default_rng_t & rng, + float * coords, + float * colors) +{ + const auto empty_count = batch_size_ / 2; + empty_tiles_sampler_.sample(stream, rng, empty_count, coords, colors); + detailed_tiles_sampler_.sample(coords + empty_count * 2, colors + empty_count * 3); +} diff --git a/tools/trainer/CombinedTilesSampler.h b/tools/trainer/CombinedTilesSampler.h new file mode 100644 index 0000000..6fbb23b --- /dev/null +++ b/tools/trainer/CombinedTilesSampler.h @@ -0,0 +1,17 @@ +#pragma once +#include "EmptyTilesSampler.h" +#include "DetailedTilesSampler.h" + +class CombinedTilesSampler +{ +public: + CombinedTilesSampler(uint32_t batch_size, const std::string & empty_tiles_path, const std::string & tiles_path); + void sample(cudaStream_t stream, tcnn::default_rng_t & rng, float * coords, float * colors); + + uint32_t batch_size() const; + +private: + const uint32_t batch_size_; + EmptyTilesSampler empty_tiles_sampler_; + DetailedTilesSampler detailed_tiles_sampler_; +}; diff --git a/tools/trainer/Common.h b/tools/trainer/Common.h new file mode 100644 index 0000000..10a7974 --- /dev/null +++ b/tools/trainer/Common.h @@ -0,0 +1,29 @@ +#pragma once +#include +#include + +#include + +template +std::vector read_file_content(const std::string & path) +{ + std::ifstream f(path, std::ios_base::binary); + f.seekg(0, std::ios::end); + const size_t size = f.tellg(); + f.seekg(0, std::ios::beg); + std::vector result(size / sizeof(T)); + f.read(reinterpret_cast(result.data()), size); + if (!f) + { + throw std::runtime_error("Failded to read file content"); + } + return result; +} + +template +tcnn::GPUMemory to_gpu(const std::vector & data) +{ + tcnn::GPUMemory result(data.size()); + result.copy_from_host(data); + return result; +} diff --git a/tools/trainer/DetailedTilesSampler.cu b/tools/trainer/DetailedTilesSampler.cu new file mode 100644 index 0000000..77a9e66 --- /dev/null +++ b/tools/trainer/DetailedTilesSampler.cu @@ -0,0 +1,129 @@ +#include + +#include "Common.h" +#include "DetailedTilesSampler.h" + +using namespace DetailedTilesSamplerImpl; + +namespace +{ + +inline Color to_color(uint8_t index) +{ + switch (index) + { + case 1: return Color{1.f, 0.f, 0.f}; + case 2: return Color{0.f, 1.f, 0.f}; + case 3: return Color{0.f, 0.f, 1.f}; + } + return {}; +} + +inline Color get_color(const Tile & tile, uint32_t x, uint32_t y) +{ + const auto subindex = x % 4; + const auto c = (tile.data[tile.resolution / 4 * (tile.resolution - 1 - y) + x / 4] >> (subindex * 2)) & 3; + return to_color(c); +} + +} + +namespace DetailedTilesSamplerImpl +{ + +class Generator +{ +public: + Generator(uint32_t resolution, const std::vector & tiles, ThreadSafeQueue & free_batches, ThreadSafeQueue & batches) + : tiles_(tiles) + , tile_count_(tiles.size()) + , inv_resolution_(1.f / resolution) + , free_batches_(free_batches) + , batches_(batches) + , thread_(&Generator::run, this) + , rng_(rand()) + { + } + + std::pair get_sample() + { + const auto i = rng_.next_uint(tile_count_); + const auto & tile = tiles_[i]; + const auto pixel_x = rng_.next_uint(tile.resolution); + const auto pixel_y = rng_.next_uint(tile.resolution); + const auto x = (static_cast(tile.x) + (pixel_x + 0.5f) * tile.inv_resolution) * inv_resolution_; + const auto y = (static_cast(tile.y) + (pixel_y + 0.5f) * tile.inv_resolution) * inv_resolution_; + const auto color = get_color(tile, pixel_x, pixel_y); + return {Coord{x, y}, color}; + } + + ~Generator() + { + thread_.join(); + } + +private: + void run() + { + while (true) + { + auto batch = free_batches_.pop(); + auto coords = batch.coords.data(); + auto colors = batch.colors.data(); + if (batch.coords.empty()) + { + return; + } + for (size_t i = 0, n = batch.coords.size(); i < n; ++i) + { + const auto sample = get_sample(); + coords[i] = sample.first; + colors[i] = sample.second; + } + batches_.push(std::move(batch)); + } + } + + std::thread thread_; + const std::vector & tiles_; + const uint32_t tile_count_; + const float inv_resolution_; + ThreadSafeQueue & free_batches_; + ThreadSafeQueue & batches_; + tcnn::default_rng_t rng_; +}; + +} + +DetailedTilesSampler::DetailedTilesSampler(uint32_t batch_size, uint32_t resolution, const std::string & path) + : batch_size_(batch_size) + , tiles_(read_file_content(path)) +{ + constexpr auto generator_count = 8; + constexpr auto sample_queue_size = generator_count * 2; + for (auto i = 0; i < sample_queue_size; ++i) + { + free_batches_.push(Batch{HostMemory(batch_size), HostMemory(batch_size)}); + } + generators_.reserve(generator_count); + for (auto i = 0; i < generator_count; ++i) + { + generators_.push_back(std::make_shared(resolution, tiles_, free_batches_, batches_)); + } +} + +void DetailedTilesSampler::sample(float * coords, float * colors) +{ + auto batch = batches_.pop(); + CUDA_CHECK_THROW(cudaMemcpy(coords, batch.coords.data(), batch.coords.size() * sizeof(Coord), cudaMemcpyHostToDevice)); + CUDA_CHECK_THROW(cudaMemcpy(colors, batch.colors.data(), batch.colors.size() * sizeof(Color), cudaMemcpyHostToDevice)); + free_batches_.push(std::move(batch)); +} + +DetailedTilesSampler::~DetailedTilesSampler() +{ + for (size_t i = 0; i < generators_.size(); ++i) + { + free_batches_.push({}); + } +} diff --git a/tools/trainer/DetailedTilesSampler.h b/tools/trainer/DetailedTilesSampler.h new file mode 100644 index 0000000..dc91355 --- /dev/null +++ b/tools/trainer/DetailedTilesSampler.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include "HostMemory.h" +#include "ThreadSafeQueue.h" + +namespace DetailedTilesSamplerImpl +{ + +struct Tile +{ + static constexpr uint32_t resolution = 32; + static constexpr float inv_resolution = 1.f / resolution; + static constexpr uint32_t pixel_count = resolution * resolution; + static constexpr uint32_t data_size = pixel_count / 4; + + uint32_t x; + uint32_t y; + std::array data; +}; + +struct Coord +{ + float x; + float y; +}; +struct Color +{ + float r; + float g; + float b; +}; + +struct Batch +{ + HostMemory coords; + HostMemory colors; +}; + +class Generator; +using GeneratorPtr = std::shared_ptr; + +} // namespace DetailedTilesSamplerImpl + +class DetailedTilesSampler +{ +public: + DetailedTilesSampler(uint32_t batch_size, uint32_t resolution, const std::string & path); + void sample(float * coords, float * colors); + ~DetailedTilesSampler(); + +private: + using Tile = DetailedTilesSamplerImpl::Tile; + using Coord = DetailedTilesSamplerImpl::Coord; + using Color = DetailedTilesSamplerImpl::Color; + using Batch = DetailedTilesSamplerImpl::Batch; + using GeneratorPtr = DetailedTilesSamplerImpl::GeneratorPtr; + std::pair get_sample(tcnn::default_rng_t & rng) const; + + const uint32_t batch_size_; + std::vector tiles_; + ThreadSafeQueue free_batches_; + ThreadSafeQueue batches_; + std::vector generators_; +}; diff --git a/tools/trainer/EmptyTilesSampler.cu b/tools/trainer/EmptyTilesSampler.cu new file mode 100644 index 0000000..69bbd6f --- /dev/null +++ b/tools/trainer/EmptyTilesSampler.cu @@ -0,0 +1,84 @@ +#include + +#include "Common.h" +#include "EmptyTilesSampler.h" + +namespace +{ + +__global__ void get_sample( + uint32_t n_elements, + uint32_t resolution, + tcnn::default_rng_t rng, + const uint8_t * __restrict__ data, + float * __restrict__ coords, + float * __restrict__ colors) +{ + const auto i = blockIdx.x * blockDim.x + threadIdx.x; + if (i >= n_elements) return; + + const auto coord_idx = i * 2; + const auto color_idx = i * 3; + + rng.advance(i * 100); + + float x; + float y; + uint32_t c; + + while (true) + { + x = rng.next_float(); + y = rng.next_float(); + const auto ix = static_cast(x * resolution); + const auto iy = static_cast(y * resolution); + const auto subindex = ix % 4; + c = (data[resolution / 4 * iy + ix / 4] >> (subindex * 2)) & 3; + if (c != 0) break; + } + + coords[coord_idx] = x; + coords[coord_idx + 1] = y; + + if (c == 1) + { + colors[color_idx] = 1.0f; + colors[color_idx + 1] = 0.0f; + colors[color_idx + 2] = 0.0f; + } + else if (c == 2) + { + colors[color_idx] = 0.0f; + colors[color_idx + 1] = 1.0f; + colors[color_idx + 2] = 0.0f; + } + else + { + colors[color_idx] = 0.0f; + colors[color_idx + 1] = 0.0f; + colors[color_idx + 2] = 1.0f; + } +} + +} + +EmptyTilesSampler::EmptyTilesSampler(const std::string & path) + : data_(to_gpu(read_file_content(path))) + , resolution_(static_cast(std::sqrt(data_.size() * 4))) +{} + +void EmptyTilesSampler::sample( + cudaStream_t stream, + tcnn::default_rng_t & rng, + uint32_t count, + float * coords, + float * colors) const +{ + tcnn::linear_kernel(get_sample, 0, stream, count, resolution_, rng, data_.data(), coords, colors); + rng.advance(count * 100); +} + +uint32_t EmptyTilesSampler::resolution() const +{ + return resolution_; +} diff --git a/tools/trainer/EmptyTilesSampler.h b/tools/trainer/EmptyTilesSampler.h new file mode 100644 index 0000000..deaad29 --- /dev/null +++ b/tools/trainer/EmptyTilesSampler.h @@ -0,0 +1,18 @@ +#include +#include + +#include +#include + +class EmptyTilesSampler +{ +public: + EmptyTilesSampler(const std::string & path); + void sample(cudaStream_t stream, tcnn::default_rng_t & rng, uint32_t count, float * coords, float * colors) const; + + uint32_t resolution() const; + +private: + tcnn::GPUMemory data_; + const uint32_t resolution_; +}; diff --git a/tools/trainer/HostMemory.h b/tools/trainer/HostMemory.h new file mode 100644 index 0000000..35d8283 --- /dev/null +++ b/tools/trainer/HostMemory.h @@ -0,0 +1,74 @@ +#pragma once +#include + +#include + +template +class HostMemory +{ +public: + HostMemory(const HostMemory & other) = delete; + HostMemory(HostMemory && other) + : size_(other.size_) + , data_(other.data_) + { + other.size_ = 0; + other.data_ = nullptr; + } + + HostMemory() + : HostMemory(0) + { + } + + explicit HostMemory(size_t size) + : size_(size) + { + if (size != 0) + { + CUDA_CHECK_THROW(cudaHostAlloc(reinterpret_cast(&data_), size * sizeof(T), 0)); + } + } + + HostMemory & operator=(const HostMemory & other) = delete; + + HostMemory & operator=(HostMemory && other) + { + size_ = other.size_; + data_ = other.data_; + other.size_ = 0; + other.data_ = nullptr; + } + + ~HostMemory() + { + if (data_) + { + cudaFreeHost(data_); + } + } + + bool empty() const + { + return size_ == 0; + } + + size_t size() const + { + return size_; + } + + T * data() + { + return data_; + } + + const T * data() const + { + return data_; + } + +private: + size_t size_; + T * data_ = nullptr; +}; diff --git a/tools/trainer/Model.cu b/tools/trainer/Model.cu new file mode 100644 index 0000000..83a7d67 --- /dev/null +++ b/tools/trainer/Model.cu @@ -0,0 +1,110 @@ +#include + +#include + +#include "Model.h" +#include "SoftmaxCrossEntropyLoss.h" + +using json = nlohmann::json; +using precision_t = tcnn::network_precision_t; + +namespace +{ + +template +std::vector to_cpu(const T * gpu_data, const size_t size) +{ + std::vector result(size); + cudaMemcpy(result.data(), gpu_data, size * sizeof(T), cudaMemcpyDeviceToHost); + return result; +} + +std::shared_ptr> create_loss(const json & opts) +{ + const auto type = opts.value("otype", std::string()); + if (type == "SoftmaxCrossEntropyLoss") + { + return std::make_shared>(); + } + + return std::shared_ptr>{tcnn::create_loss(opts)}; +} + +} + +Model::Model(const std::string & config_path) +{ + std::ifstream config_file(config_path); + json config = json::parse(config_file); + + json encoding_opts = config.value("encoding", json::object()); + json loss_opts = config.value("loss", json::object()); + json optimizer_opts = config.value("optimizer", json::object()); + json network_opts = config.value("network", json::object()); + hidden_layer_neuron_count_ = network_opts.value("n_neurons", 0); + encoding_level_count_ = encoding_opts.value("n_levels", 0); + + std::shared_ptr> loss{create_loss(loss_opts)}; + std::shared_ptr> optimizer{tcnn::create_optimizer(optimizer_opts)}; + network_ = std::make_shared>(2, 3, encoding_opts, network_opts); + encoding_ = &dynamic_cast&>(*network_->encoding()); + + trainer_ = std::make_shared>(network_, optimizer, loss); +} + +unsigned int Model::level_count() const +{ + return encoding_level_count_; +} + +float Model::training_step( + cudaStream_t stream, + const tcnn::GPUMatrix & input, + const tcnn::GPUMatrix & target, + bool calculate_loss) +{ + auto ctx = trainer_->training_step(stream, input, target); + if (calculate_loss) + { + return trainer_->loss(stream, *ctx); + } + return 0.0f; +} + +void Model::inference(cudaStream_t stream, + const tcnn::GPUMatrix & input, + tcnn::GPUMatrix & output) +{ + network_->inference(stream, input, output); +} + +void Model::set_max_levels(float * levels) +{ + encoding_->set_max_level_gpu(levels); +} + +void Model::set_max_level(float level) +{ + encoding_->set_max_level_gpu(nullptr); + encoding_->set_max_level(level); +} + +void Model::save() +{ + const auto encoding_params_count = encoding_->n_params(); + const auto network_params_count = trainer_->model()->n_params() - encoding_params_count; + { + std::ofstream f("network.data", std::ios::binary); + const auto count = network_params_count - hidden_layer_neuron_count_ * 4; + auto params = to_cpu(trainer_->params(), count); + f.write(reinterpret_cast(params.data()), count * sizeof(float)); + } + { + const auto json = trainer_->serialize(); + json::binary_t params = json["params_binary"]; + std::ofstream f("encoding.data", std::ios::binary); + f.write(reinterpret_cast( + params.data()) + network_params_count * sizeof(precision_t), + encoding_params_count * sizeof(precision_t)); + } +} diff --git a/tools/trainer/Model.h b/tools/trainer/Model.h new file mode 100644 index 0000000..61bb639 --- /dev/null +++ b/tools/trainer/Model.h @@ -0,0 +1,28 @@ +#pragma once +#include +#include +#include + +class Model +{ +public: + Model(const std::string & config_path); + float training_step( + cudaStream_t stream, + const tcnn::GPUMatrix & input, + const tcnn::GPUMatrix & target, + bool calculate_loss); + void inference(cudaStream_t stream, const tcnn::GPUMatrix & input, tcnn::GPUMatrix & output); + unsigned int level_count() const; + void set_max_levels(float * levels); + void set_max_level(float level); + void save(); + +private: + using precision_t = tcnn::network_precision_t; + std::shared_ptr> network_; + std::shared_ptr> trainer_; + tcnn::GridEncoding * encoding_ = nullptr; + unsigned int hidden_layer_neuron_count_ = 0; + unsigned int encoding_level_count_ = 0; +}; diff --git a/tools/trainer/SoftmaxCrossEntropyLoss.h b/tools/trainer/SoftmaxCrossEntropyLoss.h new file mode 100644 index 0000000..ecda1c2 --- /dev/null +++ b/tools/trainer/SoftmaxCrossEntropyLoss.h @@ -0,0 +1,114 @@ +#pragma once +#include +#include +#include +#include + +template +__global__ void softmax_cross_entropy_loss( + const uint32_t n_elements, + const uint32_t stride, + const uint32_t dims, + const float loss_scale, + const T * __restrict__ predictions, + const float * __restrict__ targets, + float * __restrict__ values, + T * __restrict__ gradients) +{ + const uint32_t i = threadIdx.x + blockIdx.x * blockDim.x; + if (i >= n_elements) + return; + + const uint32_t dim_idx = i % stride; + const uint32_t elem_idx = i / stride; + if (dim_idx >= dims) + { + values[i] = 0; + gradients[i] = 0; + return; + } + + const uint32_t target_idx = elem_idx * dims + dim_idx; + + const uint32_t n_total = n_elements / stride * dims; + + float exp_sum = 0.f; + for (uint32_t j = 0; j < dims; ++j) + { + exp_sum += expf((float)predictions[elem_idx * stride + j]); + } + + const float target = (float)targets[target_idx]; + const float prediction = (float)predictions[i]; + const float softmax = expf(prediction) / exp_sum; + + const float value = -target * (prediction - logf(exp_sum)); + const float gradient = softmax - target; + + values[i] = (T)(value / n_total); + gradients[i] = (T)(loss_scale * gradient / n_total); +} + +template +class SoftmaxCrossEntropyLoss : public tcnn::Loss +{ +public: + void evaluate( + cudaStream_t stream, + const uint32_t stride, + const uint32_t dims, + const float loss_scale, + const tcnn::GPUMatrix & prediction, + const tcnn::GPUMatrix & target, + tcnn::GPUMatrix & values, + tcnn::GPUMatrix & gradients, + const tcnn::GPUMatrix * data_pdf = nullptr) const override + { + (void)data_pdf; + + if (prediction.n() != target.n()) + { + throw std::runtime_error( + std::string("Prediction and target don't have matching batch size ") + std::to_string(prediction.n()) + + "!=" + std::to_string(target.n())); + } + + if (prediction.m() != stride) + { + throw std::runtime_error( + std::string("Prediction does not have appropriate dimensions ") + std::to_string(prediction.m()) + + "!=" + std::to_string(stride)); + } + + if (target.m() != dims) + { + throw std::runtime_error( + std::string("Target does not have appropriate dimensions ") + std::to_string(target.m()) + + "!=" + std::to_string(dims)); + } + + tcnn::linear_kernel( + softmax_cross_entropy_loss, + 0, + stream, + prediction.n_elements(), + stride, + dims, + loss_scale, + prediction.data(), + target.data(), + values.data(), + gradients.data()); + } + + void update_hyperparams(const nlohmann::json & params) override + { + } + + nlohmann::json hyperparams() const override + { + return { + {"otype", "SoftmaxCrossEntropyLoss"}, + }; + } +}; diff --git a/tools/trainer/ThreadSafeQueue.h b/tools/trainer/ThreadSafeQueue.h new file mode 100644 index 0000000..ede3c0a --- /dev/null +++ b/tools/trainer/ThreadSafeQueue.h @@ -0,0 +1,35 @@ +#pragma once +#include +#include +#include + +template +class ThreadSafeQueue +{ +public: + void push(T v) + { + std::lock_guard lock(mutex_); + queue_.push(std::move(v)); + cv_.notify_all(); + } + + T pop() + { + std::unique_lock lock(mutex_); + cv_.wait( + lock, + [this]() + { + return !queue_.empty(); + }); + auto result = std::move(queue_.front()); + queue_.pop(); + return result; + } + +private: + std::queue queue_; + std::mutex mutex_; + std::condition_variable cv_; +}; diff --git a/tools/trainer/config.json b/tools/trainer/config.json new file mode 100644 index 0000000..eeefbf8 --- /dev/null +++ b/tools/trainer/config.json @@ -0,0 +1,34 @@ +{ + "loss": { + "otype": "SoftmaxCrossEntropyLoss" + }, + "optimizer": { + "otype": "ExponentialDecay", + "decay_start": 1000000, + "decay_interval": 100000, + "decay_base": 0.5, + "nested": { + "otype": "Adam", + "learning_rate": 1e-2, + "beta1": 0.9, + "beta2": 0.99, + "epsilon": 1e-15, + "l2_reg": 1e-6 + } + }, + "encoding": { + "otype": "HashGrid", + "n_levels": 12, + "n_features_per_level": 4, + "log2_hashmap_size": 22, + "base_resolution": 32, + "per_level_scale": 2 + }, + "network": { + "otype": "CutlassMLP", + "activation": "Tanh", + "output_activation": "None", + "n_neurons": 8, + "n_hidden_layers": 1 + } +} \ No newline at end of file diff --git a/tools/trainer/main.cu b/tools/trainer/main.cu new file mode 100644 index 0000000..c5e89ae --- /dev/null +++ b/tools/trainer/main.cu @@ -0,0 +1,84 @@ +#include + +#include + +#include "Accuracy.h" +#include "CombinedTilesSampler.h" +#include "Model.h" + +void train(cudaStream_t stream, tcnn::default_rng_t & rng, Model & model, uint32_t steps, CombinedTilesSampler & tiles_sampler) +{ + const auto batch_size = tiles_sampler.batch_size(); + const auto n_input_dims = 2; + const auto n_output_dims = 3; + + tcnn::GPUMatrix coords(n_input_dims, batch_size); + tcnn::GPUMatrix colors(n_output_dims, batch_size); + + tcnn::GPUMemory max_levels(batch_size); + model.set_max_levels(max_levels.data()); + + auto begin = std::chrono::steady_clock::now(); + + float loss_sum = 0; + uint32_t loss_counter = 0; + + std::cout << "Beginning training with " << steps << " steps." << std::endl; + + uint32_t interval = 1000; + for (uint32_t i = 1; i <= steps; ++i) + { + bool print_loss = i % interval == 0; + + tiles_sampler.sample(stream, rng, coords.data(), colors.data()); + tcnn::generate_random_uniform(stream, rng, batch_size, max_levels.data(), 3.0f / model.level_count(), 1.5f); + + bool get_loss = i % 100 == 0; + const auto loss = model.training_step(stream, coords, colors, get_loss); + if (get_loss) + { + loss_sum += loss; + ++loss_counter; + } + + if (print_loss) + { + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + std::cout << "Step#" << i << ": " << "loss=" << loss_sum/(float)loss_counter << " time=" << std::chrono::duration_cast(end - begin).count() << "[ms]" << std::endl; + + loss_sum = 0; + loss_counter = 0; + begin = std::chrono::steady_clock::now(); + } + } + model.set_max_levels(nullptr); +} + +int main(int argc, char* argv[]) +{ + cxxopts::Options options_parser("trainer"); + options_parser.add_options() + ("config", "", cxxopts::value()) + ("tiles", "", cxxopts::value()) + ("empty_tiles", "", cxxopts::value()) + ("steps", "", cxxopts::value()->default_value("10000")); + const auto opt = options_parser.parse(argc, argv); + + CombinedTilesSampler tiles_sampler( + 1 << 16, + opt["empty_tiles"].as(), + opt["tiles"].as() + ); + + Model model(opt["config"].as()); + const auto n_training_steps = opt["steps"].as(); + + tcnn::default_rng_t rng(123); + + cudaStream_t stream; + CUDA_CHECK_THROW(cudaStreamCreate(&stream)); + + train(stream, rng, model, n_training_steps, tiles_sampler); + model.save(); + print_accuracy(stream, rng, tiles_sampler, model); +}