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>
21 #include <sal/log.hxx>
25 #include <vcl/opengl/OpenGLHelper.hxx>
27 #include <vcl/bitmap.hxx>
29 #include <opengl/zone.hxx>
30 #include <opengl/salbmp.hxx>
31 #include <opengl/program.hxx>
32 #include <opengl/texture.hxx>
33 #include <opengl/RenderState.hxx>
35 #include <ResampleKernel.hxx>
38 using vcl::Lanczos3Kernel
;
40 bool OpenGLSalBitmap::ImplScaleFilter(
41 const rtl::Reference
< OpenGLContext
> &xContext
,
42 const double& rScaleX
,
43 const double& rScaleY
,
46 OpenGLFramebuffer
* pFramebuffer
;
47 OpenGLProgram
* pProgram
;
49 int nNewWidth( mnWidth
* rScaleX
);
50 int nNewHeight( mnHeight
* rScaleY
);
52 pProgram
= xContext
->UseProgram( "textureVertexShader",
53 "textureFragmentShader" );
57 OpenGLTexture
aNewTex(nNewWidth
, nNewHeight
);
58 pFramebuffer
= xContext
->AcquireFramebuffer( aNewTex
);
60 pProgram
->SetTexture( "sampler", maTexture
);
61 nOldFilter
= maTexture
.GetFilter();
62 maTexture
.SetFilter( nFilter
);
63 pProgram
->ApplyMatrix(mnWidth
, mnHeight
);
64 pProgram
->DrawTexture( maTexture
);
65 maTexture
.SetFilter( nOldFilter
);
68 OpenGLContext::ReleaseFramebuffer( pFramebuffer
);
71 mnHeight
= nNewHeight
;
78 void OpenGLSalBitmap::ImplCreateKernel(
80 const Kernel
& rKernel
,
82 sal_uInt32
& aKernelSize
)
84 const double fSamplingRadius(rKernel
.GetWidth());
85 const double fScaledRadius((fScale
< 1.0) ? fSamplingRadius
/ fScale
: fSamplingRadius
);
86 const double fFilterFactor(std::min(fScale
, 1.0));
87 int aNumberOfContributions
;
90 aNumberOfContributions
= (static_cast< sal_uInt32
>(fabs(ceil(fScaledRadius
))) * 2) + 1 - 6;
91 aKernelSize
= aNumberOfContributions
/ 2 + 1;
93 // avoid a crash for now; re-think me.
97 pWeights
= new GLfloat
[16];
98 memset( pWeights
, 0, 16 * sizeof( GLfloat
) );
100 for( sal_uInt32
i(0); i
< aKernelSize
; i
++ )
102 const GLfloat
aWeight( rKernel
.Calculate( fFilterFactor
* i
) );
103 if( fabs( aWeight
) >= 0.0001 )
105 pWeights
[i
] = aWeight
;
106 aSum
+= i
> 0 ? aWeight
* 2 : aWeight
;
110 for( sal_uInt32
i(0); i
< aKernelSize
; i
++ )
116 bool OpenGLSalBitmap::ImplScaleConvolution(
117 const rtl::Reference
< OpenGLContext
> &xContext
,
118 const double& rScaleX
,
119 const double& rScaleY
,
120 const Kernel
& aKernel
)
122 OpenGLFramebuffer
* pFramebuffer
;
123 OpenGLProgram
* pProgram
;
124 GLfloat
* pWeights( nullptr );
125 sal_uInt32 nKernelSize
;
126 GLfloat aOffsets
[32];
127 int nNewWidth( mnWidth
* rScaleX
);
128 int nNewHeight( mnHeight
* rScaleY
);
130 // TODO Make sure the framebuffer is alright
132 pProgram
= xContext
->UseProgram( "textureVertexShader",
133 "convolutionFragmentShader" );
134 if( pProgram
== nullptr )
137 // horizontal scaling in scratch texture
138 if( mnWidth
!= nNewWidth
)
140 OpenGLTexture
aScratchTex(nNewWidth
, nNewHeight
);
142 pFramebuffer
= xContext
->AcquireFramebuffer( aScratchTex
);
144 for( sal_uInt32 i
= 0; i
< 16; i
++ )
146 aOffsets
[i
* 2] = i
/ static_cast<double>(mnWidth
);
147 aOffsets
[i
* 2 + 1] = 0;
149 ImplCreateKernel( rScaleX
, aKernel
, pWeights
, nKernelSize
);
150 pProgram
->SetUniform1fv( "kernel", 16, pWeights
);
151 pProgram
->SetUniform2fv( "offsets", 16, aOffsets
);
152 pProgram
->SetTexture( "sampler", maTexture
);
153 pProgram
->DrawTexture( maTexture
);
156 maTexture
= aScratchTex
;
157 OpenGLContext::ReleaseFramebuffer( pFramebuffer
);
160 // vertical scaling in final texture
161 if( mnHeight
!= nNewHeight
)
163 OpenGLTexture
aScratchTex(nNewWidth
, nNewHeight
);
165 pFramebuffer
= xContext
->AcquireFramebuffer( aScratchTex
);
167 for( sal_uInt32 i
= 0; i
< 16; i
++ )
170 aOffsets
[i
* 2 + 1] = i
/ static_cast<double>(mnHeight
);
172 ImplCreateKernel( rScaleY
, aKernel
, pWeights
, nKernelSize
);
173 pProgram
->SetUniform1fv( "kernel", 16, pWeights
);
174 pProgram
->SetUniform2fv( "offsets", 16, aOffsets
);
175 pProgram
->SetTexture( "sampler", maTexture
);
176 pProgram
->DrawTexture( maTexture
);
179 maTexture
= aScratchTex
;
180 OpenGLContext::ReleaseFramebuffer( pFramebuffer
);
184 mnHeight
= nNewHeight
;
191 "Area" scaling algorithm, which seems to give better results for downscaling
192 than other algorithms. The principle (taken from opencv, see resize.cl)
193 is that each resulting pixel is the average of all the source pixel values
194 it represents. Which is trivial in the case of exact multiples for downscaling,
195 the generic case needs to also consider that some source pixels contribute
196 only partially to their resulting pixels (because of non-integer multiples).
198 bool OpenGLSalBitmap::ImplScaleArea( const rtl::Reference
< OpenGLContext
> &xContext
,
199 double rScaleX
, double rScaleY
)
201 int nNewWidth( mnWidth
* rScaleX
);
202 int nNewHeight( mnHeight
* rScaleY
);
204 if( nNewWidth
== mnWidth
&& nNewHeight
== mnHeight
)
207 double ixscale
= 1 / rScaleX
;
208 double iyscale
= 1 / rScaleY
;
209 bool fast
= ( ixscale
== std::trunc( ixscale
) && iyscale
== std::trunc( iyscale
)
210 && int( nNewWidth
* ixscale
) == mnWidth
&& int( nNewHeight
* iyscale
) == mnHeight
);
212 bool bTwoPasses
= false;
214 // The generic case has arrays only up to 100 ratio downscaling, which is hopefully enough
215 // in practice, but protect against buffer overflows in case such an extreme case happens
216 // (and in such case the precision of the generic algorithm probably doesn't matter anyway).
217 if( ixscale
> 100 || iyscale
> 100 )
223 if (ixscale
> 16 || iyscale
> 16)
225 ixscale
= std::floor(std::sqrt(ixscale
));
226 iyscale
= std::floor(std::sqrt(iyscale
));
227 nNewWidth
= int(mnWidth
/ ixscale
);
228 rScaleX
*= ixscale
; // second pass x-scale factor
229 nNewHeight
= int(mnHeight
/ iyscale
);
230 rScaleY
*= iyscale
; // second pass y-scale factor
235 // TODO Make sure the framebuffer is alright
237 OString sUseReducedRegisterVariantDefine
;
238 if (xContext
->getOpenGLCapabilitySwitch().mbLimitedShaderRegisters
)
239 sUseReducedRegisterVariantDefine
= OString("#define USE_REDUCED_REGISTER_VARIANT\n");
241 OpenGLProgram
* pProgram
= xContext
->UseProgram( "textureVertexShader",
242 fast
? OUString( "areaScaleFastFragmentShader" ) : OUString( "areaScaleFragmentShader" ),
243 sUseReducedRegisterVariantDefine
);
245 if( pProgram
== nullptr )
248 OpenGLTexture
aScratchTex(nNewWidth
, nNewHeight
);
250 OpenGLFramebuffer
* pFramebuffer
= xContext
->AcquireFramebuffer( aScratchTex
);
252 // NOTE: This setup is also done in OpenGLSalGraphicsImpl::DrawTransformedTexture().
255 pProgram
->SetUniform1i( "xscale", ixscale
);
256 pProgram
->SetUniform1i( "yscale", iyscale
);
257 // The shader operates on pixels in the surrounding area, so it's necessary
258 // to know the step in texture coordinates to get to the next pixel.
259 // With a texture atlas the "texture" is just a subtexture of a larger texture,
260 // so while with a normal texture we'd map between <0.0,1.0> and <0,mnWidth>,
261 // with a subtexture the texture coordinates range is smaller.
262 GLfloat srcCoords
[ 8 ];
263 maTexture
.GetWholeCoord( srcCoords
);
264 pProgram
->SetUniform1f( "xstep", ( srcCoords
[ 4 ] - srcCoords
[ 0 ] ) / mnWidth
);
265 pProgram
->SetUniform1f( "ystep", ( srcCoords
[ 5 ] - srcCoords
[ 1 ] ) / mnHeight
);
266 pProgram
->SetUniform1f( "ratio", 1.0 / ( ixscale
* iyscale
));
270 pProgram
->SetUniform1f( "xscale", ixscale
);
271 pProgram
->SetUniform1f( "yscale", iyscale
);
272 pProgram
->SetUniform1i( "swidth", mnWidth
);
273 pProgram
->SetUniform1i( "sheight", mnHeight
);
274 // The shader internally actually operates on pixel coordinates,
275 // so it needs to know how to convert to those from the texture coordinates.
276 // With a simple texture that would mean converting e.g. between
277 // <0,mnWidth-1> and <0.0,1.0> coordinates.
278 // However with a texture atlas the "texture" is just a subtexture
279 // of a larger texture, so the texture coordinates need offset and ratio
281 GLfloat srcCoords
[ 8 ];
282 maTexture
.GetWholeCoord( srcCoords
);
283 pProgram
->SetUniform1f( "xoffset", srcCoords
[ 0 ] );
284 pProgram
->SetUniform1f( "yoffset", srcCoords
[ 1 ] );
285 pProgram
->SetUniform1f( "xtopixelratio", nNewWidth
/ ( srcCoords
[ 4 ] - srcCoords
[ 0 ] ));
286 pProgram
->SetUniform1f( "ytopixelratio", nNewHeight
/ ( srcCoords
[ 5 ] - srcCoords
[ 1 ] ));
287 pProgram
->SetUniform1f( "xfrompixelratio", ( srcCoords
[ 4 ] - srcCoords
[ 0 ] ) / mnWidth
);
288 pProgram
->SetUniform1f( "yfrompixelratio", ( srcCoords
[ 5 ] - srcCoords
[ 1 ] ) / mnHeight
);
291 pProgram
->SetTexture( "sampler", maTexture
);
292 pProgram
->DrawTexture( maTexture
);
295 OpenGLContext::ReleaseFramebuffer(pFramebuffer
);
302 mnHeight
= nNewHeight
;
304 nNewWidth
= round(mnWidth
* rScaleX
);
305 nNewHeight
= round(mnHeight
* rScaleY
);
307 ixscale
= 1 / rScaleX
;
308 iyscale
= 1 / rScaleY
;
310 pProgram
= xContext
->UseProgram("textureVertexShader", "areaScaleFragmentShader", sUseReducedRegisterVariantDefine
);
311 if (pProgram
== nullptr)
314 OpenGLTexture
aScratchTex2(nNewWidth
, nNewHeight
);
316 pFramebuffer
= xContext
->AcquireFramebuffer(aScratchTex2
);
318 pProgram
->SetUniform1f("xscale", ixscale
);
319 pProgram
->SetUniform1f("yscale", iyscale
);
320 pProgram
->SetUniform1i("swidth", mnWidth
);
321 pProgram
->SetUniform1i("sheight", mnHeight
);
323 GLfloat srcCoords
[ 8 ];
324 aScratchTex
.GetWholeCoord( srcCoords
);
325 pProgram
->SetUniform1f( "xoffset", srcCoords
[ 0 ] );
326 pProgram
->SetUniform1f( "yoffset", srcCoords
[ 1 ] );
327 pProgram
->SetUniform1f( "xtopixelratio", nNewWidth
/ ( srcCoords
[ 4 ] - srcCoords
[ 0 ] ));
328 pProgram
->SetUniform1f( "ytopixelratio", nNewHeight
/ ( srcCoords
[ 5 ] - srcCoords
[ 1 ] ));
329 pProgram
->SetUniform1f( "xfrompixelratio", ( srcCoords
[ 4 ] - srcCoords
[ 0 ] ) / mnWidth
);
330 pProgram
->SetUniform1f( "yfrompixelratio", ( srcCoords
[ 5 ] - srcCoords
[ 1 ] ) / mnHeight
);
332 pProgram
->SetTexture("sampler", aScratchTex
);
333 pProgram
->DrawTexture(aScratchTex
);
336 OpenGLContext::ReleaseFramebuffer(pFramebuffer
);
340 maTexture
= aScratchTex2
;
342 mnHeight
= nNewHeight
;
346 maTexture
= aScratchTex
;
348 mnHeight
= nNewHeight
;
354 void OpenGLSalBitmap::ImplScale( const double& rScaleX
, const double& rScaleY
, BmpScaleFlag nScaleFlag
)
356 VCL_GL_INFO( "::ImplScale" );
358 mpUserBuffer
.reset();
359 OpenGLVCLContextZone aContextZone
;
360 rtl::Reference
<OpenGLContext
> xContext
= OpenGLContext::getVCLContext();
361 xContext
->state().scissor().disable();
362 xContext
->state().stencil().disable();
364 if (rScaleX
<= 1 && rScaleY
<= 1)
366 nScaleFlag
= BmpScaleFlag::BestQuality
;
369 if( nScaleFlag
== BmpScaleFlag::Fast
)
371 ImplScaleFilter( xContext
, rScaleX
, rScaleY
, GL_NEAREST
);
373 else if( nScaleFlag
== BmpScaleFlag::BiLinear
)
375 ImplScaleFilter( xContext
, rScaleX
, rScaleY
, GL_LINEAR
);
377 else if( nScaleFlag
== BmpScaleFlag::Default
)
379 const Lanczos3Kernel aKernel
;
381 ImplScaleConvolution( xContext
, rScaleX
, rScaleY
, aKernel
);
383 else if( nScaleFlag
== BmpScaleFlag::BestQuality
&& rScaleX
<= 1 && rScaleY
<= 1 )
384 { // Use area scaling for best quality, but only if downscaling.
385 ImplScaleArea( xContext
, rScaleX
, rScaleY
);
387 else if( nScaleFlag
== BmpScaleFlag::Lanczos
|| nScaleFlag
== BmpScaleFlag::BestQuality
)
389 const Lanczos3Kernel aKernel
;
391 ImplScaleConvolution( xContext
, rScaleX
, rScaleY
, aKernel
);
394 SAL_WARN( "vcl.opengl", "Invalid flag for scaling operation" );
397 bool OpenGLSalBitmap::ScalingSupported() const
402 bool OpenGLSalBitmap::Scale( const double& rScaleX
, const double& rScaleY
, BmpScaleFlag nScaleFlag
)
404 OpenGLVCLContextZone aContextZone
;
406 VCL_GL_INFO("::Scale " << int(nScaleFlag
)
407 << " from " << mnWidth
<< "x" << mnHeight
408 << " to " << (mnWidth
* rScaleX
) << "x" << (mnHeight
* rScaleY
) );
410 if( nScaleFlag
== BmpScaleFlag::Fast
||
411 nScaleFlag
== BmpScaleFlag::BiLinear
||
412 nScaleFlag
== BmpScaleFlag::Lanczos
||
413 nScaleFlag
== BmpScaleFlag::Default
||
414 nScaleFlag
== BmpScaleFlag::BestQuality
)
416 ImplScale( rScaleX
, rScaleY
, nScaleFlag
);
423 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */