mirror of
https://github.com/Swordfish90/cool-retro-term.git
synced 2025-01-18 12:15:27 +00:00
Improve rasterization rendering and add LCD rasterization (with subpixels).
This commit is contained in:
parent
701cb540e5
commit
7714f7b503
@ -104,6 +104,7 @@ QtObject {
|
|||||||
readonly property int no_rasterization: 0
|
readonly property int no_rasterization: 0
|
||||||
readonly property int scanline_rasterization: 1
|
readonly property int scanline_rasterization: 1
|
||||||
readonly property int pixel_rasterization: 2
|
readonly property int pixel_rasterization: 2
|
||||||
|
readonly property int lcd_rasterization: 3
|
||||||
|
|
||||||
property int rasterization: no_rasterization
|
property int rasterization: no_rasterization
|
||||||
|
|
||||||
@ -145,6 +146,13 @@ QtObject {
|
|||||||
target: fontManager
|
target: fontManager
|
||||||
source: "FontPixels.qml"
|
source: "FontPixels.qml"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
when: rasterization == lcd_rasterization
|
||||||
|
PropertyChanges {
|
||||||
|
target: fontManager
|
||||||
|
source: "FontPixels.qml"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -89,6 +89,10 @@ Loader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShaderLibrary {
|
||||||
|
id: shaderLibrary
|
||||||
|
}
|
||||||
|
|
||||||
ShaderEffect {
|
ShaderEffect {
|
||||||
id: burnInShaderEffect
|
id: burnInShaderEffect
|
||||||
|
|
||||||
@ -118,9 +122,7 @@ Loader {
|
|||||||
|
|
||||||
uniform highp float prevLastUpdate;" +
|
uniform highp float prevLastUpdate;" +
|
||||||
|
|
||||||
"float rgb2grey(vec3 v){
|
shaderLibrary.rgb2grey +
|
||||||
return dot(v, vec3(0.21, 0.72, 0.04));
|
|
||||||
}" +
|
|
||||||
|
|
||||||
"void main() {
|
"void main() {
|
||||||
vec2 coords = qt_TexCoord0;
|
vec2 coords = qt_TexCoord0;
|
||||||
|
@ -34,6 +34,10 @@ ShaderEffect {
|
|||||||
|
|
||||||
property size aadelta: Qt.size(1.0 / width, 1.0 / height)
|
property size aadelta: Qt.size(1.0 / width, 1.0 / height)
|
||||||
|
|
||||||
|
ShaderLibrary {
|
||||||
|
id: shaderLibrary
|
||||||
|
}
|
||||||
|
|
||||||
fragmentShader: "
|
fragmentShader: "
|
||||||
#ifdef GL_ES
|
#ifdef GL_ES
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
@ -52,22 +56,14 @@ ShaderEffect {
|
|||||||
float dist = dot(cc, cc) * screenCurvature;
|
float dist = dot(cc, cc) * screenCurvature;
|
||||||
return (coords + cc * (1.0 + dist) * dist);
|
return (coords + cc * (1.0 + dist) * dist);
|
||||||
}
|
}
|
||||||
|
" +
|
||||||
|
|
||||||
float max2(vec2 v) {
|
shaderLibrary.max2 +
|
||||||
return max(v.x, v.y);
|
shaderLibrary.min2 +
|
||||||
}
|
shaderLibrary.prod2 +
|
||||||
|
shaderLibrary.sum2 +
|
||||||
|
|
||||||
float min2(vec2 v) {
|
"
|
||||||
return min(v.x, v.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
float prod2(vec2 v) {
|
|
||||||
return v.x * v.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
float sum2(vec2 v) {
|
|
||||||
return v.x + v.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
void main(){
|
void main(){
|
||||||
vec2 staticCoords = qt_TexCoord0;
|
vec2 staticCoords = qt_TexCoord0;
|
||||||
|
@ -41,7 +41,7 @@ ColumnLayout {
|
|||||||
property string selectedElement: model[currentIndex]
|
property string selectedElement: model[currentIndex]
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
model: [qsTr("Default"), qsTr("Scanlines"), qsTr("Pixels")]
|
model: [qsTr("Default"), qsTr("Scanlines"), qsTr("Pixels"), qsTr("LCD")]
|
||||||
currentIndex: appSettings.rasterization
|
currentIndex: appSettings.rasterization
|
||||||
onCurrentIndexChanged: {
|
onCurrentIndexChanged: {
|
||||||
appSettings.rasterization = currentIndex
|
appSettings.rasterization = currentIndex
|
||||||
|
88
app/qml/ShaderLibrary.qml
Normal file
88
app/qml/ShaderLibrary.qml
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import QtQuick 2.0
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
property string rasterizationShader:
|
||||||
|
(appSettings.rasterization === appSettings.no_rasterization ? "
|
||||||
|
lowp vec3 applyRasterization(vec2 screenCoords, lowp vec3 texel, vec2 virtualResolution, float intensity) {
|
||||||
|
return texel;
|
||||||
|
}" : "") +
|
||||||
|
|
||||||
|
(appSettings.rasterization === appSettings.scanline_rasterization ? "
|
||||||
|
#define INTENSITY 0.30
|
||||||
|
#define BRIGHTBOOST 0.30
|
||||||
|
|
||||||
|
lowp vec3 applyRasterization(vec2 screenCoords, lowp vec3 texel, vec2 virtualResolution, float intensity) {
|
||||||
|
lowp vec3 result = texel;
|
||||||
|
|
||||||
|
lowp vec3 pixelHigh = ((1.0 + BRIGHTBOOST) - (0.2 * texel)) * texel;
|
||||||
|
lowp vec3 pixelLow = ((1.0 - INTENSITY) + (0.1 * texel)) * texel;
|
||||||
|
|
||||||
|
vec2 coords = fract(screenCoords * virtualResolution) * 2.0 - vec2(1.0);
|
||||||
|
lowp float mask = 1.0 - abs(coords.y);
|
||||||
|
|
||||||
|
lowp vec3 rasterizationColor = mix(pixelLow, pixelHigh, mask);
|
||||||
|
return mix(texel, rasterizationColor, intensity);
|
||||||
|
}" : "") +
|
||||||
|
|
||||||
|
(appSettings.rasterization === appSettings.pixel_rasterization ? "
|
||||||
|
#define INTENSITY 0.30
|
||||||
|
#define BRIGHTBOOST 0.30
|
||||||
|
|
||||||
|
lowp vec3 applyRasterization(vec2 screenCoords, lowp vec3 texel, vec2 virtualResolution, float intensity) {
|
||||||
|
lowp vec3 result = texel;
|
||||||
|
|
||||||
|
lowp vec3 pixelHigh = ((1.0 + BRIGHTBOOST) - (0.2 * texel)) * texel;
|
||||||
|
lowp vec3 pixelLow = ((1.0 - INTENSITY) + (0.1 * texel)) * texel;
|
||||||
|
|
||||||
|
vec2 coords = fract(screenCoords * virtualResolution) * 2.0 - vec2(1.0);
|
||||||
|
coords = coords * coords;
|
||||||
|
lowp float mask = 1.0 - coords.x - coords.y;
|
||||||
|
|
||||||
|
lowp vec3 rasterizationColor = mix(pixelLow, pixelHigh, mask);
|
||||||
|
return mix(texel, rasterizationColor, intensity);
|
||||||
|
}" : "") +
|
||||||
|
|
||||||
|
(appSettings.rasterization === appSettings.lcd_rasterization ? "
|
||||||
|
#define brighten_scanlines 16.0
|
||||||
|
#define brighten_lcd 4.0
|
||||||
|
const vec3 offsets = vec3(3.141592654) * vec3(1.0/2.0,1.0/2.0 - 2.0/3.0,1.0/2.0-4.0/3.0);
|
||||||
|
|
||||||
|
lowp vec3 applyRasterization(vec2 screenCoords, lowp vec3 texel, vec2 virtualResolution, float intensity) {
|
||||||
|
vec2 omega = vec2(3.141592654) * vec2(2.0) * virtualResolution;
|
||||||
|
|
||||||
|
vec2 angle = screenCoords * omega;
|
||||||
|
|
||||||
|
float yfactor = (brighten_scanlines + sin(angle.y)) / (brighten_scanlines + 1.0);
|
||||||
|
vec3 xfactors = (brighten_lcd + sin(angle.x + offsets)) / (brighten_lcd + 1.0);
|
||||||
|
|
||||||
|
lowp vec3 rasterizationColor = yfactor * xfactors * texel;
|
||||||
|
return mix(texel, rasterizationColor, intensity);
|
||||||
|
}" : "") +
|
||||||
|
|
||||||
|
"\n\n"
|
||||||
|
|
||||||
|
property string min2: "
|
||||||
|
float min2(vec2 v) {
|
||||||
|
return min(v.x, v.y);
|
||||||
|
}\n\n"
|
||||||
|
|
||||||
|
property string rgb2grey: "
|
||||||
|
float rgb2grey(vec3 v) {
|
||||||
|
return dot(v, vec3(0.21, 0.72, 0.04));
|
||||||
|
}\n\n"
|
||||||
|
|
||||||
|
property string max2: "
|
||||||
|
float max2(vec2 v) {
|
||||||
|
return max(v.x, v.y);
|
||||||
|
}\n\n"
|
||||||
|
|
||||||
|
property string prod2: "
|
||||||
|
float prod2(vec2 v) {
|
||||||
|
return v.x * v.y;
|
||||||
|
}\n\n"
|
||||||
|
|
||||||
|
property string sum2: "
|
||||||
|
float sum2(vec2 v) {
|
||||||
|
return v.x + v.y;
|
||||||
|
}\n\n"
|
||||||
|
}
|
@ -38,11 +38,19 @@ Item {
|
|||||||
|
|
||||||
property real ambientLight: appSettings.ambientLight * 0.2
|
property real ambientLight: appSettings.ambientLight * 0.2
|
||||||
|
|
||||||
property size virtual_resolution
|
property size virtualResolution
|
||||||
|
property size screenResolution
|
||||||
|
|
||||||
|
property real _screenDensity: Math.min(
|
||||||
|
screenResolution.width / virtualResolution.width,
|
||||||
|
screenResolution.height / virtualResolution.height
|
||||||
|
)
|
||||||
|
|
||||||
ShaderEffect {
|
ShaderEffect {
|
||||||
id: dynamicShader
|
id: dynamicShader
|
||||||
|
|
||||||
|
property ShaderLibrary shaderLibrary: ShaderLibrary { }
|
||||||
|
|
||||||
property ShaderEffectSource screenBuffer: frameBuffer
|
property ShaderEffectSource screenBuffer: frameBuffer
|
||||||
property ShaderEffectSource burnInSource: burnInEffect.source
|
property ShaderEffectSource burnInSource: burnInEffect.source
|
||||||
property ShaderEffectSource frameSource: terminalFrameLoader.item
|
property ShaderEffectSource frameSource: terminalFrameLoader.item
|
||||||
@ -74,7 +82,11 @@ Item {
|
|||||||
property size scaleNoiseSize: Qt.size((width) / (noiseTexture.width * appSettings.windowScaling * appSettings.totalFontScaling),
|
property size scaleNoiseSize: Qt.size((width) / (noiseTexture.width * appSettings.windowScaling * appSettings.totalFontScaling),
|
||||||
(height) / (noiseTexture.height * appSettings.windowScaling * appSettings.totalFontScaling))
|
(height) / (noiseTexture.height * appSettings.windowScaling * appSettings.totalFontScaling))
|
||||||
|
|
||||||
property size virtual_resolution: parent.virtual_resolution
|
property size virtualResolution: parent.virtualResolution
|
||||||
|
|
||||||
|
// Rasterization might display oversamping issues if virtual resolution is close to physical display resolution.
|
||||||
|
// We progressively disable rasterization from 4x up to 2x resolution.
|
||||||
|
property real rasterizationIntensity: Utils.smoothstep(2.0, 4.0, _screenDensity)
|
||||||
|
|
||||||
property real time: timeManager.time
|
property real time: timeManager.time
|
||||||
property ShaderEffectSource noiseSource: noiseShaderSource
|
property ShaderEffectSource noiseSource: noiseShaderSource
|
||||||
@ -164,7 +176,8 @@ Item {
|
|||||||
uniform highp vec4 backgroundColor;
|
uniform highp vec4 backgroundColor;
|
||||||
uniform lowp float shadowLength;
|
uniform lowp float shadowLength;
|
||||||
|
|
||||||
uniform highp vec2 virtual_resolution;" +
|
uniform highp vec2 virtualResolution;
|
||||||
|
uniform lowp float rasterizationIntensity;\n" +
|
||||||
|
|
||||||
(burnIn !== 0 ? "
|
(burnIn !== 0 ? "
|
||||||
uniform sampler2D burnInSource;
|
uniform sampler2D burnInSource;
|
||||||
@ -174,8 +187,7 @@ Item {
|
|||||||
uniform sampler2D slowBurnInSource;" : "") +
|
uniform sampler2D slowBurnInSource;" : "") +
|
||||||
(staticNoise !== 0 ? "
|
(staticNoise !== 0 ? "
|
||||||
uniform highp float staticNoise;" : "") +
|
uniform highp float staticNoise;" : "") +
|
||||||
(((staticNoise !== 0 || jitter !== 0)
|
(((staticNoise !== 0 || jitter !== 0) ||(fallBack && (flickering || horizontalSync))) ? "
|
||||||
||(fallBack && (flickering || horizontalSync))) ? "
|
|
||||||
uniform lowp sampler2D noiseSource;
|
uniform lowp sampler2D noiseSource;
|
||||||
uniform highp vec2 scaleNoiseSize;" : "") +
|
uniform highp vec2 scaleNoiseSize;" : "") +
|
||||||
(screenCurvature !== 0 ? "
|
(screenCurvature !== 0 ? "
|
||||||
@ -203,17 +215,14 @@ Item {
|
|||||||
|
|
||||||
(glowingLine !== 0 ? "
|
(glowingLine !== 0 ? "
|
||||||
float randomPass(vec2 coords){
|
float randomPass(vec2 coords){
|
||||||
return fract(smoothstep(-120.0, 0.0, coords.y - (virtual_resolution.y + 120.0) * fract(time * 0.00015)));
|
return fract(smoothstep(-120.0, 0.0, coords.y - (virtualResolution.y + 120.0) * fract(time * 0.00015)));
|
||||||
}" : "") +
|
}" : "") +
|
||||||
|
|
||||||
"float min2(vec2 v) {
|
shaderLibrary.min2 +
|
||||||
return min(v.x, v.y);
|
shaderLibrary.rgb2grey +
|
||||||
}
|
shaderLibrary.rasterizationShader +
|
||||||
|
|
||||||
float rgb2grey(vec3 v){
|
|
||||||
return dot(v, vec3(0.21, 0.72, 0.04));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
"
|
||||||
float isInScreen(vec2 v) {
|
float isInScreen(vec2 v) {
|
||||||
return min2(step(0.0, v) - step(1.0, v));
|
return min2(step(0.0, v) - step(1.0, v));
|
||||||
}
|
}
|
||||||
@ -291,7 +300,7 @@ Item {
|
|||||||
color += noiseVal * noise * (1.0 - distance * 1.3);" : "") +
|
color += noiseVal * noise * (1.0 - distance * 1.3);" : "") +
|
||||||
|
|
||||||
(glowingLine !== 0 ? "
|
(glowingLine !== 0 ? "
|
||||||
color += randomPass(coords * virtual_resolution) * glowingLine;" : "") +
|
color += randomPass(coords * virtualResolution) * glowingLine;" : "") +
|
||||||
|
|
||||||
"vec3 txt_color = texture2D(screenBuffer, txt_coords).rgb;" +
|
"vec3 txt_color = texture2D(screenBuffer, txt_coords).rgb;" +
|
||||||
|
|
||||||
@ -309,6 +318,8 @@ Item {
|
|||||||
|
|
||||||
"txt_color += fontColor.rgb * vec3(color);" +
|
"txt_color += fontColor.rgb * vec3(color);" +
|
||||||
|
|
||||||
|
"txt_color = applyRasterization(staticCoords, txt_color, virtualResolution, rasterizationIntensity);\n" +
|
||||||
|
|
||||||
"vec3 finalColor = txt_color;" +
|
"vec3 finalColor = txt_color;" +
|
||||||
|
|
||||||
(flickering !== 0 ? "
|
(flickering !== 0 ? "
|
||||||
@ -360,6 +371,10 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShaderLibrary {
|
||||||
|
id: shaderLibrary
|
||||||
|
}
|
||||||
|
|
||||||
ShaderEffect {
|
ShaderEffect {
|
||||||
id: staticShader
|
id: staticShader
|
||||||
|
|
||||||
@ -385,7 +400,7 @@ Item {
|
|||||||
|
|
||||||
property real ambientLight: parent.ambientLight
|
property real ambientLight: parent.ambientLight
|
||||||
|
|
||||||
property size virtual_resolution: parent.virtual_resolution
|
property size virtualResolution: parent.virtualResolution
|
||||||
|
|
||||||
blending: false
|
blending: false
|
||||||
visible: false
|
visible: false
|
||||||
@ -408,7 +423,7 @@ Item {
|
|||||||
uniform highp vec4 backgroundColor;
|
uniform highp vec4 backgroundColor;
|
||||||
uniform lowp float screen_brightness;
|
uniform lowp float screen_brightness;
|
||||||
|
|
||||||
uniform highp vec2 virtual_resolution;" +
|
uniform highp vec2 virtualResolution;" +
|
||||||
|
|
||||||
(bloom !== 0 ? "
|
(bloom !== 0 ? "
|
||||||
uniform highp sampler2D bloomSource;
|
uniform highp sampler2D bloomSource;
|
||||||
@ -426,25 +441,7 @@ Item {
|
|||||||
(ambientLight !== 0 ? "
|
(ambientLight !== 0 ? "
|
||||||
uniform lowp float ambientLight;" : "") +
|
uniform lowp float ambientLight;" : "") +
|
||||||
|
|
||||||
"highp float getScanlineIntensity(vec2 coords) {
|
"
|
||||||
float result = 1.0;" +
|
|
||||||
|
|
||||||
(appSettings.rasterization != appSettings.no_rasterization ?
|
|
||||||
"float val = 0.0;
|
|
||||||
vec2 rasterizationCoords = fract(coords * virtual_resolution);
|
|
||||||
val += smoothstep(0.0, 0.5, rasterizationCoords.y);
|
|
||||||
val -= smoothstep(0.5, 1.0, rasterizationCoords.y);
|
|
||||||
result *= mix(0.5, 1.0, val);" : "") +
|
|
||||||
|
|
||||||
(appSettings.rasterization == appSettings.pixel_rasterization ?
|
|
||||||
"val = 0.0;
|
|
||||||
val += smoothstep(0.0, 0.5, rasterizationCoords.x);
|
|
||||||
val -= smoothstep(0.5, 1.0, rasterizationCoords.x);
|
|
||||||
result *= mix(0.5, 1.0, val);" : "") + "
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
float min2(vec2 v) {
|
float min2(vec2 v) {
|
||||||
return min(v.x, v.y);
|
return min(v.x, v.y);
|
||||||
}
|
}
|
||||||
@ -468,6 +465,7 @@ Item {
|
|||||||
" return outColor;
|
" return outColor;
|
||||||
}" +
|
}" +
|
||||||
|
|
||||||
|
shaderLibrary.rasterizationShader +
|
||||||
|
|
||||||
"void main() {" +
|
"void main() {" +
|
||||||
"vec2 cc = vec2(0.5) - qt_TexCoord0;" +
|
"vec2 cc = vec2(0.5) - qt_TexCoord0;" +
|
||||||
@ -490,8 +488,6 @@ Item {
|
|||||||
txt_color.b = leftColor.b * 0.30 + rightColor.b * 0.10 + txt_color.b * 0.60;
|
txt_color.b = leftColor.b * 0.30 + rightColor.b * 0.10 + txt_color.b * 0.60;
|
||||||
" : "") +
|
" : "") +
|
||||||
|
|
||||||
"txt_color *= getScanlineIntensity(txt_coords);" +
|
|
||||||
|
|
||||||
"txt_color += vec3(0.0001);" +
|
"txt_color += vec3(0.0001);" +
|
||||||
"float greyscale_color = rgb2grey(txt_color);" +
|
"float greyscale_color = rgb2grey(txt_color);" +
|
||||||
|
|
||||||
|
@ -26,13 +26,19 @@ ShaderTerminal {
|
|||||||
property alias title: terminal.title
|
property alias title: terminal.title
|
||||||
property alias terminalSize: terminal.terminalSize
|
property alias terminalSize: terminal.terminalSize
|
||||||
|
|
||||||
|
property real devicePixelRatio: terminalWindow.screen.devicePixelRatio
|
||||||
|
|
||||||
id: mainShader
|
id: mainShader
|
||||||
opacity: appSettings.windowOpacity * 0.3 + 0.7
|
opacity: appSettings.windowOpacity * 0.3 + 0.7
|
||||||
|
|
||||||
source: terminal.mainSource
|
source: terminal.mainSource
|
||||||
burnInEffect: terminal.burnInEffect
|
burnInEffect: terminal.burnInEffect
|
||||||
slowBurnInEffect: terminal.slowBurnInEffect
|
slowBurnInEffect: terminal.slowBurnInEffect
|
||||||
virtual_resolution: terminal.virtualResolution
|
virtualResolution: terminal.virtualResolution
|
||||||
|
screenResolution: Qt.size(
|
||||||
|
terminalWindow.width * devicePixelRatio * appSettings.windowScaling,
|
||||||
|
terminalWindow.height * devicePixelRatio * appSettings.windowScaling
|
||||||
|
)
|
||||||
|
|
||||||
TimeManager {
|
TimeManager {
|
||||||
id: timeManager
|
id: timeManager
|
||||||
|
@ -45,5 +45,6 @@
|
|||||||
<file>menus/WindowMenu.qml</file>
|
<file>menus/WindowMenu.qml</file>
|
||||||
<file>menus/FullContextMenu.qml</file>
|
<file>menus/FullContextMenu.qml</file>
|
||||||
<file>menus/ShortContextMenu.qml</file>
|
<file>menus/ShortContextMenu.qml</file>
|
||||||
|
<file>ShaderLibrary.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
@ -31,13 +31,18 @@ function lint(a, b, t) {
|
|||||||
return (1 - t) * a + (t) * b;
|
return (1 - t) * a + (t) * b;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mix(c1, c2, alpha){
|
function mix(c1, c2, alpha) {
|
||||||
return Qt.rgba(c1.r * alpha + c2.r * (1-alpha),
|
return Qt.rgba(c1.r * alpha + c2.r * (1-alpha),
|
||||||
c1.g * alpha + c2.g * (1-alpha),
|
c1.g * alpha + c2.g * (1-alpha),
|
||||||
c1.b * alpha + c2.b * (1-alpha),
|
c1.b * alpha + c2.b * (1-alpha),
|
||||||
c1.a * alpha + c2.a * (1-alpha))
|
c1.a * alpha + c2.a * (1-alpha))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function smoothstep(min, max, value) {
|
||||||
|
let x = Math.max(0, Math.min(1, (value - min) / (max - min)));
|
||||||
|
return x * x * (3 - 2 * x);
|
||||||
|
}
|
||||||
|
|
||||||
function strToColor(s){
|
function strToColor(s){
|
||||||
var r = parseInt(s.substring(1,3), 16) / 256;
|
var r = parseInt(s.substring(1,3), 16) / 256;
|
||||||
var g = parseInt(s.substring(3,5), 16) / 256;
|
var g = parseInt(s.substring(3,5), 16) / 256;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user