You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
929 lines
29 KiB
929 lines
29 KiB
14 years ago
|
/*
|
||
|
* Copyright (c) 2002 Patrick Julien <freak@codepimps.org>
|
||
|
* Copyright (c) 2004 Boudewijn Rempt <boud@valdyas.org>
|
||
|
* Copyright (c) 2004 Clarence Dang <dang@kde.org>
|
||
|
* Copyright (c) 2004 Adrian Page <adrian@pagenet.plus.com>
|
||
|
* Copyright (c) 2004 Cyrille Berger <cberger@cberger.net>
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation; either version 2 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program; if not, write to the Free Software
|
||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||
|
*/
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
#include <cfloat>
|
||
|
#include <cmath>
|
||
|
#include <climits>
|
||
|
#include <strings.h>
|
||
|
|
||
|
#include "tqbrush.h"
|
||
|
#include "tqfontinfo.h"
|
||
|
#include "tqfontmetrics.h"
|
||
|
#include "tqpen.h"
|
||
|
#include "tqregion.h"
|
||
|
#include "tqwmatrix.h"
|
||
|
#include <tqimage.h>
|
||
|
#include <tqmap.h>
|
||
|
#include <tqpainter.h>
|
||
|
#include <tqpixmap.h>
|
||
|
#include <tqpointarray.h>
|
||
|
#include <tqrect.h>
|
||
|
#include <tqstring.h>
|
||
|
|
||
|
#include <kdebug.h>
|
||
|
#include <kcommand.h>
|
||
|
#include <klocale.h>
|
||
|
|
||
|
#include "kis_brush.h"
|
||
|
#include "kis_debug_areas.h"
|
||
|
#include "kis_image.h"
|
||
|
#include "kis_layer.h"
|
||
|
#include "kis_paint_device.h"
|
||
|
#include "kis_painter.h"
|
||
|
#include "kis_pattern.h"
|
||
|
#include "kis_rect.h"
|
||
|
#include "kis_colorspace.h"
|
||
|
#include "kis_transaction.h"
|
||
|
#include "kis_types.h"
|
||
|
#include "kis_vec.h"
|
||
|
#include "kis_iterators_pixel.h"
|
||
|
#include "kis_paintop.h"
|
||
|
#include "kis_selection.h"
|
||
|
#include "kis_fill_painter.h"
|
||
|
#include "kis_color.h"
|
||
|
|
||
|
// Maximum distance from a Bezier control point to the line through the start
|
||
|
// and end points for the curve to be considered flat.
|
||
|
#define BEZIER_FLATNESS_THRESHOLD 0.5
|
||
|
|
||
|
KisPainter::KisPainter()
|
||
|
{
|
||
|
init();
|
||
|
}
|
||
|
|
||
|
KisPainter::KisPainter(KisPaintDeviceSP device)
|
||
|
{
|
||
|
init();
|
||
|
Q_ASSERT(device);
|
||
|
begin(device);
|
||
|
}
|
||
|
|
||
|
void KisPainter::init()
|
||
|
{
|
||
|
m_transaction = 0;
|
||
|
m_paintOp = 0;
|
||
|
m_filter = 0;
|
||
|
m_brush = 0;
|
||
|
m_pattern= 0;
|
||
|
m_opacity = OPACITY_OPAQUE;
|
||
|
m_compositeOp = COMPOSITE_OVER;
|
||
|
m_dab = 0;
|
||
|
m_fillStyle = FillStyleNone;
|
||
|
m_strokeStyle = StrokeStyleBrush;
|
||
|
m_pressure = PRESSURE_MIN;
|
||
|
m_duplicateHealing = false;
|
||
|
m_duplicateHealingRadius = 10;
|
||
|
m_duplicatePerspectiveCorrection = false;
|
||
|
m_varyBrushSpacingWithPressureWhenDrawingALine = true;
|
||
|
}
|
||
|
|
||
|
KisPainter::~KisPainter()
|
||
|
{
|
||
|
m_brush = 0;
|
||
|
delete m_paintOp;
|
||
|
end();
|
||
|
}
|
||
|
|
||
|
void KisPainter::begin(KisPaintDeviceSP device)
|
||
|
{
|
||
|
if (!device) return;
|
||
|
|
||
|
if (m_transaction)
|
||
|
delete m_transaction;
|
||
|
|
||
|
m_device = device;
|
||
|
m_colorSpace = device->colorSpace();
|
||
|
m_pixelSize = device->pixelSize();
|
||
|
}
|
||
|
|
||
|
KCommand *KisPainter::end()
|
||
|
{
|
||
|
return endTransaction();
|
||
|
}
|
||
|
|
||
|
void KisPainter::beginTransaction(const TQString& customName)
|
||
|
{
|
||
|
if (m_transaction)
|
||
|
delete m_transaction;
|
||
|
m_transaction = new KisTransaction(customName, m_device);
|
||
|
Q_CHECK_PTR(m_transaction);
|
||
|
}
|
||
|
|
||
|
void KisPainter::beginTransaction( KisTransaction* command)
|
||
|
{
|
||
|
if (m_transaction)
|
||
|
delete m_transaction;
|
||
|
m_transaction = command;
|
||
|
}
|
||
|
|
||
|
|
||
|
KCommand *KisPainter::endTransaction()
|
||
|
{
|
||
|
KCommand *command = m_transaction;
|
||
|
m_transaction = 0;
|
||
|
return command;
|
||
|
}
|
||
|
|
||
|
|
||
|
TQRect KisPainter::dirtyRect() {
|
||
|
TQRect r = m_dirtyRect;
|
||
|
m_dirtyRect = TQRect();
|
||
|
return r;
|
||
|
}
|
||
|
|
||
|
void KisPainter::bitBlt(TQ_INT32 dx, TQ_INT32 dy,
|
||
|
const KisCompositeOp& op,
|
||
|
KisPaintDeviceSP srcdev,
|
||
|
TQ_UINT8 opacity,
|
||
|
TQ_INT32 sx, TQ_INT32 sy,
|
||
|
TQ_INT32 sw, TQ_INT32 sh)
|
||
|
{
|
||
|
if (srcdev == 0) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
TQRect srcRect = TQRect(sx, sy, sw, sh);
|
||
|
|
||
|
if (srcdev->extentIsValid() && op != COMPOSITE_COPY) {
|
||
|
srcRect &= srcdev->extent();
|
||
|
}
|
||
|
|
||
|
if (srcRect.isEmpty()) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
dx += srcRect.x() - sx;
|
||
|
dy += srcRect.y() - sy;
|
||
|
|
||
|
sx = srcRect.x();
|
||
|
sy = srcRect.y();
|
||
|
sw = srcRect.width();
|
||
|
sh = srcRect.height();
|
||
|
|
||
|
addDirtyRect(TQRect(dx, dy, sw, sh));
|
||
|
|
||
|
KisColorSpace * srcCs = srcdev->colorSpace();
|
||
|
|
||
|
TQ_INT32 dstY = dy;
|
||
|
TQ_INT32 srcY = sy;
|
||
|
TQ_INT32 rowsRemaining = sh;
|
||
|
|
||
|
while (rowsRemaining > 0) {
|
||
|
|
||
|
TQ_INT32 dstX = dx;
|
||
|
TQ_INT32 srcX = sx;
|
||
|
TQ_INT32 columnsRemaining = sw;
|
||
|
TQ_INT32 numContiguousDstRows = m_device->numContiguousRows(dstY, dstX, dstX + sw - 1);
|
||
|
TQ_INT32 numContiguousSrcRows = srcdev->numContiguousRows(srcY, srcX, srcX + sw - 1);
|
||
|
|
||
|
TQ_INT32 rows = TQMIN(numContiguousDstRows, numContiguousSrcRows);
|
||
|
rows = TQMIN(rows, rowsRemaining);
|
||
|
|
||
|
while (columnsRemaining > 0) {
|
||
|
|
||
|
TQ_INT32 numContiguousDstColumns = m_device->numContiguousColumns(dstX, dstY, dstY + rows - 1);
|
||
|
TQ_INT32 numContiguousSrcColumns = srcdev->numContiguousColumns(srcX, srcY, srcY + rows - 1);
|
||
|
|
||
|
TQ_INT32 columns = TQMIN(numContiguousDstColumns, numContiguousSrcColumns);
|
||
|
columns = TQMIN(columns, columnsRemaining);
|
||
|
|
||
|
TQ_INT32 srcRowStride = srcdev->rowStride(srcX, srcY);
|
||
|
//const TQ_UINT8 *srcData = srcdev->pixel(srcX, srcY);
|
||
|
KisHLineIteratorPixel srcIt = srcdev->createHLineIterator(srcX, srcY, columns, false);
|
||
|
const TQ_UINT8 *srcData = srcIt.rawData();
|
||
|
|
||
|
//TQ_UINT8 *dstData = m_device->writablePixel(dstX, dstY);
|
||
|
TQ_INT32 dstRowStride = m_device->rowStride(dstX, dstY);
|
||
|
KisHLineIteratorPixel dstIt = m_device->createHLineIterator(dstX, dstY, columns, true);
|
||
|
TQ_UINT8 *dstData = dstIt.rawData();
|
||
|
|
||
|
|
||
|
m_colorSpace->bitBlt(dstData,
|
||
|
dstRowStride,
|
||
|
srcCs,
|
||
|
srcData,
|
||
|
srcRowStride,
|
||
|
0,
|
||
|
0,
|
||
|
opacity,
|
||
|
rows,
|
||
|
columns,
|
||
|
op);
|
||
|
|
||
|
srcX += columns;
|
||
|
dstX += columns;
|
||
|
columnsRemaining -= columns;
|
||
|
}
|
||
|
|
||
|
srcY += rows;
|
||
|
dstY += rows;
|
||
|
rowsRemaining -= rows;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void KisPainter::bltSelection(TQ_INT32 dx, TQ_INT32 dy,
|
||
|
const KisCompositeOp &op,
|
||
|
KisPaintDeviceSP srcdev,
|
||
|
KisSelectionSP seldev,
|
||
|
TQ_UINT8 opacity,
|
||
|
TQ_INT32 sx, TQ_INT32 sy,
|
||
|
TQ_INT32 sw, TQ_INT32 sh)
|
||
|
{
|
||
|
// Better use a probablistic method than a too slow one
|
||
|
if (seldev->isProbablyTotallyUnselected(TQRect(dx, dy, sw, sh))) {
|
||
|
/*
|
||
|
kdDebug() << "Blitting outside selection rect\n";
|
||
|
|
||
|
kdDebug() << "srcdev: " << srcdev << " (" << srcdev->name() << ")"
|
||
|
<< ", seldev: " << seldev << " (" << seldev->name() << ")"
|
||
|
<< ". dx, dy " << dx << "," << dy
|
||
|
<< ". sx, sy : sw, sy " << sx << "," << sy << " : " << sw << "," << sh << endl;
|
||
|
*/
|
||
|
return;
|
||
|
}
|
||
|
bltMask(dx,dy,op,srcdev,seldev.data(),opacity,sx,sy,sw,sh);
|
||
|
}
|
||
|
|
||
|
void KisPainter::bltMask(TQ_INT32 dx, TQ_INT32 dy,
|
||
|
const KisCompositeOp &op,
|
||
|
KisPaintDeviceSP srcdev,
|
||
|
KisPaintDeviceSP seldev,
|
||
|
TQ_UINT8 opacity,
|
||
|
TQ_INT32 sx, TQ_INT32 sy,
|
||
|
TQ_INT32 sw, TQ_INT32 sh)
|
||
|
|
||
|
{
|
||
|
if (srcdev == 0) return;
|
||
|
|
||
|
if (seldev == 0) return;
|
||
|
|
||
|
if (m_device == 0) return;
|
||
|
|
||
|
|
||
|
TQRect srcRect = TQRect(sx, sy, sw, sh);
|
||
|
|
||
|
if (srcdev->extentIsValid() && op != COMPOSITE_COPY) {
|
||
|
srcRect &= srcdev->extent();
|
||
|
}
|
||
|
|
||
|
if (srcRect.isEmpty()) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
dx += srcRect.x() - sx;
|
||
|
dy += srcRect.y() - sy;
|
||
|
|
||
|
sx = srcRect.x();
|
||
|
sy = srcRect.y();
|
||
|
sw = srcRect.width();
|
||
|
sh = srcRect.height();
|
||
|
|
||
|
addDirtyRect(TQRect(dx, dy, sw, sh));
|
||
|
|
||
|
KisColorSpace * srcCs = srcdev->colorSpace();
|
||
|
|
||
|
TQ_INT32 dstY = dy;
|
||
|
TQ_INT32 srcY = sy;
|
||
|
TQ_INT32 rowsRemaining = sh;
|
||
|
|
||
|
while (rowsRemaining > 0) {
|
||
|
|
||
|
TQ_INT32 dstX = dx;
|
||
|
TQ_INT32 srcX = sx;
|
||
|
TQ_INT32 columnsRemaining = sw;
|
||
|
TQ_INT32 numContiguousDstRows = m_device->numContiguousRows(dstY, dstX, dstX + sw - 1);
|
||
|
TQ_INT32 numContiguousSrcRows = srcdev->numContiguousRows(srcY, srcX, srcX + sw - 1);
|
||
|
TQ_INT32 numContiguousSelRows = seldev->numContiguousRows(dstY, dstX, dstX + sw - 1);
|
||
|
|
||
|
TQ_INT32 rows = TQMIN(numContiguousDstRows, numContiguousSrcRows);
|
||
|
rows = TQMIN(numContiguousSelRows, rows);
|
||
|
rows = TQMIN(rows, rowsRemaining);
|
||
|
|
||
|
while (columnsRemaining > 0) {
|
||
|
|
||
|
TQ_INT32 numContiguousDstColumns = m_device->numContiguousColumns(dstX, dstY, dstY + rows - 1);
|
||
|
TQ_INT32 numContiguousSrcColumns = srcdev->numContiguousColumns(srcX, srcY, srcY + rows - 1);
|
||
|
TQ_INT32 numContiguousSelColumns = seldev->numContiguousColumns(dstX, dstY, dstY + rows - 1);
|
||
|
|
||
|
TQ_INT32 columns = TQMIN(numContiguousDstColumns, numContiguousSrcColumns);
|
||
|
columns = TQMIN(numContiguousSelColumns, columns);
|
||
|
columns = TQMIN(columns, columnsRemaining);
|
||
|
|
||
|
//TQ_UINT8 *dstData = m_device->writablePixel(dstX, dstY);
|
||
|
TQ_INT32 dstRowStride = m_device->rowStride(dstX, dstY);
|
||
|
KisHLineIteratorPixel dstIt = m_device->createHLineIterator(dstX, dstY, columns, true);
|
||
|
TQ_UINT8 *dstData = dstIt.rawData();
|
||
|
|
||
|
//const TQ_UINT8 *srcData = srcdev->pixel(srcX, srcY);
|
||
|
TQ_INT32 srcRowStride = srcdev->rowStride(srcX, srcY);
|
||
|
KisHLineIteratorPixel srcIt = srcdev->createHLineIterator(srcX, srcY, columns, false);
|
||
|
const TQ_UINT8 *srcData = srcIt.rawData();
|
||
|
|
||
|
//const TQ_UINT8 *selData = seldev->pixel(dstX, dstY);
|
||
|
TQ_INT32 selRowStride = seldev->rowStride(dstX, dstY);
|
||
|
KisHLineIteratorPixel selIt = seldev->createHLineIterator(dstX, dstY, columns, false);
|
||
|
const TQ_UINT8 *selData = selIt.rawData();
|
||
|
|
||
|
m_colorSpace->bitBlt(dstData,
|
||
|
dstRowStride,
|
||
|
srcCs,
|
||
|
srcData,
|
||
|
srcRowStride,
|
||
|
selData,
|
||
|
selRowStride,
|
||
|
opacity,
|
||
|
rows,
|
||
|
columns,
|
||
|
op);
|
||
|
|
||
|
srcX += columns;
|
||
|
dstX += columns;
|
||
|
columnsRemaining -= columns;
|
||
|
}
|
||
|
|
||
|
srcY += rows;
|
||
|
dstY += rows;
|
||
|
rowsRemaining -= rows;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void KisPainter::bltSelection(TQ_INT32 dx, TQ_INT32 dy,
|
||
|
const KisCompositeOp& op,
|
||
|
KisPaintDeviceSP srcdev,
|
||
|
TQ_UINT8 opacity,
|
||
|
TQ_INT32 sx, TQ_INT32 sy,
|
||
|
TQ_INT32 sw, TQ_INT32 sh)
|
||
|
{
|
||
|
if (m_device == 0) return;
|
||
|
if (!m_device->hasSelection()) {
|
||
|
bitBlt(dx, dy, op, srcdev, opacity, sx, sy, sw, sh);
|
||
|
}
|
||
|
else
|
||
|
bltSelection(dx,dy,op,srcdev, m_device->selection(),opacity,sx,sy,sw,sh);
|
||
|
}
|
||
|
|
||
|
double KisPainter::paintLine(const KisPoint & pos1,
|
||
|
const double pressure1,
|
||
|
const double xTilt1,
|
||
|
const double yTilt1,
|
||
|
const KisPoint & pos2,
|
||
|
const double pressure2,
|
||
|
const double xTilt2,
|
||
|
const double yTilt2,
|
||
|
const double inSavedDist)
|
||
|
{
|
||
|
if (!m_device) return 0;
|
||
|
if (!m_paintOp) return 0;
|
||
|
if (!m_brush) return 0;
|
||
|
|
||
|
double savedDist = inSavedDist;
|
||
|
KisVector2D end(pos2);
|
||
|
KisVector2D start(pos1);
|
||
|
|
||
|
KisVector2D dragVec = end - start;
|
||
|
KisVector2D movement = dragVec;
|
||
|
|
||
|
if (savedDist < 0) {
|
||
|
m_paintOp->paintAt(pos1, KisPaintInformation(pressure1, xTilt1, yTilt1, movement));
|
||
|
savedDist = 0;
|
||
|
}
|
||
|
|
||
|
double xSpacing = 0;
|
||
|
double ySpacing = 0;
|
||
|
|
||
|
if ( m_varyBrushSpacingWithPressureWhenDrawingALine ) {
|
||
|
// XXX: The spacing should vary as the pressure changes along the
|
||
|
// line.
|
||
|
// This is a quick simplification.
|
||
|
xSpacing = m_brush->xSpacing((pressure1 + pressure2) / 2);
|
||
|
ySpacing = m_brush->ySpacing((pressure1 + pressure2) / 2);
|
||
|
}
|
||
|
else {
|
||
|
xSpacing = m_brush->xSpacing( PRESSURE_DEFAULT );
|
||
|
ySpacing = m_brush->ySpacing( PRESSURE_DEFAULT );
|
||
|
}
|
||
|
|
||
|
if (xSpacing < 0.5) {
|
||
|
xSpacing = 0.5;
|
||
|
}
|
||
|
if (ySpacing < 0.5) {
|
||
|
ySpacing = 0.5;
|
||
|
}
|
||
|
|
||
|
double xScale = 1;
|
||
|
double yScale = 1;
|
||
|
double spacing;
|
||
|
// Scale x or y so that we effectively have a square brush
|
||
|
// and calculate distance in that coordinate space. We reverse this scaling
|
||
|
// before drawing the brush. This produces the correct spacing in both
|
||
|
// x and y directions, even if the brush's aspect ratio is not 1:1.
|
||
|
if (xSpacing > ySpacing) {
|
||
|
yScale = xSpacing / ySpacing;
|
||
|
spacing = xSpacing;
|
||
|
}
|
||
|
else {
|
||
|
xScale = ySpacing / xSpacing;
|
||
|
spacing = ySpacing;
|
||
|
}
|
||
|
|
||
|
dragVec.setX(dragVec.x() * xScale);
|
||
|
dragVec.setY(dragVec.y() * yScale);
|
||
|
|
||
|
double newDist = dragVec.length();
|
||
|
double dist = savedDist + newDist;
|
||
|
double l_savedDist = savedDist;
|
||
|
|
||
|
if (dist < spacing) {
|
||
|
return dist;
|
||
|
}
|
||
|
|
||
|
dragVec.normalize();
|
||
|
KisVector2D step(0, 0);
|
||
|
|
||
|
while (dist >= spacing) {
|
||
|
if (l_savedDist > 0) {
|
||
|
step += dragVec * (spacing - l_savedDist);
|
||
|
l_savedDist -= spacing;
|
||
|
}
|
||
|
else {
|
||
|
step += dragVec * spacing;
|
||
|
}
|
||
|
|
||
|
KisPoint p(start.x() + (step.x() / xScale), start.y() + (step.y() / yScale));
|
||
|
|
||
|
double distanceMoved = step.length();
|
||
|
double t = 0;
|
||
|
|
||
|
if (newDist > DBL_EPSILON) {
|
||
|
t = distanceMoved / newDist;
|
||
|
}
|
||
|
|
||
|
double pressure = (1 - t) * pressure1 + t * pressure2;
|
||
|
double xTilt = (1 - t) * xTilt1 + t * xTilt2;
|
||
|
double yTilt = (1 - t) * yTilt1 + t * yTilt2;
|
||
|
|
||
|
m_paintOp->paintAt(p, KisPaintInformation(pressure, xTilt, yTilt, movement));
|
||
|
dist -= spacing;
|
||
|
}
|
||
|
|
||
|
if (dist > 0)
|
||
|
return dist;
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void KisPainter::paintPolyline (const vKisPoint &points,
|
||
|
int index, int numPoints)
|
||
|
{
|
||
|
if (index >= (int) points.count ())
|
||
|
return;
|
||
|
|
||
|
if (numPoints < 0)
|
||
|
numPoints = points.count ();
|
||
|
|
||
|
if (index + numPoints > (int) points.count ())
|
||
|
numPoints = points.count () - index;
|
||
|
|
||
|
|
||
|
for (int i = index; i < index + numPoints - 1; i++)
|
||
|
{
|
||
|
paintLine (points [index], 0/*pressure*/, 0, 0, points [index + 1],
|
||
|
0/*pressure*/, 0, 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void KisPainter::getBezierCurvePoints(const KisPoint &pos1,
|
||
|
const KisPoint &control1,
|
||
|
const KisPoint &control2,
|
||
|
const KisPoint &pos2,
|
||
|
vKisPoint& points)
|
||
|
{
|
||
|
double d1 = pointToLineDistance(control1, pos1, pos2);
|
||
|
double d2 = pointToLineDistance(control2, pos1, pos2);
|
||
|
|
||
|
if (d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD) {
|
||
|
points.push_back(pos1);
|
||
|
} else {
|
||
|
// Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508
|
||
|
KisVector2D p1 = pos1;
|
||
|
KisVector2D p2 = control1;
|
||
|
KisVector2D p3 = control2;
|
||
|
KisVector2D p4 = pos2;
|
||
|
|
||
|
KisVector2D l2 = (p1 + p2) / 2;
|
||
|
KisVector2D h = (p2 + p3) / 2;
|
||
|
KisVector2D l3 = (l2 + h) / 2;
|
||
|
KisVector2D r3 = (p3 + p4) / 2;
|
||
|
KisVector2D r2 = (h + r3) / 2;
|
||
|
KisVector2D l4 = (l3 + r2) / 2;
|
||
|
KisVector2D r1 = l4;
|
||
|
KisVector2D l1 = p1;
|
||
|
KisVector2D r4 = p4;
|
||
|
|
||
|
getBezierCurvePoints(l1.toKisPoint(), l2.toKisPoint(), l3.toKisPoint(), l4.toKisPoint(), points);
|
||
|
getBezierCurvePoints(r1.toKisPoint(), r2.toKisPoint(), r3.toKisPoint(), r4.toKisPoint(), points);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
double KisPainter::paintBezierCurve(const KisPoint &pos1,
|
||
|
const double pressure1,
|
||
|
const double xTilt1,
|
||
|
const double yTilt1,
|
||
|
const KisPoint &control1,
|
||
|
const KisPoint &control2,
|
||
|
const KisPoint &pos2,
|
||
|
const double pressure2,
|
||
|
const double xTilt2,
|
||
|
const double yTilt2,
|
||
|
const double savedDist)
|
||
|
{
|
||
|
double newDistance;
|
||
|
double d1 = pointToLineDistance(control1, pos1, pos2);
|
||
|
double d2 = pointToLineDistance(control2, pos1, pos2);
|
||
|
|
||
|
if (d1 < BEZIER_FLATNESS_THRESHOLD && d2 < BEZIER_FLATNESS_THRESHOLD) {
|
||
|
newDistance = paintLine(pos1, pressure1, xTilt1, yTilt1, pos2, pressure2, xTilt2, yTilt2, savedDist);
|
||
|
} else {
|
||
|
// Midpoint subdivision. See Foley & Van Dam Computer Graphics P.508
|
||
|
KisVector2D p1 = pos1;
|
||
|
KisVector2D p2 = control1;
|
||
|
KisVector2D p3 = control2;
|
||
|
KisVector2D p4 = pos2;
|
||
|
|
||
|
KisVector2D l2 = (p1 + p2) / 2;
|
||
|
KisVector2D h = (p2 + p3) / 2;
|
||
|
KisVector2D l3 = (l2 + h) / 2;
|
||
|
KisVector2D r3 = (p3 + p4) / 2;
|
||
|
KisVector2D r2 = (h + r3) / 2;
|
||
|
KisVector2D l4 = (l3 + r2) / 2;
|
||
|
KisVector2D r1 = l4;
|
||
|
KisVector2D l1 = p1;
|
||
|
KisVector2D r4 = p4;
|
||
|
|
||
|
double midPressure = (pressure1 + pressure2) / 2;
|
||
|
double midXTilt = (xTilt1 + xTilt2) / 2;
|
||
|
double midYTilt = (yTilt1 + yTilt2) / 2;
|
||
|
|
||
|
newDistance = paintBezierCurve(l1.toKisPoint(), pressure1, xTilt1, yTilt1,
|
||
|
l2.toKisPoint(), l3.toKisPoint(),
|
||
|
l4.toKisPoint(), midPressure, midXTilt, midYTilt,
|
||
|
savedDist);
|
||
|
newDistance = paintBezierCurve(r1.toKisPoint(), midPressure, midXTilt, midYTilt,
|
||
|
r2.toKisPoint(),
|
||
|
r3.toKisPoint(),
|
||
|
r4.toKisPoint(), pressure2, xTilt2, yTilt2, newDistance);
|
||
|
}
|
||
|
|
||
|
return newDistance;
|
||
|
}
|
||
|
|
||
|
void KisPainter::paintRect (const KisPoint &startPoint,
|
||
|
const KisPoint &endPoint,
|
||
|
const double /*pressure*/,
|
||
|
const double /*xTilt*/,
|
||
|
const double /*yTilt*/)
|
||
|
{
|
||
|
KoRect normalizedRect = KisRect (startPoint, endPoint).normalize ();
|
||
|
|
||
|
vKisPoint points;
|
||
|
|
||
|
points.push_back(normalizedRect.topLeft());
|
||
|
points.push_back(normalizedRect.bottomLeft());
|
||
|
points.push_back(normalizedRect.bottomRight());
|
||
|
points.push_back(normalizedRect.topRight());
|
||
|
|
||
|
paintPolygon(points);
|
||
|
}
|
||
|
|
||
|
void KisPainter::paintEllipse (const KisPoint &startPoint,
|
||
|
const KisPoint &endPoint,
|
||
|
const double /*pressure*/,
|
||
|
const double /*xTilt*/,
|
||
|
const double /*yTilt*/)
|
||
|
{
|
||
|
KisRect r = KisRect(startPoint, endPoint).normalize();
|
||
|
|
||
|
// See http://www.whizkidtech.redprince.net/bezier/circle/ for explanation.
|
||
|
// kappa = (4/3*(sqrt(2)-1))
|
||
|
const double kappa = 0.5522847498;
|
||
|
const double lx = (r.width() / 2) * kappa;
|
||
|
const double ly = (r.height() / 2) * kappa;
|
||
|
|
||
|
KisPoint center = r.center();
|
||
|
|
||
|
KisPoint p0(r.left(), center.y());
|
||
|
KisPoint p1(r.left(), center.y() - ly);
|
||
|
KisPoint p2(center.x() - lx, r.top());
|
||
|
KisPoint p3(center.x(), r.top());
|
||
|
|
||
|
vKisPoint points;
|
||
|
|
||
|
getBezierCurvePoints(p0, p1, p2, p3, points);
|
||
|
|
||
|
KisPoint p4(center.x() + lx, r.top());
|
||
|
KisPoint p5(r.right(), center.y() - ly);
|
||
|
KisPoint p6(r.right(), center.y());
|
||
|
|
||
|
getBezierCurvePoints(p3, p4, p5, p6, points);
|
||
|
|
||
|
KisPoint p7(r.right(), center.y() + ly);
|
||
|
KisPoint p8(center.x() + lx, r.bottom());
|
||
|
KisPoint p9(center.x(), r.bottom());
|
||
|
|
||
|
getBezierCurvePoints(p6, p7, p8, p9, points);
|
||
|
|
||
|
KisPoint p10(center.x() - lx, r.bottom());
|
||
|
KisPoint p11(r.left(), center.y() + ly);
|
||
|
|
||
|
getBezierCurvePoints(p9, p10, p11, p0, points);
|
||
|
|
||
|
paintPolygon(points);
|
||
|
}
|
||
|
|
||
|
void KisPainter::paintAt(const KisPoint & pos,
|
||
|
const double pressure,
|
||
|
const double xTilt,
|
||
|
const double yTilt)
|
||
|
{
|
||
|
if (!m_paintOp) return;
|
||
|
m_paintOp->paintAt(pos, KisPaintInformation(pressure, xTilt, yTilt, KisVector2D()));
|
||
|
}
|
||
|
|
||
|
double KisPainter::pointToLineDistance(const KisPoint& p, const KisPoint& l0, const KisPoint& l1)
|
||
|
{
|
||
|
double lineLength = sqrt((l1.x() - l0.x()) * (l1.x() - l0.x()) + (l1.y() - l0.y()) * (l1.y() - l0.y()));
|
||
|
double distance = 0;
|
||
|
|
||
|
if (lineLength > DBL_EPSILON) {
|
||
|
distance = ((l0.y() - l1.y()) * p.x() + (l1.x() - l0.x()) * p.y() + l0.x() * l1.y() - l1.x() * l0.y()) / lineLength;
|
||
|
distance = fabs(distance);
|
||
|
}
|
||
|
|
||
|
return distance;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Concave Polygon Scan Conversion
|
||
|
* by Paul Heckbert
|
||
|
* from "Graphics Gems", Academic Press, 1990
|
||
|
*/
|
||
|
|
||
|
/*
|
||
|
* concave: scan convert nvert-sided concave non-simple polygon with vertices at
|
||
|
* (point[i].x, point[i].y) for i in [0..nvert-1] within the window win by
|
||
|
* calling spanproc for each visible span of pixels.
|
||
|
* Polygon can be clockwise or counterclockwise.
|
||
|
* Algorithm does uniform point sampling at pixel centers.
|
||
|
* Inside-outside test done by Jordan's rule: a point is considered inside if
|
||
|
* an emanating ray intersects the polygon an odd number of times.
|
||
|
* drawproc should fill in pixels from xl to xr inclusive on scanline y,
|
||
|
* e.g:
|
||
|
* drawproc(y, xl, xr)
|
||
|
* int y, xl, xr;
|
||
|
* {
|
||
|
* int x;
|
||
|
* for (x=xl; x<=xr; x++)
|
||
|
* pixel_write(x, y, pixelvalue);
|
||
|
* }
|
||
|
*
|
||
|
* Paul Heckbert 30 June 81, 18 Dec 89
|
||
|
*/
|
||
|
|
||
|
typedef struct { /* a polygon edge */
|
||
|
double x; /* x coordinate of edge's intersection with current scanline */
|
||
|
double dx; /* change in x with respect to y */
|
||
|
int i; /* edge number: edge i goes from pt[i] to pt[i+1] */
|
||
|
} Edge;
|
||
|
|
||
|
static int n; /* number of vertices */
|
||
|
static const KisPoint *pt; /* vertices */
|
||
|
|
||
|
static int nact; /* number of active edges */
|
||
|
static Edge *active; /* active edge list:edges crossing scanline y */
|
||
|
|
||
|
/* comparison routines for qsort */
|
||
|
static int compare_ind(const void *pu, const void *pv)
|
||
|
{
|
||
|
const int *u = static_cast<const int *>(pu);
|
||
|
const int *v = static_cast<const int *>(pv);
|
||
|
|
||
|
return pt[*u].y() <= pt[*v].y() ? -1 : 1;
|
||
|
}
|
||
|
|
||
|
static int compare_active(const void *pu, const void *pv)
|
||
|
{
|
||
|
const Edge *u = static_cast<const Edge *>(pu);
|
||
|
const Edge *v = static_cast<const Edge *>(pv);
|
||
|
|
||
|
return u->x <= v->x ? -1 : 1;
|
||
|
}
|
||
|
|
||
|
static void cdelete(int i) /* remove edge i from active list */
|
||
|
{
|
||
|
int j;
|
||
|
|
||
|
for (j=0; j<nact && active[j].i!=i; j++);
|
||
|
if (j>=nact) return; /* edge not in active list; happens at win->y0*/
|
||
|
nact--;
|
||
|
bcopy(&active[j+1], &active[j], (nact-j)*sizeof active[0]);
|
||
|
}
|
||
|
|
||
|
static void cinsert(int i, int y) /* append edge i to end of active list */
|
||
|
{
|
||
|
int j;
|
||
|
double dx;
|
||
|
const KisPoint *p, *q;
|
||
|
|
||
|
j = i<n-1 ? i+1 : 0;
|
||
|
if (pt[i].y() < pt[j].y()) {
|
||
|
p = &pt[i]; q = &pt[j];
|
||
|
} else {
|
||
|
p = &pt[j]; q = &pt[i];
|
||
|
}
|
||
|
/* initialize x position at intersection of edge with scanline y */
|
||
|
active[nact].dx = dx = (q->x()-p->x())/(q->y()-p->y());
|
||
|
active[nact].x = dx*(y+.5-p->y())+p->x();
|
||
|
active[nact].i = i;
|
||
|
nact++;
|
||
|
}
|
||
|
|
||
|
void KisPainter::fillPolygon(const vKisPoint& points, FillStyle fillStyle)
|
||
|
{
|
||
|
int nvert = points.count();
|
||
|
int k, y0, y1, y, i, j, xl, xr;
|
||
|
int *ind; /* list of vertex indices, sorted by pt[ind[j]].y */
|
||
|
|
||
|
n = nvert;
|
||
|
pt = &(points[0]);
|
||
|
if (n<3) return;
|
||
|
if (fillStyle == FillStyleNone) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
ind = new int[n];
|
||
|
Q_CHECK_PTR(ind);
|
||
|
active = new Edge[n];
|
||
|
Q_CHECK_PTR(active);
|
||
|
|
||
|
/* create y-sorted array of indices ind[k] into vertex list */
|
||
|
for (k=0; k<n; k++)
|
||
|
ind[k] = k;
|
||
|
qsort(ind, n, sizeof ind[0], compare_ind); /* sort ind by pt[ind[k]].y */
|
||
|
|
||
|
nact = 0; /* start with empty active list */
|
||
|
k = 0; /* ind[k] is next vertex to process */
|
||
|
y0 = static_cast<int>(ceil(pt[ind[0]].y()-.5)); /* ymin of polygon */
|
||
|
y1 = static_cast<int>(floor(pt[ind[n-1]].y()-.5)); /* ymax of polygon */
|
||
|
|
||
|
int x0 = INT_MAX;
|
||
|
int x1 = INT_MIN;
|
||
|
|
||
|
for (int i = 0; i < nvert; i++) {
|
||
|
int pointHighX = static_cast<int>(ceil(points[i].x() - 0.5));
|
||
|
int pointLowX = static_cast<int>(floor(points[i].x() - 0.5));
|
||
|
|
||
|
if (pointLowX < x0) {
|
||
|
x0 = pointLowX;
|
||
|
}
|
||
|
if (pointHighX > x1) {
|
||
|
x1 = pointHighX;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Fill the polygon bounding rectangle with the required contents then we'll
|
||
|
// create a tqmask for the actual polygon coverage.
|
||
|
|
||
|
KisPaintDeviceSP polygon = new KisPaintDevice(m_device->colorSpace(), "polygon");
|
||
|
Q_CHECK_PTR(polygon);
|
||
|
|
||
|
KisFillPainter fillPainter(polygon);
|
||
|
TQRect boundingRectangle(x0, y0, x1 - x0 + 1, y1 - y0 + 1);
|
||
|
|
||
|
// Clip to the image bounds.
|
||
|
if (m_device->image()) {
|
||
|
boundingRectangle &= m_device->image()->bounds();
|
||
|
}
|
||
|
|
||
|
switch (fillStyle) {
|
||
|
default:
|
||
|
// Fall through
|
||
|
case FillStyleGradient:
|
||
|
// Currently unsupported, fall through
|
||
|
case FillStyleStrokes:
|
||
|
// Currently unsupported, fall through
|
||
|
kdWarning(DBG_AREA_CORE) << "Unknown or unsupported fill style in fillPolygon\n";
|
||
|
case FillStyleForegroundColor:
|
||
|
fillPainter.fillRect(boundingRectangle, paintColor(), OPACITY_OPAQUE);
|
||
|
break;
|
||
|
case FillStyleBackgroundColor:
|
||
|
fillPainter.fillRect(boundingRectangle, backgroundColor(), OPACITY_OPAQUE);
|
||
|
break;
|
||
|
case FillStylePattern:
|
||
|
Q_ASSERT(m_pattern != 0);
|
||
|
fillPainter.fillRect(boundingRectangle, m_pattern);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
KisSelectionSP polygonMask = new KisSelection(polygon);
|
||
|
|
||
|
for (y=y0; y<=y1; y++) { /* step through scanlines */
|
||
|
/* scanline y is at y+.5 in continuous coordinates */
|
||
|
|
||
|
/* check vertices between previous scanline and current one, if any */
|
||
|
for (; k<n && pt[ind[k]].y()<=y+.5; k++) {
|
||
|
/* to simplify, if pt.y=y+.5, pretend it's above */
|
||
|
/* invariant: y-.5 < pt[i].y <= y+.5 */
|
||
|
i = ind[k];
|
||
|
/*
|
||
|
* insert or delete edges before and after vertex i (i-1 to i,
|
||
|
* and i to i+1) from active list if they cross scanline y
|
||
|
*/
|
||
|
j = i>0 ? i-1 : n-1; /* vertex previous to i */
|
||
|
if (pt[j].y() <= y-.5) /* old edge, remove from active list */
|
||
|
cdelete(j);
|
||
|
else if (pt[j].y() > y+.5) /* new edge, add to active list */
|
||
|
cinsert(j, y);
|
||
|
j = i<n-1 ? i+1 : 0; /* vertex next after i */
|
||
|
if (pt[j].y() <= y-.5) /* old edge, remove from active list */
|
||
|
cdelete(i);
|
||
|
else if (pt[j].y() > y+.5) /* new edge, add to active list */
|
||
|
cinsert(i, y);
|
||
|
}
|
||
|
|
||
|
/* sort active edge list by active[j].x */
|
||
|
qsort(active, nact, sizeof active[0], compare_active);
|
||
|
|
||
|
/* draw horizontal segments for scanline y */
|
||
|
for (j=0; j<nact; j+=2) { /* draw horizontal segments */
|
||
|
/* span 'tween j & j+1 is inside, span tween j+1 & j+2 is outside */
|
||
|
xl = static_cast<int>(ceil(active[j].x-.5)); /* left end of span */
|
||
|
xr = static_cast<int>(floor(active[j+1].x-.5)); /* right end of span */
|
||
|
|
||
|
if (xl<=xr) {
|
||
|
KisHLineIterator it = polygonMask->createHLineIterator(xl, y, xr - xl + 1, true);
|
||
|
|
||
|
while (!it.isDone()) {
|
||
|
// We're using a selection here, that means alpha colorspace, that means one byte.
|
||
|
it.rawData()[0] = MAX_SELECTED;
|
||
|
++it;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
active[j].x += active[j].dx; /* increment edge coords */
|
||
|
active[j+1].x += active[j+1].dx;
|
||
|
}
|
||
|
}
|
||
|
delete [] ind;
|
||
|
delete [] active;
|
||
|
|
||
|
polygon->applySelectionMask(polygonMask);
|
||
|
|
||
|
TQRect r = polygon->extent();
|
||
|
|
||
|
// The strokes for the outline may have already added updated the dirtyrect, but it can't hurt,
|
||
|
// and if we're painting without outlines, then there will be no dirty rect. Let's do it ourselves...
|
||
|
// addDirtyRect( r ); // XXX the bltSelection will add to the dirtyrect
|
||
|
|
||
|
bltSelection(r.x(), r.y(), compositeOp(), polygon, opacity(), r.x(), r.y(), r.width(), r.height());
|
||
|
}
|
||
|
|
||
|
void KisPainter::paintPolygon(const vKisPoint& points)
|
||
|
{
|
||
|
if (m_fillStyle != FillStyleNone) {
|
||
|
fillPolygon(points, m_fillStyle);
|
||
|
}
|
||
|
|
||
|
if (m_strokeStyle != StrokeStyleNone) {
|
||
|
if (points.count() > 1) {
|
||
|
double distance = -1;
|
||
|
|
||
|
for (uint i = 0; i < points.count() - 1; i++) {
|
||
|
distance = paintLine(points[i], PRESSURE_DEFAULT, 0, 0, points[i + 1], PRESSURE_DEFAULT, 0, 0, distance);
|
||
|
}
|
||
|
paintLine(points[points.count() - 1], PRESSURE_DEFAULT, 0, 0, points[0], PRESSURE_DEFAULT, 0, 0, distance);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|