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>
22 #include <vcl/opengl/OpenGLHelper.hxx>
24 #include "vcl/bitmap.hxx"
26 #include "opengl/zone.hxx"
27 #include "opengl/bmpop.hxx"
28 #include "opengl/salbmp.hxx"
29 #include "opengl/program.hxx"
30 #include "opengl/texture.hxx"
32 class ScaleOp
: public OpenGLSalBitmapOp
35 OpenGLSalBitmap
* mpBitmap
;
38 BmpScaleFlag mnScaleFlag
;
41 ScaleOp( OpenGLSalBitmap
* pBitmap
, const double& rScaleX
, const double& rScaleY
, BmpScaleFlag nScaleFlag
);
43 bool Execute() SAL_OVERRIDE
;
44 void GetSize( Size
& rSize
) const SAL_OVERRIDE
;
47 bool OpenGLSalBitmap::ImplScaleFilter(
48 const double& rScaleX
,
49 const double& rScaleY
,
52 OpenGLFramebuffer
* pFramebuffer
;
53 OpenGLProgram
* pProgram
;
55 int nNewWidth( mnWidth
* rScaleX
);
56 int nNewHeight( mnHeight
* rScaleY
);
58 pProgram
= mpContext
->UseProgram( "textureVertexShader",
59 "textureFragmentShader" );
63 OpenGLTexture
aNewTex(nNewWidth
, nNewHeight
);
64 pFramebuffer
= mpContext
->AcquireFramebuffer( aNewTex
);
66 pProgram
->SetTexture( "sampler", maTexture
);
67 nOldFilter
= maTexture
.GetFilter();
68 maTexture
.SetFilter( nFilter
);
69 pProgram
->ApplyMatrix(mnWidth
, mnHeight
);
70 pProgram
->DrawTexture( maTexture
);
71 maTexture
.SetFilter( nOldFilter
);
74 OpenGLContext::ReleaseFramebuffer( pFramebuffer
);
77 mnHeight
= nNewHeight
;
84 void OpenGLSalBitmap::ImplCreateKernel(
86 const Kernel
& rKernel
,
88 sal_uInt32
& aKernelSize
)
90 const double fSamplingRadius(rKernel
.GetWidth());
91 const double fScaledRadius((fScale
< 1.0) ? fSamplingRadius
/ fScale
: fSamplingRadius
);
92 const double fFilterFactor((fScale
< 1.0) ? fScale
: 1.0);
93 int aNumberOfContributions
;
96 aNumberOfContributions
= (static_cast< sal_uInt32
>(fabs(ceil(fScaledRadius
))) * 2) + 1 - 6;
97 aKernelSize
= aNumberOfContributions
/ 2 + 1;
99 // avoid a crash for now; re-think me.
100 if (aKernelSize
> 16)
103 pWeights
= new GLfloat
[16];
104 memset( pWeights
, 0, 16 * sizeof( GLfloat
) );
106 for( sal_uInt32
i(0); i
< aKernelSize
; i
++ )
108 const GLfloat
aWeight( rKernel
.Calculate( fFilterFactor
* i
) );
109 if( fabs( aWeight
) >= 0.0001 )
111 pWeights
[i
] = aWeight
;
112 aSum
+= i
> 0 ? aWeight
* 2 : aWeight
;
116 for( sal_uInt32
i(0); i
< aKernelSize
; i
++ )
122 bool OpenGLSalBitmap::ImplScaleConvolution(
123 const double& rScaleX
,
124 const double& rScaleY
,
125 const Kernel
& aKernel
)
127 OpenGLFramebuffer
* pFramebuffer
;
128 OpenGLProgram
* pProgram
;
129 GLfloat
* pWeights( 0 );
130 sal_uInt32 nKernelSize
;
131 GLfloat aOffsets
[32];
132 int nNewWidth( mnWidth
* rScaleX
);
133 int nNewHeight( mnHeight
* rScaleY
);
135 // TODO Make sure the framebuffer is alright
137 pProgram
= mpContext
->UseProgram( "textureVertexShader",
138 "convolutionFragmentShader" );
142 // horizontal scaling in scratch texture
143 if( mnWidth
!= nNewWidth
)
145 OpenGLTexture
aScratchTex(nNewWidth
, nNewHeight
);
147 pFramebuffer
= mpContext
->AcquireFramebuffer( aScratchTex
);
149 for( sal_uInt32 i
= 0; i
< 16; i
++ )
151 aOffsets
[i
* 2] = i
/ (double) mnWidth
;
152 aOffsets
[i
* 2 + 1] = 0;
154 ImplCreateKernel( rScaleX
, aKernel
, pWeights
, nKernelSize
);
155 pProgram
->SetUniform1fv( "kernel", 16, pWeights
);
156 pProgram
->SetUniform2fv( "offsets", 16, aOffsets
);
157 pProgram
->SetTexture( "sampler", maTexture
);
158 pProgram
->DrawTexture( maTexture
);
161 maTexture
= aScratchTex
;
162 OpenGLContext::ReleaseFramebuffer( pFramebuffer
);
165 // vertical scaling in final texture
166 if( mnHeight
!= nNewHeight
)
168 OpenGLTexture
aScratchTex(nNewWidth
, nNewHeight
);
170 pFramebuffer
= mpContext
->AcquireFramebuffer( aScratchTex
);
172 for( sal_uInt32 i
= 0; i
< 16; i
++ )
175 aOffsets
[i
* 2 + 1] = i
/ (double) mnHeight
;
177 ImplCreateKernel( rScaleY
, aKernel
, pWeights
, nKernelSize
);
178 pProgram
->SetUniform1fv( "kernel", 16, pWeights
);
179 pProgram
->SetUniform2fv( "offsets", 16, aOffsets
);
180 pProgram
->SetTexture( "sampler", maTexture
);
181 pProgram
->DrawTexture( maTexture
);
184 maTexture
= aScratchTex
;
185 OpenGLContext::ReleaseFramebuffer( pFramebuffer
);
189 mnHeight
= nNewHeight
;
196 "Area" scaling algorithm, which seems to give better results for downscaling
197 than other algorithms. The principle (taken from opencv, see resize.cl)
198 is that each resulting pixel is the average of all the source pixel values
199 it represents. Which is trivial in the case of exact multiples for downscaling,
200 the generic case needs to also consider that some source pixels contribute
201 only partially to their resulting pixels (becauses of non-integer multiples).
203 bool OpenGLSalBitmap::ImplScaleArea( double rScaleX
, double rScaleY
)
205 int nNewWidth( mnWidth
* rScaleX
);
206 int nNewHeight( mnHeight
* rScaleY
);
208 if( nNewWidth
== mnWidth
&& nNewHeight
== mnHeight
)
211 double ixscale
= 1 / rScaleX
;
212 double iyscale
= 1 / rScaleY
;
213 bool fast
= ( ixscale
== int( ixscale
) && iyscale
== int( iyscale
)
214 && int( nNewWidth
* ixscale
) == mnWidth
&& int( nNewHeight
* iyscale
) == mnHeight
);
216 // The generic case has arrays only up to 100 ratio downscaling, which is hopefully enough
217 // in practice, but protect against buffer overflows in case such an extreme case happens
218 // (and in such case the precision of the generic algorithm probably doesn't matter anyway).
219 if( ixscale
> 100 || iyscale
> 100 )
222 // TODO Make sure the framebuffer is alright
224 OpenGLProgram
* pProgram
= mpContext
->UseProgram( "textureVertexShader",
225 fast
? OUString( "areaScaleFastFragmentShader" ) : OUString( "areaScaleFragmentShader" ));
229 OpenGLTexture
aScratchTex(nNewWidth
, nNewHeight
);
231 OpenGLFramebuffer
* pFramebuffer
= mpContext
->AcquireFramebuffer( aScratchTex
);
233 // NOTE: This setup is also done in OpenGLSalGraphicsImpl::DrawTransformedTexture().
236 pProgram
->SetUniform1i( "xscale", ixscale
);
237 pProgram
->SetUniform1i( "yscale", iyscale
);
238 pProgram
->SetUniform1f( "xstep", 1.0 / mnWidth
);
239 pProgram
->SetUniform1f( "ystep", 1.0 / mnHeight
);
240 pProgram
->SetUniform1f( "ratio", 1.0 / ( ixscale
* iyscale
));
244 pProgram
->SetUniform1f( "xscale", ixscale
);
245 pProgram
->SetUniform1f( "yscale", iyscale
);
246 pProgram
->SetUniform1i( "swidth", mnWidth
);
247 pProgram
->SetUniform1i( "sheight", mnHeight
);
248 // For converting between <0,mnWidth-1> and <0.0,1.0> coordinate systems.
249 pProgram
->SetUniform1f( "xsrcconvert", 1.0 / ( mnWidth
- 1 ));
250 pProgram
->SetUniform1f( "ysrcconvert", 1.0 / ( mnHeight
- 1 ));
251 pProgram
->SetUniform1f( "xdestconvert", 1.0 * ( nNewWidth
- 1 ));
252 pProgram
->SetUniform1f( "ydestconvert", 1.0 * ( nNewHeight
- 1 ));
255 pProgram
->SetTexture( "sampler", maTexture
);
256 pProgram
->DrawTexture( maTexture
);
259 maTexture
= aScratchTex
;
260 OpenGLContext::ReleaseFramebuffer( pFramebuffer
);
263 mnHeight
= nNewHeight
;
269 bool OpenGLSalBitmap::ImplScale( const double& rScaleX
, const double& rScaleY
, BmpScaleFlag nScaleFlag
)
271 VCL_GL_INFO( "vcl.opengl", "::ImplScale" );
273 maUserBuffer
.reset();
276 if( nScaleFlag
== BmpScaleFlag::Fast
)
278 return ImplScaleFilter( rScaleX
, rScaleY
, GL_NEAREST
);
280 if( nScaleFlag
== BmpScaleFlag::BiLinear
)
282 return ImplScaleFilter( rScaleX
, rScaleY
, GL_LINEAR
);
284 else if( nScaleFlag
== BmpScaleFlag::Super
|| nScaleFlag
== BmpScaleFlag::Default
)
286 const Lanczos3Kernel aKernel
;
288 return ImplScaleConvolution( rScaleX
, rScaleY
, aKernel
);
290 else if( nScaleFlag
== BmpScaleFlag::BestQuality
&& rScaleX
<= 1 && rScaleY
<= 1 )
291 { // Use are scaling for best quality, but only if downscaling.
292 return ImplScaleArea( rScaleX
, rScaleY
);
294 else if( nScaleFlag
== BmpScaleFlag::Lanczos
|| nScaleFlag
== BmpScaleFlag::BestQuality
)
296 const Lanczos3Kernel aKernel
;
298 return ImplScaleConvolution( rScaleX
, rScaleY
, aKernel
);
301 SAL_WARN( "vcl.opengl", "Invalid flag for scaling operation" );
306 OpenGLSalBitmap
* pBitmap
,
307 const double& rScaleX
,
308 const double& rScaleY
,
309 BmpScaleFlag nScaleFlag
)
310 : mpBitmap( pBitmap
)
311 , mfScaleX( rScaleX
)
312 , mfScaleY( rScaleY
)
313 , mnScaleFlag( nScaleFlag
)
317 bool ScaleOp::Execute()
319 VCL_GL_INFO( "vcl.opengl", "::Execute" );
320 return mpBitmap
->ImplScale( mfScaleX
, mfScaleY
, mnScaleFlag
);
323 void ScaleOp::GetSize( Size
& rSize
) const
325 VCL_GL_INFO( "vcl.opengl", "::GetSize" );
326 rSize
.setWidth( rSize
.Width() * mfScaleX
);
327 rSize
.setHeight( rSize
.Height() * mfScaleY
);
330 bool OpenGLSalBitmap::Scale( const double& rScaleX
, const double& rScaleY
, BmpScaleFlag nScaleFlag
)
334 VCL_GL_INFO("vcl.opengl", "::Scale " << int(nScaleFlag
)
335 << " from " << mnWidth
<< "x" << mnHeight
336 << " to " << (mnWidth
* rScaleX
) << "x" << (mnHeight
* rScaleY
) );
338 if( nScaleFlag
== BmpScaleFlag::Fast
||
339 nScaleFlag
== BmpScaleFlag::BiLinear
||
340 nScaleFlag
== BmpScaleFlag::Super
||
341 nScaleFlag
== BmpScaleFlag::Lanczos
||
342 nScaleFlag
== BmpScaleFlag::Default
||
343 nScaleFlag
== BmpScaleFlag::BestQuality
)
346 if( mpContext
== NULL
)
348 VCL_GL_INFO( "vcl.opengl", "Add ScaleOp to pending operations" );
349 maPendingOps
.push_back( new ScaleOp( this, rScaleX
, rScaleY
, nScaleFlag
) );
353 ImplScale( rScaleX
, rScaleY
, nScaleFlag
);
361 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */