8a6fb1793d
Added and corrected quite some stuff to this basic construct provided by Caolan (thanks again!). No detailed descriptions here, but it's getting more complete and faster. May contain errors and is primitive-complete in the sense that all gets rendered, but some basic stuff that should be directly supported is still missing, e.g. text rendering. Adding this to allow people to check it out (speed) also since it's not yet used as default in master, so it's safe and also safes the changes publically. To test it, use TEST_SYSTEM_PRIMITIVE_RENDERER as set EnvVar in linux systems. Change-Id: I25f795c9ea4ad4f3b99591304f8803dffa499436 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/168911 Tested-by: Jenkins Reviewed-by: Armin Le Grand <Armin.Le.Grand@me.com>
302 lines
12 KiB
C++
302 lines
12 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 <drawinglayer/processor2d/SDPRProcessor2dTools.hxx>
|
|
#include <drawinglayer/primitive2d/fillgraphicprimitive2d.hxx>
|
|
#include <drawinglayer/geometry/viewinformation2d.hxx>
|
|
#include <vcl/bitmapex.hxx>
|
|
#include <vcl/graph.hxx>
|
|
#include <basegfx/range/b2drange.hxx>
|
|
|
|
#ifdef DBG_UTIL
|
|
#include <tools/stream.hxx>
|
|
#include <vcl/filter/PngImageWriter.hxx>
|
|
#endif
|
|
|
|
namespace drawinglayer::processor2d
|
|
{
|
|
void setOffsetXYCreatedBitmap(
|
|
drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D,
|
|
const BitmapEx& rBitmap)
|
|
{
|
|
rFillGraphicPrimitive2D.impSetOffsetXYCreatedBitmap(rBitmap);
|
|
}
|
|
|
|
void takeCareOfOffsetXY(
|
|
const drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D,
|
|
BitmapEx& rTarget, basegfx::B2DRange& rFillUnitRange)
|
|
{
|
|
const attribute::FillGraphicAttribute& rFillGraphicAttribute(
|
|
rFillGraphicPrimitive2D.getFillGraphic());
|
|
const bool bOffsetXIsUsed(rFillGraphicAttribute.getOffsetX() > 0.0
|
|
&& rFillGraphicAttribute.getOffsetX() < 1.0);
|
|
const bool bOffsetYIsUsed(rFillGraphicAttribute.getOffsetY() > 0.0
|
|
&& rFillGraphicAttribute.getOffsetY() < 1.0);
|
|
|
|
if (bOffsetXIsUsed)
|
|
{
|
|
if (rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty())
|
|
{
|
|
const Size& rSize(rTarget.GetSizePixel());
|
|
const tools::Long w(rSize.Width());
|
|
const tools::Long a(
|
|
basegfx::fround<tools::Long>(w * (1.0 - rFillGraphicAttribute.getOffsetX())));
|
|
|
|
if (0 != a && w != a)
|
|
{
|
|
const tools::Long h(rSize.Height());
|
|
const tools::Long b(w - a);
|
|
BitmapEx aTarget(Size(w, h * 2), rTarget.getPixelFormat());
|
|
|
|
aTarget.SetPrefSize(
|
|
Size(rTarget.GetPrefSize().Width(), rTarget.GetPrefSize().Height() * 2));
|
|
const tools::Rectangle aSrcDst(Point(), rSize);
|
|
aTarget.CopyPixel(aSrcDst, // Dst
|
|
aSrcDst, // Src
|
|
rTarget);
|
|
const Size aSizeA(b, h);
|
|
aTarget.CopyPixel(tools::Rectangle(Point(0, h), aSizeA), // Dst
|
|
tools::Rectangle(Point(a, 0), aSizeA), // Src
|
|
rTarget);
|
|
const Size aSizeB(a, h);
|
|
aTarget.CopyPixel(tools::Rectangle(Point(b, h), aSizeB), // Dst
|
|
tools::Rectangle(Point(), aSizeB), // Src
|
|
rTarget);
|
|
|
|
setOffsetXYCreatedBitmap(
|
|
const_cast<drawinglayer::primitive2d::FillGraphicPrimitive2D&>(
|
|
rFillGraphicPrimitive2D),
|
|
aTarget);
|
|
}
|
|
}
|
|
|
|
if (!rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty())
|
|
{
|
|
rTarget = rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap();
|
|
rFillUnitRange.expand(basegfx::B2DPoint(
|
|
rFillUnitRange.getMinX(), rFillUnitRange.getMaxY() + rFillUnitRange.getHeight()));
|
|
}
|
|
}
|
|
else if (bOffsetYIsUsed)
|
|
{
|
|
if (rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty())
|
|
{
|
|
const Size& rSize(rTarget.GetSizePixel());
|
|
const tools::Long h(rSize.Height());
|
|
const tools::Long a(
|
|
basegfx::fround<tools::Long>(h * (1.0 - rFillGraphicAttribute.getOffsetY())));
|
|
|
|
if (0 != a && h != a)
|
|
{
|
|
const tools::Long w(rSize.Width());
|
|
const tools::Long b(h - a);
|
|
BitmapEx aTarget(Size(w * 2, h), rTarget.getPixelFormat());
|
|
|
|
aTarget.SetPrefSize(
|
|
Size(rTarget.GetPrefSize().Width() * 2, rTarget.GetPrefSize().Height()));
|
|
const tools::Rectangle aSrcDst(Point(), rSize);
|
|
aTarget.CopyPixel(aSrcDst, // Dst
|
|
aSrcDst, // Src
|
|
rTarget);
|
|
const Size aSizeA(w, b);
|
|
aTarget.CopyPixel(tools::Rectangle(Point(w, 0), aSizeA), // Dst
|
|
tools::Rectangle(Point(0, a), aSizeA), // Src
|
|
rTarget);
|
|
const Size aSizeB(w, a);
|
|
aTarget.CopyPixel(tools::Rectangle(Point(w, b), aSizeB), // Dst
|
|
tools::Rectangle(Point(), aSizeB), // Src
|
|
rTarget);
|
|
|
|
setOffsetXYCreatedBitmap(
|
|
const_cast<drawinglayer::primitive2d::FillGraphicPrimitive2D&>(
|
|
rFillGraphicPrimitive2D),
|
|
aTarget);
|
|
}
|
|
}
|
|
|
|
if (!rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap().IsEmpty())
|
|
{
|
|
rTarget = rFillGraphicPrimitive2D.getOffsetXYCreatedBitmap();
|
|
rFillUnitRange.expand(basegfx::B2DPoint(
|
|
rFillUnitRange.getMaxX() + rFillUnitRange.getWidth(), rFillUnitRange.getMinY()));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool prepareBitmapForDirectRender(
|
|
const drawinglayer::primitive2d::FillGraphicPrimitive2D& rFillGraphicPrimitive2D,
|
|
const drawinglayer::geometry::ViewInformation2D& rViewInformation2D, BitmapEx& rTarget,
|
|
basegfx::B2DRange& rFillUnitRange, double fBigDiscreteArea)
|
|
{
|
|
const attribute::FillGraphicAttribute& rFillGraphicAttribute(
|
|
rFillGraphicPrimitive2D.getFillGraphic());
|
|
const Graphic& rGraphic(rFillGraphicAttribute.getGraphic());
|
|
|
|
if (rFillGraphicAttribute.isDefault() || rGraphic.IsNone())
|
|
{
|
|
// default attributes or GraphicType::NONE, so no fill .-> done
|
|
return false;
|
|
}
|
|
|
|
if (!rFillGraphicAttribute.getTiling())
|
|
{
|
|
// If no tiling used, the Graphic will need to be painted just once. This
|
|
// is perfectly done using the decomposition, so use it.
|
|
// What we want to do here is to optimize tiled paint, for two reasons:
|
|
// (a) speed: draw one tile, repeat -> obvious
|
|
// (b) correctness: not so obvious, but since in AAed paint the same edge
|
|
// of touching polygons both AAed do *not* sum up, but get blended by
|
|
// multiplication (0.5 * 0.5 -> 0.25) the connection will stay visible,
|
|
// not only with filled polygons, but also with bitmaps
|
|
// Signal that paint is needed
|
|
return true;
|
|
}
|
|
|
|
if (rFillUnitRange.isEmpty())
|
|
{
|
|
// no fill range definition, no fill, done
|
|
return false;
|
|
}
|
|
|
|
const basegfx::B2DHomMatrix aLocalTransform(rViewInformation2D.getObjectToViewTransformation()
|
|
* rFillGraphicPrimitive2D.getTransformation());
|
|
const basegfx::B2DRange& rDiscreteViewPort(rViewInformation2D.getDiscreteViewport());
|
|
|
|
if (!rDiscreteViewPort.isEmpty())
|
|
{
|
|
// calculate discrete covered pixel area
|
|
basegfx::B2DRange aDiscreteRange(basegfx::B2DRange::getUnitB2DRange());
|
|
aDiscreteRange.transform(aLocalTransform);
|
|
|
|
if (!aDiscreteRange.overlaps(rDiscreteViewPort))
|
|
{
|
|
// we have a Viewport and visible range of geometry is outside -> not visible, done
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (GraphicType::Bitmap == rGraphic.GetType() && rGraphic.IsAnimated())
|
|
{
|
|
// Need to prepare specialized AnimatedGraphicPrimitive2D,
|
|
// cannot handle here. Signal that paint is needed
|
|
return true;
|
|
}
|
|
|
|
if (GraphicType::Bitmap == rGraphic.GetType() && !rGraphic.getVectorGraphicData())
|
|
{
|
|
// bitmap graphic, always handle locally, so get bitmap data independent
|
|
// if it'sie or it's discrete display size
|
|
rTarget = rGraphic.GetBitmapEx();
|
|
}
|
|
else
|
|
{
|
|
// Vector Graphic Data fill, including metafile:
|
|
// We can know about discrete pixel size here, calculate and use it.
|
|
// To do so, using Vectors is sufficient to get the lengths. It is
|
|
// not necessary to transform the whole target coordinate system.
|
|
const basegfx::B2DVector aDiscreteXAxis(
|
|
aLocalTransform
|
|
* basegfx::B2DVector(rFillUnitRange.getMaxX() - rFillUnitRange.getMinX(), 0.0));
|
|
const basegfx::B2DVector aDiscreteYAxis(
|
|
aLocalTransform
|
|
* basegfx::B2DVector(0.0, rFillUnitRange.getMaxY() - rFillUnitRange.getMinY()));
|
|
|
|
// get and ensure minimal size
|
|
const double fDiscreteWidth(std::max(1.0, aDiscreteXAxis.getLength()));
|
|
const double fDiscreteHeight(std::max(1.0, aDiscreteYAxis.getLength()));
|
|
|
|
// compare with a big visualization size in discrete pixels
|
|
const double fTargetDiscreteArea(fDiscreteWidth * fDiscreteHeight);
|
|
|
|
if (fTargetDiscreteArea > fBigDiscreteArea)
|
|
{
|
|
// When the vector data is visualized big it is better to not handle here
|
|
// but use decomposition fallback which then will visualize the vector data
|
|
// directly -> better quality, acceptable number of tile repeat(s)
|
|
// signal that paint is needed
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
// If visualized small, the amount of repeated fills gets expensive, so
|
|
// in that case use a Bitmap and the Brush technique below.
|
|
// The Bitmap may be created here exactly for the needed target size
|
|
// (using local D2DBitmapPixelProcessor2D and the vector data),
|
|
// but since we have a HW renderer and re-use of system-dependent data
|
|
// at BitmapEx is possible, just get the default fallback Bitmap from the
|
|
// vector data to continue. Trust the existing converters for now to
|
|
// do something with good quality.
|
|
rTarget = rGraphic.GetBitmapEx();
|
|
}
|
|
}
|
|
|
|
if (rTarget.IsEmpty() || rTarget.GetSizePixel().IsEmpty())
|
|
{
|
|
// no pixel data, done
|
|
return false;
|
|
}
|
|
|
|
// react if OffsetX/OffsetY of the FillGraphicAttribute is used
|
|
takeCareOfOffsetXY(rFillGraphicPrimitive2D, rTarget, rFillUnitRange);
|
|
|
|
#ifdef DBG_UTIL
|
|
// allow to check bitmap data, e.g. control OffsetX/OffsetY stuff
|
|
static bool bDoSaveForVisualControl(false); // loplugin:constvars:ignore
|
|
if (bDoSaveForVisualControl)
|
|
{
|
|
static const OUString sDumpPath(
|
|
OUString::createFromAscii(std::getenv("VCL_DUMP_BMP_PATH")));
|
|
if (!sDumpPath.isEmpty())
|
|
{
|
|
SvFileStream aNew(sDumpPath + "test_getreplacement.png",
|
|
StreamMode::WRITE | StreamMode::TRUNC);
|
|
vcl::PngImageWriter aPNGWriter(aNew);
|
|
aPNGWriter.write(rTarget);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// signal to render it
|
|
return true;
|
|
}
|
|
|
|
void calculateDiscreteVisibleRange(
|
|
basegfx::B2DRange& rDiscreteVisibleRange, const basegfx::B2DRange& rContentRange,
|
|
const drawinglayer::geometry::ViewInformation2D& rViewInformation2D)
|
|
{
|
|
if (rContentRange.isEmpty())
|
|
{
|
|
// no content, done
|
|
rDiscreteVisibleRange.reset();
|
|
return;
|
|
}
|
|
|
|
basegfx::B2DRange aDiscreteRange(rContentRange);
|
|
aDiscreteRange.transform(rViewInformation2D.getObjectToViewTransformation());
|
|
const basegfx::B2DRange& rDiscreteViewPort(rViewInformation2D.getDiscreteViewport());
|
|
rDiscreteVisibleRange = aDiscreteRange;
|
|
|
|
if (!rDiscreteViewPort.isEmpty())
|
|
{
|
|
rDiscreteVisibleRange.intersect(rDiscreteViewPort);
|
|
}
|
|
}
|
|
} // end of namespace
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|