854 lines
38 KiB
C++
854 lines
38 KiB
C++
/* -*- 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 <sal/config.h>
|
|
|
|
#include <comphelper/diagnose_ex.hxx>
|
|
|
|
#include <algorithm>
|
|
|
|
#include <rtl/math.hxx>
|
|
#include <sal/log.hxx>
|
|
|
|
#include <com/sun/star/rendering/PanoseLetterForm.hpp>
|
|
#include <com/sun/star/awt/FontSlant.hpp>
|
|
|
|
#include <basegfx/numeric/ftools.hxx>
|
|
#include <basegfx/matrix/b2dhommatrix.hxx>
|
|
#include <basegfx/matrix/b2dhommatrixtools.hxx>
|
|
|
|
#include <canvas/canvastools.hxx>
|
|
#include <cppcanvas/vclfactory.hxx>
|
|
#include <cppcanvas/basegfxfactory.hxx>
|
|
|
|
#include "viewshape.hxx"
|
|
#include <tools.hxx>
|
|
#include <utility>
|
|
|
|
using namespace ::com::sun::star;
|
|
|
|
namespace slideshow::internal
|
|
{
|
|
|
|
// TODO(F2): Provide sensible setup for mtf-related attributes (fill mode,
|
|
// char rotation etc.). Do that via mtf argument at this object
|
|
|
|
bool ViewShape::prefetch( RendererCacheEntry& io_rCacheEntry,
|
|
const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas,
|
|
const GDIMetaFileSharedPtr& rMtf,
|
|
const ShapeAttributeLayerSharedPtr& rAttr )
|
|
{
|
|
ENSURE_OR_RETURN_FALSE( rMtf,
|
|
"ViewShape::prefetch(): no valid metafile!" );
|
|
|
|
if( rMtf != io_rCacheEntry.mpMtf ||
|
|
rDestinationCanvas != io_rCacheEntry.getDestinationCanvas() )
|
|
{
|
|
// buffered renderer invalid, re-create
|
|
::cppcanvas::Renderer::Parameters aParms;
|
|
|
|
// rendering attribute override parameter struct. For
|
|
// every valid attribute, the corresponding struct
|
|
// member is filled, which in the metafile renderer
|
|
// forces rendering with the given attribute.
|
|
if( rAttr )
|
|
{
|
|
if( rAttr->isFillColorValid() )
|
|
{
|
|
// convert RGBColor to RGBA32 integer. Note
|
|
// that getIntegerColor() also truncates
|
|
// out-of-range values appropriately
|
|
aParms.maFillColor =
|
|
rAttr->getFillColor().getIntegerColor();
|
|
}
|
|
if( rAttr->isLineColorValid() )
|
|
{
|
|
// convert RGBColor to RGBA32 integer. Note
|
|
// that getIntegerColor() also truncates
|
|
// out-of-range values appropriately
|
|
aParms.maLineColor =
|
|
rAttr->getLineColor().getIntegerColor();
|
|
}
|
|
if( rAttr->isCharColorValid() )
|
|
{
|
|
// convert RGBColor to RGBA32 integer. Note
|
|
// that getIntegerColor() also truncates
|
|
// out-of-range values appropriately
|
|
aParms.maTextColor =
|
|
rAttr->getCharColor().getIntegerColor();
|
|
}
|
|
if( rAttr->isDimColorValid() )
|
|
{
|
|
// convert RGBColor to RGBA32 integer. Note
|
|
// that getIntegerColor() also truncates
|
|
// out-of-range values appropriately
|
|
|
|
// dim color overrides all other colors
|
|
aParms.maFillColor =
|
|
aParms.maLineColor =
|
|
aParms.maTextColor =
|
|
rAttr->getDimColor().getIntegerColor();
|
|
}
|
|
if( rAttr->isFontFamilyValid() )
|
|
{
|
|
aParms.maFontName =
|
|
rAttr->getFontFamily();
|
|
}
|
|
if( rAttr->isCharScaleValid() )
|
|
{
|
|
::basegfx::B2DHomMatrix aMatrix;
|
|
|
|
// enlarge text by given scale factor. Do that
|
|
// with the middle of the shape as the center
|
|
// of scaling.
|
|
aMatrix.translate( -0.5, -0.5 );
|
|
aMatrix.scale( rAttr->getCharScale(),
|
|
rAttr->getCharScale() );
|
|
aMatrix.translate( 0.5, 0.5 );
|
|
|
|
aParms.maTextTransformation = aMatrix;
|
|
}
|
|
if( rAttr->isCharWeightValid() )
|
|
{
|
|
aParms.maFontWeight =
|
|
static_cast< sal_Int8 >(
|
|
::basegfx::fround(
|
|
::std::max( 0.0,
|
|
::std::min( 11.0,
|
|
rAttr->getCharWeight() / 20.0 ) ) ) );
|
|
}
|
|
if( rAttr->isCharPostureValid() )
|
|
{
|
|
aParms.maFontLetterForm =
|
|
rAttr->getCharPosture() == sal_Int16(awt::FontSlant_NONE) ?
|
|
rendering::PanoseLetterForm::ANYTHING :
|
|
rendering::PanoseLetterForm::OBLIQUE_CONTACT;
|
|
}
|
|
if( rAttr->isUnderlineModeValid() )
|
|
{
|
|
aParms.maFontUnderline =
|
|
rAttr->getUnderlineMode();
|
|
}
|
|
}
|
|
|
|
io_rCacheEntry.mpRenderer = ::cppcanvas::VCLFactory::createRenderer( rDestinationCanvas,
|
|
*rMtf,
|
|
aParms );
|
|
|
|
io_rCacheEntry.mpMtf = rMtf;
|
|
io_rCacheEntry.mpDestinationCanvas = rDestinationCanvas;
|
|
|
|
// also invalidate alpha compositing bitmap (created
|
|
// new renderer, which possibly generates different
|
|
// output). Do NOT invalidate, if we're incidentally
|
|
// rendering INTO it.
|
|
if( rDestinationCanvas != io_rCacheEntry.mpLastBitmapCanvas )
|
|
{
|
|
io_rCacheEntry.mpLastBitmapCanvas.reset();
|
|
io_rCacheEntry.mpLastBitmap.reset();
|
|
}
|
|
}
|
|
|
|
return static_cast< bool >(io_rCacheEntry.mpRenderer);
|
|
}
|
|
|
|
bool ViewShape::draw( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas,
|
|
const GDIMetaFileSharedPtr& rMtf,
|
|
const ShapeAttributeLayerSharedPtr& rAttr,
|
|
const ::basegfx::B2DHomMatrix& rTransform,
|
|
const ::basegfx::B2DPolyPolygon* pClip,
|
|
const VectorOfDocTreeNodes& rSubsets ) const
|
|
{
|
|
::cppcanvas::RendererSharedPtr pRenderer(
|
|
getRenderer( rDestinationCanvas, rMtf, rAttr ) );
|
|
|
|
ENSURE_OR_RETURN_FALSE( pRenderer, "ViewShape::draw(): Invalid renderer" );
|
|
|
|
pRenderer->setTransformation( rTransform );
|
|
#if OSL_DEBUG_LEVEL >= 2
|
|
rendering::RenderState aRenderState;
|
|
::canvas::tools::initRenderState(aRenderState);
|
|
::canvas::tools::setRenderStateTransform(aRenderState,
|
|
rTransform);
|
|
aRenderState.DeviceColor.realloc(4);
|
|
aRenderState.DeviceColor[0] = 1.0;
|
|
aRenderState.DeviceColor[1] = 0.0;
|
|
aRenderState.DeviceColor[2] = 0.0;
|
|
aRenderState.DeviceColor[3] = 1.0;
|
|
|
|
try
|
|
{
|
|
rDestinationCanvas->getUNOCanvas()->drawLine( geometry::RealPoint2D(0.0,0.0),
|
|
geometry::RealPoint2D(1.0,1.0),
|
|
rDestinationCanvas->getViewState(),
|
|
aRenderState );
|
|
rDestinationCanvas->getUNOCanvas()->drawLine( geometry::RealPoint2D(1.0,0.0),
|
|
geometry::RealPoint2D(0.0,1.0),
|
|
rDestinationCanvas->getViewState(),
|
|
aRenderState );
|
|
}
|
|
catch( uno::Exception& )
|
|
{
|
|
DBG_UNHANDLED_EXCEPTION("slideshow");
|
|
}
|
|
#endif
|
|
if( pClip )
|
|
pRenderer->setClip( *pClip );
|
|
else
|
|
pRenderer->setClip();
|
|
|
|
if( rSubsets.empty() )
|
|
{
|
|
return pRenderer->draw();
|
|
}
|
|
else
|
|
{
|
|
// render subsets of whole metafile
|
|
|
|
|
|
bool bRet(true);
|
|
for( const auto& rSubset : rSubsets )
|
|
{
|
|
if( !pRenderer->drawSubset( rSubset.getStartIndex(),
|
|
rSubset.getEndIndex() ) )
|
|
bRet = false;
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
/// Convert untransformed shape update area to device pixel.
|
|
::basegfx::B2DRectangle shapeArea2AreaPixel( const ::basegfx::B2DHomMatrix& rCanvasTransformation,
|
|
const ::basegfx::B2DRectangle& rUntransformedArea )
|
|
{
|
|
// convert area to pixel, and add anti-aliasing border
|
|
|
|
// TODO(P1): Should the view transform some
|
|
// day contain rotation/shear, transforming
|
|
// the original bounds with the total
|
|
// transformation might result in smaller
|
|
// overall bounds.
|
|
|
|
::basegfx::B2DRectangle aBoundsPixel = ::canvas::tools::calcTransformedRectBounds(
|
|
rUntransformedArea,
|
|
rCanvasTransformation );
|
|
|
|
// add antialiasing border around the shape (AA
|
|
// touches pixel _outside_ the nominal bound rect)
|
|
aBoundsPixel.grow( ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE );
|
|
|
|
return aBoundsPixel;
|
|
}
|
|
|
|
/// Convert shape unit rect to device pixel.
|
|
::basegfx::B2DRectangle calcUpdateAreaPixel( const ::basegfx::B2DRectangle& rUnitBounds,
|
|
const ::basegfx::B2DHomMatrix& rShapeTransformation,
|
|
const ::basegfx::B2DHomMatrix& rCanvasTransformation,
|
|
const ShapeAttributeLayerSharedPtr& pAttr )
|
|
{
|
|
// calc update area for whole shape (including
|
|
// character scaling)
|
|
return shapeArea2AreaPixel( rCanvasTransformation,
|
|
getShapeUpdateArea( rUnitBounds,
|
|
rShapeTransformation,
|
|
pAttr ) );
|
|
}
|
|
}
|
|
|
|
bool ViewShape::renderSprite( const ViewLayerSharedPtr& rViewLayer,
|
|
const GDIMetaFileSharedPtr& rMtf,
|
|
const ::basegfx::B2DRectangle& rOrigBounds,
|
|
const ::basegfx::B2DRectangle& rBounds,
|
|
const ::basegfx::B2DRectangle& rUnitBounds,
|
|
UpdateFlags nUpdateFlags,
|
|
const ShapeAttributeLayerSharedPtr& pAttr,
|
|
const VectorOfDocTreeNodes& rSubsets,
|
|
double nPrio,
|
|
bool bIsVisible ) const
|
|
{
|
|
// TODO(P1): For multiple views, it might pay off to reorg Shape and ViewShape,
|
|
// in that all the common setup steps here are refactored to Shape (would then
|
|
// have to be performed only _once_ per Shape paint).
|
|
|
|
if( !bIsVisible ||
|
|
rUnitBounds.isEmpty() ||
|
|
rOrigBounds.isEmpty() ||
|
|
rBounds.isEmpty() )
|
|
{
|
|
// shape is invisible or has zero size, no need to
|
|
// update anything.
|
|
if( mpSprite )
|
|
mpSprite->hide();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// calc sprite position, size and content transformation
|
|
// =====================================================
|
|
|
|
// the shape transformation for a sprite is always a
|
|
// simple scale-up to the nominal shape size. Everything
|
|
// else is handled via the sprite transformation
|
|
::basegfx::B2DHomMatrix aNonTranslationalShapeTransformation;
|
|
aNonTranslationalShapeTransformation.scale( rOrigBounds.getWidth(),
|
|
rOrigBounds.getHeight() );
|
|
::basegfx::B2DHomMatrix aShapeTransformation( aNonTranslationalShapeTransformation );
|
|
aShapeTransformation.translate( rOrigBounds.getMinX(),
|
|
rOrigBounds.getMinY() );
|
|
|
|
const ::basegfx::B2DHomMatrix aCanvasTransform(
|
|
rViewLayer->getSpriteTransformation() );
|
|
|
|
// area actually needed for the sprite
|
|
const ::basegfx::B2DRectangle aSpriteBoundsPixel(
|
|
calcUpdateAreaPixel( rUnitBounds,
|
|
aShapeTransformation,
|
|
aCanvasTransform,
|
|
pAttr ) );
|
|
|
|
// actual area for the shape (without subsetting, but
|
|
// including char scaling)
|
|
const ::basegfx::B2DRectangle aShapeBoundsPixel(
|
|
calcUpdateAreaPixel( ::basegfx::B2DRectangle(0.0,0.0,1.0,1.0),
|
|
aShapeTransformation,
|
|
aCanvasTransform,
|
|
pAttr ) );
|
|
|
|
// nominal area for the shape (without subsetting, without
|
|
// char scaling). NOTE: to cancel the shape translation,
|
|
// contained in rSpriteBoundsPixel, this is _without_ any
|
|
// translational component.
|
|
const ::basegfx::B2DRectangle aNominalShapeBoundsPixel(
|
|
shapeArea2AreaPixel( aCanvasTransform,
|
|
::canvas::tools::calcTransformedRectBounds(
|
|
::basegfx::B2DRectangle(0.0,0.0,1.0,1.0),
|
|
aNonTranslationalShapeTransformation ) ) );
|
|
|
|
// create (or resize) sprite with sprite's pixel size, if
|
|
// not done already
|
|
auto aRange = aSpriteBoundsPixel.getRange();
|
|
basegfx::B2DSize rSpriteSizePixel(aRange.getX(), aRange.getY());
|
|
if( !mpSprite )
|
|
{
|
|
mpSprite = std::make_shared<AnimatedSprite>( mpViewLayer,
|
|
rSpriteSizePixel,
|
|
nPrio );
|
|
}
|
|
else
|
|
{
|
|
// TODO(F2): when the sprite _actually_ gets resized,
|
|
// content needs a repaint!
|
|
mpSprite->resize( rSpriteSizePixel );
|
|
}
|
|
|
|
ENSURE_OR_RETURN_FALSE( mpSprite, "ViewShape::renderSprite(): No sprite" );
|
|
|
|
SAL_INFO("slideshow", "ViewShape::renderSprite(): Rendering sprite " <<
|
|
mpSprite.get() );
|
|
|
|
|
|
// always show the sprite (might have been hidden before)
|
|
mpSprite->show();
|
|
|
|
// determine center of sprite output position in pixel
|
|
// (assumption here: all shape transformations have the
|
|
// shape center as the pivot point). From that, subtract
|
|
// distance of rSpriteBoundsPixel's left, top edge from
|
|
// rShapeBoundsPixel's center. This moves the sprite at
|
|
// the appropriate output position within the virtual
|
|
// rShapeBoundsPixel area.
|
|
::basegfx::B2DPoint aSpritePosPixel( rBounds.getCenter() );
|
|
aSpritePosPixel *= aCanvasTransform;
|
|
aSpritePosPixel -= aShapeBoundsPixel.getCenter() - aSpriteBoundsPixel.getMinimum();
|
|
|
|
// the difference between rShapeBoundsPixel and
|
|
// rSpriteBoundsPixel upper, left corner is: the offset we
|
|
// have to move sprite output to the right, top (to make
|
|
// the desired subset content visible at all)
|
|
auto aDifference = aSpriteBoundsPixel.getMinimum() - aNominalShapeBoundsPixel.getMinimum();
|
|
const basegfx::B2DSize rSpriteCorrectionOffset(aDifference.getX(), aDifference.getY());
|
|
|
|
// offset added top, left for anti-aliasing (otherwise,
|
|
// shapes fully filling the sprite will have anti-aliased
|
|
// pixel cut off)
|
|
const ::basegfx::B2DSize aAAOffset(
|
|
::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE,
|
|
::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE );
|
|
|
|
// set pixel output offset to sprite: we always leave
|
|
// ANTIALIASING_EXTRA_SIZE room atop and to the left, and,
|
|
// what's more, for subsetted shapes, we _have_ to cancel
|
|
// the effect of the shape renderer outputting the subset
|
|
// at its absolute position inside the shape, instead of
|
|
// at the origin.
|
|
// NOTE: As for now, sprites are always positioned on
|
|
// integer pixel positions on screen, have to round to
|
|
// nearest integer here, too
|
|
mpSprite->setPixelOffset(
|
|
aAAOffset - ::basegfx::B2DSize(
|
|
::basegfx::fround( rSpriteCorrectionOffset.getWidth() ),
|
|
::basegfx::fround( rSpriteCorrectionOffset.getHeight() ) ) );
|
|
|
|
// always set sprite position and transformation, since
|
|
// they do not relate directly to the update flags
|
|
// (e.g. sprite position changes when sprite size changes)
|
|
mpSprite->movePixel( aSpritePosPixel );
|
|
mpSprite->transform( getSpriteTransformation( basegfx::B2DVector(rSpriteSizePixel.getWidth(), rSpriteSizePixel.getHeight()),
|
|
rOrigBounds.getRange(),
|
|
pAttr ) );
|
|
|
|
|
|
// process flags
|
|
// =============
|
|
|
|
bool bRedrawRequired( mbForceUpdate || (nUpdateFlags & UpdateFlags::Force) );
|
|
|
|
if( mbForceUpdate || (nUpdateFlags & UpdateFlags::Alpha) )
|
|
{
|
|
mpSprite->setAlpha( (pAttr && pAttr->isAlphaValid()) ?
|
|
std::clamp(pAttr->getAlpha(),
|
|
0.0,
|
|
1.0) :
|
|
1.0 );
|
|
}
|
|
if( mbForceUpdate || (nUpdateFlags & UpdateFlags::Clip) )
|
|
{
|
|
if( pAttr && pAttr->isClipValid() )
|
|
{
|
|
::basegfx::B2DPolyPolygon aClipPoly( pAttr->getClip() );
|
|
|
|
// extract linear part of canvas view transformation
|
|
// (linear means: without translational components)
|
|
::basegfx::B2DHomMatrix aViewTransform(
|
|
mpViewLayer->getTransformation() );
|
|
aViewTransform.set( 0, 2, 0.0 );
|
|
aViewTransform.set( 1, 2, 0.0 );
|
|
|
|
// make the clip 2*ANTIALIASING_EXTRA_SIZE larger
|
|
// such that it's again centered over the sprite.
|
|
aViewTransform.scale(rSpriteSizePixel.getWidth()/
|
|
(rSpriteSizePixel.getWidth()-2*::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE),
|
|
rSpriteSizePixel.getHeight()/
|
|
(rSpriteSizePixel.getHeight()-2*::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE));
|
|
|
|
// transform clip polygon from view to device
|
|
// coordinate space
|
|
aClipPoly.transform( aViewTransform );
|
|
|
|
mpSprite->clip( aClipPoly );
|
|
}
|
|
else
|
|
mpSprite->clip();
|
|
}
|
|
if( mbForceUpdate || (nUpdateFlags & UpdateFlags::Content) )
|
|
{
|
|
bRedrawRequired = true;
|
|
|
|
// TODO(P1): maybe provide some appearance change methods at
|
|
// the Renderer interface
|
|
|
|
// force the renderer to be regenerated below, for the
|
|
// different attributes to take effect
|
|
invalidateRenderer();
|
|
}
|
|
|
|
mbForceUpdate = false;
|
|
|
|
if( !bRedrawRequired )
|
|
return true;
|
|
|
|
|
|
// sprite needs repaint - output to sprite canvas
|
|
// ==============================================
|
|
|
|
::cppcanvas::CanvasSharedPtr pContentCanvas( mpSprite->getContentCanvas() );
|
|
|
|
return draw( pContentCanvas,
|
|
rMtf,
|
|
pAttr,
|
|
aShapeTransformation,
|
|
nullptr, // clipping is done via Sprite::clip()
|
|
rSubsets );
|
|
}
|
|
|
|
bool ViewShape::render( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas,
|
|
const GDIMetaFileSharedPtr& rMtf,
|
|
const ::basegfx::B2DRectangle& rBounds,
|
|
const ::basegfx::B2DRectangle& rUpdateBounds,
|
|
UpdateFlags nUpdateFlags,
|
|
const ShapeAttributeLayerSharedPtr& pAttr,
|
|
const VectorOfDocTreeNodes& rSubsets,
|
|
bool bIsVisible ) const
|
|
{
|
|
// TODO(P1): For multiple views, it might pay off to reorg Shape and ViewShape,
|
|
// in that all the common setup steps here are refactored to Shape (would then
|
|
// have to be performed only _once_ per Shape paint).
|
|
|
|
if( !bIsVisible )
|
|
{
|
|
SAL_INFO("slideshow", "ViewShape::render(): skipping shape " << this );
|
|
|
|
// shape is invisible, no need to update anything.
|
|
return true;
|
|
}
|
|
|
|
// since we have no sprite here, _any_ update request
|
|
// translates into a required redraw.
|
|
bool bRedrawRequired( mbForceUpdate || nUpdateFlags != UpdateFlags::NONE );
|
|
|
|
if( nUpdateFlags & UpdateFlags::Content )
|
|
{
|
|
// TODO(P1): maybe provide some appearance change methods at
|
|
// the Renderer interface
|
|
|
|
// force the renderer to be regenerated below, for the
|
|
// different attributes to take effect
|
|
invalidateRenderer();
|
|
}
|
|
|
|
mbForceUpdate = false;
|
|
|
|
if( !bRedrawRequired )
|
|
return true;
|
|
|
|
SAL_INFO( "slideshow", "ViewShape::render(): rendering shape " <<
|
|
this <<
|
|
" at position (" <<
|
|
rBounds.getMinX() << "," <<
|
|
rBounds.getMinY() << ")" );
|
|
|
|
|
|
// shape needs repaint - setup all that's needed
|
|
|
|
|
|
std::optional<basegfx::B2DPolyPolygon> aClip;
|
|
|
|
if( pAttr )
|
|
{
|
|
// setup clip poly
|
|
if( pAttr->isClipValid() )
|
|
aClip = pAttr->getClip();
|
|
|
|
// emulate global shape alpha by first rendering into
|
|
// a temp bitmap, and then to screen (this would have
|
|
// been much easier if we'd be currently a sprite -
|
|
// see above)
|
|
if( pAttr->isAlphaValid() )
|
|
{
|
|
const double nAlpha( pAttr->getAlpha() );
|
|
|
|
if( !::basegfx::fTools::equalZero( nAlpha ) &&
|
|
!::rtl::math::approxEqual(nAlpha, 1.0) )
|
|
{
|
|
// render with global alpha - have to prepare
|
|
// a bitmap, and render that with modulated
|
|
// alpha
|
|
|
|
|
|
const ::basegfx::B2DHomMatrix aTransform(
|
|
getShapeTransformation( rBounds,
|
|
pAttr ) );
|
|
|
|
// TODO(P1): Should the view transform some
|
|
// day contain rotation/shear, transforming
|
|
// the original bounds with the total
|
|
// transformation might result in smaller
|
|
// overall bounds.
|
|
|
|
// determine output rect of _shape update
|
|
// area_ in device pixel
|
|
const ::basegfx::B2DHomMatrix aCanvasTransform(
|
|
rDestinationCanvas->getTransformation() );
|
|
::basegfx::B2DRectangle aTmpRect = ::canvas::tools::calcTransformedRectBounds(
|
|
rUpdateBounds,
|
|
aCanvasTransform );
|
|
|
|
// pixel size of cache bitmap: round up to
|
|
// nearest int
|
|
const ::basegfx::B2ISize aBmpSize( static_cast<sal_Int32>( aTmpRect.getWidth() )+1,
|
|
static_cast<sal_Int32>( aTmpRect.getHeight() )+1 );
|
|
|
|
// try to fetch temporary surface for alpha
|
|
// compositing (to achieve the global alpha
|
|
// blend effect, have to first render shape as
|
|
// a whole, then blit that surface with global
|
|
// alpha to the destination)
|
|
const RendererCacheVector::iterator aCompositingSurface(
|
|
getCacheEntry( rDestinationCanvas ) );
|
|
|
|
if( !aCompositingSurface->mpLastBitmapCanvas ||
|
|
aCompositingSurface->mpLastBitmapCanvas->getSize() != aBmpSize )
|
|
{
|
|
// create a bitmap of appropriate size
|
|
::cppcanvas::BitmapSharedPtr pBitmap(
|
|
::cppcanvas::BaseGfxFactory::createAlphaBitmap(
|
|
rDestinationCanvas,
|
|
aBmpSize ) );
|
|
|
|
ENSURE_OR_THROW(pBitmap,
|
|
"ViewShape::render(): Could not create compositing surface");
|
|
|
|
aCompositingSurface->mpDestinationCanvas = rDestinationCanvas;
|
|
aCompositingSurface->mpLastBitmap = pBitmap;
|
|
aCompositingSurface->mpLastBitmapCanvas = pBitmap->getBitmapCanvas();
|
|
}
|
|
|
|
// buffer aCompositingSurface iterator content
|
|
// - said one might get invalidated during
|
|
// draw() below.
|
|
::cppcanvas::BitmapCanvasSharedPtr pBitmapCanvas(
|
|
aCompositingSurface->mpLastBitmapCanvas );
|
|
|
|
::cppcanvas::BitmapSharedPtr pBitmap(
|
|
aCompositingSurface->mpLastBitmap);
|
|
|
|
// setup bitmap canvas transformation -
|
|
// which happens to be the destination
|
|
// canvas transformation without any
|
|
// translational components.
|
|
|
|
// But then, the render transformation as
|
|
// calculated by getShapeTransformation()
|
|
// above outputs the shape at its real
|
|
// destination position. Thus, we have to
|
|
// offset the output back to the origin,
|
|
// for which we simply plug in the
|
|
// negative position of the left, top edge
|
|
// of the shape's bound rect in device
|
|
// pixel into aLinearTransform below.
|
|
::basegfx::B2DHomMatrix aAdjustedCanvasTransform( aCanvasTransform );
|
|
aAdjustedCanvasTransform.translate( -aTmpRect.getMinX(),
|
|
-aTmpRect.getMinY() );
|
|
|
|
pBitmapCanvas->setTransformation( aAdjustedCanvasTransform );
|
|
|
|
// TODO(P2): If no update flags, or only
|
|
// alpha_update is set, we can save us the
|
|
// rendering into the bitmap (uh, it's not
|
|
// _that_ easy - for a forced redraw,
|
|
// e.g. when ending an animation, we always
|
|
// get UPDATE_FORCE here).
|
|
|
|
// render into this bitmap
|
|
if( !draw( pBitmapCanvas,
|
|
rMtf,
|
|
pAttr,
|
|
aTransform,
|
|
!aClip ? nullptr : &(*aClip),
|
|
rSubsets ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// render bitmap to screen, with given global
|
|
// alpha. Since the bitmap already contains
|
|
// pixel-equivalent output, we have to use the
|
|
// inverse view transformation, adjusted with
|
|
// the final shape output position (note:
|
|
// cannot simply change the view
|
|
// transformation here, as that would affect a
|
|
// possibly set clip!)
|
|
::basegfx::B2DHomMatrix aBitmapTransform( aCanvasTransform );
|
|
OSL_ENSURE( aBitmapTransform.isInvertible(),
|
|
"ViewShape::render(): View transformation is singular!" );
|
|
|
|
aBitmapTransform.invert();
|
|
|
|
const basegfx::B2DHomMatrix aTranslation(basegfx::utils::createTranslateB2DHomMatrix(
|
|
aTmpRect.getMinX(), aTmpRect.getMinY()));
|
|
|
|
aBitmapTransform = aBitmapTransform * aTranslation;
|
|
pBitmap->setTransformation( aBitmapTransform );
|
|
|
|
// finally, render bitmap alpha-modulated
|
|
pBitmap->drawAlphaModulated( nAlpha );
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// retrieve shape transformation, _with_ shape translation
|
|
// to actual page position.
|
|
const ::basegfx::B2DHomMatrix aTransform(
|
|
getShapeTransformation( rBounds,
|
|
pAttr ) );
|
|
|
|
return draw( rDestinationCanvas,
|
|
rMtf,
|
|
pAttr,
|
|
aTransform,
|
|
!aClip ? nullptr : &(*aClip),
|
|
rSubsets );
|
|
}
|
|
|
|
|
|
ViewShape::ViewShape( ViewLayerSharedPtr xViewLayer ) :
|
|
mpViewLayer(std::move( xViewLayer )),
|
|
maRenderers(),
|
|
mpSprite(),
|
|
mbAnimationMode( false ),
|
|
mbForceUpdate( true )
|
|
{
|
|
ENSURE_OR_THROW( mpViewLayer, "ViewShape::ViewShape(): Invalid View" );
|
|
}
|
|
|
|
const ViewLayerSharedPtr& ViewShape::getViewLayer() const
|
|
{
|
|
return mpViewLayer;
|
|
}
|
|
|
|
ViewShape::RendererCacheVector::iterator ViewShape::getCacheEntry( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas ) const
|
|
{
|
|
// lookup destination canvas - is there already a renderer
|
|
// created for that target?
|
|
RendererCacheVector::iterator aIter;
|
|
const RendererCacheVector::iterator aEnd( maRenderers.end() );
|
|
|
|
// already there?
|
|
if( (aIter=::std::find_if( maRenderers.begin(),
|
|
aEnd,
|
|
[&rDestinationCanvas]( const RendererCacheEntry& rCacheEntry )
|
|
{ return rDestinationCanvas == rCacheEntry.getDestinationCanvas(); } ) ) == aEnd )
|
|
{
|
|
if( maRenderers.size() >= MAX_RENDER_CACHE_ENTRIES )
|
|
{
|
|
// cache size exceeded - prune entries. For now,
|
|
// simply remove the first one, which of course
|
|
// breaks for more complex access schemes. But in
|
|
// general, this leads to most recently used
|
|
// entries to reside at the end of the vector.
|
|
maRenderers.erase( maRenderers.begin() );
|
|
|
|
// ATTENTION: after this, both aIter and aEnd are
|
|
// invalid!
|
|
}
|
|
|
|
// not yet in cache - add default-constructed cache
|
|
// entry, to have something to return
|
|
maRenderers.emplace_back( );
|
|
aIter = maRenderers.end()-1;
|
|
}
|
|
|
|
return aIter;
|
|
}
|
|
|
|
::cppcanvas::RendererSharedPtr ViewShape::getRenderer( const ::cppcanvas::CanvasSharedPtr& rDestinationCanvas,
|
|
const GDIMetaFileSharedPtr& rMtf,
|
|
const ShapeAttributeLayerSharedPtr& rAttr ) const
|
|
{
|
|
// lookup destination canvas - is there already a renderer
|
|
// created for that target?
|
|
const RendererCacheVector::iterator aIter(
|
|
getCacheEntry( rDestinationCanvas ) );
|
|
|
|
// now we have a valid entry, either way. call prefetch()
|
|
// on it, nevertheless - maybe the metafile changed, and
|
|
// the renderer still needs an update (prefetch() will
|
|
// detect that)
|
|
if( prefetch( *aIter,
|
|
rDestinationCanvas,
|
|
rMtf,
|
|
rAttr ) )
|
|
{
|
|
return aIter->mpRenderer;
|
|
}
|
|
else
|
|
{
|
|
// prefetch failed - renderer is invalid
|
|
return ::cppcanvas::RendererSharedPtr();
|
|
}
|
|
}
|
|
|
|
void ViewShape::invalidateRenderer() const
|
|
{
|
|
// simply clear the cache. Subsequent getRenderer() calls
|
|
// will regenerate the Renderers.
|
|
maRenderers.clear();
|
|
}
|
|
|
|
::basegfx::B2DSize ViewShape::getAntialiasingBorder() const
|
|
{
|
|
ENSURE_OR_THROW( mpViewLayer->getCanvas(),
|
|
"ViewShape::getAntialiasingBorder(): Invalid ViewLayer canvas" );
|
|
|
|
const ::basegfx::B2DHomMatrix aViewTransform(
|
|
mpViewLayer->getTransformation() );
|
|
|
|
// TODO(F1): As a quick shortcut (did not want to invert
|
|
// whole matrix here), taking only scale components of
|
|
// view transformation matrix. This will be wrong when
|
|
// e.g. shearing is involved.
|
|
const double nXBorder( ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE / aViewTransform.get(0,0) );
|
|
const double nYBorder( ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE / aViewTransform.get(1,1) );
|
|
|
|
return ::basegfx::B2DSize( nXBorder,
|
|
nYBorder );
|
|
}
|
|
|
|
void ViewShape::enterAnimationMode()
|
|
{
|
|
mbForceUpdate = true;
|
|
mbAnimationMode = true;
|
|
}
|
|
|
|
void ViewShape::leaveAnimationMode()
|
|
{
|
|
mpSprite.reset();
|
|
mbAnimationMode = false;
|
|
mbForceUpdate = true;
|
|
}
|
|
|
|
bool ViewShape::update( const GDIMetaFileSharedPtr& rMtf,
|
|
const RenderArgs& rArgs,
|
|
UpdateFlags nUpdateFlags,
|
|
bool bIsVisible ) const
|
|
{
|
|
ENSURE_OR_RETURN_FALSE( mpViewLayer->getCanvas(), "ViewShape::update(): Invalid layer canvas" );
|
|
|
|
// Shall we render to a sprite, or to a plain canvas?
|
|
if( mbAnimationMode )
|
|
return renderSprite( mpViewLayer,
|
|
rMtf,
|
|
rArgs.maOrigBounds,
|
|
rArgs.maBounds,
|
|
rArgs.maUnitBounds,
|
|
nUpdateFlags,
|
|
rArgs.mrAttr,
|
|
rArgs.mrSubsets,
|
|
rArgs.mnShapePriority,
|
|
bIsVisible );
|
|
else
|
|
return render( mpViewLayer->getCanvas(),
|
|
rMtf,
|
|
rArgs.maBounds,
|
|
rArgs.maUpdateBounds,
|
|
nUpdateFlags,
|
|
rArgs.mrAttr,
|
|
rArgs.mrSubsets,
|
|
bIsVisible );
|
|
}
|
|
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|