Version 6.1.4.1, tag libreoffice-6.1.4.1
[LibreOffice.git] / vcl / opengl / scale.cxx
bloba5280bf70817faf0ad9070eb1d9c61fbcfef5e30
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <sal/config.h>
22 #include <cmath>
24 #include <vcl/opengl/OpenGLHelper.hxx>
26 #include <vcl/bitmap.hxx>
28 #include <opengl/zone.hxx>
29 #include <opengl/salbmp.hxx>
30 #include <opengl/program.hxx>
31 #include <opengl/texture.hxx>
32 #include <opengl/RenderState.hxx>
34 #include <ResampleKernel.hxx>
36 using vcl::Kernel;
37 using vcl::Lanczos3Kernel;
39 bool OpenGLSalBitmap::ImplScaleFilter(
40 const rtl::Reference< OpenGLContext > &xContext,
41 const double& rScaleX,
42 const double& rScaleY,
43 GLenum nFilter )
45 OpenGLFramebuffer* pFramebuffer;
46 OpenGLProgram* pProgram;
47 GLenum nOldFilter;
48 int nNewWidth( mnWidth * rScaleX );
49 int nNewHeight( mnHeight * rScaleY );
51 pProgram = xContext->UseProgram( "textureVertexShader",
52 "textureFragmentShader" );
53 if( !pProgram )
54 return false;
56 OpenGLTexture aNewTex(nNewWidth, nNewHeight);
57 pFramebuffer = xContext->AcquireFramebuffer( aNewTex );
59 pProgram->SetTexture( "sampler", maTexture );
60 nOldFilter = maTexture.GetFilter();
61 maTexture.SetFilter( nFilter );
62 pProgram->ApplyMatrix(mnWidth, mnHeight);
63 pProgram->DrawTexture( maTexture );
64 maTexture.SetFilter( nOldFilter );
65 pProgram->Clean();
67 OpenGLContext::ReleaseFramebuffer( pFramebuffer );
69 mnWidth = nNewWidth;
70 mnHeight = nNewHeight;
71 maTexture = aNewTex;
73 CHECK_GL_ERROR();
74 return true;
77 void OpenGLSalBitmap::ImplCreateKernel(
78 const double& fScale,
79 const Kernel& rKernel,
80 GLfloat*& pWeights,
81 sal_uInt32& aKernelSize )
83 const double fSamplingRadius(rKernel.GetWidth());
84 const double fScaledRadius((fScale < 1.0) ? fSamplingRadius / fScale : fSamplingRadius);
85 const double fFilterFactor(std::min(fScale, 1.0));
86 int aNumberOfContributions;
87 double aSum( 0 );
89 aNumberOfContributions = (static_cast< sal_uInt32 >(fabs(ceil(fScaledRadius))) * 2) + 1 - 6;
90 aKernelSize = aNumberOfContributions / 2 + 1;
92 // avoid a crash for now; re-think me.
93 if (aKernelSize > 16)
94 aKernelSize = 16;
96 pWeights = new GLfloat[16];
97 memset( pWeights, 0, 16 * sizeof( GLfloat ) );
99 for( sal_uInt32 i(0); i < aKernelSize; i++ )
101 const GLfloat aWeight( rKernel.Calculate( fFilterFactor * i ) );
102 if( fabs( aWeight ) >= 0.0001 )
104 pWeights[i] = aWeight;
105 aSum += i > 0 ? aWeight * 2 : aWeight;
109 for( sal_uInt32 i(0); i < aKernelSize; i++ )
111 pWeights[i] /= aSum;
115 bool OpenGLSalBitmap::ImplScaleConvolution(
116 const rtl::Reference< OpenGLContext > &xContext,
117 const double& rScaleX,
118 const double& rScaleY,
119 const Kernel& aKernel )
121 OpenGLFramebuffer* pFramebuffer;
122 OpenGLProgram* pProgram;
123 GLfloat* pWeights( nullptr );
124 sal_uInt32 nKernelSize;
125 GLfloat aOffsets[32];
126 int nNewWidth( mnWidth * rScaleX );
127 int nNewHeight( mnHeight * rScaleY );
129 // TODO Make sure the framebuffer is alright
131 pProgram = xContext->UseProgram( "textureVertexShader",
132 "convolutionFragmentShader" );
133 if( pProgram == nullptr )
134 return false;
136 // horizontal scaling in scratch texture
137 if( mnWidth != nNewWidth )
139 OpenGLTexture aScratchTex(nNewWidth, nNewHeight);
141 pFramebuffer = xContext->AcquireFramebuffer( aScratchTex );
143 for( sal_uInt32 i = 0; i < 16; i++ )
145 aOffsets[i * 2] = i / static_cast<double>(mnWidth);
146 aOffsets[i * 2 + 1] = 0;
148 ImplCreateKernel( rScaleX, aKernel, pWeights, nKernelSize );
149 pProgram->SetUniform1fv( "kernel", 16, pWeights );
150 pProgram->SetUniform2fv( "offsets", 16, aOffsets );
151 pProgram->SetTexture( "sampler", maTexture );
152 pProgram->DrawTexture( maTexture );
153 pProgram->Clean();
155 maTexture = aScratchTex;
156 OpenGLContext::ReleaseFramebuffer( pFramebuffer );
159 // vertical scaling in final texture
160 if( mnHeight != nNewHeight )
162 OpenGLTexture aScratchTex(nNewWidth, nNewHeight);
164 pFramebuffer = xContext->AcquireFramebuffer( aScratchTex );
166 for( sal_uInt32 i = 0; i < 16; i++ )
168 aOffsets[i * 2] = 0;
169 aOffsets[i * 2 + 1] = i / static_cast<double>(mnHeight);
171 ImplCreateKernel( rScaleY, aKernel, pWeights, nKernelSize );
172 pProgram->SetUniform1fv( "kernel", 16, pWeights );
173 pProgram->SetUniform2fv( "offsets", 16, aOffsets );
174 pProgram->SetTexture( "sampler", maTexture );
175 pProgram->DrawTexture( maTexture );
176 pProgram->Clean();
178 maTexture = aScratchTex;
179 OpenGLContext::ReleaseFramebuffer( pFramebuffer );
182 mnWidth = nNewWidth;
183 mnHeight = nNewHeight;
185 CHECK_GL_ERROR();
186 return true;
190 "Area" scaling algorithm, which seems to give better results for downscaling
191 than other algorithms. The principle (taken from opencv, see resize.cl)
192 is that each resulting pixel is the average of all the source pixel values
193 it represents. Which is trivial in the case of exact multiples for downscaling,
194 the generic case needs to also consider that some source pixels contribute
195 only partially to their resulting pixels (because of non-integer multiples).
197 bool OpenGLSalBitmap::ImplScaleArea( const rtl::Reference< OpenGLContext > &xContext,
198 double rScaleX, double rScaleY )
200 int nNewWidth( mnWidth * rScaleX );
201 int nNewHeight( mnHeight * rScaleY );
203 if( nNewWidth == mnWidth && nNewHeight == mnHeight )
204 return true;
206 double ixscale = 1 / rScaleX;
207 double iyscale = 1 / rScaleY;
208 bool fast = ( ixscale == std::trunc( ixscale ) && iyscale == std::trunc( iyscale )
209 && int( nNewWidth * ixscale ) == mnWidth && int( nNewHeight * iyscale ) == mnHeight );
211 bool bTwoPasses = false;
213 // The generic case has arrays only up to 100 ratio downscaling, which is hopefully enough
214 // in practice, but protect against buffer overflows in case such an extreme case happens
215 // (and in such case the precision of the generic algorithm probably doesn't matter anyway).
216 if( ixscale > 100 || iyscale > 100 )
218 fast = true;
220 else
222 if (ixscale > 16 || iyscale > 16)
224 ixscale = std::floor(std::sqrt(ixscale));
225 iyscale = std::floor(std::sqrt(iyscale));
226 nNewWidth = int(mnWidth / ixscale);
227 rScaleX *= ixscale; // second pass x-scale factor
228 nNewHeight = int(mnHeight / iyscale);
229 rScaleY *= iyscale; // second pass y-scale factor
230 bTwoPasses = true;
234 // TODO Make sure the framebuffer is alright
236 OString sUseReducedRegisterVariantDefine;
237 if (xContext->getOpenGLCapabilitySwitch().mbLimitedShaderRegisters)
238 sUseReducedRegisterVariantDefine = OString("#define USE_REDUCED_REGISTER_VARIANT\n");
240 OpenGLProgram* pProgram = xContext->UseProgram( "textureVertexShader",
241 fast ? OUString( "areaScaleFastFragmentShader" ) : OUString( "areaScaleFragmentShader" ),
242 sUseReducedRegisterVariantDefine);
244 if( pProgram == nullptr )
245 return false;
247 OpenGLTexture aScratchTex(nNewWidth, nNewHeight);
249 OpenGLFramebuffer* pFramebuffer = xContext->AcquireFramebuffer( aScratchTex );
251 // NOTE: This setup is also done in OpenGLSalGraphicsImpl::DrawTransformedTexture().
252 if( fast )
254 pProgram->SetUniform1i( "xscale", ixscale );
255 pProgram->SetUniform1i( "yscale", iyscale );
256 pProgram->SetUniform1f( "xstep", 1.0 / mnWidth );
257 pProgram->SetUniform1f( "ystep", 1.0 / mnHeight );
258 pProgram->SetUniform1f( "ratio", 1.0 / ( ixscale * iyscale ));
260 else
262 pProgram->SetUniform1f( "xscale", ixscale );
263 pProgram->SetUniform1f( "yscale", iyscale );
264 pProgram->SetUniform1i( "swidth", mnWidth );
265 pProgram->SetUniform1i( "sheight", mnHeight );
266 // For converting between <0,mnWidth-1> and <0.0,1.0> coordinate systems.
267 pProgram->SetUniform1f( "xsrcconvert", 1.0 / ( mnWidth - 1 ));
268 pProgram->SetUniform1f( "ysrcconvert", 1.0 / ( mnHeight - 1 ));
269 pProgram->SetUniform1f( "xdestconvert", 1.0 * ( nNewWidth - 1 ));
270 pProgram->SetUniform1f( "ydestconvert", 1.0 * ( nNewHeight - 1 ));
273 pProgram->SetTexture( "sampler", maTexture );
274 pProgram->DrawTexture( maTexture );
275 pProgram->Clean();
277 OpenGLContext::ReleaseFramebuffer(pFramebuffer);
279 CHECK_GL_ERROR();
281 if (bTwoPasses)
283 mnWidth = nNewWidth;
284 mnHeight = nNewHeight;
286 nNewWidth = int(mnWidth * rScaleX);
287 nNewHeight = int (mnHeight * rScaleY);
289 ixscale = 1 / rScaleX;
290 iyscale = 1 / rScaleY;
292 pProgram = xContext->UseProgram("textureVertexShader", "areaScaleFragmentShader", sUseReducedRegisterVariantDefine);
293 if (pProgram == nullptr)
294 return false;
296 OpenGLTexture aScratchTex2(nNewWidth, nNewHeight);
298 pFramebuffer = xContext->AcquireFramebuffer(aScratchTex2);
300 pProgram->SetUniform1f("xscale", ixscale);
301 pProgram->SetUniform1f("yscale", iyscale);
302 pProgram->SetUniform1i("swidth", mnWidth);
303 pProgram->SetUniform1i("sheight", mnHeight);
304 // For converting between <0,mnWidth-1> and <0.0,1.0> coordinate systems.
305 pProgram->SetUniform1f("xsrcconvert", 1.0 / (mnWidth - 1));
306 pProgram->SetUniform1f("ysrcconvert", 1.0 / (mnHeight - 1));
307 pProgram->SetUniform1f("xdestconvert", 1.0 * (nNewWidth - 1));
308 pProgram->SetUniform1f("ydestconvert", 1.0 * (nNewHeight - 1));
310 pProgram->SetTexture("sampler", aScratchTex);
311 pProgram->DrawTexture(aScratchTex);
312 pProgram->Clean();
314 OpenGLContext::ReleaseFramebuffer(pFramebuffer);
316 CHECK_GL_ERROR();
318 maTexture = aScratchTex2;
319 mnWidth = nNewWidth;
320 mnHeight = nNewHeight;
322 else
324 maTexture = aScratchTex;
325 mnWidth = nNewWidth;
326 mnHeight = nNewHeight;
329 return true;
332 void OpenGLSalBitmap::ImplScale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag )
334 VCL_GL_INFO( "::ImplScale" );
336 mpUserBuffer.reset();
337 OpenGLVCLContextZone aContextZone;
338 rtl::Reference<OpenGLContext> xContext = OpenGLContext::getVCLContext();
339 xContext->state().scissor().disable();
340 xContext->state().stencil().disable();
342 if (rScaleX <= 1 && rScaleY <= 1)
344 nScaleFlag = BmpScaleFlag::BestQuality;
347 if( nScaleFlag == BmpScaleFlag::Fast )
349 ImplScaleFilter( xContext, rScaleX, rScaleY, GL_NEAREST );
351 if( nScaleFlag == BmpScaleFlag::BiLinear )
353 ImplScaleFilter( xContext, rScaleX, rScaleY, GL_LINEAR );
355 else if( nScaleFlag == BmpScaleFlag::Default )
357 const Lanczos3Kernel aKernel;
359 ImplScaleConvolution( xContext, rScaleX, rScaleY, aKernel );
361 else if( nScaleFlag == BmpScaleFlag::BestQuality && rScaleX <= 1 && rScaleY <= 1 )
362 { // Use are scaling for best quality, but only if downscaling.
363 ImplScaleArea( xContext, rScaleX, rScaleY );
365 else if( nScaleFlag == BmpScaleFlag::Lanczos || nScaleFlag == BmpScaleFlag::BestQuality )
367 const Lanczos3Kernel aKernel;
369 ImplScaleConvolution( xContext, rScaleX, rScaleY, aKernel );
371 else
372 SAL_WARN( "vcl.opengl", "Invalid flag for scaling operation" );
375 bool OpenGLSalBitmap::ScalingSupported() const
377 return true;
380 bool OpenGLSalBitmap::Scale( const double& rScaleX, const double& rScaleY, BmpScaleFlag nScaleFlag )
382 OpenGLVCLContextZone aContextZone;
384 VCL_GL_INFO("::Scale " << int(nScaleFlag)
385 << " from " << mnWidth << "x" << mnHeight
386 << " to " << (mnWidth * rScaleX) << "x" << (mnHeight * rScaleY) );
388 if( nScaleFlag == BmpScaleFlag::Fast ||
389 nScaleFlag == BmpScaleFlag::BiLinear ||
390 nScaleFlag == BmpScaleFlag::Lanczos ||
391 nScaleFlag == BmpScaleFlag::Default ||
392 nScaleFlag == BmpScaleFlag::BestQuality )
394 ImplScale( rScaleX, rScaleY, nScaleFlag );
395 return true;
398 return false;
401 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */