1 /************************************************************** 2 * 3 * Licensed to the Apache Software Foundation (ASF) under one 4 * or more contributor license agreements. See the NOTICE file 5 * distributed with this work for additional information 6 * regarding copyright ownership. The ASF licenses this file 7 * to you under the Apache License, Version 2.0 (the 8 * "License"); you may not use this file except in compliance 9 * with the License. You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, 14 * software distributed under the License is distributed on an 15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 * KIND, either express or implied. See the License for the 17 * specific language governing permissions and limitations 18 * under the License. 19 * 20 *************************************************************/ 21 22 23 24 // MARKER(update_precomp.py): autogen include statement, do not remove 25 #include "precompiled_canvas.hxx" 26 27 #include <canvas/debug.hxx> 28 #include <tools/diagnose_ex.h> 29 30 #include <com/sun/star/geometry/AffineMatrix2D.hpp> 31 #include <com/sun/star/geometry/Matrix2D.hpp> 32 #include <com/sun/star/awt/Rectangle.hpp> 33 #include <com/sun/star/util/Endianness.hpp> 34 #include <com/sun/star/rendering/XIntegerBitmapColorSpace.hpp> 35 #include <com/sun/star/rendering/IntegerBitmapLayout.hpp> 36 #include <com/sun/star/rendering/ColorSpaceType.hpp> 37 #include <com/sun/star/rendering/ColorComponentTag.hpp> 38 #include <com/sun/star/rendering/RenderingIntent.hpp> 39 #include <com/sun/star/rendering/RenderState.hpp> 40 #include <com/sun/star/rendering/ViewState.hpp> 41 #include <com/sun/star/rendering/XCanvas.hpp> 42 #include <com/sun/star/rendering/XColorSpace.hpp> 43 #include <com/sun/star/rendering/CompositeOperation.hpp> 44 #include <com/sun/star/beans/XPropertySet.hpp> 45 #include <com/sun/star/lang/XServiceInfo.hpp> 46 47 #include <basegfx/matrix/b2dhommatrix.hxx> 48 #include <basegfx/range/b2drange.hxx> 49 #include <basegfx/range/b2irange.hxx> 50 #include <basegfx/range/b2drectangle.hxx> 51 #include <basegfx/point/b2dpoint.hxx> 52 #include <basegfx/point/b2ipoint.hxx> 53 #include <basegfx/vector/b2ivector.hxx> 54 #include <basegfx/polygon/b2dpolygon.hxx> 55 #include <basegfx/polygon/b2dpolygontools.hxx> 56 #include <basegfx/polygon/b2dpolypolygontools.hxx> 57 #include <basegfx/tools/canvastools.hxx> 58 #include <basegfx/numeric/ftools.hxx> 59 #include <basegfx/matrix/b2dhommatrixtools.hxx> 60 61 #include <cppuhelper/compbase1.hxx> 62 #include <rtl/instance.hxx> 63 #include <toolkit/helper/vclunohelper.hxx> 64 #include <vcl/window.hxx> 65 #include <vcl/canvastools.hxx> 66 67 #include <canvas/canvastools.hxx> 68 69 #include <limits> 70 71 72 using namespace ::com::sun::star; 73 74 namespace com { namespace sun { namespace star { namespace rendering 75 { operator ==(const RenderState & renderState1,const RenderState & renderState2)76 bool operator==( const RenderState& renderState1, 77 const RenderState& renderState2 ) 78 { 79 if( renderState1.Clip != renderState2.Clip ) 80 return false; 81 82 if( renderState1.DeviceColor != renderState2.DeviceColor ) 83 return false; 84 85 if( renderState1.CompositeOperation != renderState2.CompositeOperation ) 86 return false; 87 88 ::basegfx::B2DHomMatrix mat1, mat2; 89 ::canvas::tools::getRenderStateTransform( mat1, renderState1 ); 90 ::canvas::tools::getRenderStateTransform( mat2, renderState2 ); 91 if( mat1 != mat2 ) 92 return false; 93 94 return true; 95 } 96 operator ==(const ViewState & viewState1,const ViewState & viewState2)97 bool operator==( const ViewState& viewState1, 98 const ViewState& viewState2 ) 99 { 100 if( viewState1.Clip != viewState2.Clip ) 101 return false; 102 103 ::basegfx::B2DHomMatrix mat1, mat2; 104 ::canvas::tools::getViewStateTransform( mat1, viewState1 ); 105 ::canvas::tools::getViewStateTransform( mat2, viewState2 ); 106 if( mat1 != mat2 ) 107 return false; 108 109 return true; 110 } 111 }}}} 112 113 namespace canvas 114 { 115 namespace tools 116 { createInfiniteSize2D()117 geometry::RealSize2D createInfiniteSize2D() 118 { 119 return geometry::RealSize2D( 120 ::std::numeric_limits<double>::infinity(), 121 ::std::numeric_limits<double>::infinity() ); 122 } 123 initRenderState(rendering::RenderState & renderState)124 rendering::RenderState& initRenderState( rendering::RenderState& renderState ) 125 { 126 // setup identity transform 127 setIdentityAffineMatrix2D( renderState.AffineTransform ); 128 renderState.Clip = uno::Reference< rendering::XPolyPolygon2D >(); 129 renderState.DeviceColor = uno::Sequence< double >(); 130 renderState.CompositeOperation = rendering::CompositeOperation::OVER; 131 132 return renderState; 133 } 134 initViewState(rendering::ViewState & viewState)135 rendering::ViewState& initViewState( rendering::ViewState& viewState ) 136 { 137 // setup identity transform 138 setIdentityAffineMatrix2D( viewState.AffineTransform ); 139 viewState.Clip = uno::Reference< rendering::XPolyPolygon2D >(); 140 141 return viewState; 142 } 143 getViewStateTransform(::basegfx::B2DHomMatrix & transform,const rendering::ViewState & viewState)144 ::basegfx::B2DHomMatrix& getViewStateTransform( ::basegfx::B2DHomMatrix& transform, 145 const rendering::ViewState& viewState ) 146 { 147 return ::basegfx::unotools::homMatrixFromAffineMatrix( transform, viewState.AffineTransform ); 148 } 149 setViewStateTransform(rendering::ViewState & viewState,const::basegfx::B2DHomMatrix & transform)150 rendering::ViewState& setViewStateTransform( rendering::ViewState& viewState, 151 const ::basegfx::B2DHomMatrix& transform ) 152 { 153 ::basegfx::unotools::affineMatrixFromHomMatrix( viewState.AffineTransform, transform ); 154 155 return viewState; 156 } 157 getRenderStateTransform(::basegfx::B2DHomMatrix & transform,const rendering::RenderState & renderState)158 ::basegfx::B2DHomMatrix& getRenderStateTransform( ::basegfx::B2DHomMatrix& transform, 159 const rendering::RenderState& renderState ) 160 { 161 return ::basegfx::unotools::homMatrixFromAffineMatrix( transform, renderState.AffineTransform ); 162 } 163 setRenderStateTransform(rendering::RenderState & renderState,const::basegfx::B2DHomMatrix & transform)164 rendering::RenderState& setRenderStateTransform( rendering::RenderState& renderState, 165 const ::basegfx::B2DHomMatrix& transform ) 166 { 167 ::basegfx::unotools::affineMatrixFromHomMatrix( renderState.AffineTransform, transform ); 168 169 return renderState; 170 } 171 appendToRenderState(rendering::RenderState & renderState,const::basegfx::B2DHomMatrix & rTransform)172 rendering::RenderState& appendToRenderState( rendering::RenderState& renderState, 173 const ::basegfx::B2DHomMatrix& rTransform ) 174 { 175 ::basegfx::B2DHomMatrix transform; 176 177 getRenderStateTransform( transform, renderState ); 178 return setRenderStateTransform( renderState, transform * rTransform ); 179 } 180 appendToViewState(rendering::ViewState & viewState,const::basegfx::B2DHomMatrix & rTransform)181 rendering::ViewState& appendToViewState( rendering::ViewState& viewState, 182 const ::basegfx::B2DHomMatrix& rTransform ) 183 { 184 ::basegfx::B2DHomMatrix transform; 185 186 getViewStateTransform( transform, viewState ); 187 return setViewStateTransform( viewState, transform * rTransform ); 188 } 189 prependToRenderState(rendering::RenderState & renderState,const::basegfx::B2DHomMatrix & rTransform)190 rendering::RenderState& prependToRenderState( rendering::RenderState& renderState, 191 const ::basegfx::B2DHomMatrix& rTransform ) 192 { 193 ::basegfx::B2DHomMatrix transform; 194 195 getRenderStateTransform( transform, renderState ); 196 return setRenderStateTransform( renderState, rTransform * transform ); 197 } 198 prependToViewState(rendering::ViewState & viewState,const::basegfx::B2DHomMatrix & rTransform)199 rendering::ViewState& prependToViewState( rendering::ViewState& viewState, 200 const ::basegfx::B2DHomMatrix& rTransform ) 201 { 202 ::basegfx::B2DHomMatrix transform; 203 204 getViewStateTransform( transform, viewState ); 205 return setViewStateTransform( viewState, rTransform * transform ); 206 } 207 mergeViewAndRenderTransform(::basegfx::B2DHomMatrix & combinedTransform,const rendering::ViewState & viewState,const rendering::RenderState & renderState)208 ::basegfx::B2DHomMatrix& mergeViewAndRenderTransform( ::basegfx::B2DHomMatrix& combinedTransform, 209 const rendering::ViewState& viewState, 210 const rendering::RenderState& renderState ) 211 { 212 ::basegfx::B2DHomMatrix viewTransform; 213 214 ::basegfx::unotools::homMatrixFromAffineMatrix( combinedTransform, renderState.AffineTransform ); 215 ::basegfx::unotools::homMatrixFromAffineMatrix( viewTransform, viewState.AffineTransform ); 216 217 // this statement performs combinedTransform = viewTransform * combinedTransform 218 combinedTransform *= viewTransform; 219 220 return combinedTransform; 221 } 222 mergeViewAndRenderState(rendering::ViewState & resultViewState,const rendering::ViewState & viewState,const rendering::RenderState & renderState,const uno::Reference<rendering::XCanvas> &)223 rendering::ViewState& mergeViewAndRenderState( rendering::ViewState& resultViewState, 224 const rendering::ViewState& viewState, 225 const rendering::RenderState& renderState, 226 const uno::Reference< rendering::XCanvas >& /*xCanvas*/ ) 227 { 228 ::basegfx::B2DHomMatrix aTmpMatrix; 229 geometry::AffineMatrix2D convertedMatrix; 230 231 resultViewState.Clip = NULL; // TODO(F2): intersect clippings 232 233 return setViewStateTransform( 234 resultViewState, 235 mergeViewAndRenderTransform( aTmpMatrix, 236 viewState, 237 renderState ) ); 238 } 239 setIdentityAffineMatrix2D(geometry::AffineMatrix2D & matrix)240 geometry::AffineMatrix2D& setIdentityAffineMatrix2D( geometry::AffineMatrix2D& matrix ) 241 { 242 matrix.m00 = 1.0; 243 matrix.m01 = 0.0; 244 matrix.m02 = 0.0; 245 matrix.m10 = 0.0; 246 matrix.m11 = 1.0; 247 matrix.m12 = 0.0; 248 249 return matrix; 250 } 251 setIdentityMatrix2D(geometry::Matrix2D & matrix)252 geometry::Matrix2D& setIdentityMatrix2D( geometry::Matrix2D& matrix ) 253 { 254 matrix.m00 = 1.0; 255 matrix.m01 = 0.0; 256 matrix.m10 = 0.0; 257 matrix.m11 = 1.0; 258 259 return matrix; 260 } 261 262 namespace 263 { 264 class StandardColorSpace : public cppu::WeakImplHelper1< com::sun::star::rendering::XIntegerBitmapColorSpace > 265 { 266 private: 267 uno::Sequence< sal_Int8 > maComponentTags; 268 uno::Sequence< sal_Int32 > maBitCounts; 269 getType()270 virtual ::sal_Int8 SAL_CALL getType( ) throw (uno::RuntimeException) 271 { 272 return rendering::ColorSpaceType::RGB; 273 } getComponentTags()274 virtual uno::Sequence< ::sal_Int8 > SAL_CALL getComponentTags( ) throw (uno::RuntimeException) 275 { 276 return maComponentTags; 277 } getRenderingIntent()278 virtual ::sal_Int8 SAL_CALL getRenderingIntent( ) throw (uno::RuntimeException) 279 { 280 return rendering::RenderingIntent::PERCEPTUAL; 281 } getProperties()282 virtual uno::Sequence< beans::PropertyValue > SAL_CALL getProperties( ) throw (uno::RuntimeException) 283 { 284 return uno::Sequence< beans::PropertyValue >(); 285 } convertColorSpace(const uno::Sequence<double> & deviceColor,const uno::Reference<rendering::XColorSpace> & targetColorSpace)286 virtual uno::Sequence< double > SAL_CALL convertColorSpace( const uno::Sequence< double >& deviceColor, 287 const uno::Reference< rendering::XColorSpace >& targetColorSpace ) throw (lang::IllegalArgumentException, 288 uno::RuntimeException) 289 { 290 // TODO(P3): if we know anything about target 291 // colorspace, this can be greatly sped up 292 uno::Sequence<rendering::ARGBColor> aIntermediate( 293 convertToARGB(deviceColor)); 294 return targetColorSpace->convertFromARGB(aIntermediate); 295 } convertToRGB(const uno::Sequence<double> & deviceColor)296 virtual uno::Sequence< rendering::RGBColor > SAL_CALL convertToRGB( const uno::Sequence< double >& deviceColor ) throw (lang::IllegalArgumentException, uno::RuntimeException) 297 { 298 const double* pIn( deviceColor.getConstArray() ); 299 const sal_Size nLen( deviceColor.getLength() ); 300 ENSURE_ARG_OR_THROW2(nLen%4==0, 301 "number of channels no multiple of 4", 302 static_cast<rendering::XColorSpace*>(this), 0); 303 304 uno::Sequence< rendering::RGBColor > aRes(nLen/4); 305 rendering::RGBColor* pOut( aRes.getArray() ); 306 for( sal_Size i=0; i<nLen; i+=4 ) 307 { 308 *pOut++ = rendering::RGBColor(pIn[0],pIn[1],pIn[2]); 309 pIn += 4; 310 } 311 return aRes; 312 } convertToARGB(const uno::Sequence<double> & deviceColor)313 virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToARGB( const uno::Sequence< double >& deviceColor ) throw (lang::IllegalArgumentException, uno::RuntimeException) 314 { 315 const double* pIn( deviceColor.getConstArray() ); 316 const sal_Size nLen( deviceColor.getLength() ); 317 ENSURE_ARG_OR_THROW2(nLen%4==0, 318 "number of channels no multiple of 4", 319 static_cast<rendering::XColorSpace*>(this), 0); 320 321 uno::Sequence< rendering::ARGBColor > aRes(nLen/4); 322 rendering::ARGBColor* pOut( aRes.getArray() ); 323 for( sal_Size i=0; i<nLen; i+=4 ) 324 { 325 *pOut++ = rendering::ARGBColor(pIn[3],pIn[0],pIn[1],pIn[2]); 326 pIn += 4; 327 } 328 return aRes; 329 } convertToPARGB(const uno::Sequence<double> & deviceColor)330 virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToPARGB( const uno::Sequence< double >& deviceColor ) throw (lang::IllegalArgumentException, uno::RuntimeException) 331 { 332 const double* pIn( deviceColor.getConstArray() ); 333 const sal_Size nLen( deviceColor.getLength() ); 334 ENSURE_ARG_OR_THROW2(nLen%4==0, 335 "number of channels no multiple of 4", 336 static_cast<rendering::XColorSpace*>(this), 0); 337 338 uno::Sequence< rendering::ARGBColor > aRes(nLen/4); 339 rendering::ARGBColor* pOut( aRes.getArray() ); 340 for( sal_Size i=0; i<nLen; i+=4 ) 341 { 342 *pOut++ = rendering::ARGBColor(pIn[3],pIn[3]*pIn[0],pIn[3]*pIn[1],pIn[3]*pIn[2]); 343 pIn += 4; 344 } 345 return aRes; 346 } convertFromRGB(const uno::Sequence<rendering::RGBColor> & rgbColor)347 virtual uno::Sequence< double > SAL_CALL convertFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) throw (lang::IllegalArgumentException, uno::RuntimeException) 348 { 349 const rendering::RGBColor* pIn( rgbColor.getConstArray() ); 350 const sal_Size nLen( rgbColor.getLength() ); 351 352 uno::Sequence< double > aRes(nLen*4); 353 double* pColors=aRes.getArray(); 354 for( sal_Size i=0; i<nLen; ++i ) 355 { 356 *pColors++ = pIn->Red; 357 *pColors++ = pIn->Green; 358 *pColors++ = pIn->Blue; 359 *pColors++ = 1.0; 360 ++pIn; 361 } 362 return aRes; 363 } convertFromARGB(const uno::Sequence<rendering::ARGBColor> & rgbColor)364 virtual uno::Sequence< double > SAL_CALL convertFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) throw (lang::IllegalArgumentException, uno::RuntimeException) 365 { 366 const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); 367 const sal_Size nLen( rgbColor.getLength() ); 368 369 uno::Sequence< double > aRes(nLen*4); 370 double* pColors=aRes.getArray(); 371 for( sal_Size i=0; i<nLen; ++i ) 372 { 373 *pColors++ = pIn->Red; 374 *pColors++ = pIn->Green; 375 *pColors++ = pIn->Blue; 376 *pColors++ = pIn->Alpha; 377 ++pIn; 378 } 379 return aRes; 380 } convertFromPARGB(const uno::Sequence<rendering::ARGBColor> & rgbColor)381 virtual uno::Sequence< double > SAL_CALL convertFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) throw (lang::IllegalArgumentException, uno::RuntimeException) 382 { 383 const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); 384 const sal_Size nLen( rgbColor.getLength() ); 385 386 uno::Sequence< double > aRes(nLen*4); 387 double* pColors=aRes.getArray(); 388 for( sal_Size i=0; i<nLen; ++i ) 389 { 390 *pColors++ = pIn->Red/pIn->Alpha; 391 *pColors++ = pIn->Green/pIn->Alpha; 392 *pColors++ = pIn->Blue/pIn->Alpha; 393 *pColors++ = pIn->Alpha; 394 ++pIn; 395 } 396 return aRes; 397 } 398 399 // XIntegerBitmapColorSpace getBitsPerPixel()400 virtual ::sal_Int32 SAL_CALL getBitsPerPixel( ) throw (uno::RuntimeException) 401 { 402 return 32; 403 } getComponentBitCounts()404 virtual uno::Sequence< ::sal_Int32 > SAL_CALL getComponentBitCounts( ) throw (uno::RuntimeException) 405 { 406 return maBitCounts; 407 } getEndianness()408 virtual ::sal_Int8 SAL_CALL getEndianness( ) throw (uno::RuntimeException) 409 { 410 return util::Endianness::LITTLE; 411 } convertFromIntegerColorSpace(const uno::Sequence<::sal_Int8> & deviceColor,const uno::Reference<rendering::XColorSpace> & targetColorSpace)412 virtual uno::Sequence<double> SAL_CALL convertFromIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor, 413 const uno::Reference< rendering::XColorSpace >& targetColorSpace ) throw (lang::IllegalArgumentException, 414 uno::RuntimeException) 415 { 416 if( dynamic_cast<StandardColorSpace*>(targetColorSpace.get()) ) 417 { 418 const sal_Int8* pIn( deviceColor.getConstArray() ); 419 const sal_Size nLen( deviceColor.getLength() ); 420 ENSURE_ARG_OR_THROW2(nLen%4==0, 421 "number of channels no multiple of 4", 422 static_cast<rendering::XColorSpace*>(this), 0); 423 424 uno::Sequence<double> aRes(nLen); 425 double* pOut( aRes.getArray() ); 426 for( sal_Size i=0; i<nLen; i+=4 ) 427 { 428 *pOut++ = vcl::unotools::toDoubleColor(*pIn++); 429 *pOut++ = vcl::unotools::toDoubleColor(*pIn++); 430 *pOut++ = vcl::unotools::toDoubleColor(*pIn++); 431 *pOut++ = vcl::unotools::toDoubleColor(255-*pIn++); 432 } 433 return aRes; 434 } 435 else 436 { 437 // TODO(P3): if we know anything about target 438 // colorspace, this can be greatly sped up 439 uno::Sequence<rendering::ARGBColor> aIntermediate( 440 convertIntegerToARGB(deviceColor)); 441 return targetColorSpace->convertFromARGB(aIntermediate); 442 } 443 } convertToIntegerColorSpace(const uno::Sequence<::sal_Int8> & deviceColor,const uno::Reference<rendering::XIntegerBitmapColorSpace> & targetColorSpace)444 virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertToIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor, 445 const uno::Reference< rendering::XIntegerBitmapColorSpace >& targetColorSpace ) throw (lang::IllegalArgumentException, 446 uno::RuntimeException) 447 { 448 if( dynamic_cast<StandardColorSpace*>(targetColorSpace.get()) ) 449 { 450 // it's us, so simply pass-through the data 451 return deviceColor; 452 } 453 else 454 { 455 // TODO(P3): if we know anything about target 456 // colorspace, this can be greatly sped up 457 uno::Sequence<rendering::ARGBColor> aIntermediate( 458 convertIntegerToARGB(deviceColor)); 459 return targetColorSpace->convertIntegerFromARGB(aIntermediate); 460 } 461 } convertIntegerToRGB(const uno::Sequence<::sal_Int8> & deviceColor)462 virtual uno::Sequence< rendering::RGBColor > SAL_CALL convertIntegerToRGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) throw (lang::IllegalArgumentException, uno::RuntimeException) 463 { 464 const sal_Int8* pIn( deviceColor.getConstArray() ); 465 const sal_Size nLen( deviceColor.getLength() ); 466 ENSURE_ARG_OR_THROW2(nLen%4==0, 467 "number of channels no multiple of 4", 468 static_cast<rendering::XColorSpace*>(this), 0); 469 470 uno::Sequence< rendering::RGBColor > aRes(nLen/4); 471 rendering::RGBColor* pOut( aRes.getArray() ); 472 for( sal_Size i=0; i<nLen; i+=4 ) 473 { 474 *pOut++ = rendering::RGBColor( 475 vcl::unotools::toDoubleColor(pIn[0]), 476 vcl::unotools::toDoubleColor(pIn[1]), 477 vcl::unotools::toDoubleColor(pIn[2])); 478 pIn += 4; 479 } 480 return aRes; 481 } 482 convertIntegerToARGB(const uno::Sequence<::sal_Int8> & deviceColor)483 virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertIntegerToARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) throw (lang::IllegalArgumentException, uno::RuntimeException) 484 { 485 const sal_Int8* pIn( deviceColor.getConstArray() ); 486 const sal_Size nLen( deviceColor.getLength() ); 487 ENSURE_ARG_OR_THROW2(nLen%4==0, 488 "number of channels no multiple of 4", 489 static_cast<rendering::XColorSpace*>(this), 0); 490 491 uno::Sequence< rendering::ARGBColor > aRes(nLen/4); 492 rendering::ARGBColor* pOut( aRes.getArray() ); 493 for( sal_Size i=0; i<nLen; i+=4 ) 494 { 495 *pOut++ = rendering::ARGBColor( 496 vcl::unotools::toDoubleColor(255-pIn[3]), 497 vcl::unotools::toDoubleColor(pIn[0]), 498 vcl::unotools::toDoubleColor(pIn[1]), 499 vcl::unotools::toDoubleColor(pIn[2])); 500 pIn += 4; 501 } 502 return aRes; 503 } 504 convertIntegerToPARGB(const uno::Sequence<::sal_Int8> & deviceColor)505 virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertIntegerToPARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) throw (lang::IllegalArgumentException, uno::RuntimeException) 506 { 507 const sal_Int8* pIn( deviceColor.getConstArray() ); 508 const sal_Size nLen( deviceColor.getLength() ); 509 ENSURE_ARG_OR_THROW2(nLen%4==0, 510 "number of channels no multiple of 4", 511 static_cast<rendering::XColorSpace*>(this), 0); 512 513 uno::Sequence< rendering::ARGBColor > aRes(nLen/4); 514 rendering::ARGBColor* pOut( aRes.getArray() ); 515 for( sal_Size i=0; i<nLen; i+=4 ) 516 { 517 const sal_Int8 nAlpha( 255-pIn[3] ); 518 *pOut++ = rendering::ARGBColor( 519 vcl::unotools::toDoubleColor(nAlpha), 520 vcl::unotools::toDoubleColor(nAlpha*pIn[0]), 521 vcl::unotools::toDoubleColor(nAlpha*pIn[1]), 522 vcl::unotools::toDoubleColor(nAlpha*pIn[2])); 523 pIn += 4; 524 } 525 return aRes; 526 } 527 convertIntegerFromRGB(const uno::Sequence<rendering::RGBColor> & rgbColor)528 virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) throw (lang::IllegalArgumentException, uno::RuntimeException) 529 { 530 const rendering::RGBColor* pIn( rgbColor.getConstArray() ); 531 const sal_Size nLen( rgbColor.getLength() ); 532 533 uno::Sequence< sal_Int8 > aRes(nLen*4); 534 sal_Int8* pColors=aRes.getArray(); 535 for( sal_Size i=0; i<nLen; ++i ) 536 { 537 *pColors++ = vcl::unotools::toByteColor(pIn->Red); 538 *pColors++ = vcl::unotools::toByteColor(pIn->Green); 539 *pColors++ = vcl::unotools::toByteColor(pIn->Blue); 540 *pColors++ = 0; 541 ++pIn; 542 } 543 return aRes; 544 } 545 convertIntegerFromARGB(const uno::Sequence<rendering::ARGBColor> & rgbColor)546 virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) throw (lang::IllegalArgumentException, uno::RuntimeException) 547 { 548 const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); 549 const sal_Size nLen( rgbColor.getLength() ); 550 551 uno::Sequence< sal_Int8 > aRes(nLen*4); 552 sal_Int8* pColors=aRes.getArray(); 553 for( sal_Size i=0; i<nLen; ++i ) 554 { 555 *pColors++ = vcl::unotools::toByteColor(pIn->Red); 556 *pColors++ = vcl::unotools::toByteColor(pIn->Green); 557 *pColors++ = vcl::unotools::toByteColor(pIn->Blue); 558 *pColors++ = 255-vcl::unotools::toByteColor(pIn->Alpha); 559 ++pIn; 560 } 561 return aRes; 562 } 563 convertIntegerFromPARGB(const uno::Sequence<rendering::ARGBColor> & rgbColor)564 virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) throw (lang::IllegalArgumentException, uno::RuntimeException) 565 { 566 const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); 567 const sal_Size nLen( rgbColor.getLength() ); 568 569 uno::Sequence< sal_Int8 > aRes(nLen*4); 570 sal_Int8* pColors=aRes.getArray(); 571 for( sal_Size i=0; i<nLen; ++i ) 572 { 573 *pColors++ = vcl::unotools::toByteColor(pIn->Red/pIn->Alpha); 574 *pColors++ = vcl::unotools::toByteColor(pIn->Green/pIn->Alpha); 575 *pColors++ = vcl::unotools::toByteColor(pIn->Blue/pIn->Alpha); 576 *pColors++ = 255-vcl::unotools::toByteColor(pIn->Alpha); 577 ++pIn; 578 } 579 return aRes; 580 } 581 582 public: StandardColorSpace()583 StandardColorSpace() : 584 maComponentTags(4), 585 maBitCounts(4) 586 { 587 sal_Int8* pTags = maComponentTags.getArray(); 588 sal_Int32* pBitCounts = maBitCounts.getArray(); 589 pTags[0] = rendering::ColorComponentTag::RGB_RED; 590 pTags[1] = rendering::ColorComponentTag::RGB_GREEN; 591 pTags[2] = rendering::ColorComponentTag::RGB_BLUE; 592 pTags[3] = rendering::ColorComponentTag::ALPHA; 593 594 pBitCounts[0] = 595 pBitCounts[1] = 596 pBitCounts[2] = 597 pBitCounts[3] = 8; 598 } 599 }; 600 601 struct StandardColorSpaceHolder : public rtl::StaticWithInit<uno::Reference<rendering::XIntegerBitmapColorSpace>, 602 StandardColorSpaceHolder> 603 { operator ()canvas::tools::__anon85958e0f0111::StandardColorSpaceHolder604 uno::Reference<rendering::XIntegerBitmapColorSpace> operator()() 605 { 606 return new StandardColorSpace(); 607 } 608 }; 609 } 610 getStdColorSpace()611 uno::Reference<rendering::XIntegerBitmapColorSpace> getStdColorSpace() 612 { 613 return StandardColorSpaceHolder::get(); 614 } 615 getStdMemoryLayout(const geometry::IntegerSize2D & rBmpSize)616 rendering::IntegerBitmapLayout getStdMemoryLayout( const geometry::IntegerSize2D& rBmpSize ) 617 { 618 rendering::IntegerBitmapLayout aLayout; 619 620 aLayout.ScanLines = rBmpSize.Height; 621 aLayout.ScanLineBytes = rBmpSize.Width*4; 622 aLayout.ScanLineStride = aLayout.ScanLineBytes; 623 aLayout.PlaneStride = 0; 624 aLayout.ColorSpace = getStdColorSpace(); 625 aLayout.Palette.clear(); 626 aLayout.IsMsbFirst = sal_False; 627 628 return aLayout; 629 } 630 stdIntSequenceToColor(const uno::Sequence<sal_Int8> & rColor)631 ::Color stdIntSequenceToColor( const uno::Sequence<sal_Int8>& rColor ) 632 { 633 #ifdef OSL_BIGENDIAN 634 const sal_Int8* pCols( rColor.getConstArray() ); 635 return ::Color( pCols[3], pCols[0], pCols[1], pCols[2] ); 636 #else 637 return ::Color( *reinterpret_cast< const ::ColorData* >(rColor.getConstArray()) ); 638 #endif 639 } 640 colorToStdIntSequence(const::Color & rColor)641 uno::Sequence<sal_Int8> colorToStdIntSequence( const ::Color& rColor ) 642 { 643 uno::Sequence<sal_Int8> aRet(4); 644 sal_Int8* pCols( aRet.getArray() ); 645 #ifdef OSL_BIGENDIAN 646 pCols[0] = rColor.GetRed(); 647 pCols[1] = rColor.GetGreen(); 648 pCols[2] = rColor.GetBlue(); 649 pCols[3] = 255-rColor.GetTransparency(); 650 #else 651 *reinterpret_cast<sal_Int32*>(pCols) = rColor.GetColor(); 652 #endif 653 return aRet; 654 } 655 656 // Create a corrected view transformation out of the give one, 657 // which ensures that the rectangle given by (0,0) and 658 // rSpriteSize is mapped with its left,top corner to (0,0) 659 // again. This is required to properly render sprite 660 // animations to buffer bitmaps. calcRectToOriginTransform(::basegfx::B2DHomMatrix & o_transform,const::basegfx::B2DRange & i_srcRect,const::basegfx::B2DHomMatrix & i_transformation)661 ::basegfx::B2DHomMatrix& calcRectToOriginTransform( ::basegfx::B2DHomMatrix& o_transform, 662 const ::basegfx::B2DRange& i_srcRect, 663 const ::basegfx::B2DHomMatrix& i_transformation ) 664 { 665 if( i_srcRect.isEmpty() ) 666 return o_transform=i_transformation; 667 668 // transform by given transformation 669 ::basegfx::B2DRectangle aTransformedRect; 670 671 calcTransformedRectBounds( aTransformedRect, 672 i_srcRect, 673 i_transformation ); 674 675 // now move resulting left,top point of bounds to (0,0) 676 const basegfx::B2DHomMatrix aCorrectedTransform(basegfx::tools::createTranslateB2DHomMatrix( 677 -aTransformedRect.getMinX(), -aTransformedRect.getMinY())); 678 679 // prepend to original transformation 680 o_transform = aCorrectedTransform * i_transformation; 681 682 return o_transform; 683 } 684 calcTransformedRectBounds(::basegfx::B2DRange & outRect,const::basegfx::B2DRange & inRect,const::basegfx::B2DHomMatrix & transformation)685 ::basegfx::B2DRange& calcTransformedRectBounds( ::basegfx::B2DRange& outRect, 686 const ::basegfx::B2DRange& inRect, 687 const ::basegfx::B2DHomMatrix& transformation ) 688 { 689 outRect.reset(); 690 691 if( inRect.isEmpty() ) 692 return outRect; 693 694 // transform all four extremal points of the rectangle, 695 // take bounding rect of those. 696 697 // transform left-top point 698 outRect.expand( transformation * inRect.getMinimum() ); 699 700 // transform bottom-right point 701 outRect.expand( transformation * inRect.getMaximum() ); 702 703 ::basegfx::B2DPoint aPoint; 704 705 // transform top-right point 706 aPoint.setX( inRect.getMaxX() ); 707 aPoint.setY( inRect.getMinY() ); 708 709 aPoint *= transformation; 710 outRect.expand( aPoint ); 711 712 // transform bottom-left point 713 aPoint.setX( inRect.getMinX() ); 714 aPoint.setY( inRect.getMaxY() ); 715 716 aPoint *= transformation; 717 outRect.expand( aPoint ); 718 719 // over and out. 720 return outRect; 721 } 722 calcRectToRectTransform(::basegfx::B2DHomMatrix & o_transform,const::basegfx::B2DRange & destRect,const::basegfx::B2DRange & srcRect,const::basegfx::B2DHomMatrix & transformation)723 ::basegfx::B2DHomMatrix& calcRectToRectTransform( ::basegfx::B2DHomMatrix& o_transform, 724 const ::basegfx::B2DRange& destRect, 725 const ::basegfx::B2DRange& srcRect, 726 const ::basegfx::B2DHomMatrix& transformation ) 727 { 728 if( srcRect.isEmpty() || 729 destRect.isEmpty() ) 730 { 731 return o_transform=transformation; 732 } 733 734 // transform inputRect by transformation 735 ::basegfx::B2DRectangle aTransformedRect; 736 calcTransformedRectBounds( aTransformedRect, 737 srcRect, 738 transformation ); 739 740 // now move resulting left,top point of bounds to (0,0) 741 basegfx::B2DHomMatrix aCorrectedTransform(basegfx::tools::createTranslateB2DHomMatrix( 742 -aTransformedRect.getMinX(), -aTransformedRect.getMinY())); 743 744 // scale to match outRect 745 const double xDenom( aTransformedRect.getWidth() ); 746 const double yDenom( aTransformedRect.getHeight() ); 747 if( xDenom != 0.0 && yDenom != 0.0 ) 748 aCorrectedTransform.scale( destRect.getWidth() / xDenom, 749 destRect.getHeight() / yDenom ); 750 // TODO(E2): error handling 751 752 // translate to final position 753 aCorrectedTransform.translate( destRect.getMinX(), 754 destRect.getMinY() ); 755 756 ::basegfx::B2DHomMatrix transform( transformation ); 757 o_transform = aCorrectedTransform * transform; 758 759 return o_transform; 760 } 761 isInside(const::basegfx::B2DRange & rContainedRect,const::basegfx::B2DRange & rTransformRect,const::basegfx::B2DHomMatrix & rTransformation)762 bool isInside( const ::basegfx::B2DRange& rContainedRect, 763 const ::basegfx::B2DRange& rTransformRect, 764 const ::basegfx::B2DHomMatrix& rTransformation ) 765 { 766 if( rContainedRect.isEmpty() || rTransformRect.isEmpty() ) 767 return false; 768 769 ::basegfx::B2DPolygon aPoly( 770 ::basegfx::tools::createPolygonFromRect( rTransformRect ) ); 771 aPoly.transform( rTransformation ); 772 773 return ::basegfx::tools::isInside( aPoly, 774 ::basegfx::tools::createPolygonFromRect( 775 rContainedRect ), 776 true ); 777 } 778 779 namespace 780 { clipAreaImpl(::basegfx::B2IRange * o_pDestArea,::basegfx::B2IRange & io_rSourceArea,::basegfx::B2IPoint & io_rDestPoint,const::basegfx::B2IRange & rSourceBounds,const::basegfx::B2IRange & rDestBounds)781 bool clipAreaImpl( ::basegfx::B2IRange* o_pDestArea, 782 ::basegfx::B2IRange& io_rSourceArea, 783 ::basegfx::B2IPoint& io_rDestPoint, 784 const ::basegfx::B2IRange& rSourceBounds, 785 const ::basegfx::B2IRange& rDestBounds ) 786 { 787 const ::basegfx::B2IPoint aSourceTopLeft( 788 io_rSourceArea.getMinimum() ); 789 790 ::basegfx::B2IRange aLocalSourceArea( io_rSourceArea ); 791 792 // clip source area (which must be inside rSourceBounds) 793 aLocalSourceArea.intersect( rSourceBounds ); 794 795 if( aLocalSourceArea.isEmpty() ) 796 return false; 797 798 // calc relative new source area points (relative to orig 799 // source area) 800 const ::basegfx::B2IVector aUpperLeftOffset( 801 aLocalSourceArea.getMinimum()-aSourceTopLeft ); 802 const ::basegfx::B2IVector aLowerRightOffset( 803 aLocalSourceArea.getMaximum()-aSourceTopLeft ); 804 805 ::basegfx::B2IRange aLocalDestArea( io_rDestPoint + aUpperLeftOffset, 806 io_rDestPoint + aLowerRightOffset ); 807 808 // clip dest area (which must be inside rDestBounds) 809 aLocalDestArea.intersect( rDestBounds ); 810 811 if( aLocalDestArea.isEmpty() ) 812 return false; 813 814 // calc relative new dest area points (relative to orig 815 // source area) 816 const ::basegfx::B2IVector aDestUpperLeftOffset( 817 aLocalDestArea.getMinimum()-io_rDestPoint ); 818 const ::basegfx::B2IVector aDestLowerRightOffset( 819 aLocalDestArea.getMaximum()-io_rDestPoint ); 820 821 io_rSourceArea = ::basegfx::B2IRange( aSourceTopLeft + aDestUpperLeftOffset, 822 aSourceTopLeft + aDestLowerRightOffset ); 823 io_rDestPoint = aLocalDestArea.getMinimum(); 824 825 if( o_pDestArea ) 826 *o_pDestArea = aLocalDestArea; 827 828 return true; 829 } 830 } 831 clipScrollArea(::basegfx::B2IRange & io_rSourceArea,::basegfx::B2IPoint & io_rDestPoint,::std::vector<::basegfx::B2IRange> & o_ClippedAreas,const::basegfx::B2IRange & rBounds)832 bool clipScrollArea( ::basegfx::B2IRange& io_rSourceArea, 833 ::basegfx::B2IPoint& io_rDestPoint, 834 ::std::vector< ::basegfx::B2IRange >& o_ClippedAreas, 835 const ::basegfx::B2IRange& rBounds ) 836 { 837 ::basegfx::B2IRange aResultingDestArea; 838 839 // compute full destination area (to determine uninitialized 840 // areas below) 841 const ::basegfx::B2I64Tuple& rRange( io_rSourceArea.getRange() ); 842 ::basegfx::B2IRange aInputDestArea( io_rDestPoint.getX(), 843 io_rDestPoint.getY(), 844 (io_rDestPoint.getX() 845 + static_cast<sal_Int32>(rRange.getX())), 846 (io_rDestPoint.getY() 847 + static_cast<sal_Int32>(rRange.getY())) ); 848 // limit to output area (no point updating outside of it) 849 aInputDestArea.intersect( rBounds ); 850 851 // clip to rBounds 852 if( !clipAreaImpl( &aResultingDestArea, 853 io_rSourceArea, 854 io_rDestPoint, 855 rBounds, 856 rBounds ) ) 857 return false; 858 859 // finally, compute all areas clipped off the total 860 // destination area. 861 ::basegfx::computeSetDifference( o_ClippedAreas, 862 aInputDestArea, 863 aResultingDestArea ); 864 865 return true; 866 } 867 clipBlit(::basegfx::B2IRange & io_rSourceArea,::basegfx::B2IPoint & io_rDestPoint,const::basegfx::B2IRange & rSourceBounds,const::basegfx::B2IRange & rDestBounds)868 bool clipBlit( ::basegfx::B2IRange& io_rSourceArea, 869 ::basegfx::B2IPoint& io_rDestPoint, 870 const ::basegfx::B2IRange& rSourceBounds, 871 const ::basegfx::B2IRange& rDestBounds ) 872 { 873 return clipAreaImpl( NULL, 874 io_rSourceArea, 875 io_rDestPoint, 876 rSourceBounds, 877 rDestBounds ); 878 } 879 spritePixelAreaFromB2DRange(const::basegfx::B2DRange & rRange)880 ::basegfx::B2IRange spritePixelAreaFromB2DRange( const ::basegfx::B2DRange& rRange ) 881 { 882 if( rRange.isEmpty() ) 883 return ::basegfx::B2IRange(); 884 885 const ::basegfx::B2IPoint aTopLeft( ::basegfx::fround( rRange.getMinX() ), 886 ::basegfx::fround( rRange.getMinY() ) ); 887 return ::basegfx::B2IRange( aTopLeft, 888 aTopLeft + ::basegfx::B2IPoint( 889 ::basegfx::fround( rRange.getWidth() ), 890 ::basegfx::fround( rRange.getHeight() ) ) ); 891 } 892 getDeviceInfo(const uno::Reference<rendering::XCanvas> & i_rxCanvas,uno::Sequence<uno::Any> & o_rxParams)893 uno::Sequence< uno::Any >& getDeviceInfo( const uno::Reference< rendering::XCanvas >& i_rxCanvas, 894 uno::Sequence< uno::Any >& o_rxParams ) 895 { 896 o_rxParams.realloc( 0 ); 897 898 if( i_rxCanvas.is() ) 899 { 900 try 901 { 902 uno::Reference< rendering::XGraphicDevice > xDevice( i_rxCanvas->getDevice(), 903 uno::UNO_QUERY_THROW ); 904 905 uno::Reference< lang::XServiceInfo > xServiceInfo( xDevice, 906 uno::UNO_QUERY_THROW ); 907 uno::Reference< beans::XPropertySet > xPropSet( xDevice, 908 uno::UNO_QUERY_THROW ); 909 910 o_rxParams.realloc( 2 ); 911 912 o_rxParams[ 0 ] = uno::makeAny( xServiceInfo->getImplementationName() ); 913 o_rxParams[ 1 ] = uno::makeAny( xPropSet->getPropertyValue( 914 ::rtl::OUString( RTL_CONSTASCII_USTRINGPARAM("DeviceHandle") ) ) ); 915 } 916 catch( uno::Exception& ) 917 { 918 // ignore, but return empty sequence 919 } 920 } 921 922 return o_rxParams; 923 } 924 getAbsoluteWindowRect(const awt::Rectangle & rRect,const uno::Reference<awt::XWindow2> & xWin)925 awt::Rectangle getAbsoluteWindowRect( const awt::Rectangle& rRect, 926 const uno::Reference< awt::XWindow2 >& xWin ) 927 { 928 awt::Rectangle aRetVal( rRect ); 929 930 ::Window* pWindow = VCLUnoHelper::GetWindow(xWin); 931 if( pWindow ) 932 { 933 ::Point aPoint( aRetVal.X, 934 aRetVal.Y ); 935 936 aPoint = pWindow->OutputToScreenPixel( aPoint ); 937 938 aRetVal.X = aPoint.X(); 939 aRetVal.Y = aPoint.Y(); 940 } 941 942 return aRetVal; 943 } 944 getBoundMarksPolyPolygon(const::basegfx::B2DRange & rRange)945 ::basegfx::B2DPolyPolygon getBoundMarksPolyPolygon( const ::basegfx::B2DRange& rRange ) 946 { 947 ::basegfx::B2DPolyPolygon aPolyPoly; 948 ::basegfx::B2DPolygon aPoly; 949 950 const double nX0( rRange.getMinX() ); 951 const double nY0( rRange.getMinY() ); 952 const double nX1( rRange.getMaxX() ); 953 const double nY1( rRange.getMaxY() ); 954 955 aPoly.append( ::basegfx::B2DPoint( nX0+4, 956 nY0 ) ); 957 aPoly.append( ::basegfx::B2DPoint( nX0, 958 nY0 ) ); 959 aPoly.append( ::basegfx::B2DPoint( nX0, 960 nY0+4 ) ); 961 aPolyPoly.append( aPoly ); aPoly.clear(); 962 963 aPoly.append( ::basegfx::B2DPoint( nX1-4, 964 nY0 ) ); 965 aPoly.append( ::basegfx::B2DPoint( nX1, 966 nY0 ) ); 967 aPoly.append( ::basegfx::B2DPoint( nX1, 968 nY0+4 ) ); 969 aPolyPoly.append( aPoly ); aPoly.clear(); 970 971 aPoly.append( ::basegfx::B2DPoint( nX0+4, 972 nY1 ) ); 973 aPoly.append( ::basegfx::B2DPoint( nX0, 974 nY1 ) ); 975 aPoly.append( ::basegfx::B2DPoint( nX0, 976 nY1-4 ) ); 977 aPolyPoly.append( aPoly ); aPoly.clear(); 978 979 aPoly.append( ::basegfx::B2DPoint( nX1-4, 980 nY1 ) ); 981 aPoly.append( ::basegfx::B2DPoint( nX1, 982 nY1 ) ); 983 aPoly.append( ::basegfx::B2DPoint( nX1, 984 nY1-4 ) ); 985 aPolyPoly.append( aPoly ); 986 987 return aPolyPoly; 988 } 989 calcGradientStepCount(::basegfx::B2DHomMatrix & rTotalTransform,const rendering::ViewState & viewState,const rendering::RenderState & renderState,const rendering::Texture & texture,int nColorSteps)990 int calcGradientStepCount( ::basegfx::B2DHomMatrix& rTotalTransform, 991 const rendering::ViewState& viewState, 992 const rendering::RenderState& renderState, 993 const rendering::Texture& texture, 994 int nColorSteps ) 995 { 996 // calculate overall texture transformation (directly from 997 // texture to device space). 998 ::basegfx::B2DHomMatrix aMatrix; 999 1000 rTotalTransform.identity(); 1001 ::basegfx::unotools::homMatrixFromAffineMatrix( rTotalTransform, 1002 texture.AffineTransform ); 1003 ::canvas::tools::mergeViewAndRenderTransform(aMatrix, 1004 viewState, 1005 renderState); 1006 rTotalTransform *= aMatrix; // prepend total view/render transformation 1007 1008 // determine size of gradient in device coordinate system 1009 // (to e.g. determine sensible number of gradient steps) 1010 ::basegfx::B2DPoint aLeftTop( 0.0, 0.0 ); 1011 ::basegfx::B2DPoint aLeftBottom( 0.0, 1.0 ); 1012 ::basegfx::B2DPoint aRightTop( 1.0, 0.0 ); 1013 ::basegfx::B2DPoint aRightBottom( 1.0, 1.0 ); 1014 1015 aLeftTop *= rTotalTransform; 1016 aLeftBottom *= rTotalTransform; 1017 aRightTop *= rTotalTransform; 1018 aRightBottom*= rTotalTransform; 1019 1020 // longest line in gradient bound rect 1021 const int nGradientSize( 1022 static_cast<int>( 1023 ::std::max( 1024 ::basegfx::B2DVector(aRightBottom-aLeftTop).getLength(), 1025 ::basegfx::B2DVector(aRightTop-aLeftBottom).getLength() ) + 1.0 ) ); 1026 1027 // typical number for pixel of the same color (strip size) 1028 const int nStripSize( nGradientSize < 50 ? 2 : 4 ); 1029 1030 // use at least three steps, and at utmost the number of color 1031 // steps 1032 return ::std::max( 3, 1033 ::std::min( 1034 nGradientSize / nStripSize, 1035 nColorSteps ) ); 1036 } 1037 1038 } // namespace tools 1039 1040 } // namespace canvas 1041