KisSpacingInformation KisLiquifyPaintop::paintAt(const KisPaintInformation &pi) { static const qreal sizeToSigmaCoeff = 1.0 / 3.0; const qreal size = sizeToSigmaCoeff * (m_d->props.sizeHasPressure() ? pi.pressure() * m_d->props.size(): m_d->props.size()); const qreal spacing = m_d->props.spacing() * size; const qreal reverseCoeff = m_d->props.mode() != KisLiquifyProperties::UNDO && m_d->props.reverseDirection() ? -1.0 : 1.0; const qreal amount = m_d->props.amountHasPressure() ? pi.pressure() * reverseCoeff * m_d->props.amount(): reverseCoeff * m_d->props.amount(); const bool useWashMode = m_d->props.useWashMode(); const qreal flow = m_d->props.flow(); switch (m_d->props.mode()) { case KisLiquifyProperties::MOVE: { const qreal offsetLength = size * amount; m_d->worker->translatePoints(pi.pos(), pi.drawingDirectionVector() * offsetLength, size, useWashMode, flow); break; } case KisLiquifyProperties::SCALE: m_d->worker->scalePoints(pi.pos(), amount, size, useWashMode, flow); break; case KisLiquifyProperties::ROTATE: m_d->worker->rotatePoints(pi.pos(), 2.0 * M_PI * amount, size, useWashMode, flow); break; case KisLiquifyProperties::OFFSET: { const qreal offsetLength = size * amount; m_d->worker->translatePoints(pi.pos(), KisAlgebra2D::rightUnitNormal(pi.drawingDirectionVector()) * offsetLength, size, useWashMode, flow); break; } case KisLiquifyProperties::UNDO: m_d->worker->undoPoints(pi.pos(), amount, size); break; case KisLiquifyProperties::N_MODES: qFatal("Not supported mode"); } return KisSpacingInformation(spacing); }
void KisBrushOp::paintAt(const KisPaintInformation& info) { if (!painter()->device()) return; KisBrushSP brush = m_brush; Q_ASSERT(brush); if (!brush) return; KisPaintInformation adjustedInfo = settings->m_optionsWidget->m_sizeOption->apply(info); if (!brush->canPaintFor(adjustedInfo)) return; KisPaintDeviceSP device = painter()->device(); double pScale = KisPaintOp::scaleForPressure(adjustedInfo.pressure()); // TODO: why is there scale and pScale that seems to contains the same things ? QPointF hotSpot = brush->hotSpot(pScale, pScale); QPointF pt = info.pos() - hotSpot; // Split the coordinates into integer plus fractional parts. The integer // is where the dab will be positioned and the fractional part determines // the sub-pixel positioning. qint32 x; double xFraction; qint32 y; double yFraction; splitCoordinate(pt.x(), &x, &xFraction); splitCoordinate(pt.y(), &y, &yFraction); quint8 origOpacity = settings->m_optionsWidget->m_opacityOption->apply(painter(), info.pressure()); KoColor origColor = settings->m_optionsWidget->m_darkenOption->apply(painter(), info.pressure()); double scale = KisPaintOp::scaleForPressure(adjustedInfo.pressure()); KisFixedPaintDeviceSP dab = cachedDab(device->colorSpace()); if (brush->brushType() == IMAGE || brush->brushType() == PIPE_IMAGE) { dab = brush->image(device->colorSpace(), scale, 0.0, adjustedInfo, xFraction, yFraction); } else { KoColor color = painter()->paintColor(); color.convertTo(dab->colorSpace()); brush->mask(dab, color, scale, scale, 0.0, info, xFraction, yFraction); } painter()->bltFixed(QPoint(x, y), dab, dab->bounds()); painter()->setOpacity(origOpacity); painter()->setPaintColor(origColor); }
void KisFilterOp::paintAt(const KisPaintInformation& info) { if (!painter()) { return; } KisFilterSP filter = settings->filter(); if (!filter) { return; } if (!source()) { return; } KisBrushSP brush = m_brush;; if (!brush) return; KisPaintInformation adjustedInfo = settings->m_optionsWidget->m_sizeOption->apply(info); if (! brush->canPaintFor(adjustedInfo)) return; double pScale = KisPaintOp::scaleForPressure(adjustedInfo.pressure()); // TODO: why is there scale and pScale that seems to contains the same things ? QPointF hotSpot = brush->hotSpot(pScale, pScale); QPointF pt = info.pos() - hotSpot; // Split the coordinates into integer plus fractional parts. The integer // is where the dab will be positioned and the fractional part determines // the sub-pixel positioning. qint32 x; double xFraction; qint32 y; double yFraction; splitCoordinate(pt.x(), &x, &xFraction); splitCoordinate(pt.y(), &y, &yFraction); double scale = KisPaintOp::scaleForPressure(adjustedInfo.pressure()); qint32 maskWidth = brush->maskWidth(scale, 0.0); qint32 maskHeight = brush->maskHeight(scale, 0.0); // Filter the paint device filter->process(KisConstProcessingInformation(source(), QPoint(x, y)), KisProcessingInformation(m_tmpDevice, QPoint(0, 0)), QSize(maskWidth, maskHeight), settings->filterConfig(), 0); // Apply the mask on the paint device (filter before mask because edge pixels may be important) KisFixedPaintDeviceSP fixedDab = new KisFixedPaintDevice(m_tmpDevice->colorSpace()); fixedDab->setRect(m_tmpDevice->extent()); fixedDab->initialize(); m_tmpDevice->readBytes(fixedDab->data(), fixedDab->bounds()); brush->mask(fixedDab, scale, scale, 0.0, info, xFraction, yFraction); m_tmpDevice->writeBytes(fixedDab->data(), fixedDab->bounds()); if (!settings->ignoreAlpha()) { KisHLineIteratorPixel itTmpDev = m_tmpDevice->createHLineIterator(0, 0, maskWidth); KisHLineIteratorPixel itSrc = source()->createHLineIterator(x, y, maskWidth); const KoColorSpace* cs = m_tmpDevice->colorSpace(); for (int y = 0; y < maskHeight; ++y) { while (!itTmpDev.isDone()) { quint8 alphaTmpDev = cs->alpha(itTmpDev.rawData()); quint8 alphaSrc = cs->alpha(itSrc.rawData()); cs->setAlpha(itTmpDev.rawData(), qMin(alphaTmpDev, alphaSrc), 1); ++itTmpDev; ++itSrc; } itTmpDev.nextRow(); itSrc.nextRow(); } } // Blit the paint device onto the layer QRect dabRect = QRect(0, 0, maskWidth, maskHeight); QRect dstRect = QRect(x, y, dabRect.width(), dabRect.height()); if (painter()->bounds().isValid()) { dstRect &= painter()->bounds(); } if (dstRect.isNull() || dstRect.isEmpty() || !dstRect.isValid()) return; qint32 sx = dstRect.x() - x; qint32 sy = dstRect.y() - y; qint32 sw = dstRect.width(); qint32 sh = dstRect.height(); painter()->bitBlt(dstRect.x(), dstRect.y(), m_tmpDevice, sx, sy, sw, sh); }
void SprayBrush::paint(KisPaintDeviceSP dab, KisPaintDeviceSP source, const KisPaintInformation& info, qreal rotation, qreal scale, const KoColor &color, const KoColor &bgColor) { // initializing painter if (!m_painter) { m_painter = new KisPainter(dab); m_painter->setFillStyle(KisPainter::FillStyleForegroundColor); m_painter->setMaskImageSize(m_shapeProperties->width, m_shapeProperties->height); m_dabPixelSize = dab->colorSpace()->pixelSize(); if (m_colorProperties->useRandomHSV) { m_transfo = dab->colorSpace()->createColorTransformation("hsv_adjustment", QHash<QString, QVariant>()); } m_brushQImage = m_shapeProperties->image; if (!m_brushQImage.isNull()) { m_brushQImage = m_brushQImage.scaled(m_shapeProperties->width, m_shapeProperties->height); } m_imageDevice = new KisPaintDevice(dab->colorSpace()); } qreal x = info.pos().x(); qreal y = info.pos().y(); KisRandomAccessorSP accessor = dab->createRandomAccessorNG(qRound(x), qRound(y)); Q_ASSERT(color.colorSpace()->pixelSize() == dab->pixelSize()); m_inkColor = color; KisCrossDeviceColorPicker colorPicker(source, m_inkColor); // apply size sensor m_radius = m_properties->radius * scale; // jitter movement if (m_properties->jitterMovement) { x = x + ((2 * m_radius * drand48()) - m_radius) * m_properties->amount; y = y + ((2 * m_radius * drand48()) - m_radius) * m_properties->amount; } // this is wrong for every shape except pixel and anti-aliased pixel if (m_properties->useDensity) { m_particlesCount = (m_properties->coverage * (M_PI * m_radius * m_radius)); } else { m_particlesCount = m_properties->particleCount; } QHash<QString, QVariant> params; qreal nx, ny; int ix, iy; qreal angle; qreal length; qreal rotationZ = 0.0; qreal particleScale = 1.0; bool shouldColor = true; if (m_colorProperties->fillBackground) { m_painter->setPaintColor(bgColor); paintCircle(m_painter, x, y, m_radius); } QTransform m; m.reset(); m.rotateRadians(-rotation + deg2rad(m_properties->brushRotation)); m.scale(m_properties->scale, m_properties->scale); for (quint32 i = 0; i < m_particlesCount; i++) { // generate random angle angle = drand48() * M_PI * 2; // generate random length if (m_properties->gaussian) { length = qBound<qreal>(0.0, m_rand->nextGaussian(0.0, 0.50) , 1.0); } else { length = drand48(); } if (m_shapeDynamicsProperties->enabled) { // rotation rotationZ = rotationAngle(); if (m_shapeDynamicsProperties->followCursor) { rotationZ = linearInterpolation(rotationZ, angle, m_shapeDynamicsProperties->followCursorWeigth); } if (m_shapeDynamicsProperties->followDrawingAngle) { rotationZ = linearInterpolation(rotationZ, info.drawingAngle(), m_shapeDynamicsProperties->followDrawingAngleWeight); } // random size - scale if (m_shapeDynamicsProperties->randomSize) { particleScale = drand48(); } } // generate polar coordinate nx = (m_radius * cos(angle) * length); ny = (m_radius * sin(angle) * length); // compute the height of the ellipse ny *= m_properties->aspect; // transform m.map(nx, ny, &nx, &ny); // color transformation if (shouldColor) { if (m_colorProperties->sampleInputColor) { colorPicker.pickOldColor(nx + x, ny + y, m_inkColor.data()); } // mix the color with background color if (m_colorProperties->mixBgColor) { KoMixColorsOp * mixOp = dab->colorSpace()->mixColorsOp(); const quint8 *colors[2]; colors[0] = m_inkColor.data(); colors[1] = bgColor.data(); qint16 colorWeights[2]; int MAX_16BIT = 255; qreal blend = info.pressure(); colorWeights[0] = static_cast<quint16>(blend * MAX_16BIT); colorWeights[1] = static_cast<quint16>((1.0 - blend) * MAX_16BIT); mixOp->mixColors(colors, colorWeights, 2, m_inkColor.data()); } if (m_colorProperties->useRandomHSV && m_transfo) { params["h"] = (m_colorProperties->hue / 180.0) * drand48(); params["s"] = (m_colorProperties->saturation / 100.0) * drand48(); params["v"] = (m_colorProperties->value / 100.0) * drand48(); m_transfo->setParameters(params); m_transfo->transform(m_inkColor.data(), m_inkColor.data() , 1); } if (m_colorProperties->useRandomOpacity) { quint8 alpha = qRound(drand48() * OPACITY_OPAQUE_U8); m_inkColor.setOpacity(alpha); m_painter->setOpacity(alpha); } if (!m_colorProperties->colorPerParticle) { shouldColor = false; } m_painter->setPaintColor(m_inkColor); } qreal jitteredWidth = qMax(qreal(1.0), m_shapeProperties->width * particleScale); qreal jitteredHeight = qMax(qreal(1.0), m_shapeProperties->height * particleScale); if (m_shapeProperties->enabled){ switch (m_shapeProperties->shape){ // ellipse case 0: { if (m_shapeProperties->width == m_shapeProperties->height){ paintCircle(m_painter, nx + x, ny + y, qRound(jitteredWidth * 0.5)); } else { paintEllipse(m_painter, nx + x, ny + y, qRound(jitteredWidth * 0.5) , qRound(jitteredHeight * 0.5), rotationZ); } break; } // rectangle case 1: { paintRectangle(m_painter, nx + x, ny + y, qRound(jitteredWidth) , qRound(jitteredHeight), rotationZ); break; } // wu-particle case 2: { paintParticle(accessor, m_inkColor, nx + x, ny + y); break; } // pixel case 3: { ix = qRound(nx + x); iy = qRound(ny + y); accessor->moveTo(ix, iy); memcpy(accessor->rawData(), m_inkColor.data(), m_dabPixelSize); break; } case 4: { if (!m_brushQImage.isNull()) { QTransform m; m.rotate(rad2deg(rotationZ)); if (m_shapeDynamicsProperties->randomSize) { m.scale(particleScale, particleScale); } m_transformed = m_brushQImage.transformed(m, Qt::SmoothTransformation); m_imageDevice->convertFromQImage(m_transformed, 0); KisRandomAccessorSP ac = m_imageDevice->createRandomAccessorNG(0, 0); QRect rc = m_transformed.rect(); if (m_colorProperties->useRandomHSV && m_transfo) { for (int y = rc.y(); y < rc.y() + rc.height(); y++) { for (int x = rc.x(); x < rc.x() + rc.width(); x++) { ac->moveTo(x, y); m_transfo->transform(ac->rawData(), ac->rawData() , 1); } } } ix = qRound(nx + x - rc.width() * 0.5); iy = qRound(ny + y - rc.height() * 0.5); m_painter->bitBlt(QPoint(ix, iy), m_imageDevice, rc); m_imageDevice->clear(); break; } } } // Auto-brush } else { QPointF hotSpot = m_brush->hotSpot(particleScale, particleScale, -rotationZ, info); QPointF pos(nx + x, ny + y); QPointF pt = pos - hotSpot; qint32 ix; qreal xFraction; qint32 iy; qreal yFraction; KisPaintOp::splitCoordinate(pt.x(), &ix, &xFraction); KisPaintOp::splitCoordinate(pt.y(), &iy, &yFraction); //KisFixedPaintDeviceSP dab; if (m_brush->brushType() == IMAGE || m_brush->brushType() == PIPE_IMAGE) { m_fixedDab = m_brush->paintDevice(m_fixedDab->colorSpace(), particleScale, -rotationZ, info, xFraction, yFraction); if (m_colorProperties->useRandomHSV && m_transfo) { quint8 * dabPointer = m_fixedDab->data(); int pixelCount = m_fixedDab->bounds().width() * m_fixedDab->bounds().height(); m_transfo->transform(dabPointer, dabPointer, pixelCount); } } else { m_brush->mask(m_fixedDab, m_inkColor, particleScale, particleScale, -rotationZ, info, xFraction, yFraction); } m_painter->bltFixed(QPoint(ix, iy), m_fixedDab, m_fixedDab->bounds()); } } // recover from jittering of color, // m_inkColor.opacity is recovered with every paint }
void KisDuplicateOp::paintAt(const KisPaintInformation& info) { if (!painter()) return; if (!m_duplicateStartIsSet) { m_duplicateStartIsSet = true; m_duplicateStart = info.pos(); } bool heal = settings->healing(); if (!source()) return; KisBrushSP brush = m_brush; if (!brush) return; if (! brush->canPaintFor(info)) return; double scale = KisPaintOp::scaleForPressure(info.pressure()); QPointF hotSpot = brush->hotSpot(scale, scale); QPointF pt = info.pos() - hotSpot; // Split the coordinates into integer plus fractional parts. The integer // is where the dab will be positioned and the fractional part determines // the sub-pixel positioning. qint32 x; double xFraction; qint32 y; double yFraction; splitCoordinate(pt.x(), &x, &xFraction); splitCoordinate(pt.y(), &y, &yFraction); xFraction = yFraction = 0.0; QPointF srcPointF = pt - settings->offset(); QPoint srcPoint = QPoint(x - static_cast<qint32>(settings->offset().x()), y - static_cast<qint32>(settings->offset().y())); qint32 sw = brush->maskWidth(scale, 0.0); qint32 sh = brush->maskHeight(scale, 0.0); if (srcPoint.x() < 0) srcPoint.setX(0); if (srcPoint.y() < 0) srcPoint.setY(0); if (!(m_srcdev && !(*m_srcdev->colorSpace() == *source()->colorSpace()))) { m_srcdev = new KisPaintDevice(source()->colorSpace()); } Q_CHECK_PTR(m_srcdev); // Perspective correction ? KisPainter copyPainter(m_srcdev); KisImageSP image = settings->m_image; if (settings->perspectiveCorrection() && image && image->perspectiveGrid()->countSubGrids() == 1) { Matrix3qreal startM = Matrix3qreal::Identity(); Matrix3qreal endM = Matrix3qreal::Identity(); // First look for the grid corresponding to the start point KisSubPerspectiveGrid* subGridStart = *image->perspectiveGrid()->begin(); QRect r = QRect(0, 0, image->width(), image->height()); #if 1 if (subGridStart) { startM = KisPerspectiveMath::computeMatrixTransfoFromPerspective(r, *subGridStart->topLeft(), *subGridStart->topRight(), *subGridStart->bottomLeft(), *subGridStart->bottomRight()); } #endif #if 1 // Second look for the grid corresponding to the end point KisSubPerspectiveGrid* subGridEnd = *image->perspectiveGrid()->begin(); if (subGridEnd) { endM = KisPerspectiveMath::computeMatrixTransfoToPerspective(*subGridEnd->topLeft(), *subGridEnd->topRight(), *subGridEnd->bottomLeft(), *subGridEnd->bottomRight(), r); } #endif // Compute the translation in the perspective transformation space: QPointF positionStartPaintingT = KisPerspectiveMath::matProd(endM, QPointF(m_duplicateStart)); QPointF duplicateStartPositionT = KisPerspectiveMath::matProd(endM, QPointF(m_duplicateStart) - QPointF(settings->offset())); QPointF translat = duplicateStartPositionT - positionStartPaintingT; KisRectIteratorPixel dstIt = m_srcdev->createRectIterator(0, 0, sw, sh); KisRandomSubAccessorPixel srcAcc = source()->createRandomSubAccessor(); //Action while (!dstIt.isDone()) { if (dstIt.isSelected()) { QPointF p = KisPerspectiveMath::matProd(startM, KisPerspectiveMath::matProd(endM, QPointF(dstIt.x() + x, dstIt.y() + y)) + translat); srcAcc.moveTo(p); srcAcc.sampledOldRawData(dstIt.rawData()); } ++dstIt; } } else { // Or, copy the source data on the temporary device: copyPainter.setCompositeOp(COMPOSITE_COPY); copyPainter.bitBlt(0, 0, source(), srcPoint.x(), srcPoint.y(), sw, sh); copyPainter.end(); } // heal ? if (heal) { quint16 dataDevice[4]; quint16 dataSrcDev[4]; double* matrix = new double[ 3 * sw * sh ]; // First divide const KoColorSpace* deviceCs = source()->colorSpace(); KisHLineConstIteratorPixel deviceIt = source()->createHLineConstIterator(x, y, sw); KisHLineIteratorPixel srcDevIt = m_srcdev->createHLineIterator(0, 0, sw); double* matrixIt = &matrix[0]; for (int j = 0; j < sh; j++) { for (int i = 0; !srcDevIt.isDone(); i++) { deviceCs->toLabA16(deviceIt.rawData(), (quint8*)dataDevice, 1); deviceCs->toLabA16(srcDevIt.rawData(), (quint8*)dataSrcDev, 1); // Division for (int k = 0; k < 3; k++) { matrixIt[k] = dataDevice[k] / (double)qMax((int)dataSrcDev [k], 1); } ++deviceIt; ++srcDevIt; matrixIt += 3; } deviceIt.nextRow(); srcDevIt.nextRow(); } // Minimize energy { int iter = 0; double err; double* solution = new double [ 3 * sw * sh ]; do { err = minimizeEnergy(&matrix[0], &solution[0], sw, sh); memcpy(&matrix[0], &solution[0], sw * sh * 3 * sizeof(double)); iter++; } while (err < 0.00001 && iter < 100); delete [] solution; } // Finaly multiply deviceIt = source()->createHLineIterator(x, y, sw); srcDevIt = m_srcdev->createHLineIterator(0, 0, sw); matrixIt = &matrix[0]; for (int j = 0; j < sh; j++) { for (int i = 0; !srcDevIt.isDone(); i++) { deviceCs->toLabA16(deviceIt.rawData(), (quint8*)dataDevice, 1); deviceCs->toLabA16(srcDevIt.rawData(), (quint8*)dataSrcDev, 1); // Multiplication for (int k = 0; k < 3; k++) { dataSrcDev[k] = (int)CLAMP(matrixIt[k] * qMax((int) dataSrcDev[k], 1), 0, 65535); } deviceCs->fromLabA16((quint8*)dataSrcDev, srcDevIt.rawData(), 1); ++deviceIt; ++srcDevIt; matrixIt += 3; } deviceIt.nextRow(); srcDevIt.nextRow(); } delete [] matrix; } KisFixedPaintDeviceSP fixedDab = new KisFixedPaintDevice(m_srcdev->colorSpace()); fixedDab->setRect(QRect(0, 0, sw, sh)); fixedDab->initialize(); m_srcdev->readBytes(fixedDab->data(), fixedDab->bounds()); brush->mask(fixedDab, scale, scale, 0.0, info, xFraction, yFraction); m_srcdev->writeBytes(fixedDab->data(), fixedDab->bounds()); QRect dabRect = QRect(0, 0, brush->maskWidth(scale, 0.0), brush->maskHeight(scale, 0.0)); QRect dstRect = QRect(x, y, dabRect.width(), dabRect.height()); if (painter()->bounds().isValid()) { dstRect &= painter()->bounds(); } if (dstRect.isNull() || dstRect.isEmpty() || !dstRect.isValid()) return; qint32 sx = dstRect.x() - x; qint32 sy = dstRect.y() - y; sw = dstRect.width(); sh = dstRect.height(); painter()->bitBlt(dstRect.x(), dstRect.y(), m_srcdev, sx, sy, sw, sh); }
void KisSmudgeOp::paintAt(const KisPaintInformation& info) { if (!painter()->device()) return; KisBrushSP brush = m_brush; Q_ASSERT(brush); if (!brush) return; KisPaintInformation adjustedInfo = settings->m_optionsWidget->m_sizeOption->apply(info); if (! brush->canPaintFor(adjustedInfo)) return; KisPaintDeviceSP device = painter()->device(); double pScale = KisPaintOp::scaleForPressure(adjustedInfo.pressure()); QPointF hotSpot = brush->hotSpot(pScale, pScale); QPointF pt = info.pos() - hotSpot; // Split the coordinates into integer plus fractional parts. The integer // is where the dab will be positioned and the fractional part determines // the sub-pixel positioning. qint32 x; double xFraction; qint32 y; double yFraction; splitCoordinate(pt.x(), &x, &xFraction); splitCoordinate(pt.y(), &y, &yFraction); KisFixedPaintDeviceSP dab = 0; double scale = KisPaintOp::scaleForPressure(adjustedInfo.pressure()); QRect dabRect = QRect(0, 0, brush->maskWidth(scale, 0.0), brush->maskHeight(scale, 0.0)); QRect dstRect = QRect(x, y, dabRect.width(), dabRect.height()); if (dstRect.isNull() || dstRect.isEmpty() || !dstRect.isValid()) return; if (brush->brushType() == IMAGE || brush->brushType() == PIPE_IMAGE) { dab = brush->image(device->colorSpace(), pScale, 0.0, adjustedInfo, xFraction, yFraction); dab->convertTo(KoColorSpaceRegistry::instance()->alpha8()); } else { dab = cachedDab(); KoColor color = painter()->paintColor(); dab->convertTo(KoColorSpaceRegistry::instance()->alpha8()); brush->mask(dab, color, scale, pScale, 0.0, info, xFraction, yFraction); } qint32 sw = dab->bounds().width(); qint32 sh = dab->bounds().height(); /* To smudge, one does the following: * at first, initialize a temporary paint device with a copy of the original (dab-sized piece, really). * all other times: reduce the transparency of the temporary paint device so as to let it mix gradually * combine the temp device with the piece the brush currently is 'painting', according to a mix (opacity) note that in the first step, this does the actual copying of the data * this combination is then composited upon the actual image TODO: what happened exactly in 1.6 (and should happen now) when the dab resizes halfway due to pressure? */ int opacity = OPACITY_OPAQUE; if (!m_firstRun) { opacity = settings->m_optionsWidget->m_rateOption->apply( opacity, sw, sh, m_srcdev, info.pressure() ); KisRectIterator it = m_srcdev->createRectIterator(0, 0, sw, sh); KoColorSpace* cs = m_srcdev->colorSpace(); while(!it.isDone()) { cs->setAlpha(it.rawData(), (cs->alpha(it.rawData()) * opacity) / OPACITY_OPAQUE, 1); ++it; } opacity = OPACITY_OPAQUE - opacity; } else { m_firstRun = false; } KisPainter copyPainter(m_srcdev); copyPainter.setOpacity(opacity); copyPainter.bitBlt(0, 0, device, pt.x(), pt.y(), sw, sh); copyPainter.end(); m_target = new KisPaintDevice(device->colorSpace()); // Looks hacky, but we lost bltMask, or the ability to easily convert alpha8 paintdev to selection? KisSelectionSP dabAsSelection = new KisSelection(); copyPainter.begin(dabAsSelection); copyPainter.setOpacity(OPACITY_OPAQUE); copyPainter.setCompositeOp(COMPOSITE_COPY); copyPainter.bltFixed(0, 0, dab, 0, 0, sw, sh); copyPainter.end(); copyPainter.begin(m_target); copyPainter.setCompositeOp(COMPOSITE_OVER); copyPainter.setSelection(dabAsSelection); copyPainter.bitBlt(0, 0, m_srcdev, 0, 0, sw, sh); copyPainter.end(); qint32 sx = dstRect.x() - x; qint32 sy = dstRect.y() - y; sw = dstRect.width(); sh = dstRect.height(); painter()->bitBlt(dstRect.x(), dstRect.y(), m_target, sx, sy, sw, sh); }
void KisToolFreehandHelper::paint(KoPointerEvent *event) { KisPaintInformation info = m_d->infoBuilder->continueStroke(event, elapsedStrokeTime()); info.setCanvasRotation( m_d->canvasRotation ); info.setCanvasHorizontalMirrorState( m_d->canvasMirroredH ); KisUpdateTimeMonitor::instance()->reportMouseMove(info.pos()); /** * Smooth the coordinates out using the history and the * distance. This is a heavily modified version of an algo used in * Gimp and described in https://bugs.kde.org/show_bug.cgi?id=281267 and * http://www24.atwiki.jp/sigetch_2007/pages/17.html. The main * differences are: * * 1) It uses 'distance' instead of 'velocity', since time * measurements are too unstable in realworld environment * * 2) There is no 'Quality' parameter, since the number of samples * is calculated automatically * * 3) 'Tail Aggressiveness' is used for controling the end of the * stroke * * 4) The formila is a little bit different: 'Distance' parameter * stands for $3 \Sigma$ */ if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::WEIGHTED_SMOOTHING && m_d->smoothingOptions->smoothnessDistance() > 0.0) { { // initialize current distance QPointF prevPos; if (!m_d->history.isEmpty()) { const KisPaintInformation &prevPi = m_d->history.last(); prevPos = prevPi.pos(); } else { prevPos = m_d->previousPaintInformation.pos(); } qreal currentDistance = QVector2D(info.pos() - prevPos).length(); m_d->distanceHistory.append(currentDistance); } m_d->history.append(info); qreal x = 0.0; qreal y = 0.0; if (m_d->history.size() > 3) { const qreal sigma = m_d->effectiveSmoothnessDistance() / 3.0; // '3.0' for (3 * sigma) range qreal gaussianWeight = 1 / (sqrt(2 * M_PI) * sigma); qreal gaussianWeight2 = sigma * sigma; qreal distanceSum = 0.0; qreal scaleSum = 0.0; qreal pressure = 0.0; qreal baseRate = 0.0; Q_ASSERT(m_d->history.size() == m_d->distanceHistory.size()); for (int i = m_d->history.size() - 1; i >= 0; i--) { qreal rate = 0.0; const KisPaintInformation nextInfo = m_d->history.at(i); double distance = m_d->distanceHistory.at(i); Q_ASSERT(distance >= 0.0); qreal pressureGrad = 0.0; if (i < m_d->history.size() - 1) { pressureGrad = nextInfo.pressure() - m_d->history.at(i + 1).pressure(); const qreal tailAgressiveness = 40.0 * m_d->smoothingOptions->tailAggressiveness(); if (pressureGrad > 0.0 ) { pressureGrad *= tailAgressiveness * (1.0 - nextInfo.pressure()); distance += pressureGrad * 3.0 * sigma; // (3 * sigma) --- holds > 90% of the region } } if (gaussianWeight2 != 0.0) { distanceSum += distance; rate = gaussianWeight * exp(-distanceSum * distanceSum / (2 * gaussianWeight2)); } if (m_d->history.size() - i == 1) { baseRate = rate; } else if (baseRate / rate > 100) { break; } scaleSum += rate; x += rate * nextInfo.pos().x(); y += rate * nextInfo.pos().y(); if (m_d->smoothingOptions->smoothPressure()) { pressure += rate * nextInfo.pressure(); } } if (scaleSum != 0.0) { x /= scaleSum; y /= scaleSum; if (m_d->smoothingOptions->smoothPressure()) { pressure /= scaleSum; } } if ((x != 0.0 && y != 0.0) || (x == info.pos().x() && y == info.pos().y())) { info.setPos(QPointF(x, y)); if (m_d->smoothingOptions->smoothPressure()) { info.setPressure(pressure); } m_d->history.last() = info; } } } if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::SIMPLE_SMOOTHING || m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::WEIGHTED_SMOOTHING) { // Now paint between the coordinates, using the bezier curve interpolation if (!m_d->haveTangent) { m_d->haveTangent = true; m_d->previousTangent = (info.pos() - m_d->previousPaintInformation.pos()) / qMax(qreal(1.0), info.currentTime() - m_d->previousPaintInformation.currentTime()); } else { QPointF newTangent = (info.pos() - m_d->olderPaintInformation.pos()) / qMax(qreal(1.0), info.currentTime() - m_d->olderPaintInformation.currentTime()); paintBezierSegment(m_d->olderPaintInformation, m_d->previousPaintInformation, m_d->previousTangent, newTangent); m_d->previousTangent = newTangent; } m_d->olderPaintInformation = m_d->previousPaintInformation; m_d->strokeTimeoutTimer.start(100); } else if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::NO_SMOOTHING) { paintLine(m_d->previousPaintInformation, info); } if (m_d->smoothingOptions->smoothingType() == KisSmoothingOptions::STABILIZER) { m_d->stabilizedSampler.addEvent(info); } else { m_d->previousPaintInformation = info; } if(m_d->airbrushingTimer.isActive()) { m_d->airbrushingTimer.start(); } }
void HairyBrush::paintLine(KisPaintDeviceSP dab, KisPaintDeviceSP layer, const KisPaintInformation &pi1, const KisPaintInformation &pi2, qreal scale, qreal rotation) { m_counter++; qreal x1 = pi1.pos().x(); qreal y1 = pi1.pos().y(); qreal x2 = pi2.pos().x(); qreal y2 = pi2.pos().y(); qreal dx = x2 - x1; qreal dy = y2 - y1; // TODO:this angle is different from the drawing angle in sensor (info.angle()). The bug is caused probably due to // not computing the drag vector properly in paintBezierLine when smoothing is used //qreal angle = atan2(dy, dx); qreal angle = rotation; qreal mousePressure = 1.0; if (m_properties->useMousePressure) { // want pressure from mouse movement qreal distance = sqrt(dx * dx + dy * dy); mousePressure = (1.0 - computeMousePressure(distance)); scale *= mousePressure; } // this pressure controls shear and ink depletion qreal pressure = mousePressure * (pi2.pressure() * 2); Bristle *bristle = 0; KoColor bristleColor(dab->colorSpace()); m_dabAccessor = dab->createRandomAccessorNG((int)x1, (int)y1); m_dab = dab; // initialization block if (firstStroke()) { initAndCache(); } // if this is first time the brush touches the canvas and we use soak the ink from canvas if (firstStroke() && m_properties->useSoakInk) { if (layer) { colorifyBristles(layer, pi1.pos()); } else { dbgKrita << "Can't soak the ink from the layer"; } } KisRandomSourceSP randomSource = pi2.randomSource(); qreal fx1, fy1, fx2, fy2; qreal randomX, randomY; qreal shear; float inkDeplation = 0.0; int inkDepletionSize = m_properties->inkDepletionCurve.size(); int bristleCount = m_bristles.size(); int bristlePathSize; qreal treshold = 1.0 - pi2.pressure(); for (int i = 0; i < bristleCount; i++) { if (!m_bristles.at(i)->enabled()) continue; bristle = m_bristles[i]; randomX = (randomSource->generateNormalized() * 2 - 1.0) * m_properties->randomFactor; randomY = (randomSource->generateNormalized() * 2 - 1.0) * m_properties->randomFactor; shear = pressure * m_properties->shearFactor; m_transform.reset(); m_transform.rotateRadians(-angle); m_transform.scale(scale, scale); m_transform.translate(randomX, randomY); m_transform.shear(shear, shear); if (firstStroke() || (!m_properties->connectedPath)) { // transform start dab m_transform.map(bristle->x(), bristle->y(), &fx1, &fy1); // transform end dab m_transform.map(bristle->x(), bristle->y(), &fx2, &fy2); } else { // continue the path of the bristle from the previous position fx1 = bristle->prevX(); fy1 = bristle->prevY(); m_transform.map(bristle->x(), bristle->y(), &fx2, &fy2); } // remember the end point bristle->setPrevX(fx2); bristle->setPrevY(fy2); // all coords relative to device position fx1 += x1; fy1 += y1; fx2 += x2; fy2 += y2; if (m_properties->threshold && (bristle->length() < treshold)) continue; // paint between first and last dab const QVector<QPointF> bristlePath = m_trajectory.getLinearTrajectory(QPointF(fx1, fy1), QPointF(fx2, fy2), 1.0); bristlePathSize = m_trajectory.size(); memcpy(bristleColor.data(), bristle->color().data() , m_pixelSize); for (int i = 0; i < bristlePathSize ; i++) { if (m_properties->inkDepletionEnabled) { inkDeplation = fetchInkDepletion(bristle, inkDepletionSize); if (m_properties->useSaturation && m_transfo != 0) { saturationDepletion(bristle, bristleColor, pressure, inkDeplation); } if (m_properties->useOpacity) { opacityDepletion(bristle, bristleColor, pressure, inkDeplation); } } else { if (bristleColor.opacityU8() != 0) { bristleColor.setOpacity(bristle->length()); } } addBristleInk(bristle, bristlePath.at(i), bristleColor); bristle->setInkAmount(1.0 - inkDeplation); bristle->upIncrement(); } } m_dab = 0; m_dabAccessor = 0; }