Nach dem Mausklick ist die Projektion eines vierdimensionalen Würfels zu sehen viewshape.cxx
Sprache: 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 .
*/
// 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 );
// 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();
}
}
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.
// 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();
returntrue;
}
// 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() );
// 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" );
// 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 ) );
// 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 );
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).
// shape is invisible, no need to update anything. returntrue;
}
// 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 ) returntrue;
SAL_INFO( "slideshow", "ViewShape::render(): rendering shape " << this << " at position (" <<
rBounds.getMinX() << "," <<
rBounds.getMinY() << ")" );
// 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() )
{ constdouble 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
// 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 ) );
// buffer aCompositingSurface iterator content // - said one might get invalidated during // draw() below.
::cppcanvas::BitmapCanvasSharedPtr pBitmapCanvas(
aCompositingSurface->mpLastBitmapCanvas );
// 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() );
// 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 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!" );
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();
}
// 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. constdouble nXBorder( ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE / aViewTransform.get(0,0) ); constdouble nYBorder( ::cppcanvas::Canvas::ANTIALIASING_EXTRA_SIZE / aViewTransform.get(1,1) );
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.