diff options
author | Luboš Luňák <l.lunak@collabora.com> | 2015-01-17 20:00:52 +0100 |
---|---|---|
committer | Luboš Luňák <l.lunak@collabora.com> | 2015-01-19 12:17:51 +0100 |
commit | ab65925b40134ff7d8b88c61db5235549599385f (patch) | |
tree | f4a4b61ecc8edc25bcea774456157929dd99f649 /vcl | |
parent | c22dbb2602c9c24eaa248a5a506dcb13ba435e9a (diff) |
"area" scaling for opengl that has good results for downscaling
Change-Id: I0e4ad776cbf31f9a130aedf0f9741927560b5ac1
Diffstat (limited to 'vcl')
-rw-r--r-- | vcl/Package_opengl.mk | 2 | ||||
-rw-r--r-- | vcl/inc/opengl/program.hxx | 2 | ||||
-rw-r--r-- | vcl/inc/opengl/salbmp.hxx | 1 | ||||
-rw-r--r-- | vcl/opengl/areaScaleFastFragmentShader.glsl | 43 | ||||
-rw-r--r-- | vcl/opengl/areaScaleFragmentShader.glsl | 131 | ||||
-rw-r--r-- | vcl/opengl/program.cxx | 12 | ||||
-rw-r--r-- | vcl/opengl/scale.cxx | 76 |
7 files changed, 267 insertions, 0 deletions
diff --git a/vcl/Package_opengl.mk b/vcl/Package_opengl.mk index 0aa324fc6db7..0a54ad6490c1 100644 --- a/vcl/Package_opengl.mk +++ b/vcl/Package_opengl.mk @@ -10,6 +10,8 @@ $(eval $(call gb_Package_Package,vcl_opengl_shader,$(SRCDIR)/vcl/opengl)) $(eval $(call gb_Package_add_files,vcl_opengl_shader,$(LIBO_ETC_FOLDER)/opengl,\ + areaScaleFastFragmentShader.glsl \ + areaScaleFragmentShader.glsl \ blendedTextureFragmentShader.glsl \ blendedTextureVertexShader.glsl \ dumbVertexShader.glsl \ diff --git a/vcl/inc/opengl/program.hxx b/vcl/inc/opengl/program.hxx index 6e21abfd1129..8fc74d203d45 100644 --- a/vcl/inc/opengl/program.hxx +++ b/vcl/inc/opengl/program.hxx @@ -55,6 +55,8 @@ public: void SetUniform2f( const OString& rName, GLfloat v1, GLfloat v2 ); void SetUniform1fv( const OString& rName, GLsizei nCount, GLfloat* aValues ); void SetUniform2fv( const OString& rName, GLsizei nCount, GLfloat* aValues ); + void SetUniform1i( const OString& rName, GLint v1 ); + void SetUniform1iv( const OString& rName, GLsizei nCount, GLint* aValues ); void SetColor( const OString& rName, const Color& rColor ); void SetColor( const OString& rName, SalColor nColor, sal_uInt8 nTransparency ); void SetColorf( const OString& rName, SalColor nColor, double fTransparency ); diff --git a/vcl/inc/opengl/salbmp.hxx b/vcl/inc/opengl/salbmp.hxx index 200698f7c828..84c64ede7bd4 100644 --- a/vcl/inc/opengl/salbmp.hxx +++ b/vcl/inc/opengl/salbmp.hxx @@ -104,6 +104,7 @@ private: bool ImplScaleFilter( const double& rScaleX, const double& rScaleY, GLenum nFilter ); void ImplCreateKernel( const double& fScale, const Kernel& rKernel, GLfloat*& pWeights, sal_uInt32& aKernelSize ); bool ImplScaleConvolution( const double& rScaleX, const double& rScaleY, const Kernel& aKernel ); + bool ImplScaleArea( double rScaleX, double rScaleY ); public: diff --git a/vcl/opengl/areaScaleFastFragmentShader.glsl b/vcl/opengl/areaScaleFastFragmentShader.glsl new file mode 100644 index 000000000000..b8874d1a3994 --- /dev/null +++ b/vcl/opengl/areaScaleFastFragmentShader.glsl @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/* TODO Use textureOffset for newest version of GLSL */ + +uniform sampler2D sampler; +uniform int xscale; +uniform int yscale; +uniform float xstep; +uniform float ystep; +uniform float ratio; // = 1.0/(xscale*yscale) + +varying vec2 tex_coord; + +/* + Just make the resulting color the average of all the source pixels + (which is an area (xscale)x(yscale) ). +*/ +void main(void) +{ + vec4 sum = vec4( 0.0, 0.0, 0.0, 0.0 ); + vec2 offset = vec2( 0.0, 0.0 ); + for( int y = 0; y < yscale; ++y ) + { + for( int x = 0; x < xscale; ++x ) + { + sum += texture2D( sampler, tex_coord.st + offset ); + offset.x += xstep; + } + offset.y += ystep; + offset.x = 0.0; + } + sum *= ratio; + gl_FragColor = sum; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/areaScaleFragmentShader.glsl b/vcl/opengl/areaScaleFragmentShader.glsl new file mode 100644 index 000000000000..cae5eb6030b0 --- /dev/null +++ b/vcl/opengl/areaScaleFragmentShader.glsl @@ -0,0 +1,131 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/* TODO Use textureOffset for newest version of GLSL */ + +uniform sampler2D sampler; +uniform int swidth; +uniform int sheight; +uniform float xscale; +uniform float yscale; +uniform float xsrcconvert; +uniform float ysrcconvert; +uniform float xdestconvert; +uniform float ydestconvert; + +varying vec2 tex_coord; + +void main(void) +{ + // Convert to pixel coordinates again. + int dx = int( tex_coord.s * xdestconvert ); + int dy = int( tex_coord.t * ydestconvert ); + + // Note: These values are always the same for the same X (or Y), + // so they could be precalculated in C++ and passed to the shader, + // but GLSL has limits on the size of uniforms passed to it, + // so it'd need something like texture buffer objects from newer + // GLSL versions, and it seems the hassle is not really worth it. + + // How much each column/row will contribute to the resulting pixel. + // assert( xscale <= 100 ); assert( yscale <= 100 ); + float xratio[ 100 + 2 ]; + float yratio[ 100 + 2 ]; + // For finding the first and last source pixel. + int xpixel[ 100 + 2 ]; + int ypixel[ 100 + 2 ]; + + int xpos = 0; + int ypos = 0; + + // Compute the range of source pixels which will make up this destination pixel. + float fsx1 = dx * xscale; + float fsx2 = fsx1 + xscale; + // To whole pixel coordinates. + int sx1 = ceil( fsx1 ); + int sx2 = floor( fsx2 ); + // Range checking. + sx2 = min( sx2, swidth - 1 ); + sx1 = min( sx1, sx2 ); + + // How much one full column contributes to the resulting pixel. + float width = min( xscale, swidth - fsx1 ); + + if( sx1 - fsx1 > 0.001 ) + { // The first column contributes only partially. + xpixel[ xpos ] = sx1 - 1; + xratio[ xpos ] = ( sx1 - fsx1 ) / width; + ++xpos; + } + for( int sx = sx1; sx < sx2; ++sx ) + { // Columns that fully contribute to the resulting pixel. + xpixel[ xpos ] = sx; + xratio[ xpos ] = 1.0 / width; + ++xpos; + } + if( fsx2 - sx2 > 0.001 ) + { // The last column contributes only partially. + xpixel[ xpos ] = sx2; + xratio[ xpos ] = min( min( fsx2 - sx2, 1.0 ) / width, 1.0 ); + ++xpos; + } + + // The same for Y. + float fsy1 = dy * yscale; + float fsy2 = fsy1 + yscale; + int sy1 = ceil( fsy1 ); + int sy2 = floor( fsy2 ); + sy2 = min( sy2, sheight - 1 ); + sy1 = min( sy1, sy2 ); + + float height = min( yscale, sheight - fsy1 ); + + if( sy1 - fsy1 > 0.001 ) + { + ypixel[ ypos ] = sy1 - 1; + yratio[ ypos ] = ( sy1 - fsy1 ) / height; + ++ypos; + } + for( int sy = sy1; sy < sy2; ++sy ) + { + ypixel[ ypos ] = sy; + yratio[ ypos ] = 1.0 / height; + ++ypos; + } + if( fsy2 - sy2 > 0.001 ) + { + ypixel[ ypos ] = sy2; + yratio[ ypos ] = min( min( fsy2 - sy2, 1.0 ) / height, 1.0 ); + ++ypos; + } + + int xstart = xpixel[ 0 ]; + int xend = xpixel[ xpos - 1 ]; + int ystart = ypixel[ 0 ]; + int yend = ypixel[ ypos - 1 ]; + + vec4 sum = vec4( 0.0, 0.0, 0.0, 0.0 ); + + ypos = 0; + for( int y = ystart; y <= yend; ++y, ++ypos ) + { + vec4 tmp = vec4( 0.0, 0.0, 0.0, 0.0 ); + xpos = 0; + for( int x = xstart; x <= xend; ++x, ++xpos ) + { + vec2 offset = vec2( x * xsrcconvert, y * ysrcconvert ); + tmp += texture2D( sampler, offset ) * xratio[ xpos ]; + } + sum += tmp * yratio[ ypos ]; + } + + gl_FragColor = sum; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/opengl/program.cxx b/vcl/opengl/program.cxx index 0fe460592007..d3a192c974cb 100644 --- a/vcl/opengl/program.cxx +++ b/vcl/opengl/program.cxx @@ -148,6 +148,18 @@ void OpenGLProgram::SetUniform2fv( const OString& rName, GLsizei nCount, GLfloat glUniform2fv( nUniform, nCount, aValues ); } +void OpenGLProgram::SetUniform1i( const OString& rName, GLint v1 ) +{ + GLuint nUniform = GetUniformLocation( rName ); + glUniform1i( nUniform, v1 ); +} + +void OpenGLProgram::SetUniform1iv( const OString& rName, GLsizei nCount, GLint* aValues ) +{ + GLuint nUniform = GetUniformLocation( rName ); + glUniform1iv( nUniform, nCount, aValues ); +} + void OpenGLProgram::SetColor( const OString& rName, SalColor nColor, sal_uInt8 nTransparency ) { GLuint nUniform = GetUniformLocation( rName ); diff --git a/vcl/opengl/scale.cxx b/vcl/opengl/scale.cxx index a81c63b238ca..84cf96716671 100644 --- a/vcl/opengl/scale.cxx +++ b/vcl/opengl/scale.cxx @@ -188,6 +188,78 @@ bool OpenGLSalBitmap::ImplScaleConvolution( return true; } +/* + "Area" scaling algorithm, which seems to give better results for downscaling + than other algorithms. The principle (taken from opencv, see resize.cl) + is that each resulting pixel is the average of all the source pixel values + it represents. Which is trivial in the case of exact multiples for downscaling, + the generic case needs to also consider that some source pixels contribute + only partially to their resulting pixels (becauses of non-integer multiples). +*/ +bool OpenGLSalBitmap::ImplScaleArea( double rScaleX, double rScaleY ) +{ + int nNewWidth( mnWidth * rScaleX ); + int nNewHeight( mnHeight * rScaleY ); + + if( nNewWidth == mnWidth && nNewHeight == mnHeight ) + return true; + + double ixscale = 1 / rScaleX; + double iyscale = 1 / rScaleY; + bool fast = ( ixscale == int( ixscale ) && iyscale == int( iyscale ) + && int( nNewWidth * ixscale ) == mnWidth && int( nNewHeight * iyscale ) == mnHeight ); + + // The generic case has arrays only up to 100 ratio downscaling, which is hopefully enough + // in practice, but protect against buffer overflows in case such an extreme case happens + // (and in such case the precision of the generic algorithm probably doesn't matter anyway). + if( ixscale > 100 || iyscale > 100 ) + fast = true; + + // TODO Make sure the framebuffer is alright + + OpenGLProgram* pProgram = mpContext->UseProgram( "textureVertexShader", + fast ? OUString( "areaScaleFastFragmentShader" ) : OUString( "areaScaleFragmentShader" )); + if( pProgram == 0 ) + return false; + + OpenGLTexture aScratchTex = OpenGLTexture( nNewWidth, nNewHeight ); + OpenGLFramebuffer* pFramebuffer = mpContext->AcquireFramebuffer( aScratchTex ); + + if( fast ) + { + pProgram->SetUniform1i( "xscale", ixscale ); + pProgram->SetUniform1i( "yscale", iyscale ); + pProgram->SetUniform1f( "xstep", 1.0 / mnWidth ); + pProgram->SetUniform1f( "ystep", 1.0 / mnHeight ); + pProgram->SetUniform1f( "ratio", 1.0 / ( ixscale * iyscale )); + } + else + { + pProgram->SetUniform1f( "xscale", ixscale ); + pProgram->SetUniform1f( "yscale", iyscale ); + pProgram->SetUniform1i( "swidth", mnWidth ); + pProgram->SetUniform1i( "sheight", mnHeight ); + // For converting between <0,mnWidth-1> and <0.0,1.0> coordinate systems. + pProgram->SetUniform1f( "xsrcconvert", 1.0 / ( mnWidth - 1 )); + pProgram->SetUniform1f( "ysrcconvert", 1.0 / ( mnHeight - 1 )); + pProgram->SetUniform1f( "xdestconvert", 1.0 * ( nNewWidth - 1 )); + pProgram->SetUniform1f( "ydestconvert", 1.0 * ( nNewHeight - 1 )); + } + + pProgram->SetTexture( "sampler", maTexture ); + pProgram->DrawTexture( maTexture ); + pProgram->Clean(); + + maTexture = aScratchTex; + mpContext->ReleaseFramebuffer( pFramebuffer ); + + mnWidth = nNewWidth; + mnHeight = nNewHeight; + + CHECK_GL_ERROR(); + return true; +} + bool OpenGLSalBitmap::ImplScale( const double& rScaleX, const double& rScaleY, sal_uInt32 nScaleFlag ) { SAL_INFO( "vcl.opengl", "::ImplScale" ); @@ -209,6 +281,10 @@ bool OpenGLSalBitmap::ImplScale( const double& rScaleX, const double& rScaleY, s return ImplScaleConvolution( rScaleX, rScaleY, aKernel ); } + else if( nScaleFlag == BMP_SCALE_BESTQUALITY && rScaleX <= 1 && rScaleY <= 1 ) + { // Use are scaling for best quality, but only if downscaling. + return ImplScaleArea( rScaleX, rScaleY ); + } else if( nScaleFlag == BMP_SCALE_LANCZOS || nScaleFlag == BMP_SCALE_BESTQUALITY ) { const Lanczos3Kernel aKernel; |