mirror of
https://gitlab.bixilon.de/bixilon/minosoft.git
synced 2025-09-17 19:35:00 -04:00
far improved mipmap generation
This commit is contained in:
parent
b9ce81e9db
commit
fb540c5a53
@ -27,6 +27,12 @@ class RGBColor(val rgba: Int) : ChatCode, TextFormattable {
|
|||||||
|
|
||||||
constructor(red: Double, green: Double, blue: Double, alpha: Double = 1.0) : this(red.toFloat(), green.toFloat(), blue.toFloat(), alpha.toFloat())
|
constructor(red: Double, green: Double, blue: Double, alpha: Double = 1.0) : this(red.toFloat(), green.toFloat(), blue.toFloat(), alpha.toFloat())
|
||||||
|
|
||||||
|
val argb: Int
|
||||||
|
get() = (alpha shl 24) or (red shl 16) or (green shl 8) or blue
|
||||||
|
|
||||||
|
val abgr: Int
|
||||||
|
get() = (alpha shl 24) or (blue shl 16) or (green shl 8) or red
|
||||||
|
|
||||||
val alpha: @IntRange(from = 0L, to = 255L) Int
|
val alpha: @IntRange(from = 0L, to = 255L) Int
|
||||||
get() = rgba and 0xFF
|
get() = rgba and 0xFF
|
||||||
|
|
||||||
|
@ -15,11 +15,11 @@ package de.bixilon.minosoft.gui.rendering.system.base.texture.texture
|
|||||||
|
|
||||||
import de.bixilon.minosoft.data.assets.AssetsManager
|
import de.bixilon.minosoft.data.assets.AssetsManager
|
||||||
import de.bixilon.minosoft.data.registries.ResourceLocation
|
import de.bixilon.minosoft.data.registries.ResourceLocation
|
||||||
import de.bixilon.minosoft.data.text.RGBColor
|
|
||||||
import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureStates
|
import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureStates
|
||||||
import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureTransparencies
|
import de.bixilon.minosoft.gui.rendering.system.base.texture.TextureTransparencies
|
||||||
import de.bixilon.minosoft.gui.rendering.system.opengl.texture.OpenGLTextureArray
|
import de.bixilon.minosoft.gui.rendering.system.opengl.texture.OpenGLTextureArray
|
||||||
import de.bixilon.minosoft.gui.rendering.textures.properties.ImageProperties
|
import de.bixilon.minosoft.gui.rendering.textures.properties.ImageProperties
|
||||||
|
import example.jonathan2520.SRGBAverager
|
||||||
import glm_.vec2.Vec2
|
import glm_.vec2.Vec2
|
||||||
import glm_.vec2.Vec2i
|
import glm_.vec2.Vec2i
|
||||||
import org.lwjgl.BufferUtils
|
import org.lwjgl.BufferUtils
|
||||||
@ -41,119 +41,54 @@ interface AbstractTexture {
|
|||||||
fun load(assetsManager: AssetsManager)
|
fun load(assetsManager: AssetsManager)
|
||||||
|
|
||||||
|
|
||||||
fun generateMipMaps(): Array<Pair<Vec2i, ByteBuffer>> {
|
fun generateMipMaps(): Array<ByteBuffer> {
|
||||||
val ret: MutableList<Pair<Vec2i, ByteBuffer>> = mutableListOf()
|
val images: MutableList<ByteBuffer> = mutableListOf()
|
||||||
var lastBuffer = data!!
|
|
||||||
var lastSize = size
|
var data = data!!
|
||||||
for (i in 0 until OpenGLTextureArray.MAX_MIPMAP_LEVELS) {
|
|
||||||
val size = Vec2i(size.x shr i, size.y shr i)
|
images += data
|
||||||
if (i != 0 && size.x != 0 && size.y != 0) {
|
|
||||||
lastBuffer = generateMipmap(lastBuffer, lastSize, size)
|
for (i in 1 until OpenGLTextureArray.MAX_MIPMAP_LEVELS) {
|
||||||
lastSize = size
|
val mipMapSize = Vec2i(size.x shr i, size.y shr i)
|
||||||
|
if (mipMapSize.x <= 0 || mipMapSize.y <= 0) {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
ret += Pair(size, lastBuffer)
|
data = generateMipmap(data, Vec2i(size.x shr (i - 1), size.y shr (i - 1)))
|
||||||
|
images += data
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret.toTypedArray()
|
return images.toTypedArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun generateMipmap(origin: ByteBuffer, oldSize: Vec2i): ByteBuffer {
|
||||||
|
// No Vec2i: performance reasons
|
||||||
|
val oldSizeX = oldSize.x
|
||||||
|
val newSizeX = oldSizeX shr 1
|
||||||
|
|
||||||
private fun ByteBuffer.getRGB(start: Int): RGBColor {
|
val buffer = BufferUtils.createByteBuffer(origin.capacity() shr 1)
|
||||||
return RGBColor(get(start), get(start + 1), get(start + 2), get(start + 3))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun ByteBuffer.setRGB(start: Int, color: RGBColor) {
|
|
||||||
put(start, color.red.toByte())
|
|
||||||
put(start + 1, color.green.toByte())
|
|
||||||
put(start + 2, color.blue.toByte())
|
|
||||||
put(start + 3, color.alpha.toByte())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated(message = "This is garbage, will be improved soon...")
|
|
||||||
private fun generateMipmap(biggerBuffer: ByteBuffer, oldSize: Vec2i, newSize: Vec2i): ByteBuffer {
|
|
||||||
val sizeFactor = oldSize / newSize
|
|
||||||
val buffer = BufferUtils.createByteBuffer(biggerBuffer.capacity() shr 1)
|
|
||||||
buffer.limit(buffer.capacity())
|
buffer.limit(buffer.capacity())
|
||||||
|
|
||||||
fun getRGB(x: Int, y: Int): RGBColor {
|
fun getRGB(x: Int, y: Int): Int {
|
||||||
return biggerBuffer.getRGB((y * oldSize.x + x) * 4)
|
return origin.getInt((y * oldSizeX + x) * 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setRGB(x: Int, y: Int, color: RGBColor) {
|
fun setRGB(x: Int, y: Int, color: Int) {
|
||||||
buffer.setRGB((y * newSize.x + x) * 4, color)
|
buffer.putInt((y * newSizeX + x) * 4, color)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (y in 0 until newSize.y) {
|
for (y in 0 until (oldSize.y shr 1)) {
|
||||||
for (x in 0 until newSize.x) {
|
for (x in 0 until newSizeX) {
|
||||||
|
val xOffset = x * 2
|
||||||
|
val yOffset = y * 2
|
||||||
|
|
||||||
// check what is the most used transparency
|
val output = SRGBAverager.average(
|
||||||
val transparencyPixelCount = IntArray(TextureTransparencies.VALUES.size)
|
getRGB(xOffset + 0, yOffset + 0),
|
||||||
for (mixY in 0 until sizeFactor.y) {
|
getRGB(xOffset + 1, yOffset + 0),
|
||||||
for (mixX in 0 until sizeFactor.x) {
|
getRGB(xOffset + 0, yOffset + 1),
|
||||||
val color = getRGB(x * sizeFactor.x + mixX, y * sizeFactor.y + mixY)
|
getRGB(xOffset + 1, yOffset + 1),
|
||||||
when (color.alpha) {
|
)
|
||||||
255 -> transparencyPixelCount[TextureTransparencies.OPAQUE.ordinal]++
|
|
||||||
0 -> transparencyPixelCount[TextureTransparencies.TRANSPARENT.ordinal]++
|
|
||||||
else -> transparencyPixelCount[TextureTransparencies.TRANSLUCENT.ordinal]++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var largest = 0
|
|
||||||
for (count in transparencyPixelCount) {
|
|
||||||
if (count > largest) {
|
|
||||||
largest = count
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var transparency: TextureTransparencies = TextureTransparencies.OPAQUE
|
|
||||||
for ((index, count) in transparencyPixelCount.withIndex()) {
|
|
||||||
if (count >= largest) {
|
|
||||||
transparency = TextureTransparencies[index]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var count = 0
|
setRGB(x, y, output)
|
||||||
var red = 0
|
|
||||||
var green = 0
|
|
||||||
var blue = 0
|
|
||||||
var alpha = 0
|
|
||||||
|
|
||||||
// make magic for the most used transparency
|
|
||||||
for (mixY in 0 until sizeFactor.y) {
|
|
||||||
for (mixX in 0 until sizeFactor.x) {
|
|
||||||
val color = getRGB(x * sizeFactor.x + mixX, y * sizeFactor.y + mixY)
|
|
||||||
when (transparency) {
|
|
||||||
TextureTransparencies.OPAQUE -> {
|
|
||||||
if (color.alpha != 0xFF) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
red += color.red
|
|
||||||
green += color.green
|
|
||||||
blue += color.blue
|
|
||||||
alpha += color.alpha
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
TextureTransparencies.TRANSPARENT -> {
|
|
||||||
}
|
|
||||||
TextureTransparencies.TRANSLUCENT -> {
|
|
||||||
red += color.red
|
|
||||||
green += color.green
|
|
||||||
blue += color.blue
|
|
||||||
alpha += color.alpha
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (count == 0) {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
setRGB(x, y, RGBColor(red / count, green / count, blue / count, alpha / count))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,20 +135,21 @@ class OpenGLTextureArray(
|
|||||||
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT)
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_REPEAT)
|
||||||
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT)
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_REPEAT)
|
||||||
// glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
|
// glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
|
||||||
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST)
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)
|
||||||
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
|
||||||
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, MAX_MIPMAP_LEVELS - 1)
|
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAX_LEVEL, MAX_MIPMAP_LEVELS - 1)
|
||||||
|
|
||||||
for (i in 0 until MAX_MIPMAP_LEVELS) {
|
for (level in 0 until MAX_MIPMAP_LEVELS) {
|
||||||
glTexImage3D(GL_TEXTURE_2D_ARRAY, i, GL_RGBA, resolution shr i, resolution shr i, textures.size, 0, GL_RGBA, GL_UNSIGNED_BYTE, null as ByteBuffer?)
|
glTexImage3D(GL_TEXTURE_2D_ARRAY, level, GL_RGBA, resolution shr level, resolution shr level, textures.size, 0, GL_RGBA, GL_UNSIGNED_BYTE, null as ByteBuffer?)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (texture in textures) {
|
for (texture in textures) {
|
||||||
val mipMaps = texture.generateMipMaps()
|
val mipMaps = texture.generateMipMaps()
|
||||||
|
|
||||||
val renderData = texture.renderData as OpenGLTextureData
|
val renderData = texture.renderData as OpenGLTextureData
|
||||||
for ((mipMapLevel, data) in mipMaps.withIndex()) {
|
for ((level, data) in mipMaps.withIndex()) {
|
||||||
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, mipMapLevel, 0, 0, renderData.index, data.first.x, data.first.y, mipMapLevel + 1, GL_RGBA, GL_UNSIGNED_BYTE, data.second)
|
val size = texture.size shr level
|
||||||
|
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, level, 0, 0, renderData.index, size.x, size.y, level + 1, GL_RGBA, GL_UNSIGNED_BYTE, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
72
src/main/java/example/jonathan2520/SRGBAverager.java
Normal file
72
src/main/java/example/jonathan2520/SRGBAverager.java
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Averaging of texels for mipmap generation.
|
||||||
|
|
||||||
|
package example.jonathan2520;
|
||||||
|
|
||||||
|
public class SRGBAverager {
|
||||||
|
private static final SRGBTable srgb = new SRGBTable();
|
||||||
|
|
||||||
|
public static int average(int c0, int c1, int c2, int c3) {
|
||||||
|
if ((((c0 | c1 | c2 | c3) ^ (c0 & c1 & c2 & c3)) & 0xff000000) == 0) {
|
||||||
|
// Alpha values are all equal. Simplifies computation somewhat. It's
|
||||||
|
// also a reasonable fallback when all alpha values are zero, in
|
||||||
|
// which case the resulting color would normally be undefined.
|
||||||
|
// Defining it like this allows code that uses invisible colors for
|
||||||
|
// whatever reason to work. Note that Minecraft's original code
|
||||||
|
// would set the color to black; this is added functionality.
|
||||||
|
|
||||||
|
float r = srgb.decode(c0 & 0xff)
|
||||||
|
+ srgb.decode(c1 & 0xff)
|
||||||
|
+ srgb.decode(c2 & 0xff)
|
||||||
|
+ srgb.decode(c3 & 0xff);
|
||||||
|
|
||||||
|
float g = srgb.decode(c0 >> 8 & 0xff)
|
||||||
|
+ srgb.decode(c1 >> 8 & 0xff)
|
||||||
|
+ srgb.decode(c2 >> 8 & 0xff)
|
||||||
|
+ srgb.decode(c3 >> 8 & 0xff);
|
||||||
|
|
||||||
|
float b = srgb.decode(c0 >> 16 & 0xff)
|
||||||
|
+ srgb.decode(c1 >> 16 & 0xff)
|
||||||
|
+ srgb.decode(c2 >> 16 & 0xff)
|
||||||
|
+ srgb.decode(c3 >> 16 & 0xff);
|
||||||
|
|
||||||
|
return srgb.encode(0.25F * r)
|
||||||
|
| srgb.encode(0.25F * g) << 8
|
||||||
|
| srgb.encode(0.25F * b) << 16
|
||||||
|
| c0 & 0xff000000;
|
||||||
|
} else {
|
||||||
|
// The general case. Well-defined if at least one alpha value is
|
||||||
|
// not zero. If you do try to process all zeros, you get
|
||||||
|
// r = g = b = a = 0 which will NaN out in the division and produce
|
||||||
|
// invisible black. You could remove the other case if you're okay
|
||||||
|
// with that, but mind that producing or consuming a NaN causes an
|
||||||
|
// extremely slow exception handler to be run on many CPUs.
|
||||||
|
|
||||||
|
float a0 = c0 >>> 24;
|
||||||
|
float a1 = c1 >>> 24;
|
||||||
|
float a2 = c2 >>> 24;
|
||||||
|
float a3 = c3 >>> 24;
|
||||||
|
|
||||||
|
float r = a0 * srgb.decode(c0 & 0xff)
|
||||||
|
+ a1 * srgb.decode(c1 & 0xff)
|
||||||
|
+ a2 * srgb.decode(c2 & 0xff)
|
||||||
|
+ a3 * srgb.decode(c3 & 0xff);
|
||||||
|
|
||||||
|
float g = a0 * srgb.decode(c0 >> 8 & 0xff)
|
||||||
|
+ a1 * srgb.decode(c1 >> 8 & 0xff)
|
||||||
|
+ a2 * srgb.decode(c2 >> 8 & 0xff)
|
||||||
|
+ a3 * srgb.decode(c3 >> 8 & 0xff);
|
||||||
|
|
||||||
|
float b = a0 * srgb.decode(c0 >> 16 & 0xff)
|
||||||
|
+ a1 * srgb.decode(c1 >> 16 & 0xff)
|
||||||
|
+ a2 * srgb.decode(c2 >> 16 & 0xff)
|
||||||
|
+ a3 * srgb.decode(c3 >> 16 & 0xff);
|
||||||
|
|
||||||
|
float a = a0 + a1 + a2 + a3;
|
||||||
|
|
||||||
|
return srgb.encode(r / a)
|
||||||
|
| srgb.encode(g / a) << 8
|
||||||
|
| srgb.encode(b / a) << 16
|
||||||
|
| (int) (0.25F * a + 0.5F) << 24;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
src/main/java/example/jonathan2520/SRGBCalculator.java
Normal file
56
src/main/java/example/jonathan2520/SRGBCalculator.java
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// Offers very precise sRGB encoding and decoding.
|
||||||
|
|
||||||
|
// The actual values defining sRGB are alpha = 0.055 and gamma = 2.4.
|
||||||
|
|
||||||
|
// This class works directly from that definition to take advantage of all
|
||||||
|
// available precision, unlike pre-rounded constants like 12.92 that cause a
|
||||||
|
// comparatively humongous discontinuity at a point that should be of
|
||||||
|
// differentiability class C^1.
|
||||||
|
|
||||||
|
// Stored values are chosen to speed up bulk conversion somewhat.
|
||||||
|
|
||||||
|
package example.jonathan2520;
|
||||||
|
|
||||||
|
public class SRGBCalculator {
|
||||||
|
private final double decode_threshold;
|
||||||
|
private final double decode_slope;
|
||||||
|
private final double decode_multiplier;
|
||||||
|
private final double decode_addend;
|
||||||
|
private final double decode_exponent;
|
||||||
|
private final double encode_threshold;
|
||||||
|
private final double encode_slope;
|
||||||
|
private final double encode_multiplier;
|
||||||
|
private final double encode_addend;
|
||||||
|
private final double encode_exponent;
|
||||||
|
|
||||||
|
public SRGBCalculator(double gamma, double alpha) {
|
||||||
|
encode_multiplier = alpha + 1.0;
|
||||||
|
decode_multiplier = 1.0 / encode_multiplier;
|
||||||
|
encode_addend = -alpha;
|
||||||
|
decode_addend = decode_multiplier * alpha;
|
||||||
|
encode_exponent = 1.0 / gamma;
|
||||||
|
decode_exponent = gamma;
|
||||||
|
decode_threshold = alpha / (gamma - 1.0);
|
||||||
|
encode_threshold = Math.pow(gamma * decode_threshold * decode_multiplier, gamma);
|
||||||
|
encode_slope = decode_threshold / encode_threshold;
|
||||||
|
decode_slope = encode_threshold / decode_threshold;
|
||||||
|
}
|
||||||
|
|
||||||
|
public SRGBCalculator() {
|
||||||
|
this(2.4, 0.055);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double decode(double x) {
|
||||||
|
if (x < decode_threshold)
|
||||||
|
return decode_slope * x;
|
||||||
|
else
|
||||||
|
return Math.pow(x * decode_multiplier + decode_addend, decode_exponent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double encode(double x) {
|
||||||
|
if (x < encode_threshold)
|
||||||
|
return encode_slope * x;
|
||||||
|
else
|
||||||
|
return Math.pow(x, encode_exponent) * encode_multiplier + encode_addend;
|
||||||
|
}
|
||||||
|
}
|
71
src/main/java/example/jonathan2520/SRGBTable.java
Normal file
71
src/main/java/example/jonathan2520/SRGBTable.java
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// Offers fast sRGB encoding and decoding.
|
||||||
|
|
||||||
|
// Decoding is a straightforward table look-up.
|
||||||
|
|
||||||
|
// Encoding is a little more sophisticated. It's an exact conversion using about
|
||||||
|
// 4 kB of look-up tables that's also still quick. It relies on the fact that
|
||||||
|
// thresholds that would round to the next value have a minimum spacing of about
|
||||||
|
// 0.0003. That means that any range of 0.0003 contains at most one threshold.
|
||||||
|
// The to_int table contains the smaller value in the range. The threshold table
|
||||||
|
// contains the threshold above which the value should be one greater.
|
||||||
|
|
||||||
|
// The minimum scale value that maintains proper spacing is 255 * encode_slope
|
||||||
|
// or about 3295.4. You can get away with a little bit less like 3200, taking
|
||||||
|
// advantage of the alignment of thresholds, but it's not really worth it.
|
||||||
|
|
||||||
|
package example.jonathan2520;
|
||||||
|
|
||||||
|
public class SRGBTable {
|
||||||
|
private final float scale;
|
||||||
|
private final float[] to_float;
|
||||||
|
private final float[] threshold;
|
||||||
|
private final byte[] to_int;
|
||||||
|
|
||||||
|
public SRGBTable() {
|
||||||
|
this(new SRGBCalculator(), 3295.5F);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SRGBTable(SRGBCalculator calc, float scale) {
|
||||||
|
this.scale = scale;
|
||||||
|
to_float = new float[256];
|
||||||
|
threshold = new float[256];
|
||||||
|
to_int = new byte[(int) scale + 1];
|
||||||
|
for (int i = 0; i < 255; ++i) {
|
||||||
|
to_float[i] = (float) calc.decode(i / 255.0);
|
||||||
|
double dthresh = calc.decode((i + 0.5) / 255.0);
|
||||||
|
float fthresh = (float) dthresh;
|
||||||
|
if (fthresh >= dthresh)
|
||||||
|
fthresh = Math.nextAfter(fthresh, -1);
|
||||||
|
threshold[i] = fthresh;
|
||||||
|
}
|
||||||
|
to_float[255] = 1;
|
||||||
|
threshold[255] = Float.POSITIVE_INFINITY;
|
||||||
|
int offset = 0;
|
||||||
|
for (int i = 0; i < 255; ++i) {
|
||||||
|
int up_to = (int) (threshold[i] * scale);
|
||||||
|
build_to_int_table(offset, up_to, (byte) i);
|
||||||
|
offset = up_to + 1;
|
||||||
|
}
|
||||||
|
build_to_int_table(offset, (int) scale, (byte) 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void build_to_int_table(int offset, int up_to, byte value) {
|
||||||
|
if (offset > up_to)
|
||||||
|
throw new IllegalArgumentException("scale is too small");
|
||||||
|
while (offset <= up_to)
|
||||||
|
to_int[offset++] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// x in [0, 255]
|
||||||
|
public float decode(int x) {
|
||||||
|
return to_float[x];
|
||||||
|
}
|
||||||
|
|
||||||
|
// x in about [-0.0003, 1.00015]: tolerates rounding error on top of [0, 1]
|
||||||
|
public int encode(float x) {
|
||||||
|
int index = to_int[(int) (x * scale)] & 0xff;
|
||||||
|
if (x > threshold[index])
|
||||||
|
++index;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user