/* -*- 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 class Gradient::Impl { public: GradientStyle meStyle; Color maStartColor; Color maEndColor; Degree10 mnAngle; sal_uInt16 mnBorder; sal_uInt16 mnOfsX; sal_uInt16 mnOfsY; sal_uInt16 mnIntensityStart; sal_uInt16 mnIntensityEnd; sal_uInt16 mnStepCount; Impl() : meStyle (GradientStyle::Linear) , maStartColor(COL_BLACK) , maEndColor(COL_WHITE) , mnAngle(0) , mnBorder(0) , mnOfsX(50) , mnOfsY(50) , mnIntensityStart(100) , mnIntensityEnd(100) , mnStepCount(0) { } Impl(const Impl& rImplGradient) : meStyle (rImplGradient.meStyle) , maStartColor(rImplGradient.maStartColor) , maEndColor(rImplGradient.maEndColor) , mnAngle(rImplGradient.mnAngle) , mnBorder(rImplGradient.mnBorder) , mnOfsX(rImplGradient.mnOfsX) , mnOfsY(rImplGradient.mnOfsY) , mnIntensityStart(rImplGradient.mnIntensityStart) , mnIntensityEnd(rImplGradient.mnIntensityEnd) , mnStepCount(rImplGradient.mnStepCount) { } bool operator==(const Impl& rImpl_Gradient) const { return (meStyle == rImpl_Gradient.meStyle) && (mnAngle == rImpl_Gradient.mnAngle) && (mnBorder == rImpl_Gradient.mnBorder) && (mnOfsX == rImpl_Gradient.mnOfsX) && (mnOfsY == rImpl_Gradient.mnOfsY) && (mnStepCount == rImpl_Gradient.mnStepCount) && (mnIntensityStart == rImpl_Gradient.mnIntensityStart) && (mnIntensityEnd == rImpl_Gradient.mnIntensityEnd) && (maStartColor == rImpl_Gradient.maStartColor) && (maEndColor == rImpl_Gradient.maEndColor); } }; Gradient::Gradient() = default; Gradient::Gradient( const Gradient& ) = default; Gradient::Gradient( Gradient&& ) = default; Gradient::Gradient( GradientStyle eStyle, const Color& rStartColor, const Color& rEndColor ) { mpImplGradient->meStyle = eStyle; mpImplGradient->maStartColor = rStartColor; mpImplGradient->maEndColor = rEndColor; } Gradient::~Gradient() = default; GradientStyle Gradient::GetStyle() const { return mpImplGradient->meStyle; } void Gradient::SetStyle( GradientStyle eStyle ) { mpImplGradient->meStyle = eStyle; } const Color& Gradient::GetStartColor() const { return mpImplGradient->maStartColor; } void Gradient::SetStartColor( const Color& rColor ) { mpImplGradient->maStartColor = rColor; } const Color& Gradient::GetEndColor() const { return mpImplGradient->maEndColor; } void Gradient::SetEndColor( const Color& rColor ) { mpImplGradient->maEndColor = rColor; } Degree10 Gradient::GetAngle() const { return mpImplGradient->mnAngle; } void Gradient::SetAngle( Degree10 nAngle ) { mpImplGradient->mnAngle = nAngle; } sal_uInt16 Gradient::GetBorder() const { return mpImplGradient->mnBorder; } void Gradient::SetBorder( sal_uInt16 nBorder ) { mpImplGradient->mnBorder = nBorder; } sal_uInt16 Gradient::GetOfsX() const { return mpImplGradient->mnOfsX; } void Gradient::SetOfsX( sal_uInt16 nOfsX ) { mpImplGradient->mnOfsX = nOfsX; } sal_uInt16 Gradient::GetOfsY() const { return mpImplGradient->mnOfsY; } void Gradient::SetOfsY( sal_uInt16 nOfsY ) { mpImplGradient->mnOfsY = nOfsY; } sal_uInt16 Gradient::GetStartIntensity() const { return mpImplGradient->mnIntensityStart; } void Gradient::SetStartIntensity( sal_uInt16 nIntens ) { mpImplGradient->mnIntensityStart = nIntens; } sal_uInt16 Gradient::GetEndIntensity() const { return mpImplGradient->mnIntensityEnd; } void Gradient::SetEndIntensity( sal_uInt16 nIntens ) { mpImplGradient->mnIntensityEnd = nIntens; } sal_uInt16 Gradient::GetSteps() const { return mpImplGradient->mnStepCount; } void Gradient::SetSteps( sal_uInt16 nSteps ) { mpImplGradient->mnStepCount = nSteps; } void Gradient::GetBoundRect( const tools::Rectangle& rRect, tools::Rectangle& rBoundRect, Point& rCenter ) const { tools::Rectangle aRect( rRect ); Degree10 nAngle = GetAngle() % 3600_deg10; if( GetStyle() == GradientStyle::Linear || GetStyle() == GradientStyle::Axial ) { const double fAngle = toRadians(nAngle); const double fWidth = aRect.GetWidth(); const double fHeight = aRect.GetHeight(); double fDX = fWidth * fabs( cos( fAngle ) ) + fHeight * fabs( sin( fAngle ) ); double fDY = fHeight * fabs( cos( fAngle ) ) + fWidth * fabs( sin( fAngle ) ); fDX = (fDX - fWidth) * 0.5 + 0.5; fDY = (fDY - fHeight) * 0.5 + 0.5; aRect.AdjustLeft( -static_cast(fDX) ); aRect.AdjustRight(static_cast(fDX) ); aRect.AdjustTop( -static_cast(fDY) ); aRect.AdjustBottom(static_cast(fDY) ); rBoundRect = aRect; rCenter = rRect.Center(); } else { if( GetStyle() == GradientStyle::Square || GetStyle() == GradientStyle::Rect ) { const double fAngle = toRadians(nAngle); const double fWidth = aRect.GetWidth(); const double fHeight = aRect.GetHeight(); double fDX = fWidth * fabs( cos( fAngle ) ) + fHeight * fabs( sin( fAngle ) ); double fDY = fHeight * fabs( cos( fAngle ) ) + fWidth * fabs( sin( fAngle ) ); fDX = ( fDX - fWidth ) * 0.5 + 0.5; fDY = ( fDY - fHeight ) * 0.5 + 0.5; aRect.AdjustLeft( -static_cast(fDX) ); aRect.AdjustRight(static_cast(fDX) ); aRect.AdjustTop( -static_cast(fDY) ); aRect.AdjustBottom(static_cast(fDY) ); } Size aSize( aRect.GetSize() ); if( GetStyle() == GradientStyle::Radial ) { // Calculation of radii for circle aSize.setWidth( static_cast(0.5 + sqrt(static_cast(aSize.Width())*static_cast(aSize.Width()) + static_cast(aSize.Height())*static_cast(aSize.Height()))) ); aSize.setHeight( aSize.Width() ); } else if( GetStyle() == GradientStyle::Elliptical ) { // Calculation of radii for ellipse aSize.setWidth( static_cast( 0.5 + static_cast(aSize.Width()) * M_SQRT2 ) ); aSize.setHeight( static_cast( 0.5 + static_cast(aSize.Height()) * M_SQRT2) ); } // Calculate new centers tools::Long nZWidth = aRect.GetWidth() * static_cast(GetOfsX()) / 100; tools::Long nZHeight = aRect.GetHeight() * static_cast(GetOfsY()) / 100; tools::Long nBorderX = static_cast(GetBorder()) * aSize.Width() / 100; tools::Long nBorderY = static_cast(GetBorder()) * aSize.Height() / 100; rCenter = Point( aRect.Left() + nZWidth, aRect.Top() + nZHeight ); // Respect borders aSize.AdjustWidth( -nBorderX ); aSize.AdjustHeight( -nBorderY ); // Recalculate output rectangle aRect.SetLeft( rCenter.X() - ( aSize.Width() >> 1 ) ); aRect.SetTop( rCenter.Y() - ( aSize.Height() >> 1 ) ); aRect.SetSize( aSize ); rBoundRect = aRect; } } void Gradient::MakeGrayscale() { Color aStartCol(GetStartColor()); Color aEndCol(GetEndColor()); sal_uInt8 cStartLum = aStartCol.GetLuminance(); sal_uInt8 cEndLum = aEndCol.GetLuminance(); aStartCol = Color(cStartLum, cStartLum, cStartLum); aEndCol = Color(cEndLum, cEndLum, cEndLum); SetStartColor(aStartCol); SetEndColor(aEndCol); } Gradient& Gradient::operator=( const Gradient& ) = default; Gradient& Gradient::operator=( Gradient&& ) = default; bool Gradient::operator==( const Gradient& rGradient ) const { return mpImplGradient == rGradient.mpImplGradient; } const sal_uInt32 GRADIENT_DEFAULT_STEPCOUNT = 0; void Gradient::AddGradientActions(tools::Rectangle const& rRect, GDIMetaFile& rMetaFile) { tools::Rectangle aRect(rRect); aRect.Justify(); // do nothing if the rectangle is empty if (aRect.IsEmpty()) return; rMetaFile.AddAction(new MetaPushAction(vcl::PushFlags::ALL)); rMetaFile.AddAction(new MetaISectRectClipRegionAction( aRect)); rMetaFile.AddAction(new MetaLineColorAction(Color(), false)); // because we draw with no border line, we have to expand gradient // rect to avoid missing lines on the right and bottom edge aRect.AdjustLeft( -1 ); aRect.AdjustTop( -1 ); aRect.AdjustRight( 1 ); aRect.AdjustBottom( 1 ); // calculate step count if necessary if (!GetSteps()) SetSteps(GRADIENT_DEFAULT_STEPCOUNT); if (GetStyle() == GradientStyle::Linear || GetStyle() == GradientStyle::Axial) DrawLinearGradientToMetafile(aRect, rMetaFile); else DrawComplexGradientToMetafile(aRect, rMetaFile); rMetaFile.AddAction(new MetaPopAction()); } tools::Long Gradient::GetMetafileSteps(tools::Rectangle const& rRect) const { // calculate step count tools::Long nStepCount = GetSteps(); if (nStepCount) return nStepCount; if (GetStyle() == GradientStyle::Linear || GetStyle() == GradientStyle::Axial) return rRect.GetHeight(); else return std::min(rRect.GetWidth(), rRect.GetHeight()); } static sal_uInt8 GetGradientColorValue(tools::Long nValue) { if ( nValue < 0 ) return 0; else if ( nValue > 0xFF ) return 0xFF; else return static_cast(nValue); } void Gradient::DrawLinearGradientToMetafile(tools::Rectangle const& rRect, GDIMetaFile& rMetaFile) const { // get BoundRect of rotated rectangle tools::Rectangle aRect; Point aCenter; Degree10 nAngle = GetAngle() % 3600_deg10; GetBoundRect(rRect, aRect, aCenter); bool bLinear = (GetStyle() == GradientStyle::Linear); double fBorder = GetBorder() * aRect.GetHeight() / 100.0; if ( !bLinear ) { fBorder /= 2.0; } tools::Rectangle aMirrorRect = aRect; // used in style axial aMirrorRect.SetTop( ( aRect.Top() + aRect.Bottom() ) / 2 ); if ( !bLinear ) { aRect.SetBottom( aMirrorRect.Top() ); } // colour-intensities of start- and finish; change if needed tools::Long nFactor; Color aStartCol = GetStartColor(); Color aEndCol = GetEndColor(); tools::Long nStartRed = aStartCol.GetRed(); tools::Long nStartGreen = aStartCol.GetGreen(); tools::Long nStartBlue = aStartCol.GetBlue(); tools::Long nEndRed = aEndCol.GetRed(); tools::Long nEndGreen = aEndCol.GetGreen(); tools::Long nEndBlue = aEndCol.GetBlue(); nFactor = GetStartIntensity(); nStartRed = (nStartRed * nFactor) / 100; nStartGreen = (nStartGreen * nFactor) / 100; nStartBlue = (nStartBlue * nFactor) / 100; nFactor = GetEndIntensity(); nEndRed = (nEndRed * nFactor) / 100; nEndGreen = (nEndGreen * nFactor) / 100; nEndBlue = (nEndBlue * nFactor) / 100; // gradient style axial has exchanged start and end colors if ( !bLinear) { tools::Long nTempColor = nStartRed; nStartRed = nEndRed; nEndRed = nTempColor; nTempColor = nStartGreen; nStartGreen = nEndGreen; nEndGreen = nTempColor; nTempColor = nStartBlue; nStartBlue = nEndBlue; nEndBlue = nTempColor; } sal_uInt8 nRed; sal_uInt8 nGreen; sal_uInt8 nBlue; // Create border tools::Rectangle aBorderRect = aRect; tools::Polygon aPoly( 4 ); if (fBorder > 0.0) { nRed = static_cast(nStartRed); nGreen = static_cast(nStartGreen); nBlue = static_cast(nStartBlue); rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) ); aBorderRect.SetBottom( static_cast( aBorderRect.Top() + fBorder ) ); aRect.SetTop( aBorderRect.Bottom() ); aPoly[0] = aBorderRect.TopLeft(); aPoly[1] = aBorderRect.TopRight(); aPoly[2] = aBorderRect.BottomRight(); aPoly[3] = aBorderRect.BottomLeft(); aPoly.Rotate( aCenter, nAngle ); rMetaFile.AddAction( new MetaPolygonAction( aPoly ) ); if ( !bLinear) { aBorderRect = aMirrorRect; aBorderRect.SetTop( static_cast( aBorderRect.Bottom() - fBorder ) ); aMirrorRect.SetBottom( aBorderRect.Top() ); aPoly[0] = aBorderRect.TopLeft(); aPoly[1] = aBorderRect.TopRight(); aPoly[2] = aBorderRect.BottomRight(); aPoly[3] = aBorderRect.BottomLeft(); aPoly.Rotate( aCenter, nAngle ); rMetaFile.AddAction( new MetaPolygonAction( aPoly ) ); } } tools::Long nStepCount = GetMetafileSteps(aRect); // minimal three steps and maximal as max color steps tools::Long nAbsRedSteps = std::abs( nEndRed - nStartRed ); tools::Long nAbsGreenSteps = std::abs( nEndGreen - nStartGreen ); tools::Long nAbsBlueSteps = std::abs( nEndBlue - nStartBlue ); tools::Long nMaxColorSteps = std::max( nAbsRedSteps , nAbsGreenSteps ); nMaxColorSteps = std::max( nMaxColorSteps, nAbsBlueSteps ); tools::Long nSteps = std::min( nStepCount, nMaxColorSteps ); if ( nSteps < 3) { nSteps = 3; } double fScanInc = static_cast(aRect.GetHeight()) / static_cast(nSteps); double fGradientLine = static_cast(aRect.Top()); double fMirrorGradientLine = static_cast(aMirrorRect.Bottom()); const double fStepsMinus1 = static_cast(nSteps) - 1.0; if ( !bLinear) { nSteps -= 1; // draw middle polygons as one polygon after loop to avoid gap } for ( tools::Long i = 0; i < nSteps; i++ ) { // linear interpolation of color double fAlpha = static_cast(i) / fStepsMinus1; double fTempColor = static_cast(nStartRed) * (1.0-fAlpha) + static_cast(nEndRed) * fAlpha; nRed = GetGradientColorValue(static_cast(fTempColor)); fTempColor = static_cast(nStartGreen) * (1.0-fAlpha) + static_cast(nEndGreen) * fAlpha; nGreen = GetGradientColorValue(static_cast(fTempColor)); fTempColor = static_cast(nStartBlue) * (1.0-fAlpha) + static_cast(nEndBlue) * fAlpha; nBlue = GetGradientColorValue(static_cast(fTempColor)); rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) ); // Polygon for this color step aRect.SetTop( static_cast( fGradientLine + static_cast(i) * fScanInc ) ); aRect.SetBottom( static_cast( fGradientLine + ( static_cast(i) + 1.0 ) * fScanInc ) ); aPoly[0] = aRect.TopLeft(); aPoly[1] = aRect.TopRight(); aPoly[2] = aRect.BottomRight(); aPoly[3] = aRect.BottomLeft(); aPoly.Rotate( aCenter, nAngle ); rMetaFile.AddAction( new MetaPolygonAction( aPoly ) ); if ( !bLinear ) { aMirrorRect.SetBottom( static_cast( fMirrorGradientLine - static_cast(i) * fScanInc ) ); aMirrorRect.SetTop( static_cast( fMirrorGradientLine - (static_cast(i) + 1.0)* fScanInc ) ); aPoly[0] = aMirrorRect.TopLeft(); aPoly[1] = aMirrorRect.TopRight(); aPoly[2] = aMirrorRect.BottomRight(); aPoly[3] = aMirrorRect.BottomLeft(); aPoly.Rotate( aCenter, nAngle ); rMetaFile.AddAction( new MetaPolygonAction( aPoly ) ); } } if ( bLinear) return; // draw middle polygon with end color nRed = GetGradientColorValue(nEndRed); nGreen = GetGradientColorValue(nEndGreen); nBlue = GetGradientColorValue(nEndBlue); rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) ); aRect.SetTop( static_cast( fGradientLine + static_cast(nSteps) * fScanInc ) ); aRect.SetBottom( static_cast( fMirrorGradientLine - static_cast(nSteps) * fScanInc ) ); aPoly[0] = aRect.TopLeft(); aPoly[1] = aRect.TopRight(); aPoly[2] = aRect.BottomRight(); aPoly[3] = aRect.BottomLeft(); aPoly.Rotate( aCenter, nAngle ); rMetaFile.AddAction( new MetaPolygonAction( aPoly ) ); } void Gradient::DrawComplexGradientToMetafile(tools::Rectangle const& rRect, GDIMetaFile& rMetaFile) const { // Determine if we output via Polygon or PolyPolygon // For all rasteroperations other than Overpaint always use PolyPolygon, // as we will get wrong results if we output multiple times on top of each other. // Also for printers always use PolyPolygon, as not all printers // can print polygons on top of each other. tools::Rectangle aRect; Point aCenter; GetBoundRect(rRect, aRect, aCenter); std::optional xPolyPoly; xPolyPoly = tools::PolyPolygon( 2 ); // last parameter - true if complex gradient, false if linear tools::Long nStepCount = GetMetafileSteps(rRect); // at least three steps and at most the number of colour differences tools::Long nSteps = std::max(nStepCount, tools::Long(2)); Color aStartCol(GetStartColor()); Color aEndCol(GetEndColor()); tools::Long nStartRed = (static_cast(aStartCol.GetRed()) * GetStartIntensity()) / 100; tools::Long nStartGreen = (static_cast(aStartCol.GetGreen()) * GetStartIntensity()) / 100; tools::Long nStartBlue = (static_cast(aStartCol.GetBlue()) * GetStartIntensity()) / 100; tools::Long nEndRed = (static_cast(aEndCol.GetRed()) * GetEndIntensity()) / 100; tools::Long nEndGreen = (static_cast(aEndCol.GetGreen()) * GetEndIntensity()) / 100; tools::Long nEndBlue = (static_cast(aEndCol.GetBlue()) * GetEndIntensity()) / 100; tools::Long nRedSteps = nEndRed - nStartRed; tools::Long nGreenSteps = nEndGreen - nStartGreen; tools::Long nBlueSteps = nEndBlue - nStartBlue; tools::Long nCalcSteps = std::abs(nRedSteps); tools::Long nTempSteps = std::abs(nGreenSteps); if (nTempSteps > nCalcSteps) nCalcSteps = nTempSteps; nTempSteps = std::abs( nBlueSteps ); if (nTempSteps > nCalcSteps) nCalcSteps = nTempSteps; if (nCalcSteps < nSteps) nSteps = nCalcSteps; if ( !nSteps ) nSteps = 1; // determine output limits and stepsizes for all directions tools::Polygon aPoly; double fScanLeft = aRect.Left(); double fScanTop = aRect.Top(); double fScanRight = aRect.Right(); double fScanBottom = aRect.Bottom(); double fScanIncX = static_cast(aRect.GetWidth()) / static_cast(nSteps) * 0.5; double fScanIncY = static_cast(aRect.GetHeight()) / static_cast(nSteps) * 0.5; // all gradients are rendered as nested rectangles which shrink // equally in each dimension - except for 'square' gradients // which shrink to a central vertex but are not per-se square. if (GetStyle() != GradientStyle::Square) { fScanIncY = std::min( fScanIncY, fScanIncX ); fScanIncX = fScanIncY; } sal_uInt8 nRed = static_cast(nStartRed), nGreen = static_cast(nStartGreen), nBlue = static_cast(nStartBlue); bool bPaintLastPolygon( false ); // #107349# Paint last polygon only if loop has generated any output rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) ); aPoly = rRect; xPolyPoly->Insert( aPoly ); xPolyPoly->Insert( aPoly ); // loop to output Polygon/PolyPolygon sequentially for( tools::Long i = 1; i < nSteps; i++ ) { // calculate new Polygon fScanLeft += fScanIncX; aRect.SetLeft( static_cast( fScanLeft ) ); fScanTop += fScanIncY; aRect.SetTop( static_cast( fScanTop ) ); fScanRight -= fScanIncX; aRect.SetRight( static_cast( fScanRight ) ); fScanBottom -= fScanIncY; aRect.SetBottom( static_cast( fScanBottom ) ); if( ( aRect.GetWidth() < 2 ) || ( aRect.GetHeight() < 2 ) ) break; if (GetStyle() == GradientStyle::Radial || GetStyle() == GradientStyle::Elliptical) aPoly = tools::Polygon( aRect.Center(), aRect.GetWidth() >> 1, aRect.GetHeight() >> 1 ); else aPoly = tools::Polygon( aRect ); aPoly.Rotate(aCenter, GetAngle() % 3600_deg10); // adapt colour accordingly const tools::Long nStepIndex = ( xPolyPoly ? i : ( i + 1 ) ); nRed = GetGradientColorValue( nStartRed + ( ( nRedSteps * nStepIndex ) / nSteps ) ); nGreen = GetGradientColorValue( nStartGreen + ( ( nGreenSteps * nStepIndex ) / nSteps ) ); nBlue = GetGradientColorValue( nStartBlue + ( ( nBlueSteps * nStepIndex ) / nSteps ) ); bPaintLastPolygon = true; // #107349# Paint last polygon only if loop has generated any output xPolyPoly->Replace( xPolyPoly->GetObject( 1 ), 0 ); xPolyPoly->Replace( aPoly, 1 ); rMetaFile.AddAction( new MetaPolyPolygonAction( *xPolyPoly ) ); // #107349# Set fill color _after_ geometry painting: // xPolyPoly's geometry is the band from last iteration's // aPoly to current iteration's aPoly. The window outdev // path (see else below), on the other hand, paints the // full aPoly. Thus, here, we're painting the band before // the one painted in the window outdev path below. To get // matching colors, have to delay color setting here. rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) ); } const tools::Polygon& rPoly = xPolyPoly->GetObject( 1 ); if( rPoly.GetBoundRect().IsEmpty() ) return; // #107349# Paint last polygon with end color only if loop // has generated output. Otherwise, the current // (i.e. start) color is taken, to generate _any_ output. if( bPaintLastPolygon ) { nRed = GetGradientColorValue( nEndRed ); nGreen = GetGradientColorValue( nEndGreen ); nBlue = GetGradientColorValue( nEndBlue ); } rMetaFile.AddAction( new MetaFillColorAction( Color( nRed, nGreen, nBlue ), true ) ); rMetaFile.AddAction( new MetaPolygonAction( rPoly ) ); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */