/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include using namespace com::sun::star; namespace drawinglayer::primitive2d { // Get the OuterColor. Take into account that for css::awt::GradientStyle_AXIAL // this is the last one due to inverted gradient usage (see constructor there) basegfx::BColor FillGradientPrimitive2D::getOuterColor() const { if (getFillGradient().getColorStops().empty()) return basegfx::BColor(); if (css::awt::GradientStyle_AXIAL == getFillGradient().getStyle()) return getFillGradient().getColorStops().back().getStopColor(); return getFillGradient().getColorStops().front().getStopColor(); } // Get the needed UnitPolygon dependent on the GradientStyle basegfx::B2DPolygon FillGradientPrimitive2D::getUnitPolygon() const { if (css::awt::GradientStyle_RADIAL == getFillGradient().getStyle() || css::awt::GradientStyle_ELLIPTICAL == getFillGradient().getStyle()) { return basegfx::utils::createPolygonFromCircle(basegfx::B2DPoint(0.0, 0.0), 1.0); } return basegfx::utils::createPolygonFromRect(basegfx::B2DRange(-1.0, -1.0, 1.0, 1.0)); } void FillGradientPrimitive2D::generateMatricesAndColors( std::function aCallback) const { switch(getFillGradient().getStyle()) { default: // GradientStyle_MAKE_FIXED_SIZE case css::awt::GradientStyle_LINEAR: { texture::GeoTexSvxGradientLinear aGradient( getDefinitionRange(), getOutputRange(), getFillGradient().getSteps(), getFillGradient().getColorStops(), getFillGradient().getBorder(), getFillGradient().getAngle()); aGradient.appendTransformationsAndColors(aCallback); break; } case css::awt::GradientStyle_AXIAL: { texture::GeoTexSvxGradientAxial aGradient( getDefinitionRange(), getOutputRange(), getFillGradient().getSteps(), getFillGradient().getColorStops(), getFillGradient().getBorder(), getFillGradient().getAngle()); aGradient.appendTransformationsAndColors(aCallback); break; } case css::awt::GradientStyle_RADIAL: { texture::GeoTexSvxGradientRadial aGradient( getDefinitionRange(), getFillGradient().getSteps(), getFillGradient().getColorStops(), getFillGradient().getBorder(), getFillGradient().getOffsetX(), getFillGradient().getOffsetY()); aGradient.appendTransformationsAndColors(aCallback); break; } case css::awt::GradientStyle_ELLIPTICAL: { texture::GeoTexSvxGradientElliptical aGradient( getDefinitionRange(), getFillGradient().getSteps(), getFillGradient().getColorStops(), getFillGradient().getBorder(), getFillGradient().getOffsetX(), getFillGradient().getOffsetY(), getFillGradient().getAngle()); aGradient.appendTransformationsAndColors(aCallback); break; } case css::awt::GradientStyle_SQUARE: { texture::GeoTexSvxGradientSquare aGradient( getDefinitionRange(), getFillGradient().getSteps(), getFillGradient().getColorStops(), getFillGradient().getBorder(), getFillGradient().getOffsetX(), getFillGradient().getOffsetY(), getFillGradient().getAngle()); aGradient.appendTransformationsAndColors(aCallback); break; } case css::awt::GradientStyle_RECT: { texture::GeoTexSvxGradientRect aGradient( getDefinitionRange(), getFillGradient().getSteps(), getFillGradient().getColorStops(), getFillGradient().getBorder(), getFillGradient().getOffsetX(), getFillGradient().getOffsetY(), getFillGradient().getAngle()); aGradient.appendTransformationsAndColors(aCallback); break; } } } Primitive2DReference FillGradientPrimitive2D::createFill(bool bOverlapping) const { Primitive2DContainer aContainer; if (bOverlapping) { // OverlappingFill: create solid fill with outmost color aContainer.push_back( new PolyPolygonColorPrimitive2D( basegfx::B2DPolyPolygon( basegfx::utils::createPolygonFromRect(getOutputRange())), getOuterColor())); // create solid fill steps by providing callback as lambda auto aCallback([&aContainer,this]( const basegfx::B2DHomMatrix& rMatrix, const basegfx::BColor& rColor) { // create part polygon basegfx::B2DPolygon aNewPoly(getUnitPolygon()); aNewPoly.transform(rMatrix); // create solid fill aContainer.push_back( new PolyPolygonColorPrimitive2D( basegfx::B2DPolyPolygon(aNewPoly), rColor)); }); // call value generator to trigger callbacks generateMatricesAndColors(aCallback); } else { // NonOverlappingFill if (getFillGradient().getColorStops().size() < 2) { // not really a gradient, we need to create a start primitive // entry using the single color and the covered area const basegfx::B2DRange aOutmostRange(getOutputRange()); aContainer.push_back( new PolyPolygonColorPrimitive2D( basegfx::B2DPolyPolygon(basegfx::utils::createPolygonFromRect(aOutmostRange)), getOuterColor())); } else { // gradient with stops, prepare CombinedPolyPoly, use callback basegfx::B2DPolyPolygon aCombinedPolyPoly; basegfx::BColor aLastColor; auto aCallback([&aContainer,&aCombinedPolyPoly,&aLastColor,this]( const basegfx::B2DHomMatrix& rMatrix, const basegfx::BColor& rColor) { if (aContainer.empty()) { // 1st callback, init CombinedPolyPoly & create 1st entry basegfx::B2DRange aOutmostRange(getOutputRange()); // expand aOutmostRange with transformed first polygon // to ensure confinement basegfx::B2DPolygon aFirstPoly(getUnitPolygon()); aFirstPoly.transform(rMatrix); aOutmostRange.expand(aFirstPoly.getB2DRange()); // build 1st combined polygon; outmost range 1st, then // the shaped, transformed polygon aCombinedPolyPoly.append(basegfx::utils::createPolygonFromRect(aOutmostRange)); aCombinedPolyPoly.append(aFirstPoly); // create first primitive aContainer.push_back( new PolyPolygonColorPrimitive2D( aCombinedPolyPoly, getOuterColor())); // save first polygon for re-use in next call, it's the second // one, so remove 1st aCombinedPolyPoly.remove(0); // remember color for next primitive creation aLastColor = rColor; } else { // regular n-th callback, create combined entry by re-using // CombinedPolyPoly and aLastColor basegfx::B2DPolygon aNextPoly(getUnitPolygon()); aNextPoly.transform(rMatrix); aCombinedPolyPoly.append(aNextPoly); // create primitive with correct color aContainer.push_back( new PolyPolygonColorPrimitive2D( aCombinedPolyPoly, aLastColor)); // prepare re-use of inner polygon, save color aCombinedPolyPoly.remove(0); aLastColor = rColor; } }); // call value generator to trigger callbacks generateMatricesAndColors(aCallback); // add last inner polygon with last color aContainer.push_back( new PolyPolygonColorPrimitive2D( std::move(aCombinedPolyPoly), aLastColor)); } } return new GroupPrimitive2D(std::move(aContainer)); } Primitive2DReference FillGradientPrimitive2D::create2DDecomposition(const geometry::ViewInformation2D& /*rViewInformation*/) const { // SDPR: support alpha directly now. If a primitive processor // cannot deal with it, use it's decomposition. For that purpose // this decomposition has two stages now: This 1st one will // (if needed) separate content and alpha into a TransparencePrimitive2D // and (if needed) embed that to a UnifiedTransparencePrimitive2D, // so all processors can work as before if (hasAlphaGradient() || hasTransparency()) { Primitive2DReference aRetval( new FillGradientPrimitive2D( getOutputRange(), getDefinitionRange(), getFillGradient())); if (hasAlphaGradient()) { Primitive2DContainer aAlpha{ new FillGradientPrimitive2D( getOutputRange(), getDefinitionRange(), getAlphaGradient()) }; aRetval = new TransparencePrimitive2D(Primitive2DContainer{ aRetval }, std::move(aAlpha)); } if (hasTransparency()) { aRetval = new UnifiedTransparencePrimitive2D(Primitive2DContainer{ aRetval }, getTransparency()); } return aRetval; } // default creates overlapping fill which works with AntiAliasing and without. // The non-overlapping version does not create single filled polygons, but // PolyPolygons where each one describes a 'ring' for the gradient such // that the rings will not overlap. This is useful for the old XOR-paint // 'trick' of VCL which is recorded in Metafiles; so this version may be // used from the MetafilePrimitive2D in its decomposition. if(!getFillGradient().isDefault()) { return createFill(/*bOverlapping*/true); } return nullptr; } FillGradientPrimitive2D::FillGradientPrimitive2D( const basegfx::B2DRange& rOutputRange, const attribute::FillGradientAttribute& rFillGradient) : maOutputRange(rOutputRange) , maDefinitionRange(rOutputRange) , maFillGradient(rFillGradient) , maAlphaGradient() , mfTransparency(0.0) { } FillGradientPrimitive2D::FillGradientPrimitive2D( const basegfx::B2DRange& rOutputRange, const basegfx::B2DRange& rDefinitionRange, const attribute::FillGradientAttribute& rFillGradient, const attribute::FillGradientAttribute* pAlphaGradient, double fTransparency) : maOutputRange(rOutputRange) , maDefinitionRange(rDefinitionRange) , maFillGradient(rFillGradient) , maAlphaGradient() , mfTransparency(fTransparency) { // copy alpha gradient if we got one if (nullptr != pAlphaGradient) maAlphaGradient = *pAlphaGradient; } bool FillGradientPrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const { if(!BufferedDecompositionPrimitive2D::operator==(rPrimitive)) return false; const FillGradientPrimitive2D& rCompare(static_cast(rPrimitive)); if (getOutputRange() != rCompare.getOutputRange()) return false; if (getDefinitionRange() != rCompare.getDefinitionRange()) return false; if (getFillGradient() != rCompare.getFillGradient()) return false; if (maAlphaGradient != rCompare.maAlphaGradient) return false; if (!basegfx::fTools::equal(getTransparency(), rCompare.getTransparency())) return false; return true; } basegfx::B2DRange FillGradientPrimitive2D::getB2DRange(const geometry::ViewInformation2D& /*rViewInformation*/) const { // return the geometrically visible area return getOutputRange(); } // provide unique ID sal_uInt32 FillGradientPrimitive2D::getPrimitive2DID() const { return PRIMITIVE2D_ID_FILLGRADIENTPRIMITIVE2D; } } // end of namespace /* vim:set shiftwidth=4 softtabstop=4 expandtab: */