summaryrefslogtreecommitdiff
path: root/vcl
diff options
context:
space:
mode:
authorLuboš Luňák <l.lunak@collabora.com>2015-01-17 20:00:52 +0100
committerLuboš Luňák <l.lunak@collabora.com>2015-01-19 12:17:51 +0100
commitab65925b40134ff7d8b88c61db5235549599385f (patch)
treef4a4b61ecc8edc25bcea774456157929dd99f649 /vcl
parentc22dbb2602c9c24eaa248a5a506dcb13ba435e9a (diff)
"area" scaling for opengl that has good results for downscaling
Change-Id: I0e4ad776cbf31f9a130aedf0f9741927560b5ac1
Diffstat (limited to 'vcl')
-rw-r--r--vcl/Package_opengl.mk2
-rw-r--r--vcl/inc/opengl/program.hxx2
-rw-r--r--vcl/inc/opengl/salbmp.hxx1
-rw-r--r--vcl/opengl/areaScaleFastFragmentShader.glsl43
-rw-r--r--vcl/opengl/areaScaleFragmentShader.glsl131
-rw-r--r--vcl/opengl/program.cxx12
-rw-r--r--vcl/opengl/scale.cxx76
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;