1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
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>
37 using vcl::Lanczos3Kernel
;
39 bool OpenGLSalBitmap::ImplScaleFilter(
40 const rtl::Reference
< OpenGLContext
> &xContext
,
41 const double& rScaleX
,
42 const double& rScaleY
,
45 OpenGLFramebuffer
* pFramebuffer
;
46 OpenGLProgram
* pProgram
;
48 int nNewWidth( mnWidth
* rScaleX
);
49 int nNewHeight( mnHeight
* rScaleY
);
51 pProgram
= xContext
->UseProgram( "textureVertexShader",
52 "textureFragmentShader" );
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
);
67 OpenGLContext::ReleaseFramebuffer( pFramebuffer
);
70 mnHeight
= nNewHeight
;
77 void OpenGLSalBitmap::ImplCreateKernel(
79 const Kernel
& rKernel
,
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
;
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.
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
++ )
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 )
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
);
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
++ )
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
);
178 maTexture
= aScratchTex
;
179 OpenGLContext::ReleaseFramebuffer( pFramebuffer
);
183 mnHeight
= nNewHeight
;
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
)
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 )
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
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 )
247 OpenGLTexture
aScratchTex(nNewWidth
, nNewHeight
);
249 OpenGLFramebuffer
* pFramebuffer
= xContext
->AcquireFramebuffer( aScratchTex
);
251 // NOTE: This setup is also done in OpenGLSalGraphicsImpl::DrawTransformedTexture().
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
));
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
);
277 OpenGLContext::ReleaseFramebuffer(pFramebuffer
);
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)
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
);
314 OpenGLContext::ReleaseFramebuffer(pFramebuffer
);
318 maTexture
= aScratchTex2
;
320 mnHeight
= nNewHeight
;
324 maTexture
= aScratchTex
;
326 mnHeight
= nNewHeight
;
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
);
372 SAL_WARN( "vcl.opengl", "Invalid flag for scaling operation" );
375 bool OpenGLSalBitmap::ScalingSupported() const
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
);
401 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */