diff --git a/libs/flake/text/KoSvgTextChunkShape.cpp b/libs/flake/text/KoSvgTextChunkShape.cpp index 15122653de..cf1ac88ba7 100644 --- a/libs/flake/text/KoSvgTextChunkShape.cpp +++ b/libs/flake/text/KoSvgTextChunkShape.cpp @@ -1,985 +1,985 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * 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 v * 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 "KoSvgTextChunkShape.h" #include "KoSvgTextChunkShape_p.h" #include "KoSvgText.h" #include "KoSvgTextProperties.h" #include "kis_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { void appendLazy(QVector *list, boost::optional value, int iteration, bool hasDefault = true, qreal defaultValue = 0.0) { if (!value) return; if (value && *value == defaultValue && hasDefault == true && list->isEmpty()) return; while (list->size() < iteration) { list->append(defaultValue); } list->append(*value); } void fillTransforms(QVector *xPos, QVector *yPos, QVector *dxPos, QVector *dyPos, QVector *rotate, QVector localTransformations) { for (int i = 0; i < localTransformations.size(); i++) { const KoSvgText::CharTransformation &t = localTransformations[i]; appendLazy(xPos, t.xPos, i, false); appendLazy(yPos, t.yPos, i, false); appendLazy(dxPos, t.dxPos, i); appendLazy(dyPos, t.dyPos, i); appendLazy(rotate, t.rotate, i); } } QVector parseListAttributeX(const QString &value, SvgLoadingContext &context) { QVector result; QStringList list = SvgUtil::simplifyList(value); Q_FOREACH (const QString &str, list) { result << SvgUtil::parseUnitX(context.currentGC(), str); } return result; } QVector parseListAttributeY(const QString &value, SvgLoadingContext &context) { QVector result; QStringList list = SvgUtil::simplifyList(value); Q_FOREACH (const QString &str, list) { result << SvgUtil::parseUnitY(context.currentGC(), str); } return result; } QVector parseListAttributeAngular(const QString &value, SvgLoadingContext &context) { QVector result; QStringList list = SvgUtil::simplifyList(value); Q_FOREACH (const QString &str, list) { result << SvgUtil::parseUnitAngular(context.currentGC(), str); } return result; } QString convertListAttribute(const QVector &values) { QStringList stringValues; Q_FOREACH (qreal value, values) { stringValues.append(KisDomUtils::toString(value)); } return stringValues.join(','); } } struct KoSvgTextChunkShape::Private::LayoutInterface : public KoSvgTextChunkShapeLayoutInterface { LayoutInterface(KoSvgTextChunkShape *_q) : q(_q) {} KoSvgText::AutoValue textLength() const override { return q->d->textLength; } KoSvgText::LengthAdjust lengthAdjust() const override { return q->d->lengthAdjust; } int numChars() const override { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->d->text.isEmpty(), 0); int result = 0; if (!q->shapeCount()) { result = q->d->text.size(); } else { Q_FOREACH (KoShape *shape, q->shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(chunkShape, 0); result += chunkShape->layoutInterface()->numChars(); } } return result; } int relativeCharPos(KoSvgTextChunkShape *child, int pos) const override { QList childShapes = q->shapes(); int result = -1; int numCharsPassed = 0; Q_FOREACH (KoShape *shape, q->shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(chunkShape, 0); if (chunkShape == child) { result = pos + numCharsPassed; break; } else { numCharsPassed += chunkShape->layoutInterface()->numChars(); } } return result; } bool isTextNode() const override { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->d->text.isEmpty(), false); return !q->shapeCount(); } QString nodeText() const override { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!q->shapeCount() || q->d->text.isEmpty(), 0); return !q->shapeCount() ? q->d->text : QString(); } QVector localCharTransformations() const override { KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(isTextNode(), QVector()); const QVector t = q->d->localTransformations; return t.mid(0, qMin(t.size(), q->d->text.size())); } static QString getBidiOpening(KoSvgText::Direction direction, KoSvgText::UnicodeBidi bidi) { using namespace KoSvgText; QString result; if (bidi == BidiEmbed) { result = direction == DirectionLeftToRight ? "\u202a" : "\u202b"; } else if (bidi == BidiOverride) { result = direction == DirectionLeftToRight ? "\u202d" : "\u202e"; } return result; } QVector collectSubChunks() const override { QVector result; if (isTextNode()) { const QString text = q->d->text; const KoSvgText::KoSvgCharChunkFormat format = q->fetchCharFormat(); QVector transforms = q->d->localTransformations; /** * Sometimes SVG can contain the X,Y offsets for the pieces of text that * do not exist, just skip them. */ if (text.size() <= transforms.size()) { transforms.resize(text.size()); } KoSvgText::UnicodeBidi bidi = KoSvgText::UnicodeBidi(q->d->properties.propertyOrDefault(KoSvgTextProperties::UnicodeBidiId).toInt()); KoSvgText::Direction direction = KoSvgText::Direction(q->d->properties.propertyOrDefault(KoSvgTextProperties::DirectionId).toInt()); const QString bidiOpening = getBidiOpening(direction, bidi); if (!bidiOpening.isEmpty()) { result << SubChunk(bidiOpening, format); } if (transforms.isEmpty()) { result << SubChunk(text, format); } else { for (int i = 0; i < transforms.size(); i++) { const KoSvgText::CharTransformation baseTransform = transforms[i]; int subChunkLength = 1; for (int j = i + 1; j < transforms.size(); j++) { if (transforms[j].isNull()) { subChunkLength++; } else { break; } } if (i + subChunkLength >= transforms.size()) { subChunkLength = text.size() - i; } result << SubChunk(text.mid(i, subChunkLength), format, baseTransform); i += subChunkLength - 1; } } if (!bidiOpening.isEmpty()) { result << SubChunk("\u202c", format); } } else { Q_FOREACH (KoShape *shape, q->shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_BREAK(chunkShape); result += chunkShape->layoutInterface()->collectSubChunks(); } } return result; } void addAssociatedOutline(const QRectF &rect) override { KIS_SAFE_ASSERT_RECOVER_RETURN(isTextNode()); QPainterPath path; path.addRect(rect); path |= q->d->associatedOutline; path.setFillRule(Qt::WindingFill); path = path.simplified(); q->d->associatedOutline = path; q->setSize(path.boundingRect().size()); q->notifyChanged(); q->shapeChangedPriv(KoShape::SizeChanged); } void clearAssociatedOutline() override { q->d->associatedOutline = QPainterPath(); q->setSize(QSizeF()); q->notifyChanged(); q->shapeChangedPriv(KoShape::SizeChanged); } private: KoSvgTextChunkShape *q; }; KoSvgTextChunkShape::KoSvgTextChunkShape() : KoShapeContainer() , d(new Private) { d->layoutInterface.reset(new KoSvgTextChunkShape::Private::LayoutInterface(this)); } KoSvgTextChunkShape::KoSvgTextChunkShape(const KoSvgTextChunkShape &rhs) : KoShapeContainer(rhs) , d(rhs.d) { if (rhs.model()) { SimpleShapeContainerModel *otherModel = dynamic_cast(rhs.model()); KIS_ASSERT_RECOVER_RETURN(otherModel); setModelInit(new SimpleShapeContainerModel(*otherModel)); } // XXX: this will immediately lead to a detach d->layoutInterface.reset(new KoSvgTextChunkShape::Private::LayoutInterface(this)); } KoSvgTextChunkShape::~KoSvgTextChunkShape() { } KoShape *KoSvgTextChunkShape::cloneShape() const { return new KoSvgTextChunkShape(*this); } QSizeF KoSvgTextChunkShape::size() const { return outlineRect().size(); } void KoSvgTextChunkShape::setSize(const QSizeF &size) { Q_UNUSED(size); // we do not support resizing! } QRectF KoSvgTextChunkShape::outlineRect() const { return outline().boundingRect(); } QPainterPath KoSvgTextChunkShape::outline() const { QPainterPath result; result.setFillRule(Qt::WindingFill); if (d->layoutInterface->isTextNode()) { result = d->associatedOutline; } else { Q_FOREACH (KoShape *shape, shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_BREAK(chunkShape); result |= chunkShape->outline(); } } return result.simplified(); } void KoSvgTextChunkShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { Q_UNUSED(painter); Q_UNUSED(converter); Q_UNUSED(paintContext); } void KoSvgTextChunkShape::saveOdf(KoShapeSavingContext &context) const { Q_UNUSED(context); } bool KoSvgTextChunkShape::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_UNUSED(element); Q_UNUSED(context); return false; } bool KoSvgTextChunkShape::saveHtml(HtmlSavingContext &context) { // Should we add a newline? Check for vertical movement if we're using rtl or ltr text // XXX: if vertical text, check horizontal movement. QVector xPos; QVector yPos; QVector dxPos; QVector dyPos; QVector rotate; fillTransforms(&xPos, &yPos, &dxPos, &dyPos, &rotate, d->localTransformations); for (int i = 0; i < d->localTransformations.size(); i++) { const KoSvgText::CharTransformation &t = d->localTransformations[i]; appendLazy(&xPos, t.xPos, i, false); appendLazy(&yPos, t.yPos, i, false); appendLazy(&dxPos, t.dxPos, i); appendLazy(&dyPos, t.dyPos, i); } KoSvgTextChunkShape *parent = !isRootTextNode() ? dynamic_cast(this->parent()) : 0; KoSvgTextProperties parentProperties = parent ? parent->textProperties() : KoSvgTextProperties::defaultProperties(); // XXX: we don't save fill, stroke, text length, length adjust or spacing and glyphs. KoSvgTextProperties ownProperties = textProperties().ownProperties(parentProperties); if (isRootTextNode()) { context.shapeWriter().startElement("body", false); if (layoutInterface()->isTextNode()) { context.shapeWriter().startElement("p", false); } // XXX: Save the style? } else if (parent->isRootTextNode()) { context.shapeWriter().startElement("p", false); } else { context.shapeWriter().startElement("span", false); // XXX: Save the style? } QMap attributes = ownProperties.convertToSvgTextAttributes(); if (attributes.size() > 0) { QString styleString; for (auto it = attributes.constBegin(); it != attributes.constEnd(); ++it) { if (QString(it.key().toLatin1().data()).contains("text-anchor")) { QString val = it.value(); if (it.value()=="middle") { val = "center"; } else if (it.value()=="end") { val = "right"; } else { val = "left"; } styleString.append("text-align") .append(": ") .append(val) .append(";" ); } else if (QString(it.key().toLatin1().data()).contains("fill")){ styleString.append("color") .append(": ") .append(it.value()) .append(";" ); } else if (QString(it.key().toLatin1().data()).contains("font-size")){ QString val = it.value(); if (QRegExp ("\\d*").exactMatch(val)) { val.append("pt"); } styleString.append(it.key().toLatin1().data()) .append(": ") .append(val) .append(";" ); } else { styleString.append(it.key().toLatin1().data()) .append(": ") .append(it.value()) .append(";" ); } } context.shapeWriter().addAttribute("style", styleString); } if (layoutInterface()->isTextNode()) { debugFlake << "saveHTML" << this << d->text << xPos << yPos << dxPos << dyPos; // After adding all the styling to the

element, add the text context.shapeWriter().addTextNode(d->text); } else { Q_FOREACH (KoShape *child, this->shapes()) { KoSvgTextChunkShape *childText = dynamic_cast(child); KIS_SAFE_ASSERT_RECOVER(childText) { continue; } childText->saveHtml(context); } } if (isRootTextNode() && layoutInterface()->isTextNode()) { context.shapeWriter().endElement(); // body } context.shapeWriter().endElement(); // p or span return true; } void writeTextListAttribute(const QString &attribute, const QVector &values, KoXmlWriter &writer) { const QString value = convertListAttribute(values); if (!value.isEmpty()) { writer.addAttribute(attribute.toLatin1().data(), value); } } bool KoSvgTextChunkShape::saveSvg(SvgSavingContext &context) { if (isRootTextNode()) { context.shapeWriter().startElement("text", false); if (!context.strippedTextMode()) { context.shapeWriter().addAttribute("id", context.getID(this)); context.shapeWriter().addAttribute("krita:useRichText", d->isRichTextPreferred ? "true" : "false"); SvgUtil::writeTransformAttributeLazy("transform", transformation(), context.shapeWriter()); SvgStyleWriter::saveSvgStyle(this, context); } else { SvgStyleWriter::saveSvgFill(this, context); SvgStyleWriter::saveSvgStroke(this, context); } } else { context.shapeWriter().startElement("tspan", false); if (!context.strippedTextMode()) { SvgStyleWriter::saveSvgBasicStyle(this, context); } } if (layoutInterface()->isTextNode()) { QVector xPos; QVector yPos; QVector dxPos; QVector dyPos; QVector rotate; fillTransforms(&xPos, &yPos, &dxPos, &dyPos, &rotate, d->localTransformations); writeTextListAttribute("x", xPos, context.shapeWriter()); writeTextListAttribute("y", yPos, context.shapeWriter()); writeTextListAttribute("dx", dxPos, context.shapeWriter()); writeTextListAttribute("dy", dyPos, context.shapeWriter()); writeTextListAttribute("rotate", rotate, context.shapeWriter()); } if (!d->textLength.isAuto) { context.shapeWriter().addAttribute("textLength", KisDomUtils::toString(d->textLength.customValue)); if (d->lengthAdjust == KoSvgText::LengthAdjustSpacingAndGlyphs) { context.shapeWriter().addAttribute("lengthAdjust", "spacingAndGlyphs"); } } KoSvgTextChunkShape *parent = !isRootTextNode() ? dynamic_cast(this->parent()) : 0; KoSvgTextProperties parentProperties = parent ? parent->textProperties() : KoSvgTextProperties::defaultProperties(); KoSvgTextProperties ownProperties = textProperties().ownProperties(parentProperties); // we write down stroke/fill iff they are different from the parent's value if (!isRootTextNode()) { if (ownProperties.hasProperty(KoSvgTextProperties::FillId)) { SvgStyleWriter::saveSvgFill(this, context); } if (ownProperties.hasProperty(KoSvgTextProperties::StrokeId)) { SvgStyleWriter::saveSvgStroke(this, context); } } QMap attributes = ownProperties.convertToSvgTextAttributes(); for (auto it = attributes.constBegin(); it != attributes.constEnd(); ++it) { context.shapeWriter().addAttribute(it.key().toLatin1().data(), it.value()); } if (layoutInterface()->isTextNode()) { context.shapeWriter().addTextNode(d->text); } else { Q_FOREACH (KoShape *child, this->shapes()) { KoSvgTextChunkShape *childText = dynamic_cast(child); KIS_SAFE_ASSERT_RECOVER(childText) { continue; } childText->saveSvg(context); } } context.shapeWriter().endElement(); return true; } void KoSvgTextChunkShape::Private::loadContextBasedProperties(SvgGraphicsContext *gc) { properties = gc->textProperties; font = gc->font; fontFamiliesList = gc->fontFamiliesList; } void KoSvgTextChunkShape::resetTextShape() { using namespace KoSvgText; d->properties = KoSvgTextProperties(); d->font = QFont(); d->fontFamiliesList = QStringList(); d->textLength = AutoValue(); d->lengthAdjust = LengthAdjustSpacing; d->localTransformations.clear(); d->text.clear(); // all the subchunks are destroyed! // (first detach, then destroy) QList shapesToReset = shapes(); Q_FOREACH (KoShape *shape, shapesToReset) { shape->setParent(0); delete shape; } } bool KoSvgTextChunkShape::loadSvg(const KoXmlElement &e, SvgLoadingContext &context) { SvgGraphicsContext *gc = context.currentGC(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(gc, false); d->loadContextBasedProperties(gc); d->textLength = KoSvgText::parseAutoValueXY(e.attribute("textLength", ""), context, ""); d->lengthAdjust = KoSvgText::parseLengthAdjust(e.attribute("lengthAdjust", "spacing")); QVector xPos = parseListAttributeX(e.attribute("x", ""), context); QVector yPos = parseListAttributeY(e.attribute("y", ""), context); QVector dxPos = parseListAttributeX(e.attribute("dx", ""), context); QVector dyPos = parseListAttributeY(e.attribute("dy", ""), context); QVector rotate = parseListAttributeAngular(e.attribute("rotate", ""), context); const int numLocalTransformations = std::max({xPos.size(), yPos.size(), dxPos.size(), dyPos.size(), rotate.size()}); d->localTransformations.resize(numLocalTransformations); for (int i = 0; i < numLocalTransformations; i++) { if (i < xPos.size()) { d->localTransformations[i].xPos = xPos[i]; } if (i < yPos.size()) { d->localTransformations[i].yPos = yPos[i]; } if (i < dxPos.size() && dxPos[i] != 0.0) { d->localTransformations[i].dxPos = dxPos[i]; } if (i < dyPos.size() && dyPos[i] != 0.0) { d->localTransformations[i].dyPos = dyPos[i]; } if (i < rotate.size()) { d->localTransformations[i].rotate = rotate[i]; } } return true; } namespace { QString cleanUpString(QString text) { - text.replace(QRegExp("[\\r\\n]"), ""); + text.replace(QRegExp("[\\r\\n\u2028]"), ""); text.replace(QRegExp(" {2,}"), " "); return text; } enum Result { FoundNothing, FoundText, FoundSpace }; Result hasPreviousSibling(KoXmlNode node) { while (!node.isNull()) { if (node.isElement()) { KoXmlElement element = node.toElement(); if (element.tagName() == "text") break; } while (!node.previousSibling().isNull()) { node = node.previousSibling(); while (!node.lastChild().isNull()) { node = node.lastChild(); } if (node.isText()) { KoXmlText textNode = node.toText(); const QString text = cleanUpString(textNode.data()); if (!text.isEmpty()) { // if we are the leading whitespace, we should report that // we are the last if (text == " ") { return hasPreviousSibling(node) == FoundNothing ? FoundNothing : FoundSpace; } return text[text.size() - 1] != ' ' ? FoundText : FoundSpace; } } } node = node.parentNode(); } return FoundNothing; } Result hasNextSibling(KoXmlNode node) { while (!node.isNull()) { while (!node.nextSibling().isNull()) { node = node.nextSibling(); while (!node.firstChild().isNull()) { node = node.firstChild(); } if (node.isText()) { KoXmlText textNode = node.toText(); const QString text = cleanUpString(textNode.data()); // if we are the trailing whitespace, we should report that // we are the last if (text == " ") { return hasNextSibling(node) == FoundNothing ? FoundNothing : FoundSpace; } if (!text.isEmpty()) { return text[0] != ' ' ? FoundText : FoundSpace; } } } node = node.parentNode(); } return FoundNothing; } } bool KoSvgTextChunkShape::loadSvgTextNode(const KoXmlText &text, SvgLoadingContext &context) { SvgGraphicsContext *gc = context.currentGC(); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(gc, false); d->loadContextBasedProperties(gc); QString data = cleanUpString(text.data()); const Result leftBorder = hasPreviousSibling(text); const Result rightBorder = hasNextSibling(text); if (data.startsWith(' ') && leftBorder == FoundNothing) { data.remove(0, 1); } if (data.endsWith(' ') && rightBorder != FoundText) { data.remove(data.size() - 1, 1); } if (data == " " && (leftBorder == FoundNothing || rightBorder == FoundNothing)) { data = ""; } //ENTER_FUNCTION() << text.data() << "-->" << data; d->text = data; return !data.isEmpty(); } void KoSvgTextChunkShape::normalizeCharTransformations() { applyParentCharTransformations(d->localTransformations); } void KoSvgTextChunkShape::simplifyFillStrokeInheritance() { if (!isRootTextNode()) { KoShape *parentShape = parent(); KIS_SAFE_ASSERT_RECOVER_RETURN(parentShape); QSharedPointer bg = background(); QSharedPointer parentBg = parentShape->background(); if (!inheritBackground() && ((!bg && !parentBg) || (bg && parentBg && bg->compareTo(parentShape->background().data())))) { setInheritBackground(true); } KoShapeStrokeModelSP stroke = this->stroke(); KoShapeStrokeModelSP parentStroke= parentShape->stroke(); if (!inheritStroke() && ((!stroke && !parentStroke) || (stroke && parentStroke && stroke->compareFillTo(parentShape->stroke().data()) && stroke->compareStyleTo(parentShape->stroke().data())))) { setInheritStroke(true); } } Q_FOREACH (KoShape *shape, shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(chunkShape); chunkShape->simplifyFillStrokeInheritance(); } } KoSvgTextProperties KoSvgTextChunkShape::textProperties() const { KoSvgTextProperties properties = d->properties; properties.setProperty(KoSvgTextProperties::FillId, QVariant::fromValue(KoSvgText::BackgroundProperty(background()))); properties.setProperty(KoSvgTextProperties::StrokeId, QVariant::fromValue(KoSvgText::StrokeProperty(stroke()))); return properties; } bool KoSvgTextChunkShape::isTextNode() const { return d->layoutInterface->isTextNode(); } KoSvgTextChunkShapeLayoutInterface *KoSvgTextChunkShape::layoutInterface() { return d->layoutInterface.data(); } bool KoSvgTextChunkShape::isRichTextPreferred() const { return isRootTextNode() && d->isRichTextPreferred; } void KoSvgTextChunkShape::setRichTextPreferred(bool value) { KIS_SAFE_ASSERT_RECOVER_RETURN(isRootTextNode()); d->isRichTextPreferred = value; } bool KoSvgTextChunkShape::isRootTextNode() const { return false; } /**************************************************************************************************/ /* KoSvgTextChunkShape::Private */ /**************************************************************************************************/ #include "SimpleShapeContainerModel.h" KoSvgTextChunkShape::Private::Private() : QSharedData() { } KoSvgTextChunkShape::Private::Private(const Private &rhs) : QSharedData() , properties(rhs.properties) , font(rhs.font) , fontFamiliesList(rhs.fontFamiliesList) , localTransformations(rhs.localTransformations) , textLength(rhs.textLength) , lengthAdjust(rhs.lengthAdjust) , text(rhs.text) , isRichTextPreferred(rhs.isRichTextPreferred) { } KoSvgTextChunkShape::Private::~Private() { } #include #include #include KoSvgText::KoSvgCharChunkFormat KoSvgTextChunkShape::fetchCharFormat() const { KoSvgText::KoSvgCharChunkFormat format; format.setFont(d->font); format.setTextAnchor(KoSvgText::TextAnchor(d->properties.propertyOrDefault(KoSvgTextProperties::TextAnchorId).toInt())); KoSvgText::Direction direction = KoSvgText::Direction(d->properties.propertyOrDefault(KoSvgTextProperties::DirectionId).toInt()); format.setLayoutDirection(direction == KoSvgText::DirectionLeftToRight ? Qt::LeftToRight : Qt::RightToLeft); KoSvgText::BaselineShiftMode shiftMode = KoSvgText::BaselineShiftMode(d->properties.propertyOrDefault(KoSvgTextProperties::BaselineShiftModeId).toInt()); // FIXME: we support only 'none', 'sub' and 'super' shifts at the moment. // Please implement 'percentage' as well! // WARNING!!! Qt's setVerticalAlignment() also changes the size of the font! And SVG does not(!) imply it! if (shiftMode == KoSvgText::ShiftSub) { format.setVerticalAlignment(QTextCharFormat::AlignSubScript); } else if (shiftMode == KoSvgText::ShiftSuper) { format.setVerticalAlignment(QTextCharFormat::AlignSuperScript); } KoSvgText::AutoValue letterSpacing = d->properties.propertyOrDefault(KoSvgTextProperties::LetterSpacingId).value(); if (!letterSpacing.isAuto) { format.setFontLetterSpacingType(QFont::AbsoluteSpacing); format.setFontLetterSpacing(letterSpacing.customValue); } KoSvgText::AutoValue wordSpacing = d->properties.propertyOrDefault(KoSvgTextProperties::WordSpacingId).value(); if (!wordSpacing.isAuto) { format.setFontWordSpacing(wordSpacing.customValue); } KoSvgText::AutoValue kerning = d->properties.propertyOrDefault(KoSvgTextProperties::KerningId).value(); if (!kerning.isAuto) { format.setFontKerning(false); format.setFontLetterSpacingType(QFont::AbsoluteSpacing); format.setFontLetterSpacing(format.fontLetterSpacing() + kerning.customValue); } QBrush textBrush = Qt::NoBrush; if (background()) { KoColorBackground *colorBackground = dynamic_cast(background().data()); if (!colorBackground) { qWarning() << "TODO: support gradient and pattern backgrounds for text"; textBrush = Qt::red; } if (colorBackground) { textBrush = colorBackground->brush(); } } format.setForeground(textBrush); QPen textPen = Qt::NoPen; if (stroke()) { KoShapeStroke *stroke = dynamic_cast(this->stroke().data()); if (stroke) { textPen = stroke->resultLinePen(); } } format.setTextOutline(textPen); // TODO: avoid const_cast somehow... format.setAssociatedShape(const_cast(this)); return format; } void KoSvgTextChunkShape::applyParentCharTransformations(const QVector transformations) { if (shapeCount()) { int numCharsPassed = 0; Q_FOREACH (KoShape *shape, shapes()) { KoSvgTextChunkShape *chunkShape = dynamic_cast(shape); KIS_SAFE_ASSERT_RECOVER_RETURN(chunkShape); const int numCharsInSubtree = chunkShape->layoutInterface()->numChars(); QVector t = transformations.mid(numCharsPassed, numCharsInSubtree); if (t.isEmpty()) break; chunkShape->applyParentCharTransformations(t); numCharsPassed += numCharsInSubtree; if (numCharsPassed >= transformations.size()) break; } } else { for (int i = 0; i < qMin(transformations.size(), d->text.size()); i++) { KIS_SAFE_ASSERT_RECOVER_RETURN(d->localTransformations.size() >= i); if (d->localTransformations.size() == i) { d->localTransformations.append(transformations[i]); } else { d->localTransformations[i].mergeInParentTransformation(transformations[i]); } } } } diff --git a/libs/flake/text/KoSvgTextShape.cpp b/libs/flake/text/KoSvgTextShape.cpp index 5f52abd6e2..b6b086afa7 100644 --- a/libs/flake/text/KoSvgTextShape.cpp +++ b/libs/flake/text/KoSvgTextShape.cpp @@ -1,638 +1,649 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * 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 "KoSvgTextShape.h" #include #include #include "KoSvgText.h" #include "KoSvgTextProperties.h" #include #include #include #include #include #include "kis_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class KoSvgTextShape::Private : public QSharedData { public: Private() : QSharedData() { } Private(const Private &) : QSharedData() { } std::vector> cachedLayouts; std::vector cachedLayoutsOffsets; QThread *cachedLayoutsWorkingThread = 0; void clearAssociatedOutlines(KoShape *rootShape); }; KoSvgTextShape::KoSvgTextShape() : KoSvgTextChunkShape() , d(new Private) { setShapeId(KoSvgTextShape_SHAPEID); } KoSvgTextShape::KoSvgTextShape(const KoSvgTextShape &rhs) : KoSvgTextChunkShape(rhs) , d(rhs.d) { setShapeId(KoSvgTextShape_SHAPEID); // QTextLayout has no copy-ctor, so just relayout everything! relayout(); } KoSvgTextShape::~KoSvgTextShape() { } KoShape *KoSvgTextShape::cloneShape() const { return new KoSvgTextShape(*this); } void KoSvgTextShape::shapeChanged(ChangeType type, KoShape *shape) { KoSvgTextChunkShape::shapeChanged(type, shape); if (type == StrokeChanged || type == BackgroundChanged || type == ContentChanged) { relayout(); } } void KoSvgTextShape::paintComponent(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { Q_UNUSED(paintContext); /** * HACK ALERT: * QTextLayout should only be accessed from the thread it has been created in. * If the cached layout has been created in a different thread, we should just * recreate the layouts in the current thread to be able to render them. */ if (QThread::currentThread() != d->cachedLayoutsWorkingThread) { relayout(); } applyConversion(painter, converter); for (int i = 0; i < (int)d->cachedLayouts.size(); i++) { d->cachedLayouts[i]->draw(&painter, d->cachedLayoutsOffsets[i]); } /** * HACK ALERT: * The layouts of non-gui threads must be destroyed in the same thread * they have been created. Because the thread might be restarted in the * meantime or just destroyed, meaning that the per-thread freetype data * will not be available. */ if (QThread::currentThread() != qApp->thread()) { d->cachedLayouts.clear(); d->cachedLayoutsOffsets.clear(); d->cachedLayoutsWorkingThread = 0; } } void KoSvgTextShape::paintStroke(QPainter &painter, const KoViewConverter &converter, KoShapePaintingContext &paintContext) { Q_UNUSED(painter); Q_UNUSED(converter); Q_UNUSED(paintContext); // do nothing! everything is painted in paintComponent() } QPainterPath KoSvgTextShape::textOutline() { QPainterPath result; result.setFillRule(Qt::WindingFill); for (int i = 0; i < (int)d->cachedLayouts.size(); i++) { const QPointF layoutOffset = d->cachedLayoutsOffsets[i]; const QTextLayout *layout = d->cachedLayouts[i].get(); for (int j = 0; j < layout->lineCount(); j++) { QTextLine line = layout->lineAt(j); Q_FOREACH (const QGlyphRun &run, line.glyphRuns()) { const QVector indexes = run.glyphIndexes(); const QVector positions = run.positions(); const QRawFont font = run.rawFont(); KIS_SAFE_ASSERT_RECOVER(indexes.size() == positions.size()) { continue; } for (int k = 0; k < indexes.size(); k++) { QPainterPath glyph = font.pathForGlyph(indexes[k]); glyph.translate(positions[k] + layoutOffset); result += glyph; } const qreal thickness = font.lineThickness(); const QRectF runBounds = run.boundingRect(); if (run.overline()) { // the offset is calculated to be consistent with the way how Qt renders the text const qreal y = line.y(); QRectF overlineBlob(runBounds.x(), y, runBounds.width(), thickness); overlineBlob.translate(layoutOffset); QPainterPath path; path.addRect(overlineBlob); // don't use direct addRect, because it doesn't care about Qt::WindingFill result += path; } if (run.strikeOut()) { // the offset is calculated to be consistent with the way how Qt renders the text const qreal y = line.y() + 0.5 * line.height(); QRectF strikeThroughBlob(runBounds.x(), y, runBounds.width(), thickness); strikeThroughBlob.translate(layoutOffset); QPainterPath path; path.addRect(strikeThroughBlob); // don't use direct addRect, because it doesn't care about Qt::WindingFill result += path; } if (run.underline()) { const qreal y = line.y() + line.ascent() + font.underlinePosition(); QRectF underlineBlob(runBounds.x(), y, runBounds.width(), thickness); underlineBlob.translate(layoutOffset); QPainterPath path; path.addRect(underlineBlob); // don't use direct addRect, because it doesn't care about Qt::WindingFill result += path; } } } } return result; } void KoSvgTextShape::resetTextShape() { KoSvgTextChunkShape::resetTextShape(); relayout(); } struct TextChunk { QString text; QVector formats; Qt::LayoutDirection direction = Qt::LeftToRight; Qt::Alignment alignment = Qt::AlignLeading; struct SubChunkOffset { QPointF offset; int start = 0; }; QVector offsets; boost::optional xStartPos; boost::optional yStartPos; QPointF applyStartPosOverride(const QPointF &pos) const { QPointF result = pos; if (xStartPos) { result.rx() = *xStartPos; } if (yStartPos) { result.ry() = *yStartPos; } return result; } }; QVector mergeIntoChunks(const QVector &subChunks) { QVector chunks; for (auto it = subChunks.begin(); it != subChunks.end(); ++it) { if (it->transformation.startsNewChunk() || it == subChunks.begin()) { TextChunk newChunk = TextChunk(); newChunk.direction = it->format.layoutDirection(); newChunk.alignment = it->format.calculateAlignment(); newChunk.xStartPos = it->transformation.xPos; newChunk.yStartPos = it->transformation.yPos; chunks.append(newChunk); } TextChunk ¤tChunk = chunks.last(); if (it->transformation.hasRelativeOffset()) { TextChunk::SubChunkOffset o; o.start = currentChunk.text.size(); o.offset = it->transformation.relativeOffset(); KIS_SAFE_ASSERT_RECOVER_NOOP(!o.offset.isNull()); currentChunk.offsets.append(o); } QTextLayout::FormatRange formatRange; formatRange.start = currentChunk.text.size(); formatRange.length = it->text.size(); formatRange.format = it->format; currentChunk.formats.append(formatRange); currentChunk.text += it->text; } return chunks; } /** * Qt's QTextLayout has a weird trait, it doesn't count space characters as * distinct characters in QTextLayout::setNumColumns(), that is, if we want to * position a block of text that starts with a space character in a specific * position, QTextLayout will drop this space and will move the text to the left. * * That is why we have a special wrapper object that ensures that no spaces are * dropped and their horizontal advance parameter is taken into account. */ struct LayoutChunkWrapper { LayoutChunkWrapper(QTextLayout *layout) : m_layout(layout) { } QPointF addTextChunk(int startPos, int length, const QPointF &textChunkStartPos) { QPointF currentTextPos = textChunkStartPos; const int lastPos = startPos + length - 1; KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(startPos == m_addedChars, currentTextPos); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(lastPos < m_layout->text().size(), currentTextPos); +// qDebug() << m_layout->text(); + QTextLine line; std::swap(line, m_danglingLine); if (!line.isValid()) { line = m_layout->createLine(); } // skip all the space characters that were not included into the Qt's text line const int currentLineStart = line.isValid() ? line.textStart() : startPos + length; while (startPos < currentLineStart && startPos <= lastPos) { currentTextPos.rx() += skipSpaceCharacter(startPos); startPos++; } if (startPos <= lastPos) { // defines the number of columns to look for glyphs const int numChars = lastPos - startPos + 1; // Tabs break the normal column flow // grow to avoid missing glyphs int charOffset = 0; + int noChangeCount = 0; while (line.textLength() < numChars) { + int tl = line.textLength(); line.setNumColumns(numChars + charOffset); + if (tl == line.textLength()) { + noChangeCount++; + // 5 columns max are needed to discover tab char. Set to 10 to be safe. + if (noChangeCount > 10) break; + } else { + noChangeCount = 0; + } charOffset++; } line.setPosition(currentTextPos - QPointF(0, line.ascent())); currentTextPos.rx() += line.horizontalAdvance(); // skip all the space characters that were not included into the Qt's text line for (int i = line.textStart() + line.textLength(); i < lastPos; i++) { currentTextPos.rx() += skipSpaceCharacter(i); } } else { // keep the created but unused line for future use std::swap(line, m_danglingLine); } m_addedChars += length; return currentTextPos; } private: qreal skipSpaceCharacter(int pos) { const QTextCharFormat format = formatForPos(pos, m_layout->formats()); const QChar skippedChar = m_layout->text()[pos]; KIS_SAFE_ASSERT_RECOVER_NOOP(skippedChar.isSpace() || !skippedChar.isPrint()); QFontMetrics metrics(format.font()); #if QT_VERSION >= 0x051100 return metrics.horizontalAdvance(skippedChar); #else return metrics.width(skippedChar); #endif } static QTextCharFormat formatForPos(int pos, const QVector &formats) { Q_FOREACH (const QTextLayout::FormatRange &range, formats) { if (pos >= range.start && pos < range.start + range.length) { return range.format; } } KIS_SAFE_ASSERT_RECOVER_NOOP(0 && "pos should be within the bounds of the layouted text"); return QTextCharFormat(); } private: int m_addedChars = 0; QTextLayout *m_layout; QTextLine m_danglingLine; }; void KoSvgTextShape::relayout() { d->cachedLayouts.clear(); d->cachedLayoutsOffsets.clear(); d->cachedLayoutsWorkingThread = QThread::currentThread(); QPointF currentTextPos; QVector textChunks = mergeIntoChunks(layoutInterface()->collectSubChunks()); Q_FOREACH (const TextChunk &chunk, textChunks) { std::unique_ptr layout(new QTextLayout()); QTextOption option; // WARNING: never activate this option! It breaks the RTL text layout! //option.setFlags(QTextOption::ShowTabsAndSpaces); option.setWrapMode(QTextOption::WrapAnywhere); option.setUseDesignMetrics(true); // TODO: investigate if it is needed? option.setTextDirection(chunk.direction); layout->setText(chunk.text); layout->setTextOption(option); layout->setFormats(chunk.formats); layout->setCacheEnabled(true); layout->beginLayout(); currentTextPos = chunk.applyStartPosOverride(currentTextPos); const QPointF anchorPointPos = currentTextPos; int lastSubChunkStart = 0; QPointF lastSubChunkOffset; LayoutChunkWrapper wrapper(layout.get()); for (int i = 0; i <= chunk.offsets.size(); i++) { const bool isFinalPass = i == chunk.offsets.size(); const int length = !isFinalPass ? chunk.offsets[i].start - lastSubChunkStart : chunk.text.size() - lastSubChunkStart; if (length > 0) { currentTextPos += lastSubChunkOffset; currentTextPos = wrapper.addTextChunk(lastSubChunkStart, length, currentTextPos); } if (!isFinalPass) { lastSubChunkOffset = chunk.offsets[i].offset; lastSubChunkStart = chunk.offsets[i].start; } } layout->endLayout(); QPointF diff; if (chunk.alignment & Qt::AlignTrailing || chunk.alignment & Qt::AlignHCenter) { if (chunk.alignment & Qt::AlignTrailing) { diff = currentTextPos - anchorPointPos; } else if (chunk.alignment & Qt::AlignHCenter) { diff = 0.5 * (currentTextPos - anchorPointPos); } // TODO: fix after t2b text implemented diff.ry() = 0; } d->cachedLayouts.push_back(std::move(layout)); d->cachedLayoutsOffsets.push_back(-diff); } d->clearAssociatedOutlines(this); for (int i = 0; i < int(d->cachedLayouts.size()); i++) { const QTextLayout &layout = *d->cachedLayouts[i]; const QPointF layoutOffset = d->cachedLayoutsOffsets[i]; using namespace KoSvgText; Q_FOREACH (const QTextLayout::FormatRange &range, layout.formats()) { const KoSvgCharChunkFormat &format = static_cast(range.format); AssociatedShapeWrapper wrapper = format.associatedShapeWrapper(); const int rangeStart = range.start; const int safeRangeLength = range.length > 0 ? range.length : layout.text().size() - rangeStart; if (safeRangeLength <= 0) continue; const int rangeEnd = range.start + safeRangeLength - 1; const int firstLineIndex = layout.lineForTextPosition(rangeStart).lineNumber(); const int lastLineIndex = layout.lineForTextPosition(rangeEnd).lineNumber(); for (int i = firstLineIndex; i <= lastLineIndex; i++) { const QTextLine line = layout.lineAt(i); // It might happen that the range contains only one (or two) // symbol that is a whitespace symbol. In such a case we should // just skip this (invalid) line. if (!line.isValid()) continue; const int posStart = qMax(line.textStart(), rangeStart); const int posEnd = qMin(line.textStart() + line.textLength() - 1, rangeEnd); const QList glyphRuns = line.glyphRuns(posStart, posEnd - posStart + 1); Q_FOREACH (const QGlyphRun &run, glyphRuns) { const QPointF firstPosition = run.positions().first(); const quint32 firstGlyphIndex = run.glyphIndexes().first(); const QPointF lastPosition = run.positions().last(); const quint32 lastGlyphIndex = run.glyphIndexes().last(); const QRawFont rawFont = run.rawFont(); const QRectF firstGlyphRect = rawFont.boundingRect(firstGlyphIndex).translated(firstPosition); const QRectF lastGlyphRect = rawFont.boundingRect(lastGlyphIndex).translated(lastPosition); QRectF rect = run.boundingRect(); /** * HACK ALERT: there is a bug in a way how Qt calculates boundingRect() * of the glyph run. It doesn't care about left and right bearings * of the border chars in the run, therefore it becomes cropped. * * Here we just add a half-char width margin to both sides * of the glyph run to make sure the glyphs are fully painted. * * BUG: 389528 * BUG: 392068 */ rect.setLeft(qMin(rect.left(), lastGlyphRect.left()) - 0.5 * firstGlyphRect.width()); rect.setRight(qMax(rect.right(), lastGlyphRect.right()) + 0.5 * lastGlyphRect.width()); wrapper.addCharacterRect(rect.translated(layoutOffset)); } } } } } void KoSvgTextShape::Private::clearAssociatedOutlines(KoShape *rootShape) { KoSvgTextChunkShape *chunkShape = dynamic_cast(rootShape); KIS_SAFE_ASSERT_RECOVER_RETURN(chunkShape); chunkShape->layoutInterface()->clearAssociatedOutline(); Q_FOREACH (KoShape *child, chunkShape->shapes()) { clearAssociatedOutlines(child); } } bool KoSvgTextShape::isRootTextNode() const { return true; } KoSvgTextShapeFactory::KoSvgTextShapeFactory() : KoShapeFactoryBase(KoSvgTextShape_SHAPEID, i18n("Text")) { setToolTip(i18n("SVG Text Shape")); setIconName(koIconNameCStr("x-shape-text")); setLoadingPriority(5); setXmlElementNames(KoXmlNS::svg, QStringList("text")); KoShapeTemplate t; t.name = i18n("SVG Text"); t.iconName = koIconName("x-shape-text"); t.toolTip = i18n("SVG Text Shape"); addTemplate(t); } KoShape *KoSvgTextShapeFactory::createDefaultShape(KoDocumentResourceManager *documentResources) const { debugFlake << "Create default svg text shape"; KoSvgTextShape *shape = new KoSvgTextShape(); shape->setShapeId(KoSvgTextShape_SHAPEID); KoSvgTextShapeMarkupConverter converter(shape); converter.convertFromSvg("Lorem ipsum dolor sit amet, consectetur adipiscing elit.", "", QRectF(0, 0, 200, 60), documentResources->documentResolution()); debugFlake << converter.errors() << converter.warnings(); return shape; } KoShape *KoSvgTextShapeFactory::createShape(const KoProperties *params, KoDocumentResourceManager *documentResources) const { KoSvgTextShape *shape = new KoSvgTextShape(); shape->setShapeId(KoSvgTextShape_SHAPEID); QString svgText = params->stringProperty("svgText", "Lorem ipsum dolor sit amet, consectetur adipiscing elit."); QString defs = params->stringProperty("defs" , ""); QRectF shapeRect = QRectF(0, 0, 200, 60); QVariant rect = params->property("shapeRect"); if (rect.type()==QVariant::RectF) { shapeRect = rect.toRectF(); } KoSvgTextShapeMarkupConverter converter(shape); converter.convertFromSvg(svgText, defs, shapeRect, documentResources->documentResolution()); shape->setPosition(shapeRect.topLeft()); return shape; } bool KoSvgTextShapeFactory::supports(const KoXmlElement &/*e*/, KoShapeLoadingContext &/*context*/) const { return false; } diff --git a/libs/image/floodfill/kis_scanline_fill.cpp b/libs/image/floodfill/kis_scanline_fill.cpp index 750d827c09..26ce86a28f 100644 --- a/libs/image/floodfill/kis_scanline_fill.cpp +++ b/libs/image/floodfill/kis_scanline_fill.cpp @@ -1,732 +1,732 @@ /* * Copyright (c) 2014 Dmitry Kazakov * * 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 "kis_scanline_fill.h" #include #include #include #include #include #include "kis_image.h" #include "kis_fill_interval_map.h" #include "kis_pixel_selection.h" #include "kis_random_accessor_ng.h" #include "kis_fill_sanity_checks.h" template class CopyToSelection : public BaseClass { public: typedef KisRandomConstAccessorSP SourceAccessorType; SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) { return device->createRandomConstAccessorNG(0, 0); } public: void setDestinationSelection(KisPaintDeviceSP pixelSelection) { m_pixelSelection = pixelSelection; m_it = m_pixelSelection->createRandomAccessorNG(0,0); } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(dstPtr); m_it->moveTo(x, y); *m_it->rawData() = opacity; } private: KisPaintDeviceSP m_pixelSelection; KisRandomAccessorSP m_it; }; template class FillWithColor : public BaseClass { public: typedef KisRandomAccessorSP SourceAccessorType; SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) { return device->createRandomAccessorNG(0, 0); } public: FillWithColor() : m_pixelSize(0) {} void setFillColor(const KoColor &sourceColor) { m_sourceColor = sourceColor; m_pixelSize = sourceColor.colorSpace()->pixelSize(); m_data = m_sourceColor.data(); } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(x); Q_UNUSED(y); if (opacity == MAX_SELECTED) { memcpy(dstPtr, m_data, m_pixelSize); } } private: KoColor m_sourceColor; const quint8 *m_data; int m_pixelSize; }; template class FillWithColorExternal : public BaseClass { public: typedef KisRandomConstAccessorSP SourceAccessorType; SourceAccessorType createSourceDeviceAccessor(KisPaintDeviceSP device) { return device->createRandomConstAccessorNG(0, 0); } public: void setDestinationDevice(KisPaintDeviceSP device) { m_externalDevice = device; m_it = m_externalDevice->createRandomAccessorNG(0,0); } void setFillColor(const KoColor &sourceColor) { m_sourceColor = sourceColor; m_pixelSize = sourceColor.colorSpace()->pixelSize(); m_data = m_sourceColor.data(); } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(dstPtr); m_it->moveTo(x, y); if (opacity == MAX_SELECTED) { memcpy(m_it->rawData(), m_data, m_pixelSize); } } private: KisPaintDeviceSP m_externalDevice; KisRandomAccessorSP m_it; KoColor m_sourceColor; - const quint8 *m_data; + const quint8 *m_data {0}; int m_pixelSize; }; class DifferencePolicySlow { public: ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold) { m_colorSpace = device->colorSpace(); m_srcPixel = srcPixel; m_srcPixelPtr = m_srcPixel.data(); m_threshold = threshold; } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { if (m_threshold == 1) { if (memcmp(m_srcPixelPtr, pixelPtr, m_colorSpace->pixelSize()) == 0) { return 0; } return quint8_MAX; } else { return m_colorSpace->difference(m_srcPixelPtr, pixelPtr); } } private: const KoColorSpace *m_colorSpace; KoColor m_srcPixel; const quint8 *m_srcPixelPtr; int m_threshold; }; template class DifferencePolicyOptimized { typedef SrcPixelType HashKeyType; typedef QHash HashType; public: ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold) { m_colorSpace = device->colorSpace(); m_srcPixel = srcPixel; m_srcPixelPtr = m_srcPixel.data(); m_threshold = threshold; } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { HashKeyType key = *reinterpret_cast(pixelPtr); quint8 result; typename HashType::iterator it = m_differences.find(key); if (it != m_differences.end()) { result = *it; } else { if (m_threshold == 1) { if (memcmp(m_srcPixelPtr, pixelPtr, m_colorSpace->pixelSize()) == 0) { result = 0; } else { result = quint8_MAX; } } else { result = m_colorSpace->difference(m_srcPixelPtr, pixelPtr); } m_differences.insert(key, result); } return result; } private: HashType m_differences; const KoColorSpace *m_colorSpace; KoColor m_srcPixel; const quint8 *m_srcPixelPtr; int m_threshold; }; template class PixelFiller> class SelectionPolicy : public PixelFiller { public: typename PixelFiller::SourceAccessorType m_srcIt; public: SelectionPolicy(KisPaintDeviceSP device, const KoColor &srcPixel, int threshold) : m_threshold(threshold) { this->initDifferences(device, srcPixel, threshold); m_srcIt = this->createSourceDeviceAccessor(device); } ALWAYS_INLINE quint8 calculateOpacity(quint8* pixelPtr) { quint8 diff = this->calculateDifference(pixelPtr); if (!useSmoothSelection) { return diff <= m_threshold ? MAX_SELECTED : MIN_SELECTED; } else { quint8 selectionValue = qMax(0, m_threshold - diff); quint8 result = MIN_SELECTED; if (selectionValue > 0) { qreal selectionNorm = qreal(selectionValue) / m_threshold; result = MAX_SELECTED * selectionNorm; } return result; } } private: int m_threshold; }; class IsNonNullPolicySlow { public: ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int /*threshold*/) { Q_UNUSED(srcPixel); m_pixelSize = device->pixelSize(); m_testPixel.resize(m_pixelSize); } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { if (memcmp(m_testPixel.data(), pixelPtr, m_pixelSize) == 0) { return 0; } return quint8_MAX; } private: int m_pixelSize; QByteArray m_testPixel; }; template class IsNonNullPolicyOptimized { public: ALWAYS_INLINE void initDifferences(KisPaintDeviceSP device, const KoColor &srcPixel, int /*threshold*/) { Q_UNUSED(device); Q_UNUSED(srcPixel); } ALWAYS_INLINE quint8 calculateDifference(quint8* pixelPtr) { SrcPixelType *pixel = reinterpret_cast(pixelPtr); return *pixel == 0; } }; class GroupSplitPolicy { public: typedef KisRandomAccessorSP SourceAccessorType; SourceAccessorType m_srcIt; public: GroupSplitPolicy(KisPaintDeviceSP scribbleDevice, KisPaintDeviceSP groupMapDevice, qint32 groupIndex, quint8 referenceValue, int threshold) : m_threshold(threshold), m_groupIndex(groupIndex), m_referenceValue(referenceValue) { KIS_SAFE_ASSERT_RECOVER_NOOP(m_groupIndex > 0); m_srcIt = scribbleDevice->createRandomAccessorNG(0,0); m_groupMapIt = groupMapDevice->createRandomAccessorNG(0,0); } ALWAYS_INLINE quint8 calculateOpacity(quint8* pixelPtr) { // TODO: either threshold should always be null, or there should be a special // case for *pixelPtr == 0, which is different from all the other groups, // whatever the threshold is int diff = qAbs(int(*pixelPtr) - m_referenceValue); return diff <= m_threshold ? MAX_SELECTED : MIN_SELECTED; } ALWAYS_INLINE void fillPixel(quint8 *dstPtr, quint8 opacity, int x, int y) { Q_UNUSED(opacity); // erase the scribble *dstPtr = 0; // write group index into the map m_groupMapIt->moveTo(x, y); qint32 *groupMapPtr = reinterpret_cast(m_groupMapIt->rawData()); if (*groupMapPtr != 0) { dbgImage << ppVar(*groupMapPtr) << ppVar(m_groupIndex); } KIS_SAFE_ASSERT_RECOVER_NOOP(*groupMapPtr == 0); *groupMapPtr = m_groupIndex; } private: int m_threshold; qint32 m_groupIndex; quint8 m_referenceValue; KisRandomAccessorSP m_groupMapIt; }; struct Q_DECL_HIDDEN KisScanlineFill::Private { KisPaintDeviceSP device; KisRandomAccessorSP it; QPoint startPoint; QRect boundingRect; int threshold; int rowIncrement; KisFillIntervalMap backwardMap; QStack forwardStack; inline void swapDirection() { rowIncrement *= -1; KIS_SAFE_ASSERT_RECOVER_NOOP(forwardStack.isEmpty() && "FATAL: the forward stack must be empty " "on a direction swap"); forwardStack = QStack(backwardMap.fetchAllIntervals(rowIncrement)); backwardMap.clear(); } }; KisScanlineFill::KisScanlineFill(KisPaintDeviceSP device, const QPoint &startPoint, const QRect &boundingRect) : m_d(new Private) { m_d->device = device; m_d->it = device->createRandomAccessorNG(startPoint.x(), startPoint.y()); m_d->startPoint = startPoint; m_d->boundingRect = boundingRect; m_d->rowIncrement = 1; m_d->threshold = 0; } KisScanlineFill::~KisScanlineFill() { } void KisScanlineFill::setThreshold(int threshold) { m_d->threshold = threshold; } template void KisScanlineFill::extendedPass(KisFillInterval *currentInterval, int srcRow, bool extendRight, T &pixelPolicy) { int x; int endX; int columnIncrement; int *intervalBorder; int *backwardIntervalBorder; KisFillInterval backwardInterval(currentInterval->start, currentInterval->end, srcRow); if (extendRight) { x = currentInterval->end; endX = m_d->boundingRect.right(); if (x >= endX) return; columnIncrement = 1; intervalBorder = ¤tInterval->end; backwardInterval.start = currentInterval->end + 1; backwardIntervalBorder = &backwardInterval.end; } else { x = currentInterval->start; endX = m_d->boundingRect.left(); if (x <= endX) return; columnIncrement = -1; intervalBorder = ¤tInterval->start; backwardInterval.end = currentInterval->start - 1; backwardIntervalBorder = &backwardInterval.start; } do { x += columnIncrement; pixelPolicy.m_srcIt->moveTo(x, srcRow); quint8 *pixelPtr = const_cast(pixelPolicy.m_srcIt->rawDataConst()); // TODO: avoid doing const_cast quint8 opacity = pixelPolicy.calculateOpacity(pixelPtr); if (opacity) { *intervalBorder = x; *backwardIntervalBorder = x; pixelPolicy.fillPixel(pixelPtr, opacity, x, srcRow); } else { break; } } while (x != endX); if (backwardInterval.isValid()) { m_d->backwardMap.insertInterval(backwardInterval); } } template void KisScanlineFill::processLine(KisFillInterval interval, const int rowIncrement, T &pixelPolicy) { m_d->backwardMap.cropInterval(&interval); if (!interval.isValid()) return; int firstX = interval.start; int lastX = interval.end; int x = firstX; int row = interval.row; int nextRow = row + rowIncrement; KisFillInterval currentForwardInterval; int numPixelsLeft = 0; quint8 *dataPtr = 0; const int pixelSize = m_d->device->pixelSize(); while(x <= lastX) { // a bit of optimzation for not calling slow random accessor // methods too often if (numPixelsLeft <= 0) { pixelPolicy.m_srcIt->moveTo(x, row); numPixelsLeft = pixelPolicy.m_srcIt->numContiguousColumns(x) - 1; dataPtr = const_cast(pixelPolicy.m_srcIt->rawDataConst()); } else { numPixelsLeft--; dataPtr += pixelSize; } quint8 *pixelPtr = dataPtr; quint8 opacity = pixelPolicy.calculateOpacity(pixelPtr); if (opacity) { if (!currentForwardInterval.isValid()) { currentForwardInterval.start = x; currentForwardInterval.end = x; currentForwardInterval.row = nextRow; } else { currentForwardInterval.end = x; } pixelPolicy.fillPixel(pixelPtr, opacity, x, row); if (x == firstX) { extendedPass(¤tForwardInterval, row, false, pixelPolicy); } if (x == lastX) { extendedPass(¤tForwardInterval, row, true, pixelPolicy); } } else { if (currentForwardInterval.isValid()) { m_d->forwardStack.push(currentForwardInterval); currentForwardInterval.invalidate(); } } x++; } if (currentForwardInterval.isValid()) { m_d->forwardStack.push(currentForwardInterval); } } template void KisScanlineFill::runImpl(T &pixelPolicy) { KIS_ASSERT_RECOVER_RETURN(m_d->forwardStack.isEmpty()); KisFillInterval startInterval(m_d->startPoint.x(), m_d->startPoint.x(), m_d->startPoint.y()); m_d->forwardStack.push(startInterval); /** * In the end of the first pass we should add an interval * containing the starting pixel, but directed into the opposite * direction. We cannot do it in the very beginning because the * intervals are offset by 1 pixel during every swap operation. */ bool firstPass = true; while (!m_d->forwardStack.isEmpty()) { while (!m_d->forwardStack.isEmpty()) { KisFillInterval interval = m_d->forwardStack.pop(); if (interval.row > m_d->boundingRect.bottom() || interval.row < m_d->boundingRect.top()) { continue; } processLine(interval, m_d->rowIncrement, pixelPolicy); } m_d->swapDirection(); if (firstPass) { startInterval.row--; m_d->forwardStack.push(startInterval); firstPass = false; } } } void KisScanlineFill::fillColor(const KoColor &originalFillColor) { KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y()); KoColor srcColor(it->rawDataConst(), m_d->device->colorSpace()); KoColor fillColor(originalFillColor); fillColor.convertTo(m_d->device->colorSpace()); const int pixelSize = m_d->device->pixelSize(); if (pixelSize == 1) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); runImpl(policy); } } void KisScanlineFill::fillColor(const KoColor &originalFillColor, KisPaintDeviceSP externalDevice) { KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y()); KoColor srcColor(it->rawDataConst(), m_d->device->colorSpace()); KoColor fillColor(originalFillColor); fillColor.convertTo(m_d->device->colorSpace()); const int pixelSize = m_d->device->pixelSize(); if (pixelSize == 1) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, FillWithColorExternal> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationDevice(externalDevice); policy.setFillColor(fillColor); runImpl(policy); } } void KisScanlineFill::fillSelection(KisPixelSelectionSP pixelSelection) { KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y()); KoColor srcColor(it->rawDataConst(), m_d->device->colorSpace()); const int pixelSize = m_d->device->pixelSize(); if (pixelSize == 1) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, CopyToSelection> policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setDestinationSelection(pixelSelection); runImpl(policy); } } void KisScanlineFill::clearNonZeroComponent() { const int pixelSize = m_d->device->pixelSize(); KoColor srcColor(Qt::transparent, m_d->device->colorSpace()); if (pixelSize == 1) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else if (pixelSize == 2) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else if (pixelSize == 4) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else if (pixelSize == 8) { SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } else { SelectionPolicy policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(srcColor); runImpl(policy); } } void KisScanlineFill::fillContiguousGroup(KisPaintDeviceSP groupMapDevice, qint32 groupIndex) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_d->device->pixelSize() == 1); KIS_SAFE_ASSERT_RECOVER_RETURN(groupMapDevice->pixelSize() == 4); KisRandomConstAccessorSP it = m_d->device->createRandomConstAccessorNG(m_d->startPoint.x(), m_d->startPoint.y()); const quint8 referenceValue = *it->rawDataConst(); GroupSplitPolicy policy(m_d->device, groupMapDevice, groupIndex, referenceValue, m_d->threshold); runImpl(policy); } void KisScanlineFill::testingProcessLine(const KisFillInterval &processInterval) { KoColor srcColor(QColor(0,0,0,0), m_d->device->colorSpace()); KoColor fillColor(QColor(200,200,200,200), m_d->device->colorSpace()); SelectionPolicy, FillWithColor> policy(m_d->device, srcColor, m_d->threshold); policy.setFillColor(fillColor); processLine(processInterval, 1, policy); } QVector KisScanlineFill::testingGetForwardIntervals() const { return QVector(m_d->forwardStack); } KisFillIntervalMap* KisScanlineFill::testingGetBackwardIntervals() const { return &m_d->backwardMap; } diff --git a/libs/image/kis_scalar_keyframe_channel.cpp b/libs/image/kis_scalar_keyframe_channel.cpp index ba73be65d7..2cc74ea0e0 100644 --- a/libs/image/kis_scalar_keyframe_channel.cpp +++ b/libs/image/kis_scalar_keyframe_channel.cpp @@ -1,487 +1,489 @@ /* * Copyright (c) 2015 Jouni Pentikäinen * * 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 "kis_scalar_keyframe_channel.h" #include "kis_node.h" #include "kundo2command.h" #include "kis_time_range.h" #include #include struct KisScalarKeyframe : public KisKeyframe { KisScalarKeyframe(KisKeyframeChannel *channel, int time, qreal value) : KisKeyframe(channel, time) , value(value) {} KisScalarKeyframe(const KisScalarKeyframe *rhs, KisKeyframeChannel *channel) : KisKeyframe(rhs, channel) , value(rhs->value) {} qreal value; KisKeyframeSP cloneFor(KisKeyframeChannel *channel) const override { return toQShared(new KisScalarKeyframe(this, channel)); } }; KisScalarKeyframeChannel::AddKeyframeCommand::AddKeyframeCommand(KisScalarKeyframeChannel *channel, int time, qreal value, KUndo2Command *parentCommand) : KisReplaceKeyframeCommand(channel, time, channel->createKeyframe(time, value, parentCommand), parentCommand) {} struct KisScalarKeyframeChannel::Private { public: Private(qreal min, qreal max, KisKeyframe::InterpolationMode defaultInterpolation) : minValue(min), maxValue(max), firstFreeIndex(0), defaultInterpolation(defaultInterpolation) {} Private(const Private &rhs) : minValue(rhs.minValue), maxValue(rhs.maxValue), firstFreeIndex(rhs.firstFreeIndex), defaultInterpolation(rhs.defaultInterpolation) {} qreal minValue; qreal maxValue; int firstFreeIndex; KisKeyframe::InterpolationMode defaultInterpolation; struct SetValueCommand; struct SetTangentsCommand; struct SetInterpolationModeCommand; }; KisScalarKeyframeChannel::KisScalarKeyframeChannel(const KoID &id, qreal minValue, qreal maxValue, KisDefaultBoundsBaseSP defaultBounds, KisKeyframe::InterpolationMode defaultInterpolation) : KisKeyframeChannel(id, defaultBounds), m_d(new Private(minValue, maxValue, defaultInterpolation)) { } KisScalarKeyframeChannel::KisScalarKeyframeChannel(const KisScalarKeyframeChannel &rhs, KisNode *newParentNode) : KisKeyframeChannel(rhs, newParentNode), m_d(new Private(*rhs.m_d)) { } KisScalarKeyframeChannel::~KisScalarKeyframeChannel() {} bool KisScalarKeyframeChannel::hasScalarValue() const { return true; } qreal KisScalarKeyframeChannel::minScalarValue() const { return m_d->minValue; } qreal KisScalarKeyframeChannel::maxScalarValue() const { return m_d->maxValue; } qreal KisScalarKeyframeChannel::scalarValue(const KisKeyframeSP keyframe) const { KisScalarKeyframe *key = dynamic_cast(keyframe.data()); Q_ASSERT(key != 0); return key->value; } struct KisScalarKeyframeChannel::Private::SetValueCommand : public KUndo2Command { SetValueCommand(KisScalarKeyframeChannel *channel, KisKeyframeSP keyframe, qreal oldValue, qreal newValue, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_channel(channel), m_keyframe(keyframe), m_oldValue(oldValue), m_newValue(newValue) { } void redo() override { setValue(m_newValue); } void undo() override { setValue(m_oldValue); } void setValue(qreal value) { KisScalarKeyframe *key = dynamic_cast(m_keyframe.data()); Q_ASSERT(key != 0); key->value = value; m_channel->notifyKeyframeChanged(m_keyframe); } private: KisScalarKeyframeChannel *m_channel; KisKeyframeSP m_keyframe; qreal m_oldValue; qreal m_newValue; }; struct KisScalarKeyframeChannel::Private::SetTangentsCommand : public KUndo2Command { SetTangentsCommand(KisScalarKeyframeChannel *channel, KisKeyframeSP keyframe, KisKeyframe::InterpolationTangentsMode oldMode, QPointF oldLeftTangent, QPointF oldRightTangent, KisKeyframe::InterpolationTangentsMode newMode, QPointF newLeftTangent, QPointF newRightTangent, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_channel(channel), m_keyframe(keyframe), m_oldMode(oldMode), m_oldLeftTangent(oldLeftTangent), m_oldRightTangent(oldRightTangent), m_newMode(newMode), m_newLeftTangent(newLeftTangent), m_newRightTangent(newRightTangent) { } void redo() override { m_keyframe->setTangentsMode(m_newMode); m_keyframe->setInterpolationTangents(m_newLeftTangent, m_newRightTangent); m_channel->notifyKeyframeChanged(m_keyframe); } void undo() override { m_keyframe->setTangentsMode(m_oldMode); m_keyframe->setInterpolationTangents(m_oldLeftTangent, m_oldRightTangent); m_channel->notifyKeyframeChanged(m_keyframe); } private: KisScalarKeyframeChannel *m_channel; KisKeyframeSP m_keyframe; KisKeyframe::InterpolationTangentsMode m_oldMode; QPointF m_oldLeftTangent; QPointF m_oldRightTangent; KisKeyframe::InterpolationTangentsMode m_newMode; QPointF m_newLeftTangent; QPointF m_newRightTangent; }; struct KisScalarKeyframeChannel::Private::SetInterpolationModeCommand : public KUndo2Command { SetInterpolationModeCommand(KisScalarKeyframeChannel *channel, KisKeyframeSP keyframe, KisKeyframe::InterpolationMode oldMode, KisKeyframe::InterpolationMode newMode, KUndo2Command *parentCommand) : KUndo2Command(parentCommand), m_channel(channel), m_keyframe(keyframe), m_oldMode(oldMode), m_newMode(newMode) { } void redo() override { m_keyframe->setInterpolationMode(m_newMode); m_channel->notifyKeyframeChanged(m_keyframe); } void undo() override { m_keyframe->setInterpolationMode(m_oldMode); m_channel->notifyKeyframeChanged(m_keyframe); } private: KisScalarKeyframeChannel *m_channel; KisKeyframeSP m_keyframe; KisKeyframe::InterpolationMode m_oldMode; KisKeyframe::InterpolationMode m_newMode; }; void KisScalarKeyframeChannel::setScalarValue(KisKeyframeSP keyframe, qreal value, KUndo2Command *parentCommand) { QScopedPointer tempCommand; if (!parentCommand) { tempCommand.reset(new KUndo2Command()); parentCommand = tempCommand.data(); } qreal oldValue = scalarValue(keyframe); KUndo2Command *cmd = new Private::SetValueCommand(this, keyframe, oldValue, value, parentCommand); cmd->redo(); } void KisScalarKeyframeChannel::setInterpolationMode(KisKeyframeSP keyframe, KisKeyframe::InterpolationMode mode, KUndo2Command *parentCommand) { QScopedPointer tempCommand; if (!parentCommand) { tempCommand.reset(new KUndo2Command()); parentCommand = tempCommand.data(); } KisKeyframe::InterpolationMode oldMode = keyframe->interpolationMode(); KUndo2Command *cmd = new Private::SetInterpolationModeCommand(this, keyframe, oldMode, mode, parentCommand); cmd->redo(); } void KisScalarKeyframeChannel::setInterpolationTangents(KisKeyframeSP keyframe, KisKeyframe::InterpolationTangentsMode mode, QPointF leftTangent, QPointF rightTangent, KUndo2Command *parentCommand) { QScopedPointer tempCommand; if (!parentCommand) { tempCommand.reset(new KUndo2Command()); parentCommand = tempCommand.data(); } KisKeyframe::InterpolationTangentsMode oldMode = keyframe->tangentsMode(); QPointF oldLeftTangent = keyframe->leftTangent(); QPointF oldRightTangent = keyframe->rightTangent(); KUndo2Command *cmd = new Private::SetTangentsCommand(this, keyframe, oldMode, oldLeftTangent, oldRightTangent, mode, leftTangent, rightTangent, parentCommand); cmd->redo(); } qreal cubicBezier(qreal p0, qreal delta1, qreal delta2, qreal p3, qreal t) { qreal p1 = p0 + delta1; qreal p2 = p3 + delta2; qreal c = 1-t; return c*c*c * p0 + 3*c*c*t * p1 + 3*c*t*t * p2 + t*t*t * p3; } void normalizeTangents(const QPointF point1, QPointF &rightTangent, QPointF &leftTangent, const QPointF point2) { // To ensure that the curve is monotonic wrt time, // check that control points lie between the endpoints. // If not, force them into range by scaling down the tangents float interval = point2.x() - point1.x(); if (rightTangent.x() < 0) rightTangent *= 0; if (leftTangent.x() > 0) leftTangent *= 0; if (rightTangent.x() > interval) { rightTangent *= interval / rightTangent.x(); } if (leftTangent.x() < -interval) { leftTangent *= interval / -leftTangent.x(); } } QPointF KisScalarKeyframeChannel::interpolate(QPointF point1, QPointF rightTangent, QPointF leftTangent, QPointF point2, qreal t) { normalizeTangents(point1, rightTangent, leftTangent, point2); qreal x = cubicBezier(point1.x(), rightTangent.x(), leftTangent.x(), point2.x(), t); qreal y = cubicBezier(point1.y(), rightTangent.y(), leftTangent.y(), point2.y(), t); return QPointF(x,y); } qreal findCubicCurveParameter(int time0, qreal delta0, qreal delta1, int time1, int time) { if (time == time0) return 0.0; if (time == time1) return 1.0; qreal min_t = 0.0; qreal max_t = 1.0; while (true) { qreal t = (max_t + min_t) / 2; qreal time_t = cubicBezier(time0, delta0, delta1, time1, t); if (time_t < time - 0.05) { min_t = t; } else if (time_t > time + 0.05) { max_t = t; } else { // Close enough return t; } } } qreal KisScalarKeyframeChannel::interpolatedValue(int time) const { KisKeyframeSP activeKey = activeKeyframeAt(time); if (activeKey.isNull()) return qQNaN(); KisKeyframeSP nextKey = nextKeyframe(activeKey); qreal result = qQNaN(); if (time == activeKey->time() || nextKey.isNull()) { result = scalarValue(activeKey); } else { switch (activeKey->interpolationMode()) { case KisKeyframe::Constant: result = scalarValue(activeKey); break; case KisKeyframe::Linear: { int time0 = activeKey->time(); int time1 = nextKey->time(); qreal value0 = scalarValue(activeKey); qreal value1 = scalarValue(nextKey); result = value0 + (value1 - value0) * (time - time0) / (time1 - time0); } break; case KisKeyframe::Bezier: { QPointF point0 = QPointF(activeKey->time(), scalarValue(activeKey)); QPointF point1 = QPointF(nextKey->time(), scalarValue(nextKey)); QPointF tangent0 = activeKey->rightTangent(); QPointF tangent1 = nextKey->leftTangent(); normalizeTangents(point0, tangent0, tangent1, point1); qreal t = findCubicCurveParameter(point0.x(), tangent0.x(), tangent1.x(), point1.x(), time); result = interpolate(point0, tangent0, tangent1, point1, t).y(); } break; default: KIS_ASSERT_RECOVER_BREAK(false); break; } } if (result > m_d->maxValue) return m_d->maxValue; if (result < m_d->minValue) return m_d->minValue; return result; } qreal KisScalarKeyframeChannel::currentValue() const { return interpolatedValue(currentTime()); } KisKeyframeSP KisScalarKeyframeChannel::createKeyframe(int time, const KisKeyframeSP copySrc, KUndo2Command *parentCommand) { if (copySrc) { KisScalarKeyframe *srcKeyframe = dynamic_cast(copySrc.data()); Q_ASSERT(srcKeyframe); KisScalarKeyframe *keyframe = new KisScalarKeyframe(srcKeyframe, this); keyframe->setTime(time); return toQShared(keyframe); } else { return createKeyframe(time, 0, parentCommand); } } KisKeyframeSP KisScalarKeyframeChannel::createKeyframe(int time, qreal value, KUndo2Command *parentCommand) { Q_UNUSED(parentCommand); KisScalarKeyframe *keyframe = new KisScalarKeyframe(this, time, value); keyframe->setInterpolationMode(m_d->defaultInterpolation); return toQShared(keyframe); } void KisScalarKeyframeChannel::destroyKeyframe(KisKeyframeSP key, KUndo2Command *parentCommand) { Q_UNUSED(parentCommand); Q_UNUSED(key); } void KisScalarKeyframeChannel::uploadExternalKeyframe(KisKeyframeChannel *srcChannel, int srcTime, KisKeyframeSP dstFrame) { KisScalarKeyframeChannel *srcScalarChannel = dynamic_cast(srcChannel); KIS_ASSERT_RECOVER_RETURN(srcScalarChannel); KisKeyframeSP srcFrame = srcScalarChannel->keyframeAt(srcTime); KIS_ASSERT_RECOVER_RETURN(srcFrame); KisScalarKeyframe *dstKey = dynamic_cast(dstFrame.data()); - dstKey->value = srcChannel->scalarValue(srcFrame); - notifyKeyframeChanged(dstFrame); + if (dstKey) { + dstKey->value = srcChannel->scalarValue(srcFrame); + notifyKeyframeChanged(dstFrame); + } } QRect KisScalarKeyframeChannel::affectedRect(KisKeyframeSP key) { Q_UNUSED(key); if (node()) { return node()->extent(); } else { return QRect(); } } void KisScalarKeyframeChannel::saveKeyframe(KisKeyframeSP keyframe, QDomElement keyframeElement, const QString &layerFilename) { Q_UNUSED(layerFilename); keyframeElement.setAttribute("value", KisDomUtils::toString(scalarValue(keyframe))); QString interpolationMode; if (keyframe->interpolationMode() == KisKeyframe::Constant) interpolationMode = "constant"; if (keyframe->interpolationMode() == KisKeyframe::Linear) interpolationMode = "linear"; if (keyframe->interpolationMode() == KisKeyframe::Bezier) interpolationMode = "bezier"; QString tangentsMode; if (keyframe->tangentsMode() == KisKeyframe::Smooth) tangentsMode = "smooth"; if (keyframe->tangentsMode() == KisKeyframe::Sharp) tangentsMode = "sharp"; keyframeElement.setAttribute("interpolation", interpolationMode); keyframeElement.setAttribute("tangents", tangentsMode); KisDomUtils::saveValue(&keyframeElement, "leftTangent", keyframe->leftTangent()); KisDomUtils::saveValue(&keyframeElement, "rightTangent", keyframe->rightTangent()); } KisKeyframeSP KisScalarKeyframeChannel::loadKeyframe(const QDomElement &keyframeNode) { int time = keyframeNode.toElement().attribute("time").toInt(); workaroundBrokenFrameTimeBug(&time); qreal value = KisDomUtils::toDouble(keyframeNode.toElement().attribute("value")); KUndo2Command tempParentCommand; KisKeyframeSP keyframe = createKeyframe(time, KisKeyframeSP(), &tempParentCommand); setScalarValue(keyframe, value); QString interpolationMode = keyframeNode.toElement().attribute("interpolation"); if (interpolationMode == "constant") { keyframe->setInterpolationMode(KisKeyframe::Constant); } else if (interpolationMode == "linear") { keyframe->setInterpolationMode(KisKeyframe::Linear); } else if (interpolationMode == "bezier") { keyframe->setInterpolationMode(KisKeyframe::Bezier); } QString tangentsMode = keyframeNode.toElement().attribute("tangents"); if (tangentsMode == "smooth") { keyframe->setTangentsMode(KisKeyframe::Smooth); } else if (tangentsMode == "sharp") { keyframe->setTangentsMode(KisKeyframe::Sharp); } QPointF leftTangent; QPointF rightTangent; KisDomUtils::loadValue(keyframeNode, "leftTangent", &leftTangent); KisDomUtils::loadValue(keyframeNode, "rightTangent", &rightTangent); keyframe->setInterpolationTangents(leftTangent, rightTangent); return keyframe; } void KisScalarKeyframeChannel::notifyKeyframeChanged(KisKeyframeSP keyframe) { QRect rect = affectedRect(keyframe); KisTimeRange range = affectedFrames(keyframe->time()); requestUpdate(range, rect); emit sigKeyframeChanged(keyframe); } diff --git a/libs/libkis/FilterLayer.cpp b/libs/libkis/FilterLayer.cpp index fdffed5742..46373d49ec 100644 --- a/libs/libkis/FilterLayer.cpp +++ b/libs/libkis/FilterLayer.cpp @@ -1,63 +1,66 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser 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 Lesser 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 "FilterLayer.h" #include #include #include #include #include #include FilterLayer::FilterLayer(KisImageSP image, QString name, Filter &filter, Selection &selection, QObject *parent) : Node(image, new KisAdjustmentLayer(image, name, filter.filterConfig(), selection.selection()), parent) { } FilterLayer::FilterLayer(KisAdjustmentLayerSP layer, QObject *parent): Node(layer->image(), layer, parent) { } FilterLayer::~FilterLayer() { } QString FilterLayer::type() const { return "filterlayer"; } void FilterLayer::setFilter(Filter &filter) { + if (!this->node()) return; KisAdjustmentLayer *layer = dynamic_cast(this->node().data()); //getting the default configuration here avoids trouble with versioning. - layer->setFilter(filter.filterConfig()); + if (layer) { + layer->setFilter(filter.filterConfig()); + } } Filter * FilterLayer::filter() { Filter* filter = new Filter(); const KisAdjustmentLayer *layer = qobject_cast(this->node()); filter->setName(layer->filter()->name()); filter->setConfiguration(new InfoObject(layer->filter())); return filter; } diff --git a/libs/libkis/Shape.cpp b/libs/libkis/Shape.cpp index f66280e947..393e5e7718 100644 --- a/libs/libkis/Shape.cpp +++ b/libs/libkis/Shape.cpp @@ -1,106 +1,106 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser 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 Lesser 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 "Shape.h" #include #include #include #include #include #include struct Shape::Private { Private() {} - KoShape *shape; + KoShape *shape {0}; }; Shape::Shape(KoShape *shape, QObject *parent) : QObject(parent) , d(new Private) { d->shape = shape; } Shape::~Shape() { delete d; } QString Shape::name() const { return d->shape->name(); } void Shape::setName(const QString &name) { d->shape->setName(name); } QString Shape::type() const { return d->shape->shapeId(); } bool Shape::visible() { return d->shape->isVisible(); } void Shape::setVisible(bool visible) { d->shape->setVisible(visible); } QRectF Shape::boundingBox() const { return d->shape->boundingRect(); } QPointF Shape::position() const { return d->shape->position(); } void Shape::setPosition(QPointF point) { d->shape->setPosition(point); } QString Shape::toSvg() { QBuffer shapesBuffer; QBuffer stylesBuffer; shapesBuffer.open(QIODevice::WriteOnly); stylesBuffer.open(QIODevice::WriteOnly); { SvgSavingContext savingContext(shapesBuffer, stylesBuffer); savingContext.setStrippedTextMode(true); SvgWriter writer({d->shape}); writer.saveDetached(savingContext); } shapesBuffer.close(); stylesBuffer.close(); return QString::fromUtf8(shapesBuffer.data()); } KoShape *Shape::shape() { return d->shape; } diff --git a/libs/pigment/compositeops/KoCompositeOpGreater.h b/libs/pigment/compositeops/KoCompositeOpGreater.h index 9d638d6564..ba22a127ea 100644 --- a/libs/pigment/compositeops/KoCompositeOpGreater.h +++ b/libs/pigment/compositeops/KoCompositeOpGreater.h @@ -1,104 +1,106 @@ /* * Copyright (c) 2014 Nicholas Guttenberg * * Based on KoCompositeOpBehind.h, * Copyright (c) 2012 José Luis Vergara * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KOCOMPOSITEOPGREATER_H_ #define _KOCOMPOSITEOPGREATER_H_ #include "KoCompositeOpBase.h" /** * Greater-than compositor - uses the greater of two alpha values to determine the color */ template class KoCompositeOpGreater : public KoCompositeOpBase > { typedef KoCompositeOpBase > base_class; typedef typename CS_Traits::channels_type channels_type; typedef typename KoColorSpaceMathsTraits::compositetype composite_type; static const qint8 channels_nb = CS_Traits::channels_nb; static const qint8 alpha_pos = CS_Traits::alpha_pos; public: KoCompositeOpGreater(const KoColorSpace * cs) : base_class(cs, COMPOSITE_GREATER, i18n("Greater"), KoCompositeOp::categoryMix()) { } public: template inline static channels_type composeColorChannels(const channels_type* src, channels_type srcAlpha, channels_type* dst, channels_type dstAlpha, channels_type maskAlpha, channels_type opacity, const QBitArray& channelFlags ) { using namespace Arithmetic; if (dstAlpha == unitValue()) return dstAlpha; channels_type appliedAlpha = mul(maskAlpha, srcAlpha, opacity); if (appliedAlpha == zeroValue()) return dstAlpha; channels_type newDstAlpha; float dA = scale(dstAlpha); float w = 1.0/(1.0+exp(-40.0*(dA - scale(appliedAlpha)))); float a = dA*w + scale(appliedAlpha)*(1.0-w); if (a < 0.0f) { a = 0.0f; } if (a > 1.0f) { a = 1.0f; } // For a standard Over, the resulting alpha is: a = opacity*dstAlpha + (1-opacity)*srcAlpha // Let us assume we're blending with a color with srcAlpha = 1 here // Therefore, opacity = (1.0 - a)/(1.0 - dstAlpha) if (a(a); if (dstAlpha != zeroValue()) { for (qint8 channel = 0; channel < channels_nb; ++channel) if(channel != alpha_pos && (allChannelFlags || channelFlags.testBit(channel))) { typedef typename KoColorSpaceMathsTraits::compositetype composite_type; channels_type dstMult = mul(dst[channel], dstAlpha); channels_type srcMult = mul(src[channel], unitValue()); channels_type blendedValue = lerp(dstMult, srcMult, scale(fakeOpacity)); - + // CID 249016 (#1 of 15): + // Division or modulo by zero (DIVIDE_BY_ZERO)12. divide_by_zero: In function call divide, division by expression newDstAlpha which may be zero has undefined behavior. + if (newDstAlpha == 0) newDstAlpha = 1; composite_type normedValue = KoColorSpaceMaths::divide(blendedValue, newDstAlpha); dst[channel] = KoColorSpaceMaths::clampAfterScale(normedValue); } } else { // don't blend if the color of the destination is undefined (has zero opacity) // copy the source channel instead for (qint8 channel = 0; channel < channels_nb; ++channel) if(channel != alpha_pos && (allChannelFlags || channelFlags.testBit(channel))) dst[channel] = src[channel]; } return newDstAlpha; } }; #endif // _KOCOMPOSITEOPGREATER_H_ diff --git a/libs/ui/KisImportExportFilter.cpp b/libs/ui/KisImportExportFilter.cpp index baabd4875c..2885797d7a 100644 --- a/libs/ui/KisImportExportFilter.cpp +++ b/libs/ui/KisImportExportFilter.cpp @@ -1,344 +1,349 @@ /* This file is part of the KDE libraries Copyright (C) 2001 Werner Trobin 2002 Werner Trobin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KisImportExportFilter.h" #include #include #include #include #include "KisImportExportManager.h" #include #include #include #include #include "KoUpdater.h" #include #include "kis_config.h" #include #include const QString KisImportExportFilter::ImageContainsTransparencyTag = "ImageContainsTransparency"; const QString KisImportExportFilter::ColorModelIDTag = "ColorModelID"; const QString KisImportExportFilter::ColorDepthIDTag = "ColorDepthID"; const QString KisImportExportFilter::sRGBTag = "sRGB"; class Q_DECL_HIDDEN KisImportExportFilter::Private { public: QPointer updater; QByteArray mime; QString filename; QString realFilename; bool batchmode; QMap capabilities; Private() : updater(0), mime("") , batchmode(false) {} ~Private() { qDeleteAll(capabilities); } }; KisImportExportFilter::KisImportExportFilter(QObject *parent) : QObject(parent) , d(new Private) { } KisImportExportFilter::~KisImportExportFilter() { if (d->updater) { d->updater->setProgress(100); } delete d; } QString KisImportExportFilter::filename() const { return d->filename; } QString KisImportExportFilter::realFilename() const { return d->realFilename; } bool KisImportExportFilter::batchMode() const { return d->batchmode; } void KisImportExportFilter::setBatchMode(bool batchmode) { d->batchmode = batchmode; } void KisImportExportFilter::setFilename(const QString &filename) { d->filename = filename; } void KisImportExportFilter::setRealFilename(const QString &filename) { d->realFilename = filename; } void KisImportExportFilter::setMimeType(const QString &mime) { d->mime = mime.toLatin1(); } QByteArray KisImportExportFilter::mimeType() const { return d->mime; } QString KisImportExportFilter::conversionStatusString(ConversionStatus status) { QString msg; switch (status) { case OK: break; case FilterCreationError: msg = i18n("Krita does not support this file format"); break; case CreationError: msg = i18n("Could not create the output document"); break; case FileNotFound: msg = i18n("File not found"); break; case StorageCreationError: msg = i18n("Cannot create storage"); break; case BadMimeType: msg = i18n("Bad MIME type"); break; case WrongFormat: msg = i18n("Format not recognized"); break; case NotImplemented: msg = i18n("Not implemented"); break; case ParsingError: msg = i18n("Parsing error"); break; case InvalidFormat: msg = i18n("Invalid file format"); break; case InternalError: case UsageError: msg = i18n("Internal error"); break; case ProgressCancelled: msg = i18n("Cancelled by user"); break; case BadConversionGraph: msg = i18n("Unknown file type"); break; case UnsupportedVersion: msg = i18n("Unsupported file version"); break; case UserCancelled: // intentionally we do not prompt the error message here break; default: msg = i18n("Unknown error"); break; } return msg; } KisPropertiesConfigurationSP KisImportExportFilter::defaultConfiguration(const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); return 0; } KisPropertiesConfigurationSP KisImportExportFilter::lastSavedConfiguration(const QByteArray &from, const QByteArray &to) const { KisPropertiesConfigurationSP cfg = defaultConfiguration(from, to); const QString filterConfig = KisConfig(true).exportConfigurationXML(to); if (cfg && !filterConfig.isEmpty()) { cfg->fromXML(filterConfig, false); } return cfg; } KisConfigWidget *KisImportExportFilter::createConfigurationWidget(QWidget *, const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); return 0; } QMap KisImportExportFilter::exportChecks() { qDeleteAll(d->capabilities); initializeCapabilities(); return d->capabilities; } QString KisImportExportFilter::verify(const QString &fileName) const { QFileInfo fi(fileName); if (!fi.exists()) { return i18n("%1 does not exist after writing. Try saving again under a different name, in another location.", fileName); } if (!fi.isReadable()) { return i18n("%1 is not readable", fileName); } if (fi.size() < 10) { return i18n("%1 is smaller than 10 bytes, it must be corrupt. Try saving again under a different name, in another location.", fileName); } QFile f(fileName); f.open(QFile::ReadOnly); QByteArray ba = f.read(std::min(f.size(), (qint64)1000)); bool found = false; for(int i = 0; i < ba.size(); ++i) { if (ba.at(i) > 0) { found = true; break; } } if (!found) { return i18n("%1 has only zero bytes in the first 1000 bytes, it's probably corrupt. Try saving again under a different name, in another location.", fileName); } return QString(); } void KisImportExportFilter::setUpdater(QPointer updater) { d->updater = updater; } +QPointer KisImportExportFilter::updater() +{ + return d->updater; +} + void KisImportExportFilter::setProgress(int value) { if (d->updater) { d->updater->setValue(value); } } void KisImportExportFilter::initializeCapabilities() { // XXX: Initialize everything to fully supported? } void KisImportExportFilter::addCapability(KisExportCheckBase *capability) { d->capabilities[capability->id()] = capability; } void KisImportExportFilter::addSupportedColorModels(QList > supportedColorModels, const QString &name, KisExportCheckBase::Level level) { Q_ASSERT(level != KisExportCheckBase::SUPPORTED); QString layerMessage; QString imageMessage; QList allColorModels = KoColorSpaceRegistry::instance()->colorModelsList(KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(const KoID &colorModelID, allColorModels) { QList allColorDepths = KoColorSpaceRegistry::instance()->colorDepthList(colorModelID.id(), KoColorSpaceRegistry::AllColorSpaces); Q_FOREACH(const KoID &colorDepthID, allColorDepths) { KisExportCheckFactory *colorModelCheckFactory = KisExportCheckRegistry::instance()->get("ColorModelCheck/" + colorModelID.id() + "/" + colorDepthID.id()); KisExportCheckFactory *colorModelPerLayerCheckFactory = KisExportCheckRegistry::instance()->get("ColorModelPerLayerCheck/" + colorModelID.id() + "/" + colorDepthID.id()); if(!colorModelCheckFactory || !colorModelPerLayerCheckFactory) { qWarning() << "No factory for" << colorModelID << colorDepthID; continue; } if (supportedColorModels.contains(QPair(colorModelID, colorDepthID))) { addCapability(colorModelCheckFactory->create(KisExportCheckBase::SUPPORTED)); addCapability(colorModelPerLayerCheckFactory->create(KisExportCheckBase::SUPPORTED)); } else { if (level == KisExportCheckBase::PARTIALLY) { imageMessage = i18nc("image conversion warning", "%1 cannot save images with color model %2 and depth %3. The image will be converted." ,name, colorModelID.name(), colorDepthID.name()); layerMessage = i18nc("image conversion warning", "%1 cannot save layers with color model %2 and depth %3. The layers will be converted or skipped." ,name, colorModelID.name(), colorDepthID.name()); } else { imageMessage = i18nc("image conversion warning", "%1 cannot save images with color model %2 and depth %3. The image will not be saved." ,name, colorModelID.name(), colorDepthID.name()); layerMessage = i18nc("image conversion warning", "%1 cannot save layers with color model %2 and depth %3. The layers will be skipped." , name, colorModelID.name(), colorDepthID.name()); } addCapability(colorModelCheckFactory->create(level, imageMessage)); addCapability(colorModelPerLayerCheckFactory->create(level, layerMessage)); } } } } QString KisImportExportFilter::verifyZiPBasedFiles(const QString &fileName, const QStringList &filesToCheck) const { QScopedPointer store(KoStore::createStore(fileName, KoStore::Read, KIS_MIME_TYPE, KoStore::Zip)); if (!store || store->bad()) { return i18n("Could not open the saved file %1. Please try to save again in a different location.", fileName); } Q_FOREACH(const QString &file, filesToCheck) { if (!store->hasFile(file)) { return i18n("File %1 is missing in %2 and is broken. Please try to save again in a different location.", file, fileName); } } return QString(); } diff --git a/libs/ui/KisImportExportFilter.h b/libs/ui/KisImportExportFilter.h index 190b803617..455f47cb35 100644 --- a/libs/ui/KisImportExportFilter.h +++ b/libs/ui/KisImportExportFilter.h @@ -1,190 +1,191 @@ /* This file is part of the Calligra libraries Copyright (C) 2001 Werner Trobin 2002 Werner Trobin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KIS_IMPORT_EXPORT_FILTER_H #define KIS_IMPORT_EXPORT_FILTER_H #include "KisImageBuilderResult.h" #include #include #include #include #include #include #include #include #include #include #include #include class KoUpdater; class KisDocument; class KisConfigWidget; #include "kritaui_export.h" #include "KisImportExportErrorCode.h" /** * @brief The base class for import and export filters. * * Derive your filter class from this base class and implement * the @ref convert() method. Don't forget to specify the Q_OBJECT * macro in your class even if you don't use signals or slots. * This is needed as filters are created on the fly. * * @note Take care: The m_chain pointer is invalid while the constructor * runs due to the implementation -- @em don't use it in the constructor. * After the constructor, when running the @ref convert() method it's * guaranteed to be valid, so no need to check against 0. * * @note If the code is compiled in debug mode, setting CALLIGRA_DEBUG_FILTERS * environment variable to any value disables deletion of temporary files while * importing/exporting. This is useful for testing purposes. * * @author Werner Trobin * @todo the class has no constructor and therefore cannot initialize its private class */ class KRITAUI_EXPORT KisImportExportFilter : public QObject { Q_OBJECT public: static const QString ImageContainsTransparencyTag; static const QString ColorModelIDTag; static const QString ColorDepthIDTag; static const QString sRGBTag; public: /** * This enum is used to signal the return state of your filter. * Return OK in @ref convert() in case everything worked as expected. * Feel free to add some more error conditions @em before the last item * if it's needed. */ enum ConversionStatus { OK, UsageError, CreationError, FileNotFound, StorageCreationError, BadMimeType, BadConversionGraph, WrongFormat, NotImplemented, ParsingError, InternalError, UserCancelled, InvalidFormat, FilterCreationError, ProgressCancelled, UnsupportedVersion, JustInCaseSomeBrokenCompilerUsesLessThanAByte = 255 }; ~KisImportExportFilter() override; void setBatchMode(bool batchmode); void setFilename(const QString &filename); void setRealFilename(const QString &filename); void setMimeType(const QString &mime); void setUpdater(QPointer updater); + QPointer updater(); /** * The filter chain calls this method to perform the actual conversion. * The passed mimetypes should be a pair of those you specified in your * .desktop file. * You @em have to implement this method to make the filter work. * * @return The error status, see the @ref #ConversionStatus enum. * KisImportExportFilter::OK means that everything is alright. */ virtual KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) = 0; /** * Get the text version of the status value */ static QString conversionStatusString(ConversionStatus status); /** * @brief defaultConfiguration defines the default settings for the given import export filter * @param from The mimetype of the source file/document * @param to The mimetype of the destination file/document * @return a serializable KisPropertiesConfiguration object */ virtual KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const; /** * @brief lastSavedConfiguration return the last saved configuration for this filter * @param from The mimetype of the source file/document * @param to The mimetype of the destination file/document * @return a serializable KisPropertiesConfiguration object */ KisPropertiesConfigurationSP lastSavedConfiguration(const QByteArray &from = "", const QByteArray &to = "") const; /** * @brief createConfigurationWidget creates a widget that can be used to define the settings for a given import/export filter * @param parent the owner of the widget; the caller is responsible for deleting * @param from The mimetype of the source file/document * @param to The mimetype of the destination file/document * * @return the widget */ virtual KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const; /** * @brief generate and return the list of capabilities of this export filter. The list * @return returns the list of capabilities of this export filter */ virtual QMap exportChecks(); /// Override and return false for the filters that use a library that cannot handle file handles, only file names. virtual bool supportsIO() const { return true; } /// Verify whether the given file is correct and readable virtual QString verify(const QString &fileName) const; protected: /** * This is the constructor your filter has to call, obviously. */ KisImportExportFilter(QObject *parent = 0); QString filename() const; QString realFilename() const; bool batchMode() const; QByteArray mimeType() const; void setProgress(int value); virtual void initializeCapabilities(); void addCapability(KisExportCheckBase *capability); void addSupportedColorModels(QList > supportedColorModels, const QString &name, KisExportCheckBase::Level level = KisExportCheckBase::PARTIALLY); QString verifyZiPBasedFiles(const QString &fileName, const QStringList &filesToCheck) const; private: KisImportExportFilter(const KisImportExportFilter& rhs); KisImportExportFilter& operator=(const KisImportExportFilter& rhs); class Private; Private *const d; }; #endif diff --git a/libs/ui/canvas/kis_canvas_controller.cpp b/libs/ui/canvas/kis_canvas_controller.cpp index 8e5b209c97..fa9e787de6 100644 --- a/libs/ui/canvas/kis_canvas_controller.cpp +++ b/libs/ui/canvas/kis_canvas_controller.cpp @@ -1,386 +1,384 @@ /* * Copyright (c) 2010 Dmitry Kazakov * * 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 "kis_canvas_controller.h" #include #include #include #include #include #include "kis_canvas_decoration.h" #include "kis_coordinates_converter.h" #include "kis_canvas2.h" #include "opengl/kis_opengl_canvas2.h" #include "KisDocument.h" #include "kis_image.h" #include "KisViewManager.h" #include "KisView.h" #include "krita_utils.h" #include "kis_config.h" #include "kis_signal_compressor_with_param.h" #include "kis_config_notifier.h" static const int gRulersUpdateDelay = 80 /* ms */; struct KisCanvasController::Private { Private(KisCanvasController *qq) : q(qq) { using namespace std::placeholders; std::function callback( std::bind(&KisCanvasController::Private::emitPointerPositionChangedSignals, this, _1)); mousePositionCompressor.reset( new KisSignalCompressorWithParam( gRulersUpdateDelay, callback, KisSignalCompressor::FIRST_ACTIVE)); } QPointer view; KisCoordinatesConverter *coordinatesConverter; KisCanvasController *q; QScopedPointer > mousePositionCompressor; void emitPointerPositionChangedSignals(QPoint pointerPos); void updateDocumentSizeAfterTransform(); void showRotationValueOnCanvas(); void showMirrorStateOnCanvas(); }; void KisCanvasController::Private::emitPointerPositionChangedSignals(QPoint pointerPos) { if (!coordinatesConverter) return; QPointF documentPos = coordinatesConverter->widgetToDocument(pointerPos); q->proxyObject->emitDocumentMousePositionChanged(documentPos); q->proxyObject->emitCanvasMousePositionChanged(pointerPos); } void KisCanvasController::Private::updateDocumentSizeAfterTransform() { // round the size of the area to the nearest integer instead of getting aligned rect QSize widgetSize = coordinatesConverter->imageRectInWidgetPixels().toRect().size(); q->updateDocumentSize(widgetSize, true); KisCanvas2 *kritaCanvas = dynamic_cast(q->canvas()); Q_ASSERT(kritaCanvas); kritaCanvas->notifyZoomChanged(); } KisCanvasController::KisCanvasController(QPointerparent, KoCanvasSupervisor *observerProvider, KActionCollection * actionCollection) : KoCanvasControllerWidget(actionCollection, observerProvider, parent), m_d(new Private(this)) { m_d->view = parent; } KisCanvasController::~KisCanvasController() { delete m_d; } void KisCanvasController::setCanvas(KoCanvasBase *canvas) { if (canvas) { - KisCanvas2 *kritaCanvas = dynamic_cast(canvas); - KIS_SAFE_ASSERT_RECOVER_RETURN(kritaCanvas); - + KisCanvas2 *kritaCanvas = qobject_cast(canvas); m_d->coordinatesConverter = const_cast(kritaCanvas->coordinatesConverter()); } else { m_d->coordinatesConverter = 0; } KoCanvasControllerWidget::setCanvas(canvas); } void KisCanvasController::activate() { KoCanvasControllerWidget::activate(); } QPointF KisCanvasController::currentCursorPosition() const { KoCanvasBase *canvas = m_d->view->canvasBase(); QWidget *canvasWidget = canvas->canvasWidget(); const QPointF cursorPosWidget = canvasWidget->mapFromGlobal(QCursor::pos()); return m_d->coordinatesConverter->widgetToDocument(cursorPosWidget); } void KisCanvasController::keyPressEvent(QKeyEvent *event) { /** * Dirty Hack Alert: * Do not call the KoCanvasControllerWidget::keyPressEvent() * to avoid activation of Pan and Default tool activation shortcuts */ Q_UNUSED(event); } void KisCanvasController::wheelEvent(QWheelEvent *event) { /** * Dirty Hack Alert: * Do not call the KoCanvasControllerWidget::wheelEvent() * to disable the default behavior of KoCanvasControllerWidget and QAbstractScrollArea */ Q_UNUSED(event); } bool KisCanvasController::eventFilter(QObject *watched, QEvent *event) { KoCanvasBase *canvas = this->canvas(); if (!canvas || !canvas->canvasWidget() || canvas->canvasWidget() != watched) return false; if (event->type() == QEvent::MouseMove) { QMouseEvent *mevent = static_cast(event); m_d->mousePositionCompressor->start(mevent->pos()); } else if (event->type() == QEvent::TabletMove) { QTabletEvent *tevent = static_cast(event); m_d->mousePositionCompressor->start(tevent->pos()); } else if (event->type() == QEvent::FocusIn) { m_d->view->syncLastActiveNodeToDocument(); } return false; } void KisCanvasController::updateDocumentSize(const QSizeF &sz, bool recalculateCenter) { KoCanvasControllerWidget::updateDocumentSize(sz, recalculateCenter); emit documentSizeChanged(); } void KisCanvasController::Private::showMirrorStateOnCanvas() { bool isXMirrored = coordinatesConverter->xAxisMirrored(); view->viewManager()-> showFloatingMessage( i18nc("floating message about mirroring", "Horizontal mirroring: %1 ", isXMirrored ? i18n("ON") : i18n("OFF")), QIcon(), 500, KisFloatingMessage::Low); } void KisCanvasController::mirrorCanvas(bool enable) { QPoint newOffset = m_d->coordinatesConverter->mirror(m_d->coordinatesConverter->widgetCenterPoint(), enable, false); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); m_d->showMirrorStateOnCanvas(); } void KisCanvasController::Private::showRotationValueOnCanvas() { qreal rotationAngle = coordinatesConverter->rotationAngle(); view->viewManager()-> showFloatingMessage( i18nc("floating message about rotation", "Rotation: %1° ", KritaUtils::prettyFormatReal(rotationAngle)), QIcon(), 500, KisFloatingMessage::Low, Qt::AlignCenter); } void KisCanvasController::rotateCanvas(qreal angle, const QPointF ¢er) { QPoint newOffset = m_d->coordinatesConverter->rotate(center, angle); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); m_d->showRotationValueOnCanvas(); } void KisCanvasController::rotateCanvas(qreal angle) { rotateCanvas(angle, m_d->coordinatesConverter->widgetCenterPoint()); } void KisCanvasController::rotateCanvasRight15() { rotateCanvas(15.0); } void KisCanvasController::rotateCanvasLeft15() { rotateCanvas(-15.0); } qreal KisCanvasController::rotation() const { return m_d->coordinatesConverter->rotationAngle(); } void KisCanvasController::resetCanvasRotation() { QPoint newOffset = m_d->coordinatesConverter->resetRotation(m_d->coordinatesConverter->widgetCenterPoint()); m_d->updateDocumentSizeAfterTransform(); setScrollBarValue(newOffset); m_d->showRotationValueOnCanvas(); } void KisCanvasController::slotToggleWrapAroundMode(bool value) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); if (!canvas()->canvasIsOpenGL() && value) { m_d->view->viewManager()->showFloatingMessage(i18n("You are activating wrap-around mode, but have not enabled OpenGL.\n" "To visualize wrap-around mode, enable OpenGL."), QIcon()); } kritaCanvas->setWrapAroundViewingMode(value); kritaCanvas->image()->setWrapAroundModePermitted(value); } bool KisCanvasController::wrapAroundMode() const { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->wrapAroundViewingMode(); } void KisCanvasController::slotTogglePixelGrid(bool value) { KisConfig cfg(false); cfg.enablePixelGrid(value); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); } void KisCanvasController::slotToggleLevelOfDetailMode(bool value) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); kritaCanvas->setLodAllowedInCanvas(value); bool result = levelOfDetailMode(); if (!value || result) { m_d->view->viewManager()->showFloatingMessage( i18n("Instant Preview Mode: %1", result ? i18n("ON") : i18n("OFF")), QIcon(), 500, KisFloatingMessage::Low); } else { QString reason; if (!kritaCanvas->canvasIsOpenGL()) { reason = i18n("Instant Preview is only supported with OpenGL activated"); } else if (kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode || kritaCanvas->openGLFilterMode() == KisOpenGL::NearestFilterMode) { QString filteringMode = kritaCanvas->openGLFilterMode() == KisOpenGL::BilinearFilterMode ? i18n("Bilinear") : i18n("Nearest Neighbour"); reason = i18n("Instant Preview is supported\n in Trilinear or High Quality filtering modes.\nCurrent mode is %1", filteringMode); } m_d->view->viewManager()->showFloatingMessage( i18n("Failed activating Instant Preview mode!\n\n%1", reason), QIcon(), 5000, KisFloatingMessage::Low); } } bool KisCanvasController::levelOfDetailMode() const { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); return kritaCanvas->lodAllowedInCanvas(); } void KisCanvasController::saveCanvasState(KisPropertiesConfiguration &config) const { const QPointF ¢er = preferredCenter(); config.setProperty("panX", center.x()); config.setProperty("panY", center.y()); config.setProperty("rotation", rotation()); config.setProperty("mirror", m_d->coordinatesConverter->xAxisMirrored()); config.setProperty("wrapAround", wrapAroundMode()); config.setProperty("enableInstantPreview", levelOfDetailMode()); } void KisCanvasController::restoreCanvasState(const KisPropertiesConfiguration &config) { KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); Q_ASSERT(kritaCanvas); mirrorCanvas(config.getBool("mirror", false)); rotateCanvas(config.getFloat("rotation", 0.0f)); const QPointF ¢er = preferredCenter(); float panX = config.getFloat("panX", center.x()); float panY = config.getFloat("panY", center.y()); setPreferredCenter(QPointF(panX, panY)); slotToggleWrapAroundMode(config.getBool("wrapAround", false)); kritaCanvas->setLodAllowedInCanvas(config.getBool("enableInstantPreview", false)); } void KisCanvasController::resetScrollBars() { // The scrollbar value always points at the top-left corner of the // bit of image we paint. KisDocument *doc = m_d->view->document(); if (!doc) return; QRectF documentBounds = doc->documentBounds(); QRectF viewRect = m_d->coordinatesConverter->imageToWidget(documentBounds); // Cancel out any existing pan const QRectF imageBounds = m_d->view->image()->bounds(); const QRectF imageBB = m_d->coordinatesConverter->imageToWidget(imageBounds); QPointF pan = imageBB.topLeft(); viewRect.translate(-pan); int drawH = viewport()->height(); int drawW = viewport()->width(); qreal horizontalReserve = vastScrollingFactor() * drawW; qreal verticalReserve = vastScrollingFactor() * drawH; qreal xMin = viewRect.left() - horizontalReserve; qreal yMin = viewRect.top() - verticalReserve; qreal xMax = viewRect.right() - drawW + horizontalReserve; qreal yMax = viewRect.bottom() - drawH + verticalReserve; QScrollBar *hScroll = horizontalScrollBar(); QScrollBar *vScroll = verticalScrollBar(); hScroll->setRange(static_cast(xMin), static_cast(xMax)); vScroll->setRange(static_cast(yMin), static_cast(yMax)); int fontHeight = QFontMetrics(font()).height(); vScroll->setPageStep(drawH); vScroll->setSingleStep(fontHeight); hScroll->setPageStep(drawW); hScroll->setSingleStep(fontHeight); } diff --git a/libs/ui/tool/kis_smoothing_options.cpp b/libs/ui/tool/kis_smoothing_options.cpp index f91a3b05b3..592f1d1e1e 100644 --- a/libs/ui/tool/kis_smoothing_options.cpp +++ b/libs/ui/tool/kis_smoothing_options.cpp @@ -1,171 +1,175 @@ /* * Copyright (c) 2012 Boudewijn Rempt * * 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 "kis_smoothing_options.h" #include "kis_config.h" #include "kis_signal_compressor.h" struct KisSmoothingOptions::Private { - Private() : writeCompressor(500, KisSignalCompressor::FIRST_ACTIVE) {} + Private(bool useSavedSmoothing) + : writeCompressor(500, KisSignalCompressor::FIRST_ACTIVE) + { + KisConfig cfg(true); + smoothingType = (SmoothingType)cfg.lineSmoothingType(!useSavedSmoothing); + smoothnessDistance = cfg.lineSmoothingDistance(!useSavedSmoothing); + tailAggressiveness = cfg.lineSmoothingTailAggressiveness(!useSavedSmoothing); + smoothPressure = cfg.lineSmoothingSmoothPressure(!useSavedSmoothing); + useScalableDistance = cfg.lineSmoothingScalableDistance(!useSavedSmoothing); + delayDistance = cfg.lineSmoothingDelayDistance(!useSavedSmoothing); + useDelayDistance = cfg.lineSmoothingUseDelayDistance(!useSavedSmoothing); + finishStabilizedCurve = cfg.lineSmoothingFinishStabilizedCurve(!useSavedSmoothing); + stabilizeSensors = cfg.lineSmoothingStabilizeSensors(!useSavedSmoothing); + } + KisSignalCompressor writeCompressor; SmoothingType smoothingType; qreal smoothnessDistance; qreal tailAggressiveness; bool smoothPressure; bool useScalableDistance; qreal delayDistance; bool useDelayDistance; bool finishStabilizedCurve; bool stabilizeSensors; }; KisSmoothingOptions::KisSmoothingOptions(bool useSavedSmoothing) - : m_d(new Private) -{ - KisConfig cfg(true); - m_d->smoothingType = (SmoothingType)cfg.lineSmoothingType(!useSavedSmoothing); - m_d->smoothnessDistance = cfg.lineSmoothingDistance(!useSavedSmoothing); - m_d->tailAggressiveness = cfg.lineSmoothingTailAggressiveness(!useSavedSmoothing); - m_d->smoothPressure = cfg.lineSmoothingSmoothPressure(!useSavedSmoothing); - m_d->useScalableDistance = cfg.lineSmoothingScalableDistance(!useSavedSmoothing); - m_d->delayDistance = cfg.lineSmoothingDelayDistance(!useSavedSmoothing); - m_d->useDelayDistance = cfg.lineSmoothingUseDelayDistance(!useSavedSmoothing); - m_d->finishStabilizedCurve = cfg.lineSmoothingFinishStabilizedCurve(!useSavedSmoothing); - m_d->stabilizeSensors = cfg.lineSmoothingStabilizeSensors(!useSavedSmoothing); + : m_d(new Private(useSavedSmoothing)) +{ connect(&m_d->writeCompressor, SIGNAL(timeout()), this, SLOT(slotWriteConfig())); } KisSmoothingOptions::~KisSmoothingOptions() { } KisSmoothingOptions::SmoothingType KisSmoothingOptions::smoothingType() const { return m_d->smoothingType; } void KisSmoothingOptions::setSmoothingType(KisSmoothingOptions::SmoothingType value) { m_d->smoothingType = value; emit sigSmoothingTypeChanged(); m_d->writeCompressor.start(); } qreal KisSmoothingOptions::smoothnessDistance() const { return m_d->smoothnessDistance; } void KisSmoothingOptions::setSmoothnessDistance(qreal value) { m_d->smoothnessDistance = value; m_d->writeCompressor.start(); } qreal KisSmoothingOptions::tailAggressiveness() const { return m_d->tailAggressiveness; } void KisSmoothingOptions::setTailAggressiveness(qreal value) { m_d->tailAggressiveness = value; m_d->writeCompressor.start(); } bool KisSmoothingOptions::smoothPressure() const { return m_d->smoothPressure; } void KisSmoothingOptions::setSmoothPressure(bool value) { m_d->smoothPressure = value; m_d->writeCompressor.start(); } bool KisSmoothingOptions::useScalableDistance() const { return m_d->useScalableDistance; } void KisSmoothingOptions::setUseScalableDistance(bool value) { m_d->useScalableDistance = value; m_d->writeCompressor.start(); } qreal KisSmoothingOptions::delayDistance() const { return m_d->delayDistance; } void KisSmoothingOptions::setDelayDistance(qreal value) { m_d->delayDistance = value; m_d->writeCompressor.start(); } bool KisSmoothingOptions::useDelayDistance() const { return m_d->useDelayDistance; } void KisSmoothingOptions::setUseDelayDistance(bool value) { m_d->useDelayDistance = value; m_d->writeCompressor.start(); } void KisSmoothingOptions::setFinishStabilizedCurve(bool value) { m_d->finishStabilizedCurve = value; m_d->writeCompressor.start(); } bool KisSmoothingOptions::finishStabilizedCurve() const { return m_d->finishStabilizedCurve; } void KisSmoothingOptions::setStabilizeSensors(bool value) { m_d->stabilizeSensors = value; m_d->writeCompressor.start(); } bool KisSmoothingOptions::stabilizeSensors() const { return m_d->stabilizeSensors; } void KisSmoothingOptions::slotWriteConfig() { KisConfig cfg(false); cfg.setLineSmoothingType(m_d->smoothingType); cfg.setLineSmoothingDistance(m_d->smoothnessDistance); cfg.setLineSmoothingTailAggressiveness(m_d->tailAggressiveness); cfg.setLineSmoothingSmoothPressure(m_d->smoothPressure); cfg.setLineSmoothingScalableDistance(m_d->useScalableDistance); cfg.setLineSmoothingDelayDistance(m_d->delayDistance); cfg.setLineSmoothingUseDelayDistance(m_d->useDelayDistance); cfg.setLineSmoothingFinishStabilizedCurve(m_d->finishStabilizedCurve); cfg.setLineSmoothingStabilizeSensors(m_d->stabilizeSensors); } diff --git a/libs/ui/tool/kis_tool_utils.cpp b/libs/ui/tool/kis_tool_utils.cpp index f1b06f191a..84e8ebea49 100644 --- a/libs/ui/tool/kis_tool_utils.cpp +++ b/libs/ui/tool/kis_tool_utils.cpp @@ -1,214 +1,214 @@ /* * Copyright (c) 2009 Boudewijn Rempt * Copyright (c) 2018 Emmet & Eoin O'Neill * * 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 #include #include #include #include #include #include #include #include "kis_command_utils.h" #include "kis_processing_applicator.h" namespace KisToolUtils { bool pickColor(KoColor &out_color, KisPaintDeviceSP dev, const QPoint &pos, KoColor const *const blendColor, int radius, int blend, bool pure) { KIS_ASSERT(dev); // Bugfix hack forcing pure on first sample to avoid wrong // format blendColor on newly initialized Krita. static bool firstTime = true; if (firstTime == true) { pure = true; firstTime = false; } const KoColorSpace *cs = dev->colorSpace(); KoColor pickedColor(Qt::transparent, cs); // Sampling radius. if (!pure && radius > 1) { QVector pixels; const int effectiveRadius = radius - 1; const QRect pickRect(pos.x() - effectiveRadius, pos.y() - effectiveRadius, 2 * effectiveRadius + 1, 2 * effectiveRadius + 1); KisSequentialConstIterator it(dev, pickRect); const int radiusSq = pow2(effectiveRadius); while (it.nextPixel()) { const QPoint realPos(it.x(), it.y()); const QPoint pt = realPos - pos; if (pow2(pt.x()) + pow2(pt.y()) < radiusSq) { pixels << it.oldRawData(); } } const quint8 **cpixels = const_cast(pixels.constData()); cs->mixColorsOp()->mixColors(cpixels, pixels.size(), pickedColor.data()); } else { dev->pixel(pos.x(), pos.y(), &pickedColor); } // Color blending. if (!pure && blendColor && blend < 100) { //Scale from 0..100% to 0..255 range for mixOp weights. quint8 blendScaled = static_cast(blend * 2.55f); const quint8 *colors[2]; colors[0] = blendColor->data(); colors[1] = pickedColor.data(); qint16 weights[2]; weights[0] = 255 - blendScaled; weights[1] = blendScaled; const KoMixColorsOp *mixOp = dev->colorSpace()->mixColorsOp(); mixOp->mixColors(colors, weights, 2, pickedColor.data()); } pickedColor.convertTo(dev->compositionSourceColorSpace()); bool validColorPicked = pickedColor.opacityU8() != OPACITY_TRANSPARENT_U8; if (validColorPicked) { out_color = pickedColor; } return validColorPicked; } KisNodeSP findNode(KisNodeSP node, const QPoint &point, bool wholeGroup, bool editableOnly) { KisNodeSP foundNode = 0; while (node) { KisLayerSP layer = qobject_cast(node.data()); if (!layer || !layer->isEditable()) { node = node->prevSibling(); continue; } KoColor color(layer->projection()->colorSpace()); layer->projection()->pixel(point.x(), point.y(), &color); KisGroupLayerSP group = dynamic_cast(layer.data()); if ((group && group->passThroughMode()) || color.opacityU8() != OPACITY_TRANSPARENT_U8) { if (layer->inherits("KisGroupLayer") && (!editableOnly || layer->isEditable())) { // if this is a group and the pixel is transparent, don't even enter it foundNode = findNode(node->lastChild(), point, wholeGroup, editableOnly); } else { foundNode = !wholeGroup ? node : node->parent(); } } if (foundNode) break; node = node->prevSibling(); } return foundNode; } bool clearImage(KisImageSP image, KisNodeSP node, KisSelectionSP selection) { if(node && node->hasEditablePaintDevice()) { KUndo2Command *cmd = new KisCommandUtils::LambdaCommand(kundo2_i18n("Clear"), [node, selection] () { KisPaintDeviceSP device = node->paintDevice(); KisTransaction transaction(kundo2_noi18n("internal-clear-command"), device); QRect dirtyRect; if (selection) { dirtyRect = selection->selectedRect(); device->clearSelection(selection); } else { dirtyRect = device->extent(); device->clear(); } device->setDirty(dirtyRect); return transaction.endAndTake(); }); KisProcessingApplicator::runSingleCommandStroke(image, cmd); return true; } return false; } const QString ColorPickerConfig::CONFIG_GROUP_NAME = "tool_color_picker"; ColorPickerConfig::ColorPickerConfig() : toForegroundColor(true) , updateColor(true) - , addPalette(false) + , addColorToCurrentPalette(false) , normaliseValues(false) , sampleMerged(true) , radius(1) , blend(100) { } inline QString getConfigKey(bool defaultActivation) { return defaultActivation ? "ColorPickerDefaultActivation" : "ColorPickerTemporaryActivation"; } void ColorPickerConfig::save(bool defaultActivation) const { KisPropertiesConfiguration props; props.setProperty("toForegroundColor", toForegroundColor); props.setProperty("updateColor", updateColor); - props.setProperty("addPalette", addPalette); + props.setProperty("addPalette", addColorToCurrentPalette); props.setProperty("normaliseValues", normaliseValues); props.setProperty("sampleMerged", sampleMerged); props.setProperty("radius", radius); props.setProperty("blend", blend); KConfigGroup config = KSharedConfig::openConfig()->group(CONFIG_GROUP_NAME); config.writeEntry(getConfigKey(defaultActivation), props.toXML()); } void ColorPickerConfig::load(bool defaultActivation) { KisPropertiesConfiguration props; KConfigGroup config = KSharedConfig::openConfig()->group(CONFIG_GROUP_NAME); props.fromXML(config.readEntry(getConfigKey(defaultActivation))); toForegroundColor = props.getBool("toForegroundColor", true); updateColor = props.getBool("updateColor", true); - addPalette = props.getBool("addPalette", false); + addColorToCurrentPalette = props.getBool("addPalette", false); normaliseValues = props.getBool("normaliseValues", false); sampleMerged = props.getBool("sampleMerged", !defaultActivation ? false : true); radius = props.getInt("radius", 1); blend = props.getInt("blend", 100); } } diff --git a/libs/ui/tool/kis_tool_utils.h b/libs/ui/tool/kis_tool_utils.h index e9b997dc32..601edd3d6a 100644 --- a/libs/ui/tool/kis_tool_utils.h +++ b/libs/ui/tool/kis_tool_utils.h @@ -1,77 +1,77 @@ /* * Copyright (c) 2009 Boudewijn Rempt * Copyright (c) 2018 Emmet & Eoin O'Neill * * 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. */ #ifndef KIS_TOOL_UTILS_H #define KIS_TOOL_UTILS_H #include #include class QPoint; class KoColor; namespace KisToolUtils { struct KRITAUI_EXPORT ColorPickerConfig { ColorPickerConfig(); bool toForegroundColor; bool updateColor; - bool addPalette; + bool addColorToCurrentPalette; bool normaliseValues; bool sampleMerged; int radius; int blend; void save(bool defaultActivation = true) const; void load(bool defaultActivation = true); private: static const QString CONFIG_GROUP_NAME; }; /** * Pick a color based on the given position on the given paint device. * * out_color - Output parameter returning newly picked color. * dev - Paint device to pick from. * pos - Position to pick from. * blendColor - Optional color to be blended with. * radius - Picking area radius in pixels. * blend - Blend percentage. 100% all picked, 0% all blendColor. * pure - Whether to bypass radius, blending, and active layer settings for pure picking. * * RETURN - Returns TRUE whenever a valid color is picked. */ bool KRITAUI_EXPORT pickColor(KoColor &out_color, KisPaintDeviceSP dev, const QPoint &pos, KoColor const *const blendColor = nullptr, int radius = 1, int blend = 100, bool pure = false); /** * Recursively search a node with a non-transparent pixel */ KisNodeSP KRITAUI_EXPORT findNode(KisNodeSP node, const QPoint &point, bool wholeGroup, bool editableOnly = true); /** * return true if success * Clears the image. Selection is optional, use 0 to clear everything. */ bool KRITAUI_EXPORT clearImage(KisImageSP image, KisNodeSP node, KisSelectionSP selection); } #endif // KIS_TOOL_UTILS_H diff --git a/libs/ui/tool/strokes/KisFreehandStrokeInfo.cpp b/libs/ui/tool/strokes/KisFreehandStrokeInfo.cpp index e3f95cabd6..89c82949be 100644 --- a/libs/ui/tool/strokes/KisFreehandStrokeInfo.cpp +++ b/libs/ui/tool/strokes/KisFreehandStrokeInfo.cpp @@ -1,62 +1,58 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * 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 "KisFreehandStrokeInfo.h" #include #include KisFreehandStrokeInfo::KisFreehandStrokeInfo() - : painter(new KisPainter()), - dragDistance(new KisDistanceInformation()), - m_parentStrokeInfo(0), - m_childStrokeInfo(0) + : painter(new KisPainter()) + , dragDistance(new KisDistanceInformation()) { } KisFreehandStrokeInfo::KisFreehandStrokeInfo(const KisDistanceInformation &startDist) - : painter(new KisPainter()), - dragDistance(new KisDistanceInformation(startDist)), - m_parentStrokeInfo(0), - m_childStrokeInfo(0) + : painter(new KisPainter()) + , dragDistance(new KisDistanceInformation(startDist)) { } KisFreehandStrokeInfo::KisFreehandStrokeInfo(KisFreehandStrokeInfo *rhs, int levelOfDetail) - : painter(new KisPainter()), - dragDistance(new KisDistanceInformation(*rhs->dragDistance, levelOfDetail)), - m_parentStrokeInfo(rhs) + : painter(new KisPainter()) + , dragDistance(new KisDistanceInformation(*rhs->dragDistance, levelOfDetail)) + , m_parentStrokeInfo(rhs) { rhs->m_childStrokeInfo = this; } KisFreehandStrokeInfo::~KisFreehandStrokeInfo() { if (m_parentStrokeInfo) { m_parentStrokeInfo->m_childStrokeInfo = 0; } delete(painter); delete(dragDistance); } KisDistanceInformation* KisFreehandStrokeInfo::buddyDragDistance() { return m_childStrokeInfo ? m_childStrokeInfo->dragDistance : 0; } diff --git a/libs/ui/tool/strokes/KisFreehandStrokeInfo.h b/libs/ui/tool/strokes/KisFreehandStrokeInfo.h index 88dee42217..c430e0c5e6 100644 --- a/libs/ui/tool/strokes/KisFreehandStrokeInfo.h +++ b/libs/ui/tool/strokes/KisFreehandStrokeInfo.h @@ -1,56 +1,56 @@ /* * Copyright (c) 2017 Dmitry Kazakov * * 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. */ #ifndef KISPAINTINGSTROKEDATA_H #define KISPAINTINGSTROKEDATA_H #include "kritaui_export.h" class KisPainter; class KisDistanceInformation; /** * The distance information should be associated with each * painting stroke individually, so we store and manipulate * with them together using KisPaintingStrokeInfo structure */ class KRITAUI_EXPORT KisFreehandStrokeInfo { public: KisFreehandStrokeInfo(); KisFreehandStrokeInfo(const KisDistanceInformation &startDist); KisFreehandStrokeInfo(KisFreehandStrokeInfo *rhs, int levelOfDetail); ~KisFreehandStrokeInfo(); KisPainter *painter; KisDistanceInformation *dragDistance; /** * The distance inforametion of the associated LodN * stroke. Returns zero if LodN stroke has already finished * execution or does not exist. */ KisDistanceInformation* buddyDragDistance(); private: - KisFreehandStrokeInfo *m_parentStrokeInfo; - KisFreehandStrokeInfo *m_childStrokeInfo; + KisFreehandStrokeInfo *m_parentStrokeInfo {0}; + KisFreehandStrokeInfo *m_childStrokeInfo {0}; }; #endif // KISPAINTINGSTROKEDATA_H diff --git a/libs/ui/widgets/KisScreenColorPicker.cpp b/libs/ui/widgets/KisScreenColorPicker.cpp index d4e61741af..7e223038e2 100644 --- a/libs/ui/widgets/KisScreenColorPicker.cpp +++ b/libs/ui/widgets/KisScreenColorPicker.cpp @@ -1,284 +1,297 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * 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 #include #include #include #include #include #include #include #include #include #include #include "kis_shared_ptr.h" #include "kis_icon.h" #include "kis_image.h" #include "kis_wrapped_rect.h" +#include "KisDocument.h" #include "KisPart.h" +#include "KisReferenceImagesLayer.h" #include "KisScreenColorPicker.h" #include "KisDlgInternalColorSelector.h" struct KisScreenColorPicker::Private { QPushButton *screenColorPickerButton = 0; QLabel *lblScreenColorInfo = 0; KoColor currentColor = KoColor(); KoColor beforeScreenColorPicking = KoColor(); KisScreenColorPickingEventFilter *colorPickingEventFilter = 0; #ifdef Q_OS_WIN32 QTimer *updateTimer = 0; QWindow dummyTransparentWindow; #endif }; KisScreenColorPicker::KisScreenColorPicker(bool showInfoLabel, QWidget *parent) : KisScreenColorPickerBase(parent), m_d(new Private) { QVBoxLayout *layout = new QVBoxLayout(); this->setLayout(layout); m_d->screenColorPickerButton = new QPushButton(); m_d->screenColorPickerButton->setMinimumHeight(25); this->layout()->addWidget(m_d->screenColorPickerButton); if (showInfoLabel) { m_d->lblScreenColorInfo = new QLabel(QLatin1String("\n")); this->layout()->addWidget(m_d->lblScreenColorInfo); } connect(m_d->screenColorPickerButton, SIGNAL(clicked()), SLOT(pickScreenColor())); updateIcons(); #ifdef Q_OS_WIN32 m_d->updateTimer = new QTimer(this); m_d->dummyTransparentWindow.resize(1, 1); m_d->dummyTransparentWindow.setFlags(Qt::Tool | Qt::FramelessWindowHint); connect(m_d->updateTimer, SIGNAL(timeout()), SLOT(updateColorPicking())); #endif } KisScreenColorPicker::~KisScreenColorPicker() { } void KisScreenColorPicker::updateIcons() { m_d->screenColorPickerButton->setIcon(kisIcon("krita_tool_color_picker")); } KoColor KisScreenColorPicker::currentColor() { return m_d->currentColor; } void KisScreenColorPicker::pickScreenColor() { if (!m_d->colorPickingEventFilter) m_d->colorPickingEventFilter = new KisScreenColorPickingEventFilter(this); this->installEventFilter(m_d->colorPickingEventFilter); // If user pushes Escape, the last color before picking will be restored. m_d->beforeScreenColorPicking = currentColor(); grabMouse(Qt::CrossCursor); #ifdef Q_OS_WIN32 // excludes WinCE and WinRT // On Windows mouse tracking doesn't work over other processes's windows m_d->updateTimer->start(30); // HACK: Because mouse grabbing doesn't work across processes, we have to have a dummy, // invisible window to catch the mouse click, otherwise we will click whatever we clicked // and loose focus. m_d->dummyTransparentWindow.show(); #endif grabKeyboard(); /* With setMouseTracking(true) the desired color can be more precisely picked up, * and continuously pushing the mouse button is not necessary. */ setMouseTracking(true); m_d->screenColorPickerButton->setDisabled(true); const QPoint globalPos = QCursor::pos(); setCurrentColor(grabScreenColor(globalPos)); updateColorLabelText(globalPos); } void KisScreenColorPicker::setCurrentColor(KoColor c) { m_d->currentColor = c; } KoColor KisScreenColorPicker::grabScreenColor(const QPoint &p) { // First check whether we're clicking on a Krita window for some real color picking Q_FOREACH(KisView *view, KisPart::instance()->views()) { - QWidget *canvasWidget = view->canvasBase()->canvasWidget(); + const KisCanvas2 *canvas = view->canvasBase(); + const QWidget *canvasWidget = canvas->canvasWidget(); QPoint widgetPoint = canvasWidget->mapFromGlobal(p); - if (canvasWidget->rect().contains(widgetPoint)) { - QPointF imagePoint = view->canvasBase()->coordinatesConverter()->widgetToImage(widgetPoint); + if (canvasWidget->visibleRegion().contains(widgetPoint)) { KisImageWSP image = view->image(); if (image) { + QPointF imagePoint = canvas->coordinatesConverter()->widgetToImage(widgetPoint); + // pick from reference images first + KisSharedPtr referenceImageLayer = view->document()->referenceImagesLayer(); + + if (referenceImageLayer && canvas->referenceImagesDecoration()->visible()) { + QColor color = referenceImageLayer->getPixel(imagePoint); + if (color.isValid()) { + return KoColor(color, image->colorSpace()); + } + } + if (image->wrapAroundModePermitted()) { imagePoint = KisWrappedRect::ptToWrappedPt(imagePoint.toPoint(), image->bounds()); } KoColor pickedColor = KoColor(); image->projection()->pixel(imagePoint.x(), imagePoint.y(), &pickedColor); return pickedColor; } } } // And otherwise, we'll check the desktop const QDesktopWidget *desktop = QApplication::desktop(); const QPixmap pixmap = QGuiApplication::screens().at(desktop->screenNumber())->grabWindow(desktop->winId(), p.x(), p.y(), 1, 1); QImage i = pixmap.toImage(); KoColor col = KoColor(); col.fromQColor(QColor::fromRgb(i.pixel(0, 0))); return col; } void KisScreenColorPicker::updateColorLabelText(const QPoint &globalPos) { if (m_d->lblScreenColorInfo) { KoColor col = grabScreenColor(globalPos); QString colname = KoColor::toQString(col); QString location = QString::number(globalPos.x())+QString(", ")+QString::number(globalPos.y()); m_d->lblScreenColorInfo->setWordWrap(true); m_d->lblScreenColorInfo->setText(location+QString(": ")+colname); } } bool KisScreenColorPicker::handleColorPickingMouseMove(QMouseEvent *e) { // If the cross is visible the grabbed color will be black most of the times //cp->setCrossVisible(!cp->geometry().contains(e->pos())); continueUpdateColorPicking(e->globalPos()); return true; } bool KisScreenColorPicker::handleColorPickingMouseButtonRelease(QMouseEvent *e) { setCurrentColor(grabScreenColor(e->globalPos())); Q_EMIT sigNewColorPicked(currentColor()); releaseColorPicking(); return true; } bool KisScreenColorPicker::handleColorPickingKeyPress(QKeyEvent *e) { if (e->matches(QKeySequence::Cancel)) { releaseColorPicking(); setCurrentColor(m_d->beforeScreenColorPicking); } else if (e->key() == Qt::Key_Return || e->key() == Qt::Key_Enter) { setCurrentColor(grabScreenColor(QCursor::pos())); releaseColorPicking(); } e->accept(); return true; } void KisScreenColorPicker::releaseColorPicking() { removeEventFilter(m_d->colorPickingEventFilter); releaseMouse(); #ifdef Q_OS_WIN32 m_d->updateTimer->stop(); m_d->dummyTransparentWindow.setVisible(false); #endif releaseKeyboard(); setMouseTracking(false); if (m_d->lblScreenColorInfo) { m_d->lblScreenColorInfo->setText(QLatin1String("\n")); } m_d->screenColorPickerButton->setDisabled(false); } void KisScreenColorPicker::changeEvent(QEvent *e) { QWidget::changeEvent(e); } void KisScreenColorPicker::updateColorPicking() { static QPoint lastGlobalPos; QPoint newGlobalPos = QCursor::pos(); if (lastGlobalPos == newGlobalPos) return; lastGlobalPos = newGlobalPos; if (!rect().contains(mapFromGlobal(newGlobalPos))) { // Inside the dialog mouse tracking works, handleColorPickingMouseMove will be called continueUpdateColorPicking(newGlobalPos); #ifdef Q_OS_WIN32 m_d->dummyTransparentWindow.setPosition(newGlobalPos); #endif } } void KisScreenColorPicker::continueUpdateColorPicking(const QPoint &globalPos) { const KoColor color = grabScreenColor(globalPos); // QTBUG-39792, do not change standard, custom color selectors while moving as // otherwise it is not possible to pre-select a custom cell for assignment. setCurrentColor(color); updateColorLabelText(globalPos); } // Event filter to be installed on the dialog while in color-picking mode. KisScreenColorPickingEventFilter::KisScreenColorPickingEventFilter(KisScreenColorPicker *w, QObject *parent) : QObject(parent) , m_w(w) {} bool KisScreenColorPickingEventFilter::eventFilter(QObject *, QEvent *event) { switch (event->type()) { case QEvent::MouseMove: return m_w->handleColorPickingMouseMove(static_cast(event)); case QEvent::MouseButtonRelease: return m_w->handleColorPickingMouseButtonRelease(static_cast(event)); case QEvent::KeyPress: return m_w->handleColorPickingKeyPress(static_cast(event)); default: break; } return false; } // Register the color picker factory with the internal color selector struct ColorPickerRegistrar { ColorPickerRegistrar() { KisDlgInternalColorSelector::setScreenColorPickerFactory(KisScreenColorPicker::createScreenColorPicker); } }; static ColorPickerRegistrar s_colorPickerRegistrar; diff --git a/libs/widgets/KisVisualEllipticalSelectorShape.cpp b/libs/widgets/KisVisualEllipticalSelectorShape.cpp index f6e7ed63fc..c69dc4e42d 100644 --- a/libs/widgets/KisVisualEllipticalSelectorShape.cpp +++ b/libs/widgets/KisVisualEllipticalSelectorShape.cpp @@ -1,283 +1,283 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * 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 "KisVisualEllipticalSelectorShape.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColorConversions.h" #include "KoColorDisplayRendererInterface.h" #include "KoChannelInfo.h" #include #include #include "kis_signal_compressor.h" #include "kis_debug.h" #include "kis_global.h" KisVisualEllipticalSelectorShape::KisVisualEllipticalSelectorShape(QWidget *parent, Dimensions dimension, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer, int barWidth, singelDTypes d) : KisVisualColorSelectorShape(parent, dimension, cs, channel1, channel2, displayRenderer) { //qDebug() << "creating KisVisualEllipticalSelectorShape" << this; m_type = d; m_barWidth = barWidth; } KisVisualEllipticalSelectorShape::~KisVisualEllipticalSelectorShape() { //qDebug() << "deleting KisVisualEllipticalSelectorShape" << this; } QSize KisVisualEllipticalSelectorShape::sizeHint() const { return QSize(180,180); } void KisVisualEllipticalSelectorShape::setBorderWidth(int width) { m_barWidth = width; forceImageUpdate(); update(); } QRect KisVisualEllipticalSelectorShape::getSpaceForSquare(QRect geom) { int sizeValue = qMin(width(),height()); QRect b(geom.left(), geom.top(), sizeValue, sizeValue); QLineF radius(b.center(), QPointF(b.left()+m_barWidth, b.center().y()) ); radius.setAngle(135); QPointF tl = radius.p2(); radius.setAngle(315); QPointF br = radius.p2(); QRect r(tl.toPoint(), br.toPoint()); return r; } QRect KisVisualEllipticalSelectorShape::getSpaceForCircle(QRect geom) { int sizeValue = qMin(width(),height()); QRect b(geom.left(), geom.top(), sizeValue, sizeValue); QPointF tl = QPointF (b.topLeft().x()+m_barWidth, b.topLeft().y()+m_barWidth); QPointF br = QPointF (b.bottomRight().x()-m_barWidth, b.bottomRight().y()-m_barWidth); QRect r(tl.toPoint(), br.toPoint()); return r; } QRect KisVisualEllipticalSelectorShape::getSpaceForTriangle(QRect geom) { int sizeValue = qMin(width(),height()); QRect b(geom.left(), geom.top(), sizeValue, sizeValue); QLineF radius(b.center(), QPointF(b.left()+m_barWidth, b.center().y()) ); radius.setAngle(90);//point at yellowgreen :) QPointF t = radius.p2(); radius.setAngle(330);//point to purple :) QPointF br = radius.p2(); radius.setAngle(210);//point to cerulean :) QPointF bl = radius.p2(); QPointF tl = QPoint(bl.x(),t.y()); QRect r(tl.toPoint(), br.toPoint()); return r; } QPointF KisVisualEllipticalSelectorShape::convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) const { qreal x; qreal y; qreal offset=7.0; qreal a = (qreal)width()*0.5; QPointF center(a, a); QLineF line(center, QPoint((m_barWidth*0.5),a)); qreal angle = coordinate.x()*360.0; angle = fmod(angle+180.0,360.0); angle = 180.0-angle; angle = angle+180.0; if (m_type==KisVisualEllipticalSelectorShape::borderMirrored) { angle = (coordinate.x()/2)*360.0; angle = fmod((angle+270.0), 360.0); } line.setAngle(angle); if (getDimensions()!=KisVisualColorSelectorShape::onedimensional) { line.setLength(qMin(coordinate.y()*(a-offset), a-offset)); } x = qRound(line.p2().x()); y = qRound(line.p2().y()); return QPointF(x,y); } QPointF KisVisualEllipticalSelectorShape::convertWidgetCoordinateToShapeCoordinate(QPoint coordinate) const { //default implementation: qreal x = 0.5; qreal y = 1.0; qreal offset = 7.0; QPointF center = QRectF(QPointF(0.0, 0.0), this->size()).center(); - qreal a = (this->width()/2); + qreal a = (qreal(this->width()) / qreal(2)); qreal xRel = center.x()-coordinate.x(); qreal yRel = center.y()-coordinate.y(); qreal radius = sqrt(xRel*xRel+yRel*yRel); if (m_type!=KisVisualEllipticalSelectorShape::borderMirrored){ qreal angle = atan2(yRel, xRel); angle = kisRadiansToDegrees(angle); angle = fmod(angle+360, 360.0); x = angle/360.0; if (getDimensions()==KisVisualColorSelectorShape::twodimensional) { y = qBound(0.0,radius/(a-offset), 1.0); } } else { qreal angle = atan2(xRel, yRel); angle = kisRadiansToDegrees(angle); angle = fmod(angle+180, 360.0); if (angle>180.0) { angle = 360.0-angle; } x = (angle/360.0)*2; if (getDimensions()==KisVisualColorSelectorShape::twodimensional) { y = qBound(0.0,(radius+offset)/a, 1.0); } } return QPointF(x, y); } QRegion KisVisualEllipticalSelectorShape::getMaskMap() { QRegion mask = QRegion(0,0,width(),height(), QRegion::Ellipse); if (getDimensions()==KisVisualColorSelectorShape::onedimensional) { mask = mask.subtracted(QRegion(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2), QRegion::Ellipse)); } return mask; } QImage KisVisualEllipticalSelectorShape::renderBackground(const QVector4D &channelValues, quint32 pixelSize) const { const KisVisualColorSelector *selector = qobject_cast(parent()); Q_ASSERT(selector); // optimization assumes widget is (close to) square, but should still render correctly as ellipse int rMaxSquare = qRound(qMax(width(), height()) * 0.5f + 0.5f); rMaxSquare *= rMaxSquare; int rMinSquare = 0; if (getDimensions() == Dimensions::onedimensional) { rMinSquare = qMax(0, qRound(qMin(width(), height()) * 0.5f - m_barWidth)); rMinSquare *= rMinSquare; } int cx = width()/2; int cy = height()/2; // Fill a buffer with the right kocolors quint32 imageSize = width() * height() * pixelSize; QScopedArrayPointer raw(new quint8[imageSize] {}); quint8 *dataPtr = raw.data(); bool is2D = (getDimensions() == Dimensions::twodimensional); QVector4D coordinates = channelValues; QVector channels = getChannels(); for (int y = 0; y < height(); y++) { int dy = y - cy; for (int x=0; x < width(); x++) { int dx = x - cx; int radSquare = dx*dx + dy*dy; if (radSquare >= rMinSquare && radSquare < rMaxSquare) { QPointF newcoordinate = convertWidgetCoordinateToShapeCoordinate(QPoint(x, y)); coordinates[channels.at(0)] = newcoordinate.x(); if (is2D){ coordinates[channels.at(1)] = newcoordinate.y(); } KoColor c = selector->convertShapeCoordsToKoColor(coordinates); memcpy(dataPtr, c.data(), pixelSize); } dataPtr += pixelSize; } } QImage image = convertImageMap(raw.data(), imageSize); // cleanup edges by erasing with antialiased circles QPainter painter(&image); painter.setRenderHint(QPainter::Antialiasing); painter.setCompositionMode(QPainter::CompositionMode_Clear); QPen pen; pen.setWidth(5); painter.setPen(pen); painter.drawEllipse(QRect(0,0,width(),height())); if (getDimensions()==KisVisualColorSelectorShape::onedimensional) { QRect innerRect(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2)); painter.setBrush(Qt::SolidPattern); painter.drawEllipse(innerRect); } return image; } void KisVisualEllipticalSelectorShape::drawCursor() { //qDebug() << this << "KisVisualEllipticalSelectorShape::drawCursor: image needs update" << imagesNeedUpdate(); QPointF cursorPoint = convertShapeCoordinateToWidgetCoordinate(getCursorPosition()); QImage fullSelector = getImageMap(); QColor col = getColorFromConverter(getCurrentColor()); QPainter painter; painter.begin(&fullSelector); painter.setRenderHint(QPainter::Antialiasing); QRect innerRect(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2)); QBrush fill; fill.setStyle(Qt::SolidPattern); int cursorwidth = 5; if (m_type==KisVisualEllipticalSelectorShape::borderMirrored) { painter.setPen(Qt::white); fill.setColor(Qt::white); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); QPoint mirror(innerRect.center().x()+(innerRect.center().x()-cursorPoint.x()),cursorPoint.y()); painter.drawEllipse(mirror, cursorwidth, cursorwidth); fill.setColor(col); painter.setPen(Qt::black); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth-1, cursorwidth-1); painter.drawEllipse(mirror, cursorwidth-1, cursorwidth-1); } else { painter.setPen(Qt::white); fill.setColor(Qt::white); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); fill.setColor(col); painter.setPen(Qt::black); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth-1.0, cursorwidth-1.0); } painter.end(); setFullImage(fullSelector); } diff --git a/libs/widgets/KisVisualTriangleSelectorShape.cpp b/libs/widgets/KisVisualTriangleSelectorShape.cpp index 18cc17c0f1..39c60fe0bf 100644 --- a/libs/widgets/KisVisualTriangleSelectorShape.cpp +++ b/libs/widgets/KisVisualTriangleSelectorShape.cpp @@ -1,205 +1,205 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * 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 "KisVisualTriangleSelectorShape.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColorConversions.h" #include "KoColorDisplayRendererInterface.h" #include "KoChannelInfo.h" #include #include #include "kis_signal_compressor.h" #include "kis_debug.h" #include "kis_global.h" KisVisualTriangleSelectorShape::KisVisualTriangleSelectorShape(QWidget *parent, - Dimensions dimension, - const KoColorSpace *cs, - int channel1, int channel2, - const KoColorDisplayRendererInterface *displayRenderer, - int barwidth) + Dimensions dimension, + const KoColorSpace *cs, + int channel1, int channel2, + const KoColorDisplayRendererInterface *displayRenderer, + int barwidth) : KisVisualColorSelectorShape(parent, dimension, cs, channel1, channel2, displayRenderer) { //qDebug() << "creating KisVisualTriangleSelectorShape" << this; m_barWidth = barwidth; setTriangle(); } KisVisualTriangleSelectorShape::~KisVisualTriangleSelectorShape() { //qDebug() << "deleting KisVisualTriangleSelectorShape" << this; } void KisVisualTriangleSelectorShape::setBorderWidth(int width) { m_barWidth = width; } QRect KisVisualTriangleSelectorShape::getSpaceForSquare(QRect geom) { return geom; } QRect KisVisualTriangleSelectorShape::getSpaceForCircle(QRect geom) { return geom; } QRect KisVisualTriangleSelectorShape::getSpaceForTriangle(QRect geom) { return geom; } void KisVisualTriangleSelectorShape::setTriangle() { QPoint apex = QPoint (width()*0.5,0); QPolygon triangle; triangle<< QPoint(0,height()) << apex << QPoint(width(),height()) << QPoint(0,height()); m_triangle = triangle; QLineF a(triangle.at(0),triangle.at(1)); QLineF b(triangle.at(0),triangle.at(2)); QLineF ap(triangle.at(2), a.pointAt(0.5)); QLineF bp(triangle.at(1), b.pointAt(0.5)); QPointF intersect; ap.intersect(bp,&intersect); m_center = intersect; QLineF r(triangle.at(0), intersect); m_radius = r.length(); } QPointF KisVisualTriangleSelectorShape::convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) const { qreal offset=7.0;//the offset is so we get a nice little border that allows selecting extreme colors better. qreal yOffset = (cos(kisDegreesToRadians(30))*offset)*2; qreal xOffset = qFloor(sin(kisDegreesToRadians(30))*offset); qreal y = qMax(qMin((coordinate.y()*(height()-yOffset-offset))+yOffset, (qreal)height()-offset),yOffset); qreal triWidth = width(); qreal horizontalLineLength = ((y-yOffset)*(2./sqrt(3.))); qreal horizontalLineStart = (triWidth*0.5)-(horizontalLineLength*0.5); qreal relativeX = qMin(coordinate.x()*(horizontalLineLength), horizontalLineLength); qreal x = qMax(relativeX + horizontalLineStart + xOffset, horizontalLineStart+xOffset); if (y<=yOffset){ x = 0.5*width(); } return QPointF(x,y); } QPointF KisVisualTriangleSelectorShape::convertWidgetCoordinateToShapeCoordinate(QPoint coordinate) const { //default implementation: gotten from the kotrianglecolorselector/kis_color_selector_triangle. qreal x = 0.5; qreal y = 0.5; qreal offset=7.0; //the offset is so we get a nice little border that allows selecting extreme colors better. qreal yOffset = (cos(kisDegreesToRadians(30))*offset)*2; qreal xOffset = qFloor(sin(kisDegreesToRadians(30))*offset); y = qMin(qMax((qreal)coordinate.y()-yOffset, 0.0)/(height()-yOffset-offset), 1.0); qreal triWidth = width(); qreal horizontalLineLength = ((qreal)coordinate.y()-yOffset)*(2./sqrt(3.)); qreal horizontalLineStart = (triWidth*0.5)-(horizontalLineLength*0.5); qreal relativeX = qMax((qreal)coordinate.x()-xOffset-horizontalLineStart,0.0); x = qMin(relativeX/horizontalLineLength, 1.0); if (coordinate.y()<=yOffset){ x = 0.5; } return QPointF(x, y); } QRegion KisVisualTriangleSelectorShape::getMaskMap() { QRegion mask = QRegion(m_triangle); //QRegion mask = QRegion(); //if (getDimensions()==KisVisualColorSelectorShape::onedimensional) { // mask = mask.subtracted(QRegion(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2))); //} return mask; } void KisVisualTriangleSelectorShape::resizeEvent(QResizeEvent *e) { //qDebug() << this << "KisVisualTriangleSelectorShape::resizeEvent(QResizeEvent *)"; setTriangle(); KisVisualColorSelectorShape::resizeEvent(e); } void KisVisualTriangleSelectorShape::drawCursor() { //qDebug() << this << "KisVisualTriangleSelectorShape::drawCursor: image needs update" << imagesNeedUpdate(); QPointF cursorPoint = convertShapeCoordinateToWidgetCoordinate(getCursorPosition()); QImage fullSelector = getImageMap(); QColor col = getColorFromConverter(getCurrentColor()); QPainter painter; painter.begin(&fullSelector); painter.setRenderHint(QPainter::Antialiasing); painter.save(); painter.setCompositionMode(QPainter::CompositionMode_Clear); QPen pen; pen.setWidth(10); painter.setPen(pen); painter.drawPolygon(m_triangle); painter.restore(); //QPainterPath path; QBrush fill; fill.setStyle(Qt::SolidPattern); int cursorwidth = 5; //QRect innerRect(m_barWidth, m_barWidth, width()-(m_barWidth*2), height()-(m_barWidth*2)); /*if(m_type==KisVisualTriangleSelectorShape::borderMirrored){ painter.setPen(Qt::white); fill.setColor(Qt::white); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); QPoint mirror(innerRect.center().x()+(innerRect.center().x()-cursorPoint.x()),cursorPoint.y()); painter.drawEllipse(mirror, cursorwidth, cursorwidth); fill.setColor(col); painter.setPen(Qt::black); painter.setBrush(fill); painter.drawEllipse(cursorPoint, cursorwidth-1, cursorwidth-1); painter.drawEllipse(mirror, cursorwidth-1, cursorwidth-1); } else {*/ - painter.setPen(Qt::white); - fill.setColor(Qt::white); - painter.setBrush(fill); - painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); - fill.setColor(col); - painter.setPen(Qt::black); - painter.setBrush(fill); - painter.drawEllipse(cursorPoint, cursorwidth-1.0, cursorwidth-1.0); + painter.setPen(Qt::white); + fill.setColor(Qt::white); + painter.setBrush(fill); + painter.drawEllipse(cursorPoint, cursorwidth, cursorwidth); + fill.setColor(col); + painter.setPen(Qt::black); + painter.setBrush(fill); + painter.drawEllipse(cursorPoint, cursorwidth-1.0, cursorwidth-1.0); //} painter.end(); setFullImage(fullSelector); } diff --git a/libs/widgets/KisVisualTriangleSelectorShape.h b/libs/widgets/KisVisualTriangleSelectorShape.h index 8e7ce92526..9d4f052702 100644 --- a/libs/widgets/KisVisualTriangleSelectorShape.h +++ b/libs/widgets/KisVisualTriangleSelectorShape.h @@ -1,76 +1,74 @@ /* * Copyright (C) Wolthera van Hovell tot Westerflier , (C) 2016 * * 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. */ #ifndef KIS_VISUAL_TRIANGLE_SELECTOR_SHAPE_H #define KIS_VISUAL_TRIANGLE_SELECTOR_SHAPE_H #include #include #include #include #include #include #include #include "KoColorDisplayRendererInterface.h" #include "KisColorSelectorConfiguration.h" #include "KisVisualColorSelectorShape.h" class KisVisualTriangleSelectorShape : public KisVisualColorSelectorShape { Q_OBJECT public: - enum singelDTypes{border, borderMirrored}; explicit KisVisualTriangleSelectorShape(QWidget *parent, Dimensions dimension, const KoColorSpace *cs, int channel1, int channel2, const KoColorDisplayRendererInterface *displayRenderer = KoDumbColorDisplayRenderer::instance(), int barwidth=20 ); ~KisVisualTriangleSelectorShape() override; void setBorderWidth(int width) override; void setTriangle(); /** * @brief getSpaceForSquare * @param geom the full widget rectangle * @return rectangle with enough space for second widget */ QRect getSpaceForSquare(QRect geom) override; QRect getSpaceForCircle(QRect geom) override; QRect getSpaceForTriangle(QRect geom) override; protected: void resizeEvent(QResizeEvent *e) override; private: QPointF convertShapeCoordinateToWidgetCoordinate(QPointF coordinate) const override; QPointF convertWidgetCoordinateToShapeCoordinate(QPoint coordinate) const override; - singelDTypes m_type; int m_barWidth; QPolygon m_triangle; QPointF m_center; qreal m_radius; QRegion getMaskMap() override; void drawCursor() override; }; #endif diff --git a/plugins/color/lcms2engine/LcmsColorSpace.h b/plugins/color/lcms2engine/LcmsColorSpace.h index 84d193daf6..669ccbf44e 100644 --- a/plugins/color/lcms2engine/LcmsColorSpace.h +++ b/plugins/color/lcms2engine/LcmsColorSpace.h @@ -1,488 +1,493 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2005-2006 C. Boemann * Copyright (c) 2004,2006-2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOLCMSCOLORSPACE_H_ #define KOLCMSCOLORSPACE_H_ #include #include #include #include +#include "kis_assert.h" + + class LcmsColorProfileContainer; class KoLcmsInfo { struct Private { cmsUInt32Number cmType; // The colorspace type as defined by littlecms cmsColorSpaceSignature colorSpaceSignature; // The colorspace signature as defined in icm/icc files }; public: KoLcmsInfo(cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature) : d(new Private) { d->cmType = cmType; d->colorSpaceSignature = colorSpaceSignature; } virtual ~KoLcmsInfo() { delete d; } virtual quint32 colorSpaceType() const { return d->cmType; } virtual cmsColorSpaceSignature colorSpaceSignature() const { return d->colorSpaceSignature; } private: Private *const d; }; struct KoLcmsDefaultTransformations { cmsHTRANSFORM toRGB; cmsHTRANSFORM fromRGB; static cmsHPROFILE s_RGBProfile; static QMap< QString, QMap< LcmsColorProfileContainer *, KoLcmsDefaultTransformations * > > s_transformations; }; /** * This is the base class for all colorspaces that are based on the lcms library, for instance * RGB 8bits and 16bits, CMYK 8bits and 16bits, LAB... */ template class LcmsColorSpace : public KoColorSpaceAbstract<_CSTraits>, public KoLcmsInfo { struct KoLcmsColorTransformation : public KoColorTransformation { KoLcmsColorTransformation(const KoColorSpace *colorSpace) : KoColorTransformation() , m_colorSpace(colorSpace) { csProfile = 0; cmstransform = 0; cmsAlphaTransform = 0; profiles[0] = 0; profiles[1] = 0; profiles[2] = 0; } ~KoLcmsColorTransformation() override { if (cmstransform) { cmsDeleteTransform(cmstransform); } if (profiles[0] && profiles[0] != csProfile) { cmsCloseProfile(profiles[0]); } if (profiles[1] && profiles[1] != csProfile) { cmsCloseProfile(profiles[1]); } if (profiles[2] && profiles[2] != csProfile) { cmsCloseProfile(profiles[2]); } } void transform(const quint8 *src, quint8 *dst, qint32 nPixels) const override { cmsDoTransform(cmstransform, const_cast(src), dst, nPixels); qint32 numPixels = nPixels; qint32 pixelSize = m_colorSpace->pixelSize(); int index = 0; if (cmsAlphaTransform) { qreal *alpha = new qreal[nPixels]; qreal *dstalpha = new qreal[nPixels]; while (index < nPixels) { alpha[index] = m_colorSpace->opacityF(src); src += pixelSize; index++; } cmsDoTransform(cmsAlphaTransform, const_cast(alpha), static_cast(dstalpha), nPixels); for (int i = 0; i < numPixels; i++) { m_colorSpace->setOpacity(dst, dstalpha[i], 1); dst += pixelSize; } delete [] alpha; delete [] dstalpha; } else { while (numPixels > 0) { qreal alpha = m_colorSpace->opacityF(src); m_colorSpace->setOpacity(dst, alpha, 1); src += pixelSize; dst += pixelSize; numPixels--; } } } const KoColorSpace *m_colorSpace; cmsHPROFILE csProfile; cmsHPROFILE profiles[3]; cmsHTRANSFORM cmstransform; cmsHTRANSFORM cmsAlphaTransform; }; struct Private { mutable quint8 *qcolordata; // A small buffer for conversion from and to qcolor. KoLcmsDefaultTransformations *defaultTransformations; mutable cmsHPROFILE lastRGBProfile; // Last used profile to transform to/from RGB mutable cmsHTRANSFORM lastToRGB; // Last used transform to transform to RGB mutable cmsHTRANSFORM lastFromRGB; // Last used transform to transform from RGB LcmsColorProfileContainer *profile; KoColorProfile *colorProfile; QMutex mutex; }; protected: LcmsColorSpace(const QString &id, const QString &name, cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature, KoColorProfile *p) : KoColorSpaceAbstract<_CSTraits>(id, name) , KoLcmsInfo(cmType, colorSpaceSignature) , d(new Private()) { Q_ASSERT(p); // No profile means the lcms color space can't work Q_ASSERT(profileIsCompatible(p)); d->profile = asLcmsProfile(p); Q_ASSERT(d->profile); d->colorProfile = p; d->qcolordata = 0; d->lastRGBProfile = 0; d->lastToRGB = 0; d->lastFromRGB = 0; d->defaultTransformations = 0; } ~LcmsColorSpace() override { delete d->colorProfile; delete[] d->qcolordata; delete d->defaultTransformations; delete d; } void init() { // Default pixel buffer for QColor conversion d->qcolordata = new quint8[3]; Q_CHECK_PTR(d->qcolordata); - Q_ASSERT(d->profile); + KIS_ASSERT(d->profile); if (KoLcmsDefaultTransformations::s_RGBProfile == 0) { KoLcmsDefaultTransformations::s_RGBProfile = cmsCreate_sRGBProfile(); } d->defaultTransformations = KoLcmsDefaultTransformations::s_transformations[this->id()][ d->profile]; if (!d->defaultTransformations) { d->defaultTransformations = new KoLcmsDefaultTransformations; d->defaultTransformations->fromRGB = cmsCreateTransform(KoLcmsDefaultTransformations::s_RGBProfile, TYPE_BGR_8, d->profile->lcmsProfile(), this->colorSpaceType(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); - Q_ASSERT(d->defaultTransformations->fromRGB); + KIS_SAFE_ASSERT_RECOVER_NOOP(d->defaultTransformations->fromRGB || !d->colorProfile->isSuitableForOutput()); + d->defaultTransformations->toRGB = cmsCreateTransform(d->profile->lcmsProfile(), this->colorSpaceType(), KoLcmsDefaultTransformations::s_RGBProfile, TYPE_BGR_8, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); - Q_ASSERT(d->defaultTransformations->toRGB); + KIS_SAFE_ASSERT_RECOVER_NOOP(d->defaultTransformations->toRGB); KoLcmsDefaultTransformations::s_transformations[ this->id()][ d->profile ] = d->defaultTransformations; } } public: bool hasHighDynamicRange() const override { return false; } const KoColorProfile *profile() const override { return d->colorProfile; } bool profileIsCompatible(const KoColorProfile *profile) const override { const IccColorProfile *p = dynamic_cast(profile); return (p && p->asLcms()->colorSpaceSignature() == colorSpaceSignature()); } void fromQColor(const QColor &color, quint8 *dst, const KoColorProfile *koprofile = 0) const override { QMutexLocker locker(&d->mutex); d->qcolordata[2] = color.red(); d->qcolordata[1] = color.green(); d->qcolordata[0] = color.blue(); LcmsColorProfileContainer *profile = asLcmsProfile(koprofile); if (profile == 0) { // Default sRGB - Q_ASSERT(d->defaultTransformations && d->defaultTransformations->fromRGB); + KIS_ASSERT(d->defaultTransformations && d->defaultTransformations->fromRGB); cmsDoTransform(d->defaultTransformations->fromRGB, d->qcolordata, dst, 1); } else { if (d->lastFromRGB == 0 || (d->lastFromRGB != 0 && d->lastRGBProfile != profile->lcmsProfile())) { d->lastFromRGB = cmsCreateTransform(profile->lcmsProfile(), TYPE_BGR_8, d->profile->lcmsProfile(), this->colorSpaceType(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); d->lastRGBProfile = profile->lcmsProfile(); } + KIS_ASSERT(d->lastFromRGB); cmsDoTransform(d->lastFromRGB, d->qcolordata, dst, 1); } this->setOpacity(dst, (quint8)(color.alpha()), 1); } void toQColor(const quint8 *src, QColor *c, const KoColorProfile *koprofile = 0) const override { QMutexLocker locker(&d->mutex); LcmsColorProfileContainer *profile = asLcmsProfile(koprofile); if (profile == 0) { // Default sRGB transform Q_ASSERT(d->defaultTransformations && d->defaultTransformations->toRGB); cmsDoTransform(d->defaultTransformations->toRGB, const_cast (src), d->qcolordata, 1); } else { if (d->lastToRGB == 0 || (d->lastToRGB != 0 && d->lastRGBProfile != profile->lcmsProfile())) { d->lastToRGB = cmsCreateTransform(d->profile->lcmsProfile(), this->colorSpaceType(), profile->lcmsProfile(), TYPE_BGR_8, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); d->lastRGBProfile = profile->lcmsProfile(); } cmsDoTransform(d->lastToRGB, const_cast (src), d->qcolordata, 1); } c->setRgb(d->qcolordata[2], d->qcolordata[1], d->qcolordata[0]); c->setAlpha(this->opacityU8(src)); } KoColorTransformation *createBrightnessContrastAdjustment(const quint16 *transferValues) const override { if (!d->profile) { return 0; } cmsToneCurve *transferFunctions[3]; transferFunctions[0] = cmsBuildTabulatedToneCurve16(0, 256, transferValues); transferFunctions[1] = cmsBuildGamma(0, 1.0); transferFunctions[2] = cmsBuildGamma(0, 1.0); KoLcmsColorTransformation *adj = new KoLcmsColorTransformation(this); adj->profiles[1] = cmsCreateLinearizationDeviceLink(cmsSigLabData, transferFunctions); cmsSetDeviceClass(adj->profiles[1], cmsSigAbstractClass); adj->profiles[0] = d->profile->lcmsProfile(); adj->profiles[2] = d->profile->lcmsProfile(); adj->cmstransform = cmsCreateMultiprofileTransform(adj->profiles, 3, this->colorSpaceType(), this->colorSpaceType(), KoColorConversionTransformation::adjustmentRenderingIntent(), KoColorConversionTransformation::adjustmentConversionFlags()); adj->csProfile = d->profile->lcmsProfile(); return adj; } KoColorTransformation *createPerChannelAdjustment(const quint16 *const *transferValues) const override { if (!d->profile) { return 0; } cmsToneCurve **transferFunctions = new cmsToneCurve*[ this->colorChannelCount()]; for (uint ch = 0; ch < this->colorChannelCount(); ch++) { transferFunctions[ch] = transferValues[ch] ? cmsBuildTabulatedToneCurve16(0, 256, transferValues[ch]) : cmsBuildGamma(0, 1.0); } cmsToneCurve **alphaTransferFunctions = new cmsToneCurve*[1]; alphaTransferFunctions[0] = transferValues[this->colorChannelCount()] ? cmsBuildTabulatedToneCurve16(0, 256, transferValues[this->colorChannelCount()]) : cmsBuildGamma(0, 1.0); KoLcmsColorTransformation *adj = new KoLcmsColorTransformation(this); adj->profiles[0] = cmsCreateLinearizationDeviceLink(this->colorSpaceSignature(), transferFunctions); adj->profiles[1] = cmsCreateLinearizationDeviceLink(cmsSigGrayData, alphaTransferFunctions); adj->profiles[2] = 0; adj->csProfile = d->profile->lcmsProfile(); adj->cmstransform = cmsCreateTransform(adj->profiles[0], this->colorSpaceType(), 0, this->colorSpaceType(), KoColorConversionTransformation::adjustmentRenderingIntent(), KoColorConversionTransformation::adjustmentConversionFlags()); adj->cmsAlphaTransform = cmsCreateTransform(adj->profiles[1], TYPE_GRAY_DBL, 0, TYPE_GRAY_DBL, KoColorConversionTransformation::adjustmentRenderingIntent(), KoColorConversionTransformation::adjustmentConversionFlags()); delete [] transferFunctions; delete [] alphaTransferFunctions; return adj; } quint8 difference(const quint8 *src1, const quint8 *src2) const override { quint8 lab1[8], lab2[8]; cmsCIELab labF1, labF2; if (this->opacityU8(src1) == OPACITY_TRANSPARENT_U8 || this->opacityU8(src2) == OPACITY_TRANSPARENT_U8) { return (this->opacityU8(src1) == this->opacityU8(src2) ? 0 : 255); } Q_ASSERT(this->toLabA16Converter()); this->toLabA16Converter()->transform(src1, lab1, 1); this->toLabA16Converter()->transform(src2, lab2, 1); cmsLabEncoded2Float(&labF1, (cmsUInt16Number *)lab1); cmsLabEncoded2Float(&labF2, (cmsUInt16Number *)lab2); qreal diff = cmsDeltaE(&labF1, &labF2); if (diff > 255.0) { return 255; } else { return quint8(diff); } } quint8 differenceA(const quint8 *src1, const quint8 *src2) const override { quint8 lab1[8]; quint8 lab2[8]; cmsCIELab labF1; cmsCIELab labF2; if (this->opacityU8(src1) == OPACITY_TRANSPARENT_U8 || this->opacityU8(src2) == OPACITY_TRANSPARENT_U8) { return (this->opacityU8(src1) == this->opacityU8(src2) ? 0 : 255); } Q_ASSERT(this->toLabA16Converter()); this->toLabA16Converter()->transform(src1, lab1, 1); this->toLabA16Converter()->transform(src2, lab2, 1); cmsLabEncoded2Float(&labF1, (cmsUInt16Number *)lab1); cmsLabEncoded2Float(&labF2, (cmsUInt16Number *)lab2); cmsFloat64Number dL; cmsFloat64Number da; cmsFloat64Number db; cmsFloat64Number dAlpha; dL = fabs((qreal)(labF1.L - labF2.L)); da = fabs((qreal)(labF1.a - labF2.a)); db = fabs((qreal)(labF1.b - labF2.b)); static const int LabAAlphaPos = 3; static const cmsFloat64Number alphaScale = 100.0 / KoColorSpaceMathsTraits::max; quint16 alpha1 = reinterpret_cast(lab1)[LabAAlphaPos]; quint16 alpha2 = reinterpret_cast(lab2)[LabAAlphaPos]; dAlpha = fabs((qreal)(alpha1 - alpha2)) * alphaScale; qreal diff = pow(dL * dL + da * da + db * db + dAlpha * dAlpha, 0.5); if (diff > 255.0) { return 255; } else { return quint8(diff); } } private: inline LcmsColorProfileContainer *lcmsProfile() const { return d->profile; } inline static LcmsColorProfileContainer *asLcmsProfile(const KoColorProfile *p) { if (!p) { return 0; } const IccColorProfile *iccp = dynamic_cast(p); if (!iccp) { return 0; } Q_ASSERT(iccp->asLcms()); return iccp->asLcms(); } Private *const d; }; /** * Base class for all LCMS based ColorSpace factories. */ class LcmsColorSpaceFactory : public KoColorSpaceFactory, private KoLcmsInfo { public: LcmsColorSpaceFactory(cmsUInt32Number cmType, cmsColorSpaceSignature colorSpaceSignature) : KoLcmsInfo(cmType, colorSpaceSignature) { } bool profileIsCompatible(const KoColorProfile *profile) const override { const IccColorProfile *p = dynamic_cast(profile); return (p && p->asLcms()->colorSpaceSignature() == colorSpaceSignature()); } QString colorSpaceEngine() const override { return "icc"; } bool isHdr() const override { return false; } int crossingCost() const override { return 1; } QList colorConversionLinks() const override; KoColorProfile *createColorProfile(const QByteArray &rawData) const override; }; #endif diff --git a/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp b/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp index f463f52438..f9c6e3bd68 100644 --- a/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp +++ b/plugins/color/lcms2engine/colorprofiles/IccColorProfile.cpp @@ -1,389 +1,406 @@ /* * Copyright (c) 2007 Cyrille Berger * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "IccColorProfile.h" #include #include #include #include #include "QDebug" #include "LcmsColorProfileContainer.h" #include "lcms2.h" +#include "kis_assert.h" + + struct IccColorProfile::Data::Private { QByteArray rawData; }; IccColorProfile::Data::Data() : d(new Private) { } IccColorProfile::Data::Data(const QByteArray &rawData) : d(new Private) { d->rawData = rawData; } IccColorProfile::Data::~Data() { } QByteArray IccColorProfile::Data::rawData() { return d->rawData; } void IccColorProfile::Data::setRawData(const QByteArray &rawData) { d->rawData = rawData; } IccColorProfile::Container::Container() { } IccColorProfile::Container::~Container() { } struct IccColorProfile::Private { struct Shared { QScopedPointer data; QScopedPointer lcmsProfile; QVector uiMinMaxes; + bool canCreateCyclicTransform = false; }; QSharedPointer shared; }; IccColorProfile::IccColorProfile(const QString &fileName) : KoColorProfile(fileName), d(new Private) { // QSharedPointer lacks a reset in Qt 4.x d->shared = QSharedPointer(new Private::Shared()); d->shared->data.reset(new Data()); } IccColorProfile::IccColorProfile(const QByteArray &rawData) : KoColorProfile(QString()), d(new Private) { d->shared = QSharedPointer(new Private::Shared()); d->shared->data.reset(new Data()); setRawData(rawData); init(); } IccColorProfile::IccColorProfile(const IccColorProfile &rhs) : KoColorProfile(rhs) , d(new Private(*rhs.d)) { Q_ASSERT(d->shared); } IccColorProfile::~IccColorProfile() { Q_ASSERT(d->shared); } KoColorProfile *IccColorProfile::clone() const { return new IccColorProfile(*this); } QByteArray IccColorProfile::rawData() const { return d->shared->data->rawData(); } void IccColorProfile::setRawData(const QByteArray &rawData) { d->shared->data->setRawData(rawData); } bool IccColorProfile::valid() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->valid(); } return false; } float IccColorProfile::version() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->version(); } return 0.0; } bool IccColorProfile::isSuitableForOutput() const { if (d->shared->lcmsProfile) { - return d->shared->lcmsProfile->isSuitableForOutput(); + return d->shared->lcmsProfile->isSuitableForOutput() && d->shared->canCreateCyclicTransform; } return false; } bool IccColorProfile::isSuitableForPrinting() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->isSuitableForPrinting(); } return false; } bool IccColorProfile::isSuitableForDisplay() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->isSuitableForDisplay(); } return false; } bool IccColorProfile::supportsPerceptual() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->supportsPerceptual(); } return false; } bool IccColorProfile::supportsSaturation() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->supportsSaturation(); } return false; } bool IccColorProfile::supportsAbsolute() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->supportsAbsolute(); } return false; } bool IccColorProfile::supportsRelative() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->supportsRelative(); } return false; } bool IccColorProfile::hasColorants() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->hasColorants(); } return false; } bool IccColorProfile::hasTRC() const { if (d->shared->lcmsProfile) return d->shared->lcmsProfile->hasTRC(); return false; } bool IccColorProfile::isLinear() const { if (d->shared->lcmsProfile) return d->shared->lcmsProfile->isLinear(); return false; } QVector IccColorProfile::getColorantsXYZ() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->getColorantsXYZ(); } return QVector(9); } QVector IccColorProfile::getColorantsxyY() const { if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->getColorantsxyY(); } return QVector(9); } QVector IccColorProfile::getWhitePointXYZ() const { QVector d50Dummy(3); d50Dummy << 0.9642 << 1.0000 << 0.8249; if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->getWhitePointXYZ(); } return d50Dummy; } QVector IccColorProfile::getWhitePointxyY() const { QVector d50Dummy(3); d50Dummy << 0.34773 << 0.35952 << 1.0; if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->getWhitePointxyY(); } return d50Dummy; } QVector IccColorProfile::getEstimatedTRC() const { QVector dummy(3); dummy.fill(2.2);//estimated sRGB trc. if (d->shared->lcmsProfile) { return d->shared->lcmsProfile->getEstimatedTRC(); } return dummy; } void IccColorProfile::linearizeFloatValue(QVector & Value) const { if (d->shared->lcmsProfile) d->shared->lcmsProfile->LinearizeFloatValue(Value); } void IccColorProfile::delinearizeFloatValue(QVector & Value) const { if (d->shared->lcmsProfile) d->shared->lcmsProfile->DelinearizeFloatValue(Value); } void IccColorProfile::linearizeFloatValueFast(QVector & Value) const { if (d->shared->lcmsProfile) d->shared->lcmsProfile->LinearizeFloatValueFast(Value); } void IccColorProfile::delinearizeFloatValueFast(QVector &Value) const { if (d->shared->lcmsProfile) d->shared->lcmsProfile->DelinearizeFloatValueFast(Value); } QByteArray IccColorProfile::uniqueId() const { QByteArray dummy; if (d->shared->lcmsProfile) { dummy = d->shared->lcmsProfile->getProfileUniqueId(); } return dummy; } bool IccColorProfile::load() { QFile file(fileName()); file.open(QIODevice::ReadOnly); QByteArray rawData = file.readAll(); setRawData(rawData); file.close(); if (init()) { return true; } qWarning() << "Failed to load profile from " << fileName(); return false; } bool IccColorProfile::save() { return false; } bool IccColorProfile::init() { if (!d->shared->lcmsProfile) { d->shared->lcmsProfile.reset(new LcmsColorProfileContainer(d->shared->data.data())); } if (d->shared->lcmsProfile->init()) { setName(d->shared->lcmsProfile->name()); setInfo(d->shared->lcmsProfile->info()); setManufacturer(d->shared->lcmsProfile->manufacturer()); setCopyright(d->shared->lcmsProfile->copyright()); if (d->shared->lcmsProfile->valid()) { calculateFloatUIMinMax(); } return true; } else { return false; } } LcmsColorProfileContainer *IccColorProfile::asLcms() const { Q_ASSERT(d->shared->lcmsProfile); return d->shared->lcmsProfile.data(); } bool IccColorProfile::operator==(const KoColorProfile &rhs) const { const IccColorProfile *rhsIcc = dynamic_cast(&rhs); if (rhsIcc) { return d->shared == rhsIcc->d->shared; } return false; } const QVector &IccColorProfile::getFloatUIMinMax(void) const { Q_ASSERT(!d->shared->uiMinMaxes.isEmpty()); return d->shared->uiMinMaxes; } void IccColorProfile::calculateFloatUIMinMax(void) { QVector &ret = d->shared->uiMinMaxes; cmsHPROFILE cprofile = d->shared->lcmsProfile->lcmsProfile(); Q_ASSERT(cprofile); cmsColorSpaceSignature color_space_sig = cmsGetColorSpace(cprofile); unsigned int num_channels = cmsChannelsOf(color_space_sig); unsigned int color_space_mask = _cmsLCMScolorSpace(color_space_sig); Q_ASSERT(num_channels >= 1 && num_channels <= 4); // num_channels==1 is for grayscale, we need to handle it Q_ASSERT(color_space_mask); // to try to find the max range of float/doubles for this profile, // pass in min/max int and make the profile convert that // this is far from perfect, we need a better way, if possible to get the "bounds" of a profile uint16_t in_min_pixel[4] = {0, 0, 0, 0}; uint16_t in_max_pixel[4] = {0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF}; qreal out_min_pixel[4] = {0, 0, 0, 0}; qreal out_max_pixel[4] = {0, 0, 0, 0}; cmsHTRANSFORM trans = cmsCreateTransform( cprofile, (COLORSPACE_SH(color_space_mask) | CHANNELS_SH(num_channels) | BYTES_SH(2)), cprofile, (COLORSPACE_SH(color_space_mask) | FLOAT_SH(1) | CHANNELS_SH(num_channels) | BYTES_SH(0)), //NOTE THAT 'BYTES' FIELD IS SET TO ZERO ON DLB because 8 bytes overflows the bitfield INTENT_PERCEPTUAL, 0); // does the intent matter in this case? if (trans) { cmsDoTransform(trans, in_min_pixel, out_min_pixel, 1); cmsDoTransform(trans, in_max_pixel, out_max_pixel, 1); cmsDeleteTransform(trans); }//else, we'll just default to [0..1] below + // Some (calibration) proifles may have a weird RGB->XYZ transformation matrix, + // which is not invertible. Therefore, such profile cannot be used as + // a workspace color profile and we should convert the image to sRGB + // right on image loading + + // LCMS doesn't have a separate method for checking if conversion matrix + // is invertible, therefore we just try to create a simple transformation, + // where the profile is both, input and output. If the transformation + // is created successfully, then this profile is probably suitable for + // usage as a working color space. + + d->shared->canCreateCyclicTransform = bool(trans); + ret.resize(num_channels); for (unsigned int i = 0; i < num_channels; ++i) { if (out_min_pixel[i] < out_max_pixel[i]) { ret[i].minVal = out_min_pixel[i]; ret[i].maxVal = out_max_pixel[i]; } else { // apparently we can't even guarantee that converted_to_double(0x0000) < converted_to_double(0xFFFF) // assume [0..1] in such cases // we need to find a really solid way of determining the bounds of a profile, if possible ret[i].minVal = 0; ret[i].maxVal = 1; } } } diff --git a/plugins/filters/blur/kis_motion_blur_filter.cpp b/plugins/filters/blur/kis_motion_blur_filter.cpp index ac8a1ed952..60ee8ef7f9 100644 --- a/plugins/filters/blur/kis_motion_blur_filter.cpp +++ b/plugins/filters/blur/kis_motion_blur_filter.cpp @@ -1,167 +1,174 @@ /* * This file is part of Krita * * Copyright (c) 2010 Edward Apap * * 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 "kis_motion_blur_filter.h" #include "kis_wdg_motion_blur.h" #include #include #include #include "ui_wdg_motion_blur.h" #include #include #include #include #include #include "kis_lod_transform.h" #include #include KisMotionBlurFilter::KisMotionBlurFilter() : KisFilter(id(), FiltersCategoryBlurId, i18n("&Motion Blur...")) { setSupportsPainting(true); setSupportsAdjustmentLayers(true); setSupportsLevelOfDetail(true); setColorSpaceIndependence(FULLY_INDEPENDENT); } KisConfigWidget * KisMotionBlurFilter::createConfigurationWidget(QWidget* parent, const KisPaintDeviceSP, bool) const { return new KisWdgMotionBlur(parent); } KisFilterConfigurationSP KisMotionBlurFilter::factoryConfiguration() const { KisFilterConfigurationSP config = new KisFilterConfiguration(id().id(), 1); config->setProperty("blurAngle", 0); config->setProperty("blurLength", 5); return config; } void KisMotionBlurFilter::processImpl(KisPaintDeviceSP device, const QRect& rect, const KisFilterConfigurationSP _config, KoUpdater* progressUpdater ) const { QPoint srcTopLeft = rect.topLeft(); - Q_ASSERT(device != 0); + Q_ASSERT(device); KisFilterConfigurationSP config = _config ? _config : new KisFilterConfiguration(id().id(), 1); QVariant value; - config->getProperty("blurAngle", value); - uint blurAngle = value.toUInt(); + uint blurAngle = 0; + if (config->getProperty("blurAngle", value)) { + blurAngle = value.toUInt(); + } KisLodTransformScalar t(device); - config->getProperty("blurLength", value); - uint blurLength = t.scale(value.toUInt()); + uint blurLength = 0; + if (config->getProperty("blurLength", value)) { + blurLength = t.scale(value.toUInt()); + } - if (blurLength == 0) + if (blurLength == 0) { return; + } QBitArray channelFlags; + if (config) { channelFlags = config->channelFlags(); } + if (channelFlags.isEmpty() || !config) { channelFlags = QBitArray(device->colorSpace()->channelCount(), true); } // convert angle to radians qreal angleRadians = blurAngle / 360.0 * 2 * M_PI; // construct image qreal halfWidth = blurLength / 2.0 * cos(angleRadians); qreal halfHeight = blurLength / 2.0 * sin(angleRadians); int kernelWidth = ceil(fabs(halfWidth)) * 2; int kernelHeight = ceil(fabs(halfHeight)) * 2; // check for zero dimensions (vertical/horizontal motion vectors) kernelWidth = (kernelWidth == 0) ? 1 : kernelWidth; kernelHeight = (kernelHeight == 0) ? 1 : kernelHeight; QImage kernelRepresentation(kernelWidth, kernelHeight, QImage::Format_RGB32); kernelRepresentation.fill(0); QPainter imagePainter(&kernelRepresentation); imagePainter.setRenderHint(QPainter::Antialiasing); imagePainter.setPen(QPen(QColor::fromRgb(255, 255, 255), 1.0)); imagePainter.drawLine(QPointF(kernelWidth / 2 - halfWidth, kernelHeight / 2 + halfHeight), QPointF(kernelWidth / 2 + halfWidth, kernelHeight / 2 - halfHeight)); // construct kernel from image Eigen::Matrix motionBlurKernel(kernelHeight, kernelWidth); for (int j = 0; j < kernelHeight; ++j) { for (int i = 0; i < kernelWidth; ++i) { motionBlurKernel(j, i) = qRed(kernelRepresentation.pixel(i, j)); } } // apply convolution KisConvolutionPainter painter(device); painter.setChannelFlags(channelFlags); painter.setProgress(progressUpdater); KisConvolutionKernelSP kernel = KisConvolutionKernel::fromMatrix(motionBlurKernel, 0, motionBlurKernel.sum()); painter.applyMatrix(kernel, device, srcTopLeft, srcTopLeft, rect.size(), BORDER_REPEAT); } QRect KisMotionBlurFilter::neededRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; uint blurAngle = _config->getProperty("blurAngle", value) ? value.toUInt() : 0; uint blurLength = t.scale(_config->getProperty("blurLength", value) ? value.toUInt() : 5); qreal angleRadians = blurAngle / 360.0 * 2 * M_PI; const int halfWidth = ceil(fabs(blurLength / 2.0 * cos(angleRadians))); const int halfHeight = ceil(fabs(blurLength / 2.0 * cos(angleRadians))); return rect.adjusted(-halfWidth * 2, -halfHeight * 2, halfWidth * 2, halfHeight * 2); } QRect KisMotionBlurFilter::changedRect(const QRect & rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; uint blurAngle = _config->getProperty("blurAngle", value) ? value.toUInt() : 0; uint blurLength = t.scale(_config->getProperty("blurLength", value) ? value.toUInt() : 5); qreal angleRadians = blurAngle / 360.0 * 2 * M_PI; const int halfWidth = ceil(fabs(blurLength * cos(angleRadians))); const int halfHeight = ceil(fabs(blurLength * cos(angleRadians))); return rect.adjusted(-halfWidth * 2, -halfHeight * 2, halfWidth * 2, halfHeight * 2); } diff --git a/plugins/filters/convertheightnormalmap/kis_convert_height_to_normal_map_filter.cpp b/plugins/filters/convertheightnormalmap/kis_convert_height_to_normal_map_filter.cpp index b2dce21a6b..0d513e3cb6 100644 --- a/plugins/filters/convertheightnormalmap/kis_convert_height_to_normal_map_filter.cpp +++ b/plugins/filters/convertheightnormalmap/kis_convert_height_to_normal_map_filter.cpp @@ -1,170 +1,174 @@ /* * Copyright (c) 2017 Wolthera van Hövell tot Westerflier * * 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 "kis_convert_height_to_normal_map_filter.h" #include "kis_wdg_convert_height_to_normal_map.h" #include #include #include #include #include #include "kis_lod_transform.h" #include K_PLUGIN_FACTORY_WITH_JSON(KritaConvertHeightToNormalMapFilterFactory, "kritaconvertheighttonormalmap.json", registerPlugin();) KritaConvertHeightToNormalMapFilter::KritaConvertHeightToNormalMapFilter(QObject *parent, const QVariantList &) : QObject(parent) { KisFilterRegistry::instance()->add(KisFilterSP(new KisConvertHeightToNormalMapFilter())); } KritaConvertHeightToNormalMapFilter::~KritaConvertHeightToNormalMapFilter() { } KisConvertHeightToNormalMapFilter::KisConvertHeightToNormalMapFilter(): KisFilter(id(), FiltersCategoryEdgeDetectionId, i18n("&Height to Normal Map...")) { setSupportsPainting(true); setSupportsAdjustmentLayers(true); setSupportsLevelOfDetail(true); setColorSpaceIndependence(FULLY_INDEPENDENT); setShowConfigurationWidget(true); } void KisConvertHeightToNormalMapFilter::processImpl(KisPaintDeviceSP device, const QRect &rect, const KisFilterConfigurationSP config, KoUpdater *progressUpdater) const { - Q_ASSERT(device != 0); + Q_ASSERT(device); KisFilterConfigurationSP configuration = config ? config : new KisFilterConfiguration(id().id(), 1); KisLodTransformScalar t(device); QVariant value; - configuration->getProperty("horizRadius", value); - float horizontalRadius = t.scale(value.toFloat()); - configuration->getProperty("vertRadius", value); - float verticalRadius = t.scale(value.toFloat()); + float horizontalRadius = 1.0; + if (configuration->getProperty("horizRadius", value)) { + horizontalRadius = t.scale(value.toFloat()); + } + float verticalRadius = 1.0; + if (configuration->getProperty("vertRadius", value)) { + verticalRadius = t.scale(value.toFloat()); + } QBitArray channelFlags; if (configuration) { channelFlags = configuration->channelFlags(); } if (channelFlags.isEmpty() || !configuration) { channelFlags = device->colorSpace()->channelFlags(); } KisEdgeDetectionKernel::FilterType type = KisEdgeDetectionKernel::SobelVector; if (configuration->getString("type") == "prewitt") { type = KisEdgeDetectionKernel::Prewit; } else if (configuration->getString("type") == "simple") { type = KisEdgeDetectionKernel::Simple; } int channelToConvert = configuration->getInt("channelToConvert", 0); QVector channelOrder(3); QVector channelFlip(3); channelFlip.fill(false); int i = config->getInt("redSwizzle", 0); if (i%2==1 || i==2) { channelFlip[0] = true; } if (i==3) { channelFlip[0] = false; } channelOrder[device->colorSpace()->channels().at(0)->displayPosition()] = qMax(i/2,0); i = config->getInt("greenSwizzle", 2); if (i%2==1 || i==2) { channelFlip[1] = true; } if (i==3) { channelFlip[1] = false; } channelOrder[device->colorSpace()->channels().at(1)->displayPosition()] = qMax(i/2,0); i = config->getInt("blueSwizzle", 4); if (i%2==1 || i==2) { channelFlip[2] = true; } if (i==3) { channelFlip[2] = false; } channelOrder[device->colorSpace()->channels().at(2)->displayPosition()] = qMax(i/2,0); KisEdgeDetectionKernel::convertToNormalMap(device, rect, horizontalRadius, verticalRadius, type, channelToConvert, channelOrder, channelFlip, channelFlags, progressUpdater); } KisFilterConfigurationSP KisConvertHeightToNormalMapFilter::factoryConfiguration() const { KisFilterConfigurationSP config = new KisFilterConfiguration(id().id(), 1); config->setProperty("horizRadius", 1); config->setProperty("vertRadius", 1); config->setProperty("type", "sobol"); config->setProperty("channelToConvert", 0); config->setProperty("lockAspect", true); config->setProperty("redSwizzle", KisWdgConvertHeightToNormalMap::xPlus); config->setProperty("greenSwizzle", KisWdgConvertHeightToNormalMap::yPlus); config->setProperty("blueSwizzle", KisWdgConvertHeightToNormalMap::zPlus); return config; } KisConfigWidget *KisConvertHeightToNormalMapFilter::createConfigurationWidget(QWidget *parent, const KisPaintDeviceSP dev, bool) const { return new KisWdgConvertHeightToNormalMap(parent, dev->colorSpace()); } QRect KisConvertHeightToNormalMapFilter::neededRect(const QRect &rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; /** * NOTE: integer division by two is done on purpose, * because the kernel size is always odd */ const int halfWidth = _config->getProperty("horizRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; const int halfHeight = _config->getProperty("vertRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; return rect.adjusted(-halfWidth * 2, -halfHeight * 2, halfWidth * 2, halfHeight * 2); } QRect KisConvertHeightToNormalMapFilter::changedRect(const QRect &rect, const KisFilterConfigurationSP _config, int lod) const { KisLodTransformScalar t(lod); QVariant value; const int halfWidth = _config->getProperty("horizRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; const int halfHeight = _config->getProperty("vertRadius", value) ? KisEdgeDetectionKernel::kernelSizeFromRadius(t.scale(value.toFloat())) / 2 : 5; return rect.adjusted( -halfWidth, -halfHeight, halfWidth, halfHeight); } #include "kis_convert_height_to_normal_map_filter.moc" diff --git a/plugins/filters/phongbumpmap/phong_pixel_processor.cpp b/plugins/filters/phongbumpmap/phong_pixel_processor.cpp index a906860bc7..9484331745 100644 --- a/plugins/filters/phongbumpmap/phong_pixel_processor.cpp +++ b/plugins/filters/phongbumpmap/phong_pixel_processor.cpp @@ -1,202 +1,203 @@ /* * Copyright (c) 2010-2012 José Luis Vergara * * 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 "phong_pixel_processor.h" #include #include #include PhongPixelProcessor::PhongPixelProcessor(quint32 pixelArea, const KisPropertiesConfigurationSP config) { m_pixelArea = pixelArea; initialize(config); } void PhongPixelProcessor::initialize(const KisPropertiesConfigurationSP config) { // Basic, fundamental normal_vector = QVector3D(0, 0, 1); vision_vector = QVector3D(0, 0, 1); x_vector = QVector3D(1, 0, 0); y_vector = QVector3D(0, 1, 0); // Mutable light_vector = QVector3D(0, 0, 0); reflection_vector = QVector3D(0, 0, 0); //setLightVector(QVector3D(-8, 8, 2)); Illuminant light[PHONG_TOTAL_ILLUMINANTS]; QVariant guiLight[PHONG_TOTAL_ILLUMINANTS]; qint32 azimuth; qint32 inclination; for (int i = 0; i < PHONG_TOTAL_ILLUMINANTS; i++) { if (config->getBool(PHONG_ILLUMINANT_IS_ENABLED[i])) { - config->getProperty(PHONG_ILLUMINANT_COLOR[i], guiLight[i]); - light[i].RGBvalue << guiLight[i].value().redF(); - light[i].RGBvalue << guiLight[i].value().greenF(); - light[i].RGBvalue << guiLight[i].value().blueF(); - - azimuth = config->getInt(PHONG_ILLUMINANT_AZIMUTH[i]) - 90; - inclination = config->getInt(PHONG_ILLUMINANT_INCLINATION[i]); - - qreal m; //2D vector magnitude - light[i].lightVector.setZ( sin( inclination * M_PI / 180 ) ); - m = cos( inclination * M_PI / 180); - - light[i].lightVector.setX( cos( azimuth * M_PI / 180 ) * m ); - light[i].lightVector.setY( sin( azimuth * M_PI / 180 ) * m ); - //Pay close attention to this, indexes will move in this line - lightSources.append(light[i]); + if (config->getProperty(PHONG_ILLUMINANT_COLOR[i], guiLight[i])) { + light[i].RGBvalue << guiLight[i].value().redF(); + light[i].RGBvalue << guiLight[i].value().greenF(); + light[i].RGBvalue << guiLight[i].value().blueF(); + + azimuth = config->getInt(PHONG_ILLUMINANT_AZIMUTH[i]) - 90; + inclination = config->getInt(PHONG_ILLUMINANT_INCLINATION[i]); + + qreal m; //2D vector magnitude + light[i].lightVector.setZ( sin( inclination * M_PI / 180 ) ); + m = cos( inclination * M_PI / 180); + + light[i].lightVector.setX( cos( azimuth * M_PI / 180 ) * m ); + light[i].lightVector.setY( sin( azimuth * M_PI / 180 ) * m ); + //Pay close attention to this, indexes will move in this line + lightSources.append(light[i]); + } } } size = lightSources.size(); //Code that exists only to swiftly switch to the other algorithm (reallyFastIlluminatePixel) to test if (size > 0) { fastLight = light[0]; fastLight2 = light[0]; } //Ka, Kd and Ks must be between 0 and 1 or grave errors will happen Ka = config->getDouble(PHONG_AMBIENT_REFLECTIVITY); Kd = config->getDouble(PHONG_DIFFUSE_REFLECTIVITY); Ks = config->getDouble(PHONG_SPECULAR_REFLECTIVITY); shiny_exp = config->getInt(PHONG_SHINYNESS_EXPONENT); Ia = Id = Is = 0; diffuseLightIsEnabled = config->getBool(PHONG_DIFFUSE_REFLECTIVITY_IS_ENABLED); specularLightIsEnabled = config->getBool(PHONG_SPECULAR_REFLECTIVITY_IS_ENABLED); realheightmap = QVector(m_pixelArea, 0); } PhongPixelProcessor::~PhongPixelProcessor() { } void PhongPixelProcessor::setLightVector(QVector3D lightVector) { lightVector.normalize(); light_vector = lightVector; } QVector PhongPixelProcessor::IlluminatePixelFromHeightmap(quint32 posup, quint32 posdown, quint32 posleft, quint32 posright) { QVector finalPixel(4, 0xFFFF); if (lightSources.size() == 0) return finalPixel; // Algorithm begins, Phong Illumination Model normal_vector.setX(- realheightmap[posright] + realheightmap[posleft]); normal_vector.setY(- realheightmap[posup] + realheightmap[posdown]); normal_vector.setZ(8); normal_vector.normalize(); // PREPARE ALGORITHM HERE finalPixel = IlluminatePixel(); return finalPixel; - } - +} + QVector PhongPixelProcessor::IlluminatePixel() { qreal temp; quint8 channel = 0; const quint8 totalChannels = 3; // The 4th is alpha and we'll fill it with a nice 0xFFFF qreal computation[] = {0, 0, 0}; QVector finalPixel(4, 0xFFFF); if (lightSources.size() == 0) return finalPixel; // PREPARE ALGORITHM HERE for (int i = 0; i < size; i++) { light_vector = lightSources.at(i).lightVector; for (channel = 0; channel < totalChannels; channel++) { Ia = lightSources.at(i).RGBvalue.at(channel) * Ka; computation[channel] += Ia; } if (diffuseLightIsEnabled) { temp = Kd * QVector3D::dotProduct(normal_vector, light_vector); for (channel = 0; channel < totalChannels; channel++) { Id = lightSources.at(i).RGBvalue.at(channel) * temp; if (Id < 0) Id = 0; if (Id > 1) Id = 1; computation[channel] += Id; } } if (specularLightIsEnabled) { reflection_vector = (2 * pow(QVector3D::dotProduct(normal_vector, light_vector), shiny_exp)) * normal_vector - light_vector; temp = Ks * QVector3D::dotProduct(vision_vector, reflection_vector); for (channel = 0; channel < totalChannels; channel++) { Is = lightSources.at(i).RGBvalue.at(channel) * temp; if (Is < 0) Is = 0; if (Is > 1) Is = 1; computation[channel] += Is; } } } for (channel = 0; channel < totalChannels; channel++) { if (computation[channel] > 1) computation[channel] = 1; if (computation[channel] < 0) computation[channel] = 0; } //RGBA actually uses the BGRA order of channels, hence the disorder finalPixel[2] = quint16(computation[0] * 0xFFFF); finalPixel[1] = quint16(computation[1] * 0xFFFF); finalPixel[0] = quint16(computation[2] * 0xFFFF); return finalPixel; - } - +} + QVector PhongPixelProcessor::IlluminatePixelFromNormalmap(qreal r, qreal g, qreal b) - { +{ QVector finalPixel(4, 0xFFFF); if (lightSources.size() == 0) return finalPixel; // if () // Algorithm begins, Phong Illumination Model normal_vector.setX(r*2-1.0); normal_vector.setY(-(g*2-1.0)); normal_vector.setZ(b*2-1.0); //normal_vector.normalize(); // PREPARE ALGORITHM HERE finalPixel = IlluminatePixel(); return finalPixel; - } +} diff --git a/plugins/impex/jpeg/kis_jpeg_converter.cc b/plugins/impex/jpeg/kis_jpeg_converter.cc index ec60598e10..a7b047c6ce 100644 --- a/plugins/impex/jpeg/kis_jpeg_converter.cc +++ b/plugins/impex/jpeg/kis_jpeg_converter.cc @@ -1,738 +1,741 @@ /* * Copyright (c) 2005 Cyrille Berger * * 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 "kis_jpeg_converter.h" #include #include #include #ifdef HAVE_LCMS2 # include #else # include #endif extern "C" { #include } #include #include #include #include #include #include #include #include #include #include #include #include #include "KoColorModelStandardIds.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */ #define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ #define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */ #define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN) const char photoshopMarker[] = "Photoshop 3.0\0"; //const char photoshopBimId_[] = "8BIM"; const uint16_t photoshopIptc = 0x0404; const char xmpMarker[] = "http://ns.adobe.com/xap/1.0/\0"; const QByteArray photoshopIptc_((char*)&photoshopIptc, 2); namespace { void jpegErrorExit (j_common_ptr cinfo) { char jpegLastErrorMsg[JMSG_LENGTH_MAX]; ( *( cinfo->err->format_message ) ) ( cinfo, jpegLastErrorMsg ); throw std::runtime_error(jpegLastErrorMsg); } J_COLOR_SPACE getColorTypeforColorSpace(const KoColorSpace * cs) { if (KoID(cs->id()) == KoID("GRAYA") || cs->id() == "GRAYAU16" || cs->id() == "GRAYA16") { return JCS_GRAYSCALE; } if (KoID(cs->id()) == KoID("RGBA") || KoID(cs->id()) == KoID("RGBA16")) { return JCS_RGB; } if (KoID(cs->id()) == KoID("CMYK") || KoID(cs->id()) == KoID("CMYKAU16")) { return JCS_CMYK; } return JCS_UNKNOWN; } QString getColorSpaceModelForColorType(J_COLOR_SPACE color_type) { dbgFile << "color_type =" << color_type; if (color_type == JCS_GRAYSCALE) { return GrayAColorModelID.id(); } else if (color_type == JCS_RGB) { return RGBAColorModelID.id(); } else if (color_type == JCS_CMYK) { return CMYKAColorModelID.id(); } return ""; } } struct KisJPEGConverter::Private { Private(KisDocument *doc, bool batchMode) : doc(doc), stop(false), batchMode(batchMode) {} KisImageSP image; KisDocument *doc; bool stop; bool batchMode; }; KisJPEGConverter::KisJPEGConverter(KisDocument *doc, bool batchMode) : m_d(new Private(doc, batchMode)) { } KisJPEGConverter::~KisJPEGConverter() { } KisImportExportErrorCode KisJPEGConverter::decode(QIODevice *io) { struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jerr.error_exit = jpegErrorExit; try { jpeg_create_decompress(&cinfo); KisJPEGSource::setSource(&cinfo, io); jpeg_save_markers(&cinfo, JPEG_COM, 0xFFFF); /* Save APP0..APP15 markers */ for (int m = 0; m < 16; m++) jpeg_save_markers(&cinfo, JPEG_APP0 + m, 0xFFFF); // setup_read_icc_profile(&cinfo); // read header jpeg_read_header(&cinfo, (boolean)true); // start reading jpeg_start_decompress(&cinfo); // Get the colorspace QString modelId = getColorSpaceModelForColorType(cinfo.out_color_space); if (modelId.isEmpty()) { dbgFile << "unsupported colorspace :" << cinfo.out_color_space; jpeg_destroy_decompress(&cinfo); return ImportExportCodes::FormatColorSpaceUnsupported; } - uchar* profile_data; - uint profile_len; + + uchar* profile_data = 0; + uint profile_len = 0; const KoColorProfile* profile = 0; QByteArray profile_rawdata; if (read_icc_profile(&cinfo, &profile_data, &profile_len)) { profile_rawdata.resize(profile_len); memcpy(profile_rawdata.data(), profile_data, profile_len); cmsHPROFILE hProfile = cmsOpenProfileFromMem(profile_data, profile_len); if (hProfile != (cmsHPROFILE) 0) { profile = KoColorSpaceRegistry::instance()->createColorProfile(modelId, Integer8BitsColorDepthID.id(), profile_rawdata); Q_CHECK_PTR(profile); dbgFile <<"profile name:" << profile->name() <<" product information:" << profile->info(); if (!profile->isSuitableForOutput()) { dbgFile << "the profile is not suitable for output and therefore cannot be used in krita, we need to convert the image to a standard profile"; // TODO: in ko2 popup a selection menu to inform the user } } + + free(profile_data); } const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(modelId, Integer8BitsColorDepthID.id()); // Check that the profile is used by the color space if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) { warnFile << "The profile " << profile->name() << " is not compatible with the color space model " << modelId; profile = 0; } // Retrieve a pointer to the colorspace const KoColorSpace* cs; if (profile && profile->isSuitableForOutput()) { dbgFile << "image has embedded profile:" << profile -> name() << ""; cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), profile); } else cs = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), ""); if (cs == 0) { dbgFile << "unknown colorspace"; jpeg_destroy_decompress(&cinfo); return ImportExportCodes::FormatColorSpaceUnsupported; } // TODO fixit // Create the cmsTransform if needed KoColorTransformation* transform = 0; if (profile && !profile->isSuitableForOutput()) { transform = KoColorSpaceRegistry::instance()->colorSpace(modelId, Integer8BitsColorDepthID.id(), profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } // Apparently an invalid transform was created from the profile. See bug https://bugs.kde.org/show_bug.cgi?id=255451. // After 2.3: warn the user! if (transform && !transform->isValid()) { delete transform; transform = 0; } // Creating the KisImageSP if (!m_d->image) { m_d->image = new KisImage(m_d->doc->createUndoStore(), cinfo.image_width, cinfo.image_height, cs, "built image"); Q_CHECK_PTR(m_d->image); } // Set resolution double xres = 72, yres = 72; if (cinfo.density_unit == 1) { xres = cinfo.X_density; yres = cinfo.Y_density; } else if (cinfo.density_unit == 2) { xres = cinfo.X_density * 2.54; yres = cinfo.Y_density * 2.54; } if (xres < 72) { xres = 72; } if (yres < 72) { yres = 72; } m_d->image->setResolution(POINT_TO_INCH(xres), POINT_TO_INCH(yres)); // It is the "invert" macro because we convert from pointer-per-inchs to points // Create layer KisPaintLayerSP layer = KisPaintLayerSP(new KisPaintLayer(m_d->image.data(), m_d->image -> nextLayerName(), quint8_MAX)); // Read data JSAMPROW row_pointer = new JSAMPLE[cinfo.image_width*cinfo.num_components]; for (; cinfo.output_scanline < cinfo.image_height;) { KisHLineIteratorSP it = layer->paintDevice()->createHLineIteratorNG(0, cinfo.output_scanline, cinfo.image_width); jpeg_read_scanlines(&cinfo, &row_pointer, 1); quint8 *src = row_pointer; switch (cinfo.out_color_space) { case JCS_GRAYSCALE: do { quint8 *d = it->rawData(); d[0] = *(src++); if (transform) transform->transform(d, d, 1); d[1] = quint8_MAX; } while (it->nextPixel()); break; case JCS_RGB: do { quint8 *d = it->rawData(); d[2] = *(src++); d[1] = *(src++); d[0] = *(src++); if (transform) transform->transform(d, d, 1); d[3] = quint8_MAX; } while (it->nextPixel()); break; case JCS_CMYK: do { quint8 *d = it->rawData(); d[0] = quint8_MAX - *(src++); d[1] = quint8_MAX - *(src++); d[2] = quint8_MAX - *(src++); d[3] = quint8_MAX - *(src++); if (transform) transform->transform(d, d, 1); d[4] = quint8_MAX; } while (it->nextPixel()); break; default: return ImportExportCodes::FormatFeaturesUnsupported; } } m_d->image->addNode(KisNodeSP(layer.data()), m_d->image->rootLayer().data()); // Read exif information dbgFile << "Looking for exif information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 1) || marker->data_length < 14) { continue; /* Exif data is in an APP1 marker of at least 14 octets */ } if (GETJOCTET(marker->data[0]) != (JOCTET) 0x45 || GETJOCTET(marker->data[1]) != (JOCTET) 0x78 || GETJOCTET(marker->data[2]) != (JOCTET) 0x69 || GETJOCTET(marker->data[3]) != (JOCTET) 0x66 || GETJOCTET(marker->data[4]) != (JOCTET) 0x00 || GETJOCTET(marker->data[5]) != (JOCTET) 0x00) continue; /* no Exif header */ dbgFile << "Found exif information of length :" << marker->data_length; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); Q_ASSERT(exifIO); QByteArray byteArray((const char*)marker->data + 6, marker->data_length - 6); QBuffer buf(&byteArray); exifIO->loadFrom(layer->metaData(), &buf); // Interpret orientation tag if (layer->metaData()->containsEntry("http://ns.adobe.com/tiff/1.0/", "Orientation")) { KisMetaData::Entry& entry = layer->metaData()->getEntry("http://ns.adobe.com/tiff/1.0/", "Orientation"); if (entry.value().type() == KisMetaData::Value::Variant) { switch (entry.value().asVariant().toInt()) { case 2: KisTransformWorker::mirrorY(layer->paintDevice()); break; case 3: image()->rotateImage(M_PI); break; case 4: KisTransformWorker::mirrorX(layer->paintDevice()); break; case 5: image()->rotateImage(M_PI / 2); KisTransformWorker::mirrorY(layer->paintDevice()); break; case 6: image()->rotateImage(M_PI / 2); break; case 7: image()->rotateImage(M_PI / 2); KisTransformWorker::mirrorX(layer->paintDevice()); break; case 8: image()->rotateImage(-M_PI / 2 + M_PI*2); break; default: break; } } entry.value().setVariant(1); } break; } dbgFile << "Looking for IPTC information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 13) || marker->data_length < 14) { continue; /* IPTC data is in an APP13 marker of at least 16 octets */ } if (memcmp(marker->data, photoshopMarker, 14) != 0) { for (int i = 0; i < 14; i++) { dbgFile << (int)(*(marker->data + i)) << "" << (int)(photoshopMarker[i]); } dbgFile << "No photoshop marker"; continue; /* No IPTC Header */ } dbgFile << "Found Photoshop information of length :" << marker->data_length; KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc"); Q_ASSERT(iptcIO); const Exiv2::byte *record = 0; uint32_t sizeIptc = 0; uint32_t sizeHdr = 0; // Find actual Iptc data within the APP13 segment if (!Exiv2::Photoshop::locateIptcIrb((Exiv2::byte*)(marker->data + 14), marker->data_length - 14, &record, &sizeHdr, &sizeIptc)) { if (sizeIptc) { // Decode the IPTC data QByteArray byteArray((const char*)(record + sizeHdr), sizeIptc); QBuffer buf(&byteArray); iptcIO->loadFrom(layer->metaData(), &buf); } else { dbgFile << "IPTC Not found in Photoshop marker"; } } break; } dbgFile << "Looking for XMP information"; for (jpeg_saved_marker_ptr marker = cinfo.marker_list; marker != 0; marker = marker->next) { dbgFile << "Marker is" << marker->marker; if (marker->marker != (JOCTET)(JPEG_APP0 + 1) || marker->data_length < 31) { continue; /* XMP data is in an APP1 marker of at least 31 octets */ } if (memcmp(marker->data, xmpMarker, 29) != 0) { dbgFile << "Not XMP marker"; continue; /* No xmp Header */ } dbgFile << "Found XMP Marker of length " << marker->data_length; QByteArray byteArray((const char*)marker->data + 29, marker->data_length - 29); KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); Q_ASSERT(xmpIO); xmpIO->loadFrom(layer->metaData(), new QBuffer(&byteArray)); break; } // Dump loaded metadata layer->metaData()->debugDump(); // Check whether the metadata has resolution info, too... if (cinfo.density_unit == 0 && layer->metaData()->containsEntry("tiff:XResolution") && layer->metaData()->containsEntry("tiff:YResolution")) { double xres = layer->metaData()->getEntry("tiff:XResolution").value().asDouble(); double yres = layer->metaData()->getEntry("tiff:YResolution").value().asDouble(); if (xres != 0 && yres != 0) { m_d->image->setResolution(POINT_TO_INCH(xres), POINT_TO_INCH(yres)); // It is the "invert" macro because we convert from pointer-per-inchs to points } } // Finish decompression jpeg_finish_decompress(&cinfo); jpeg_destroy_decompress(&cinfo); delete [] row_pointer; return ImportExportCodes::OK; } catch( std::runtime_error &e) { jpeg_destroy_decompress(&cinfo); return ImportExportCodes::FileFormatIncorrect; } } KisImportExportErrorCode KisJPEGConverter::buildImage(QIODevice *io) { return decode(io); } KisImageSP KisJPEGConverter::image() { return m_d->image; } KisImportExportErrorCode KisJPEGConverter::buildFile(QIODevice *io, KisPaintLayerSP layer, KisJPEGOptions options, KisMetaData::Store* metaData) { KIS_ASSERT_RECOVER_RETURN_VALUE(layer, ImportExportCodes::InternalError); KisImageSP image = KisImageSP(layer->image()); KIS_ASSERT_RECOVER_RETURN_VALUE(image, ImportExportCodes::InternalError); const KoColorSpace * cs = layer->colorSpace(); J_COLOR_SPACE color_type = getColorTypeforColorSpace(cs); if (color_type == JCS_UNKNOWN) { layer->paintDevice()->convertTo(KoColorSpaceRegistry::instance()->rgb8(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); cs = KoColorSpaceRegistry::instance()->rgb8(); color_type = JCS_RGB; } if (options.forceSRGB) { const KoColorSpace* dst = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), layer->colorSpace()->colorDepthId().id(), "sRGB built-in - (lcms internal)"); layer->paintDevice()->convertTo(dst); cs = dst; color_type = JCS_RGB; } uint height = image->height(); uint width = image->width(); // Initialize structure struct jpeg_compress_struct cinfo; // Initialize error output struct jpeg_error_mgr jerr; cinfo.err = jpeg_std_error(&jerr); jerr.error_exit = jpegErrorExit; try { jpeg_create_compress(&cinfo); // Initialize output stream KisJPEGDestination::setDestination(&cinfo, io); cinfo.image_width = width; // image width and height, in pixels cinfo.image_height = height; cinfo.input_components = cs->colorChannelCount(); // number of color channels per pixel */ cinfo.in_color_space = color_type; // colorspace of input image // Set default compression parameters jpeg_set_defaults(&cinfo); // Customize them jpeg_set_quality(&cinfo, options.quality, (boolean)options.baseLineJPEG); if (options.progressive) { jpeg_simple_progression(&cinfo); } // Optimize ? cinfo.optimize_coding = (boolean)options.optimize; // Smoothing cinfo.smoothing_factor = (boolean)options.smooth; // Subsampling switch (options.subsampling) { default: case 0: { cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 2; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 1: { cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 2: { cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 2; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; case 3: { cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[1].h_samp_factor = 1; cinfo.comp_info[1].v_samp_factor = 1; cinfo.comp_info[2].h_samp_factor = 1; cinfo.comp_info[2].v_samp_factor = 1; } break; } // Save resolution cinfo.X_density = INCH_TO_POINT(image->xRes()); // It is the "invert" macro because we convert from pointer-per-inchs to points cinfo.Y_density = INCH_TO_POINT(image->yRes()); // It is the "invert" macro because we convert from pointer-per-inchs to points cinfo.density_unit = 1; cinfo.write_JFIF_header = (boolean)true; // Start compression jpeg_start_compress(&cinfo, (boolean)true); // Save exif and iptc information if any available if (metaData && !metaData->empty()) { metaData->applyFilters(options.filters); // Save EXIF if (options.exif) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); Q_ASSERT(exifIO); QBuffer buffer; exifIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "Exif information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 1, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "EXIF information could not be saved."; // TODO: warn the user ? } } // Save IPTC if (options.iptc) { dbgFile << "Trying to save exif information"; KisMetaData::IOBackend* iptcIO = KisMetaData::IOBackendRegistry::instance()->value("iptc"); Q_ASSERT(iptcIO); QBuffer buffer; iptcIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "IPTC information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 13, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "IPTC information could not be saved."; // TODO: warn the user ? } } // Save XMP if (options.xmp) { dbgFile << "Trying to save XMP information"; KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); Q_ASSERT(xmpIO); QBuffer buffer; xmpIO->saveTo(metaData, &buffer, KisMetaData::IOBackend::JpegHeader); dbgFile << "XMP information size is" << buffer.data().size(); QByteArray data = buffer.data(); if (data.size() < MAX_DATA_BYTES_IN_MARKER) { jpeg_write_marker(&cinfo, JPEG_APP0 + 14, (const JOCTET*)data.data(), data.size()); } else { dbgFile << "XMP information could not be saved."; // TODO: warn the user ? } } } KisPaintDeviceSP dev = new KisPaintDevice(layer->colorSpace()); KoColor c(options.transparencyFillColor, layer->colorSpace()); dev->fill(QRect(0, 0, width, height), c); KisPainter gc(dev); gc.bitBlt(QPoint(0, 0), layer->paintDevice(), QRect(0, 0, width, height)); gc.end(); if (options.saveProfile) { const KoColorProfile* colorProfile = layer->colorSpace()->profile(); QByteArray colorProfileData = colorProfile->rawData(); write_icc_profile(& cinfo, (uchar*) colorProfileData.data(), colorProfileData.size()); } // Write data information JSAMPROW row_pointer = new JSAMPLE[width*cinfo.input_components]; int color_nb_bits = 8 * layer->paintDevice()->pixelSize() / layer->paintDevice()->channelCount(); for (; cinfo.next_scanline < height;) { KisHLineConstIteratorSP it = dev->createHLineConstIteratorNG(0, cinfo.next_scanline, width); quint8 *dst = row_pointer; switch (color_type) { case JCS_GRAYSCALE: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = cs->scaleToU8(d, 0);//d[0] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = d[0]; } while (it->nextPixel()); } break; case JCS_RGB: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = cs->scaleToU8(d, 2); //d[2] / quint8_MAX; *(dst++) = cs->scaleToU8(d, 1); //d[1] / quint8_MAX; *(dst++) = cs->scaleToU8(d, 0); //d[0] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = d[2]; *(dst++) = d[1]; *(dst++) = d[0]; } while (it->nextPixel()); } break; case JCS_CMYK: if (color_nb_bits == 16) { do { //const quint16 *d = reinterpret_cast(it->oldRawData()); const quint8 *d = it->oldRawData(); *(dst++) = quint8_MAX - cs->scaleToU8(d, 0);//quint8_MAX - d[0] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 1);//quint8_MAX - d[1] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 2);//quint8_MAX - d[2] / quint8_MAX; *(dst++) = quint8_MAX - cs->scaleToU8(d, 3);//quint8_MAX - d[3] / quint8_MAX; } while (it->nextPixel()); } else { do { const quint8 *d = it->oldRawData(); *(dst++) = quint8_MAX - d[0]; *(dst++) = quint8_MAX - d[1]; *(dst++) = quint8_MAX - d[2]; *(dst++) = quint8_MAX - d[3]; } while (it->nextPixel()); } break; default: delete [] row_pointer; jpeg_destroy_compress(&cinfo); return ImportExportCodes::FormatFeaturesUnsupported; } jpeg_write_scanlines(&cinfo, &row_pointer, 1); } // Writing is over jpeg_finish_compress(&cinfo); delete [] row_pointer; // Free memory jpeg_destroy_compress(&cinfo); return ImportExportCodes::OK; } catch( std::runtime_error &e) { jpeg_destroy_compress(&cinfo); return ImportExportCodes::ErrorWhileWriting; } } void KisJPEGConverter::cancel() { m_d->stop = true; } diff --git a/plugins/impex/kra/kra_converter.cpp b/plugins/impex/kra/kra_converter.cpp index 191042cb03..64b6f34de1 100644 --- a/plugins/impex/kra/kra_converter.cpp +++ b/plugins/impex/kra/kra_converter.cpp @@ -1,387 +1,407 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kra_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const char CURRENT_DTD_VERSION[] = "2.0"; KraConverter::KraConverter(KisDocument *doc) : m_doc(doc) , m_image(doc->savingImage()) { } +KraConverter::KraConverter(KisDocument *doc, QPointer updater) + : m_doc(doc) + , m_image(doc->savingImage()) + , m_updater(updater) +{ +} + KraConverter::~KraConverter() { delete m_store; delete m_kraSaver; delete m_kraLoader; } KisImportExportErrorCode KraConverter::buildImage(QIODevice *io) { m_store = KoStore::createStore(io, KoStore::Read, "", KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Not a valid Krita file")); return ImportExportCodes::FileFormatIncorrect; } bool success; { if (m_store->hasFile("root") || m_store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml) KoXmlDocument doc; KisImportExportErrorCode res = oldLoadAndParse(m_store, "root", doc); if (res.isOk()) res = loadXML(doc, m_store); if (!res.isOk()) { return res; } } else { errUI << "ERROR: No maindoc.xml" << endl; m_doc->setErrorMessage(i18n("Invalid document: no file 'maindoc.xml'.")); return ImportExportCodes::FileFormatIncorrect; } if (m_store->hasFile("documentinfo.xml")) { KoXmlDocument doc; if (oldLoadAndParse(m_store, "documentinfo.xml", doc).isOk()) { m_doc->documentInfo()->load(doc); } } success = completeLoading(m_store); } return success ? ImportExportCodes::OK : ImportExportCodes::Failure; } KisImageSP KraConverter::image() { return m_image; } vKisNodeSP KraConverter::activeNodes() { return m_activeNodes; } QList KraConverter::assistants() { return m_assistants; } KisImportExportErrorCode KraConverter::buildFile(QIODevice *io, const QString &filename) { + setProgress(5); m_store = KoStore::createStore(io, KoStore::Write, m_doc->nativeFormatMimeType(), KoStore::Zip); if (m_store->bad()) { m_doc->setErrorMessage(i18n("Could not create the file for saving")); return ImportExportCodes::CannotCreateFile; } + setProgress(20); m_kraSaver = new KisKraSaver(m_doc, filename); KisImportExportErrorCode resultCode = saveRootDocuments(m_store); if (!resultCode.isOk()) { return resultCode; } + setProgress(40); bool result; result = m_kraSaver->saveKeyframes(m_store, m_doc->url().toLocalFile(), true); if (!result) { qWarning() << "saving key frames failed"; } + setProgress(60); result = m_kraSaver->saveBinaryData(m_store, m_image, m_doc->url().toLocalFile(), true, m_doc->isAutosaving()); if (!result) { qWarning() << "saving binary data failed"; } + setProgress(70); result = m_kraSaver->savePalettes(m_store, m_image, m_doc->url().toLocalFile()); if (!result) { qWarning() << "saving palettes data failed"; } + setProgress(80); if (!m_store->finalize()) { return ImportExportCodes::Failure; } if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); return ImportExportCodes::Failure; } + setProgress(90); return ImportExportCodes::OK; } KisImportExportErrorCode KraConverter::saveRootDocuments(KoStore *store) { dbgFile << "Saving root"; if (store->open("root")) { KoStoreDevice dev(store); if (!saveToStream(&dev) || !store->close()) { dbgUI << "saveToStream failed"; return ImportExportCodes::NoAccessToWrite; } } else { m_doc->setErrorMessage(i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml"))); return ImportExportCodes::ErrorWhileWriting; } if (store->open("documentinfo.xml")) { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = m_doc->documentInfo()->save(doc); KoStoreDevice dev(store); QByteArray s = doc.toByteArray(); // this is already Utf8! bool success = dev.write(s.data(), s.size()); if (!success) { return ImportExportCodes::ErrorWhileWriting; } store->close(); } else { return ImportExportCodes::Failure; } if (store->open("preview.png")) { // ### TODO: missing error checking (The partition could be full!) KisImportExportErrorCode result = savePreview(store); (void)store->close(); if (!result.isOk()) { return result; } } else { return ImportExportCodes::Failure; } dbgUI << "Saving done of url:" << m_doc->url().toLocalFile(); return ImportExportCodes::OK; } bool KraConverter::saveToStream(QIODevice *dev) { QDomDocument doc = createDomDocument(); // Save to buffer QByteArray s = doc.toByteArray(); // utf8 already dev->open(QIODevice::WriteOnly); int nwritten = dev->write(s.data(), s.size()); if (nwritten != (int)s.size()) { warnUI << "wrote " << nwritten << "- expected" << s.size(); } return nwritten == (int)s.size(); } QDomDocument KraConverter::createDomDocument() { QDomDocument doc = m_doc->createDomDocument("DOC", CURRENT_DTD_VERSION); QDomElement root = doc.documentElement(); root.setAttribute("editor", "Krita"); root.setAttribute("syntaxVersion", "2"); root.setAttribute("kritaVersion", KritaVersionWrapper::versionString(false)); root.appendChild(m_kraSaver->saveXML(doc, m_image)); if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); } return doc; } KisImportExportErrorCode KraConverter::savePreview(KoStore *store) { QPixmap pix = m_doc->generatePreview(QSize(256, 256)); QImage preview(pix.toImage().convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly)); if (preview.size() == QSize(0,0)) { QSize newSize = m_doc->savingImage()->bounds().size(); newSize.scale(QSize(256, 256), Qt::KeepAspectRatio); preview = QImage(newSize, QImage::Format_ARGB32); preview.fill(QColor(0, 0, 0, 0)); } KoStoreDevice io(store); if (!io.open(QIODevice::WriteOnly)) { return ImportExportCodes::NoAccessToWrite; } bool ret = preview.save(&io, "PNG"); io.close(); return ret ? ImportExportCodes::OK : ImportExportCodes::ErrorWhileWriting; } KisImportExportErrorCode KraConverter::oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc) { //dbgUI <<"Trying to open" << filename; if (!store->open(filename)) { warnUI << "Entry " << filename << " not found!"; m_doc->setErrorMessage(i18n("Could not find %1", filename)); return ImportExportCodes::FileNotExist; } // Error variables for QDomDocument::setContent QString errorMsg; int errorLine, errorColumn; bool ok = xmldoc.setContent(store->device(), &errorMsg, &errorLine, &errorColumn); store->close(); if (!ok) { errUI << "Parsing error in " << filename << "! Aborting!" << endl << " In line: " << errorLine << ", column: " << errorColumn << endl << " Error message: " << errorMsg << endl; m_doc->setErrorMessage(i18n("Parsing error in %1 at line %2, column %3\nError message: %4", filename, errorLine, errorColumn, QCoreApplication::translate("QXml", errorMsg.toUtf8(), 0))); return ImportExportCodes::FileFormatIncorrect; } dbgUI << "File" << filename << " loaded and parsed"; return ImportExportCodes::OK; } KisImportExportErrorCode KraConverter::loadXML(const KoXmlDocument &doc, KoStore *store) { Q_UNUSED(store); KoXmlElement root; KoXmlNode node; if (doc.doctype().name() != "DOC") { errUI << "The format is not supported or the file is corrupted"; m_doc->setErrorMessage(i18n("The format is not supported or the file is corrupted")); return ImportExportCodes::FileFormatIncorrect; } root = doc.documentElement(); int syntaxVersion = root.attribute("syntaxVersion", "3").toInt(); if (syntaxVersion > 2) { errUI << "The file is too new for this version of Krita: " + QString::number(syntaxVersion); m_doc->setErrorMessage(i18n("The file is too new for this version of Krita (%1).", syntaxVersion)); return ImportExportCodes::FormatFeaturesUnsupported; } if (!root.hasChildNodes()) { errUI << "The file has no layers."; m_doc->setErrorMessage(i18n("The file has no layers.")); return ImportExportCodes::FileFormatIncorrect; } m_kraLoader = new KisKraLoader(m_doc, syntaxVersion); // reset the old image before loading the next one m_doc->setCurrentImage(0, false); for (node = root.firstChild(); !node.isNull(); node = node.nextSibling()) { if (node.isElement()) { if (node.nodeName() == "IMAGE") { KoXmlElement elem = node.toElement(); if (!(m_image = m_kraLoader->loadXML(elem))) { if (m_kraLoader->errorMessages().isEmpty()) { errUI << "Unknown error while opening the .kra file."; m_doc->setErrorMessage(i18n("Unknown error.")); } else { m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); errUI << m_kraLoader->errorMessages().join("\n"); } return ImportExportCodes::Failure; } // HACK ALERT! m_doc->hackPreliminarySetImage(m_image); return ImportExportCodes::OK; } else { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("The file does not contain an image.")); } return ImportExportCodes::FileFormatIncorrect; } } } return ImportExportCodes::Failure; } bool KraConverter::completeLoading(KoStore* store) { if (!m_image) { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("Unknown error.")); } else { m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); } return false; } m_image->blockUpdates(); QString layerPathName = m_kraLoader->imageName(); if (!m_store->hasDirectory(layerPathName)) { // We might be hitting an encoding problem. Get the only folder in the toplevel Q_FOREACH (const QString &entry, m_store->directoryList()) { if (entry.contains("/layers/")) { layerPathName = entry.split("/layers/").first(); m_store->setSubstitution(m_kraLoader->imageName(), layerPathName); break; } } } m_kraLoader->loadBinaryData(store, m_image, m_doc->localFilePath(), true); m_kraLoader->loadPalettes(store, m_doc); m_image->unblockUpdates(); if (!m_kraLoader->warningMessages().isEmpty()) { // warnings do not interrupt loading process, so we do not return here m_doc->setWarningMessage(m_kraLoader->warningMessages().join("\n")); } m_activeNodes = m_kraLoader->selectedNodes(); m_assistants = m_kraLoader->assistants(); return true; } void KraConverter::cancel() { m_stop = true; } +void KraConverter::setProgress(int progress) +{ + if (m_updater) { + m_updater->setProgress(progress); + } +} diff --git a/plugins/impex/kra/kra_converter.h b/plugins/impex/kra/kra_converter.h index 46a634e9d7..aa7eeff718 100644 --- a/plugins/impex/kra/kra_converter.h +++ b/plugins/impex/kra/kra_converter.h @@ -1,80 +1,88 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KRA_CONVERTER_H_ #define _KRA_CONVERTER_H_ #include #include #include #include #include #include #include #include +#include +#include +#include + class KisDocument; class KraConverter : public QObject { Q_OBJECT public: KraConverter(KisDocument *doc); + KraConverter(KisDocument *doc, QPointer updater); ~KraConverter() override; KisImportExportErrorCode buildImage(QIODevice *io); KisImportExportErrorCode buildFile(QIODevice *io, const QString &filename); /** * Retrieve the constructed image */ KisImageSP image(); vKisNodeSP activeNodes(); QList assistants(); public Q_SLOTS: virtual void cancel(); private: KisImportExportErrorCode saveRootDocuments(KoStore *store); bool saveToStream(QIODevice *dev); QDomDocument createDomDocument(); KisImportExportErrorCode savePreview(KoStore *store); KisImportExportErrorCode oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc); KisImportExportErrorCode loadXML(const KoXmlDocument &doc, KoStore *store); bool completeLoading(KoStore *store); + void setProgress(int progress); + KisDocument *m_doc {0}; KisImageSP m_image; vKisNodeSP m_activeNodes; QList m_assistants; bool m_stop {false}; KoStore *m_store {0}; KisKraSaver *m_kraSaver {0}; KisKraLoader *m_kraLoader {0}; + QPointer m_updater; }; #endif diff --git a/plugins/impex/kra/kra_export.cpp b/plugins/impex/kra/kra_export.cpp index 5db4f6535d..a8a9346c35 100644 --- a/plugins/impex/kra/kra_export.cpp +++ b/plugins/impex/kra/kra_export.cpp @@ -1,93 +1,93 @@ /* * Copyright (C) 2016 Boudewijn Rempt * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kra_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kra_converter.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_kra_export.json", registerPlugin();) KraExport::KraExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KraExport::~KraExport() { } KisImportExportErrorCode KraExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { KisImageSP image = document->savingImage(); KIS_ASSERT_RECOVER_RETURN_VALUE(image, ImportExportCodes::InternalError); - KraConverter kraConverter(document); + KraConverter kraConverter(document, updater()); KisImportExportErrorCode res = kraConverter.buildFile(io, filename()); dbgFile << "KraExport::convert result =" << res; return res; } void KraExport::initializeCapabilities() { // Kra supports everything, by definition KisExportCheckFactory *factory = 0; Q_FOREACH(const QString &id, KisExportCheckRegistry::instance()->keys()) { factory = KisExportCheckRegistry::instance()->get(id); addCapability(factory->create(KisExportCheckBase::SUPPORTED)); } } QString KraExport::verify(const QString &fileName) const { QString error = KisImportExportFilter::verify(fileName); if (error.isEmpty()) { return KisImportExportFilter::verifyZiPBasedFiles(fileName, QStringList() << "mimetype" << "documentinfo.xml" << "maindoc.xml" << "preview.png"); } return error; } #include diff --git a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.cpp b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.cpp index 4ae0d6544b..3ca5011b3f 100644 --- a/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.cpp +++ b/plugins/impex/raw/3rdparty/libkdcraw/src/kdcraw.cpp @@ -1,581 +1,583 @@ /** =========================================================== * @file * * This file is a part of digiKam project * http://www.digikam.org * * @date 2006-12-09 * @brief a tread-safe libraw C++ program interface * * @author Copyright (C) 2006-2015 by Gilles Caulier * caulier dot gilles at gmail dot com * @author Copyright (C) 2006-2013 by Marcel Wiesweg * marcel dot wiesweg at gmx dot de * @author Copyright (C) 2007-2008 by Guillaume Castagnino * casta at xwing dot info * * 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, 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. * * ============================================================ */ #include "kdcraw.h" #include "kdcraw_p.h" // Qt includes #include #include #include // KDE includes //#include // LibRaw includes #include #ifdef LIBRAW_HAS_CONFIG #include #endif // Local includes #include "libkdcraw_debug.h" #include "libkdcraw_version.h" #include "rawfiles.h" namespace KDcrawIface { KDcraw::KDcraw() : d(new Private(this)) { m_cancel = false; } KDcraw::~KDcraw() { cancel(); delete d; } QString KDcraw::version() { return QString(KDCRAW_VERSION_STRING); } void KDcraw::cancel() { m_cancel = true; } bool KDcraw::loadRawPreview(QImage& image, const QString& path) { // In first, try to extract the embedded JPEG preview. Very fast. bool ret = loadEmbeddedPreview(image, path); if (ret) return true; // In second, decode and half size of RAW picture. More slow. return (loadHalfPreview(image, path)); } bool KDcraw::loadEmbeddedPreview(QImage& image, const QString& path) { QByteArray imgData; if ( loadEmbeddedPreview(imgData, path) ) { qCDebug(LIBKDCRAW_LOG) << "Preview data size:" << imgData.size(); if (image.loadFromData( imgData )) { qCDebug(LIBKDCRAW_LOG) << "Using embedded RAW preview extraction"; return true; } } qCDebug(LIBKDCRAW_LOG) << "Failed to load embedded RAW preview"; return false; } bool KDcraw::loadEmbeddedPreview(QByteArray& imgData, const QString& path) { QFileInfo fileInfo(path); QString rawFilesExt(rawFiles()); QString ext = fileInfo.suffix().toUpper(); if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext)) return false; LibRaw raw; int ret = raw.open_file((const char*)(QFile::encodeName(path)).constData()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_file: " << libraw_strerror(ret); raw.recycle(); return false; } return (Private::loadEmbeddedPreview(imgData, raw)); } bool KDcraw::loadEmbeddedPreview(QByteArray& imgData, const QBuffer& buffer) { QString rawFilesExt(KDcrawIface::KDcraw::rawFiles()); LibRaw raw; QByteArray inData = buffer.data(); int ret = raw.open_buffer((void*) inData.data(), (size_t) inData.size()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_buffer: " << libraw_strerror(ret); raw.recycle(); return false; } return (Private::loadEmbeddedPreview(imgData, raw)); } bool KDcraw::loadHalfPreview(QImage& image, const QString& path) { QFileInfo fileInfo(path); QString rawFilesExt(rawFiles()); QString ext = fileInfo.suffix().toUpper(); if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext)) return false; qCDebug(LIBKDCRAW_LOG) << "Try to use reduced RAW picture extraction"; LibRaw raw; raw.imgdata.params.use_auto_wb = 1; // Use automatic white balance. raw.imgdata.params.use_camera_wb = 1; // Use camera white balance, if possible. raw.imgdata.params.half_size = 1; // Half-size color image (3x faster than -q). int ret = raw.open_file((const char*)(QFile::encodeName(path)).constData()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_file: " << libraw_strerror(ret); raw.recycle(); return false; } if(!Private::loadHalfPreview(image, raw)) { qCDebug(LIBKDCRAW_LOG) << "Failed to get half preview from LibRaw!"; return false; } qCDebug(LIBKDCRAW_LOG) << "Using reduced RAW picture extraction"; return true; } bool KDcraw::loadHalfPreview(QByteArray& imgData, const QString& path) { QFileInfo fileInfo(path); QString rawFilesExt(rawFiles()); QString ext = fileInfo.suffix().toUpper(); if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext)) return false; qCDebug(LIBKDCRAW_LOG) << "Try to use reduced RAW picture extraction"; LibRaw raw; int ret = raw.open_file((const char*)(QFile::encodeName(path)).constData()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run dcraw_process: " << libraw_strerror(ret); raw.recycle(); return false; } QImage image; if (!Private::loadHalfPreview(image, raw)) { qCDebug(LIBKDCRAW_LOG) << "KDcraw: failed to get half preview: " << libraw_strerror(ret); return false; } QBuffer buffer(&imgData); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "JPEG"); return true; } bool KDcraw::loadHalfPreview(QByteArray& imgData, const QBuffer& inBuffer) { QString rawFilesExt(KDcrawIface::KDcraw::rawFiles()); LibRaw raw; QByteArray inData = inBuffer.data(); int ret = raw.open_buffer((void*) inData.data(), (size_t) inData.size()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run dcraw_make_mem_image: " << libraw_strerror(ret); raw.recycle(); return false; } QImage image; if (!Private::loadHalfPreview(image, raw)) { qCDebug(LIBKDCRAW_LOG) << "KDcraw: failed to get half preview: " << libraw_strerror(ret); return false; } QBuffer buffer(&imgData); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "JPG"); return true; } bool KDcraw::loadFullImage(QImage& image, const QString& path, const RawDecodingSettings& settings) { QFileInfo fileInfo(path); QString rawFilesExt(rawFiles()); QString ext = fileInfo.suffix().toUpper(); if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext)) return false; qCDebug(LIBKDCRAW_LOG) << "Try to load full RAW picture..."; RawDecodingSettings prm = settings; prm.sixteenBitsImage = false; QByteArray imgData; int width, height, rgbmax; KDcraw decoder; bool ret = decoder.decodeRAWImage(path, prm, imgData, width, height, rgbmax); if (!ret) { qCDebug(LIBKDCRAW_LOG) << "Failled to load full RAW picture"; return false; } uchar* sptr = (uchar*)imgData.data(); uchar tmp8[2]; // Set RGB color components. for (int i = 0 ; i < width * height ; ++i) { // Swap Red and Blue tmp8[0] = sptr[2]; tmp8[1] = sptr[0]; sptr[0] = tmp8[0]; sptr[2] = tmp8[1]; sptr += 3; } image = QImage(width, height, QImage::Format_ARGB32); uint* dptr = reinterpret_cast(image.bits()); sptr = (uchar*)imgData.data(); for (int i = 0 ; i < width * height ; ++i) { *dptr++ = qRgba(sptr[2], sptr[1], sptr[0], 0xFF); sptr += 3; } qCDebug(LIBKDCRAW_LOG) << "Load full RAW picture done"; return true; } bool KDcraw::rawFileIdentify(DcrawInfoContainer& identify, const QString& path) { QFileInfo fileInfo(path); QString rawFilesExt(rawFiles()); QString ext = fileInfo.suffix().toUpper(); identify.isDecodable = false; if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext)) return false; LibRaw raw; int ret = raw.open_file((const char*)(QFile::encodeName(path)).constData()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_file: " << libraw_strerror(ret); raw.recycle(); return false; } ret = raw.adjust_sizes_info_only(); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run adjust_sizes_info_only: " << libraw_strerror(ret); raw.recycle(); return false; } Private::fillIndentifyInfo(&raw, identify); raw.recycle(); return true; } // ---------------------------------------------------------------------------------- bool KDcraw::extractRAWData(const QString& filePath, QByteArray& rawData, DcrawInfoContainer& identify, unsigned int shotSelect) { QFileInfo fileInfo(filePath); QString rawFilesExt(rawFiles()); QString ext = fileInfo.suffix().toUpper(); identify.isDecodable = false; if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.toUpper().contains(ext)) return false; if (m_cancel) return false; d->setProgress(0.1); LibRaw raw; // Set progress call back function. raw.set_progress_handler(callbackForLibRaw, d); int ret = raw.open_file((const char*)(QFile::encodeName(filePath)).constData()); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run open_file: " << libraw_strerror(ret); raw.recycle(); return false; } if (m_cancel) { raw.recycle(); return false; } d->setProgress(0.3); raw.imgdata.params.output_bps = 16; raw.imgdata.params.shot_select = shotSelect; ret = raw.unpack(); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run unpack: " << libraw_strerror(ret); raw.recycle(); return false; } if (m_cancel) { raw.recycle(); return false; } d->setProgress(0.4); ret = raw.raw2image(); if (ret != LIBRAW_SUCCESS) { qCDebug(LIBKDCRAW_LOG) << "LibRaw: failed to run raw2image: " << libraw_strerror(ret); raw.recycle(); return false; } if (m_cancel) { raw.recycle(); return false; } d->setProgress(0.6); Private::fillIndentifyInfo(&raw, identify); if (m_cancel) { raw.recycle(); return false; } d->setProgress(0.8); rawData = QByteArray(); if (raw.imgdata.idata.filters == 0) { rawData.resize((int)(raw.imgdata.sizes.iwidth * raw.imgdata.sizes.iheight * raw.imgdata.idata.colors * sizeof(unsigned short))); unsigned short* output = reinterpret_cast(rawData.data()); for (unsigned int row = 0; row < raw.imgdata.sizes.iheight; row++) { for (unsigned int col = 0; col < raw.imgdata.sizes.iwidth; col++) { for (int color = 0; color < raw.imgdata.idata.colors; color++) { *output = raw.imgdata.image[raw.imgdata.sizes.iwidth*row + col][color]; output++; } } } } else { - rawData.resize((int)(raw.imgdata.sizes.iwidth * raw.imgdata.sizes.iheight * sizeof(unsigned short))); + int w = raw.imgdata.sizes.iwidth; + int h = raw.imgdata.sizes.iheight; + rawData.resize(w * h * sizeof(unsigned short)); unsigned short* output = reinterpret_cast(rawData.data()); for (uint row = 0; row < raw.imgdata.sizes.iheight; row++) { for (uint col = 0; col < raw.imgdata.sizes.iwidth; col++) { *output = raw.imgdata.image[raw.imgdata.sizes.iwidth*row + col][raw.COLOR(row, col)]; output++; } } } raw.recycle(); d->setProgress(1.0); return true; } bool KDcraw::decodeHalfRAWImage(const QString& filePath, const RawDecodingSettings& rawDecodingSettings, QByteArray& imageData, int& width, int& height, int& rgbmax) { m_rawDecodingSettings = rawDecodingSettings; m_rawDecodingSettings.halfSizeColorImage = true; return (d->loadFromLibraw(filePath, imageData, width, height, rgbmax)); } bool KDcraw::decodeRAWImage(const QString& filePath, const RawDecodingSettings& rawDecodingSettings, QByteArray& imageData, int& width, int& height, int& rgbmax) { m_rawDecodingSettings = rawDecodingSettings; return (d->loadFromLibraw(filePath, imageData, width, height, rgbmax)); } bool KDcraw::checkToCancelWaitingData() { return m_cancel; } void KDcraw::setWaitingDataProgress(double) { } const char* KDcraw::rawFiles() { return raw_file_extentions; } QStringList KDcraw::rawFilesList() { QString string = QString::fromLatin1(rawFiles()); return string.remove("*.").split(' '); } int KDcraw::rawFilesVersion() { return raw_file_extensions_version; } QStringList KDcraw::supportedCamera() { QStringList camera; const char** const list = LibRaw::cameraList(); for (int i = 0; i < LibRaw::cameraCount(); i++) camera.append(list[i]); return camera; } QString KDcraw::librawVersion() { return QString(LIBRAW_VERSION_STR).remove("-Release"); } int KDcraw::librawUseGomp() { #ifdef LIBRAW_HAS_CONFIG # ifdef LIBRAW_USE_OPENMP return true; # else return false; # endif #else return -1; #endif } int KDcraw::librawUseRawSpeed() { #ifdef LIBRAW_HAS_CONFIG # ifdef LIBRAW_USE_RAWSPEED return true; # else return false; # endif #else return -1; #endif } int KDcraw::librawUseGPL2DemosaicPack() { #ifdef LIBRAW_HAS_CONFIG # ifdef LIBRAW_USE_DEMOSAIC_PACK_GPL2 return true; # else return false; # endif #else return -1; #endif } int KDcraw::librawUseGPL3DemosaicPack() { #ifdef LIBRAW_HAS_CONFIG # ifdef LIBRAW_USE_DEMOSAIC_PACK_GPL3 return true; # else return false; # endif #else return -1; #endif } } // namespace KDcrawIface diff --git a/plugins/impex/xcf/3rdparty/xcftools/flatten.c b/plugins/impex/xcf/3rdparty/xcftools/flatten.c index bfce8bf099..fb10ca7c5d 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/flatten.c +++ b/plugins/impex/xcf/3rdparty/xcftools/flatten.c @@ -1,725 +1,727 @@ /* Flattning functions for xcftools * * This file was written by Henning Makholm * It is hereby in the public domain. * * In jurisdictions that do not recognise grants of copyright to the * public domain: I, the author and (presumably, in those jurisdictions) * copyright holder, hereby permit anyone to distribute and use this code, * in source code or binary form, with or without modifications. This * permission is world-wide and irrevocable. * * Of course, I will not be liable for any errors or shortcomings in the * code, since I give it away without asking any compenstations. * * If you use or distribute this code, I would appreciate receiving * credit for writing it, in whichever way you find proper and customary. */ #include "xcftools.h" #include "flatten.h" #include "pixels.h" #include #include #include static rgba __ATTRIBUTE__((noinline,const)) composite_one(rgba bot,rgba top) { unsigned tfrac, alpha ; tfrac = ALPHA(top) ; alpha = 255 ; if( !FULLALPHA(bot) ) { alpha = 255 ^ scaletable[255-ALPHA(bot)][255-ALPHA(top)] ; /* This peculiar combination of ^ and - makes the GCC code * generator for i386 particularly happy. */ tfrac = (256*ALPHA(top) - 1) / alpha ; /* Tfrac is the fraction of the coposited pixel's covered area * that comes from the top pixel. * For mathematical accuracy we ought to scale by 255 and * subtract alpha/2, but this is faster, and never misses the * true value by more than one 1/255. This effect is completely * overshadowed by the linear interpolation in the first place. * (I.e. gamma is ignored when combining intensities). * [In any case, complete fairness is not possible: if the * bottom pixel had alpha=170 and the top has alpha=102, * each should contribute equally to the color of the * resulting alpha=204 pixel, which is not possible in general] * Subtracting one helps the topfrac never be 256, which would * be bad. * On the other hand it means that we would get tfrac=-1 if the * top pixel is completely transparent, and we get a division * by zero if _both_ pixels are fully transparent. These cases * must be handled by all callers. * More snooping in the Gimp sources reveal that it uses * floating-point for its equivalent of tfrac when the * bottom layer has an alpha channel. (alphify() macro * in paint-funcs.c). What gives? */ } return (alpha << ALPHA_SHIFT) + ((uint32_t)scaletable[ tfrac ][255&(top>>RED_SHIFT )] << RED_SHIFT ) + ((uint32_t)scaletable[ tfrac ][255&(top>>GREEN_SHIFT)] << GREEN_SHIFT ) + ((uint32_t)scaletable[ tfrac ][255&(top>>BLUE_SHIFT )] << BLUE_SHIFT ) + ((uint32_t)scaletable[255^tfrac][255&(bot>>RED_SHIFT )] << RED_SHIFT ) + ((uint32_t)scaletable[255^tfrac][255&(bot>>GREEN_SHIFT)] << GREEN_SHIFT ) + ((uint32_t)scaletable[255^tfrac][255&(bot>>BLUE_SHIFT )] << BLUE_SHIFT ) ; } /* merge_normal() takes ownership of bot. * merge_normal() will share ownership of top. * Return: may be shared. */ static struct Tile * __ATTRIBUTE__((noinline)) merge_normal(struct Tile *bot, struct Tile *top) { unsigned i ; assertTileCompatibility(bot,top); /* See if there is an easy winner */ if( (bot->summary & TILESUMMARY_ALLNULL) || (top->summary & TILESUMMARY_ALLFULL) ) { freeTile(bot); return top ; } if( top->summary & TILESUMMARY_ALLNULL ) { freeTile(top); return bot ; } /* Try hard to make top win */ for( i=0; ; i++ ) { if( i == top->count ) { freeTile(bot); return top ; } if( !(NULLALPHA(bot->pixels[i]) || FULLALPHA(top->pixels[i])) ) break ; } INIT_SCALETABLE_IF( !(top->summary & TILESUMMARY_CRISP) ); /* Otherwise bot wins, but is forever changed ... */ if( (top->summary & TILESUMMARY_ALLNULL) == 0 ) { unsigned i ; invalidateSummary(bot,0); for( i=0 ; i < top->count ; i++ ) { if( !NULLALPHA(top->pixels[i]) ) { if( FULLALPHA(top->pixels[i]) || NULLALPHA(bot->pixels[i]) ) bot->pixels[i] = top->pixels[i] ; else bot->pixels[i] = composite_one(bot->pixels[i],top->pixels[i]); } } } freeTile(top); return bot ; } #define exotic_combinator static unsigned __ATTRIBUTE__((const)) exotic_combinator ucombine_ADDITION(uint8_t bot,uint8_t top) { return bot+top > 255 ? 255 : bot+top ; } exotic_combinator ucombine_SUBTRACT(uint8_t bot,uint8_t top) { return top>bot ? 0 : bot-top ; } exotic_combinator ucombine_LIGHTEN_ONLY(uint8_t bot,uint8_t top) { return top > bot ? top : bot ; } exotic_combinator ucombine_DARKEN_ONLY(uint8_t bot,uint8_t top) { return top < bot ? top : bot ; } exotic_combinator ucombine_DIFFERENCE(uint8_t bot,uint8_t top) { return top > bot ? top-bot : bot-top ; } exotic_combinator ucombine_MULTIPLY(uint8_t bot,uint8_t top) { return scaletable[bot][top] ; } exotic_combinator ucombine_DIVIDE(uint8_t bot,uint8_t top) { int result = (int)bot*256 / (1+top) ; return result >= 256 ? 255 : result ; } exotic_combinator ucombine_SCREEN(uint8_t bot,uint8_t top) { /* An inverted version of "multiply" */ return 255 ^ scaletable[255-bot][255-top] ; } exotic_combinator ucombine_OVERLAY(uint8_t bot,uint8_t top) { return scaletable[bot][bot] + 2*scaletable[top][scaletable[bot][255-bot]] ; /* This strange formula is equivalent to * (1-top)*(bot^2) + top*(1-(1-top)^2) * that is, the top value is used to interpolate between * the self-multiply and the self-screen of the bottom. */ /* Note: This is exactly what the "Soft light" effect also * does, though with different code in the Gimp. */ } exotic_combinator ucombine_DODGE(uint8_t bot,uint8_t top) { return ucombine_DIVIDE(bot,255-top); } exotic_combinator ucombine_BURN(uint8_t bot,uint8_t top) { return 255 - ucombine_DIVIDE(255-bot,top); } exotic_combinator ucombine_HARDLIGHT(uint8_t bot,uint8_t top) { if( top >= 128 ) return 255 ^ scaletable[255-bot][2*(255-top)] ; else return scaletable[bot][2*top]; /* The code that implements "hardlight" in Gimp 2.2.10 has some * rounding errors, but this is undoubtedly what is meant. */ } exotic_combinator ucombine_GRAIN_EXTRACT(uint8_t bot,uint8_t top) { int temp = (int)bot - (int)top + 128 ; return temp < 0 ? 0 : temp >= 256 ? 255 : temp ; } exotic_combinator ucombine_GRAIN_MERGE(uint8_t bot,uint8_t top) { int temp = (int)bot + (int)top - 128 ; return temp < 0 ? 0 : temp >= 256 ? 255 : temp ; } struct HSV { enum { HUE_RED_GREEN_BLUE,HUE_RED_BLUE_GREEN,HUE_BLUE_RED_GREEN, HUE_BLUE_GREEN_RED,HUE_GREEN_BLUE_RED,HUE_GREEN_RED_BLUE } hue; unsigned ch1, ch2, ch3 ; }; static void RGBtoHSV(rgba rgb,struct HSV *hsv) { unsigned RED = (uint8_t)(rgb >> RED_SHIFT); unsigned GREEN = (uint8_t)(rgb >> GREEN_SHIFT); unsigned BLUE = (uint8_t)(rgb >> BLUE_SHIFT) ; #define HEXTANT(b,m,t) hsv->ch1 = b, hsv->ch2 = m, hsv->ch3 = t, \ hsv->hue = HUE_ ## b ## _ ## m ## _ ## t if( GREEN <= RED ) if( BLUE <= RED ) if( GREEN <= BLUE ) HEXTANT(GREEN,BLUE,RED); else HEXTANT(BLUE,GREEN,RED); else HEXTANT(GREEN,RED,BLUE); else if( BLUE <= RED ) HEXTANT(BLUE,RED,GREEN); else if( BLUE <= GREEN ) HEXTANT(RED,BLUE,GREEN); else HEXTANT(RED,GREEN,BLUE); #undef HEXTANT } /* merge_exotic() destructively updates bot. * merge_exotic() reads but does not free top. */ static int __ATTRIBUTE__((noinline)) merge_exotic(struct Tile *bot, const struct Tile *top, GimpLayerModeEffects mode) { unsigned i ; assertTileCompatibility(bot,top); if( (bot->summary & TILESUMMARY_ALLNULL) != 0 ) return XCF_OK; if( (top->summary & TILESUMMARY_ALLNULL) != 0 ) return XCF_OK; assert( bot->refcount == 1 ); /* The transparency status of bot never changes */ INIT_SCALETABLE_IF(1); for( i=0; i < top->count ; i++ ) { uint32_t RED, GREEN, BLUE ; if( NULLALPHA(bot->pixels[i]) || NULLALPHA(top->pixels[i]) ) continue ; #define UNIFORM(mode) case GIMP_ ## mode ## _MODE: \ RED = ucombine_ ## mode (bot->pixels[i]>>RED_SHIFT , \ top->pixels[i]>>RED_SHIFT ); \ GREEN = ucombine_ ## mode (bot->pixels[i]>>GREEN_SHIFT, \ top->pixels[i]>>GREEN_SHIFT); \ BLUE = ucombine_ ## mode (bot->pixels[i]>>BLUE_SHIFT , \ top->pixels[i]>>BLUE_SHIFT ); \ break ; switch( mode ) { case GIMP_NORMAL_MODE: case GIMP_DISSOLVE_MODE: { FatalUnexpected("Normal and Dissolve mode can't happen here!"); return XCF_ERROR; } UNIFORM(ADDITION); UNIFORM(SUBTRACT); UNIFORM(LIGHTEN_ONLY); UNIFORM(DARKEN_ONLY); UNIFORM(DIFFERENCE); UNIFORM(MULTIPLY); UNIFORM(DIVIDE); UNIFORM(SCREEN); case GIMP_SOFTLIGHT_MODE: /* A synonym for "overlay"! */ UNIFORM(OVERLAY); UNIFORM(DODGE); UNIFORM(BURN); UNIFORM(HARDLIGHT); UNIFORM(GRAIN_EXTRACT); UNIFORM(GRAIN_MERGE); case GIMP_HUE_MODE: case GIMP_SATURATION_MODE: case GIMP_VALUE_MODE: case GIMP_COLOR_MODE: { static struct HSV hsvTop, hsvBot ; RGBtoHSV(top->pixels[i],&hsvTop); if( mode == GIMP_HUE_MODE && hsvTop.ch1 == hsvTop.ch3 ) continue ; RGBtoHSV(bot->pixels[i],&hsvBot); if( mode == GIMP_VALUE_MODE ) { if( hsvBot.ch3 ) { hsvBot.ch1 = (hsvBot.ch1*hsvTop.ch3 + hsvBot.ch3/2) / hsvBot.ch3; hsvBot.ch2 = (hsvBot.ch2*hsvTop.ch3 + hsvBot.ch3/2) / hsvBot.ch3; hsvBot.ch3 = hsvTop.ch3 ; } else { hsvBot.ch1 = hsvBot.ch2 = hsvBot.ch3 = hsvTop.ch3 ; } } else { unsigned mfNum, mfDenom ; if( mode == GIMP_HUE_MODE || mode == GIMP_COLOR_MODE ) { mfNum = hsvTop.ch2-hsvTop.ch1 ; mfDenom = hsvTop.ch3-hsvTop.ch1 ; hsvBot.hue = hsvTop.hue ; } else { mfNum = hsvBot.ch2-hsvBot.ch1 ; mfDenom = hsvBot.ch3-hsvBot.ch1 ; } if( mode == GIMP_SATURATION_MODE ) { if( hsvTop.ch3 == 0 ) hsvBot.ch1 = hsvBot.ch3 ; /* Black has no saturation */ else hsvBot.ch1 = (hsvTop.ch1*hsvBot.ch3 + hsvTop.ch3/2) / hsvTop.ch3; } else if( mode == GIMP_COLOR_MODE ) { /* GIMP_COLOR_MODE works in HSL space instead of HSV. We must * transfer H and S, keeping the L = ch1+ch3 of the bottom pixel, * but the S we transfer works differently from the S in HSV. */ unsigned L = hsvTop.ch1 + hsvTop.ch3 ; unsigned sNum = hsvTop.ch3 - hsvTop.ch1 ; unsigned sDenom = L < 256 ? L : 510-L ; if( sDenom == 0 ) sDenom = 1 ; /* sNum will be 0 */ L = hsvBot.ch1 + hsvBot.ch3 ; if( L < 256 ) { /* Ideally we want to compute L/2 * (1-sNum/sDenom) * But shuffle this a bit so we can use integer arithmetic. * The "-1" in the rounding prevents us from ending up with * ch1 > ch3. */ hsvBot.ch1 = (L*(sDenom-sNum)+sDenom-1)/(2*sDenom); hsvBot.ch3 = L - hsvBot.ch1 ; } else { /* Here our goal is 255 - (510-L)/2 * (1-sNum/sDenom) */ hsvBot.ch3 = 255 - ((510-L)*(sDenom-sNum)+sDenom-1)/(2*sDenom); hsvBot.ch1 = L - hsvBot.ch3 ; } assert(hsvBot.ch3 <= 255); assert(hsvBot.ch3 >= hsvBot.ch1); } if( mfDenom == 0 ) hsvBot.ch2 = hsvBot.ch1 ; else hsvBot.ch2 = hsvBot.ch1 + (mfNum*(hsvBot.ch3-hsvBot.ch1) + mfDenom/2) / mfDenom ; } switch( hsvBot.hue ) { #define HEXTANT(b,m,t) case HUE_ ## b ## _ ## m ## _ ## t : \ b = hsvBot.ch1; m = hsvBot.ch2; t = hsvBot.ch3; break; HEXTANT(RED,GREEN,BLUE); HEXTANT(RED,BLUE,GREEN); HEXTANT(BLUE,RED,GREEN); HEXTANT(BLUE,GREEN,RED); HEXTANT(GREEN,BLUE,RED); HEXTANT(GREEN,RED,BLUE); #undef HEXTANT default: { FatalUnexpected("Hue hextant is %d", hsvBot.hue); return XCF_ERROR; } } break ; } default: { FatalUnsupportedXCF(_("'%s' layer mode"), _(showGimpLayerModeEffects(mode))); return XCF_ERROR; } } if( FULLALPHA(bot->pixels[i] & top->pixels[i]) ) bot->pixels[i] = (bot->pixels[i] & (255 << ALPHA_SHIFT)) + (RED << RED_SHIFT) + (GREEN << GREEN_SHIFT) + (BLUE << BLUE_SHIFT) ; else { rgba bp = bot->pixels[i] ; /* In a sane world, the alpha of the top pixel would simply be * used to interpolate linearly between the bottom pixel's base * color and the effect-computed color. * But no! What the Gimp actually does is empirically * described by the following (which borrows code from * composite_one() that makes no theoretical sense here): */ unsigned tfrac = ALPHA(top->pixels[i]) ; if( !FULLALPHA(bp) ) { unsigned pseudotop = (tfrac < ALPHA(bp) ? tfrac : ALPHA(bp)); unsigned alpha = 255 ^ scaletable[255-ALPHA(bp)][255-pseudotop] ; tfrac = (256*pseudotop - 1) / alpha ; } bot->pixels[i] = (bp & (255 << ALPHA_SHIFT)) + ((rgba)scaletable[ tfrac ][ RED ] << RED_SHIFT ) + ((rgba)scaletable[ tfrac ][ GREEN ] << GREEN_SHIFT) + ((rgba)scaletable[ tfrac ][ BLUE ] << BLUE_SHIFT ) + ((rgba)scaletable[255^tfrac][255&(bp>>RED_SHIFT )] << RED_SHIFT ) + ((rgba)scaletable[255^tfrac][255&(bp>>GREEN_SHIFT)] << GREEN_SHIFT) + ((rgba)scaletable[255^tfrac][255&(bp>>BLUE_SHIFT )] << BLUE_SHIFT ) ; } } return XCF_OK; } static void dissolveTile(struct Tile *tile) { unsigned i ; summary_t summary ; assert( tile->refcount == 1 ); if( (tile->summary & TILESUMMARY_CRISP) ) return ; summary = TILESUMMARY_UPTODATE + TILESUMMARY_ALLNULL + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ; for( i = 0 ; i < tile->count ; i++ ) { if( FULLALPHA(tile->pixels[i]) ) summary &= ~TILESUMMARY_ALLNULL ; else if ( NULLALPHA(tile->pixels[i]) ) summary &= ~TILESUMMARY_ALLFULL ; else if( ALPHA(tile->pixels[i]) > rand() % 0xFF ) { tile->pixels[i] |= 255 << ALPHA_SHIFT ; summary &= ~TILESUMMARY_ALLNULL ; } else { tile->pixels[i] = 0 ; summary &= ~TILESUMMARY_ALLFULL ; } } tile->summary = summary ; } static void roundAlpha(struct Tile *tile) { unsigned i ; summary_t summary ; assert( tile->refcount == 1 ); if( (tile->summary & TILESUMMARY_CRISP) ) return ; summary = TILESUMMARY_UPTODATE + TILESUMMARY_ALLNULL + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ; for( i = 0 ; i < tile->count ; i++ ) { if( ALPHA(tile->pixels[i]) >= 128 ) { tile->pixels[i] |= 255 << ALPHA_SHIFT ; summary &= ~TILESUMMARY_ALLNULL ; } else { tile->pixels[i] = 0 ; summary &= ~TILESUMMARY_ALLFULL ; } } tile->summary = summary ; } /* flattenTopdown() shares ownership of top. * The return value may be a shared tile. */ static struct Tile * flattenTopdown(struct FlattenSpec *spec, struct Tile *top, unsigned nlayers, const struct rect *where) { - struct Tile *tile; + struct Tile *tile = 0; while( nlayers-- ) { - if( tileSummary(top) & TILESUMMARY_ALLFULL ) + if( tileSummary(top) & TILESUMMARY_ALLFULL ) { + freeTile(tile); return top ; + } if( !spec->layers[nlayers].isVisible ) continue ; tile = getLayerTile(&spec->layers[nlayers],where); if (tile == XCF_PTR_EMPTY) { return XCF_PTR_EMPTY; } if( tile->summary & TILESUMMARY_ALLNULL ) continue ; /* Simulate a tail call */ switch( spec->layers[nlayers].mode ) { case GIMP_NORMAL_NOPARTIAL_MODE: roundAlpha(tile) ; /* Falls through */ case GIMP_DISSOLVE_MODE: dissolveTile(tile); /* Falls through */ case GIMP_NORMAL_MODE: top = merge_normal(tile,top); break ; default: { struct Tile *below, *above ; unsigned i ; if( !(top->summary & TILESUMMARY_ALLNULL) ) { rgba tile_or = 0 ; invalidateSummary(tile,0); for( i=0; icount; i++ ) if( FULLALPHA(top->pixels[i]) ) tile->pixels[i] = 0 ; else tile_or |= tile->pixels[i] ; /* If the tile only has pixels that will be covered by 'top' anyway, * forget it anyway. */ if( ALPHA(tile_or) == 0 ) { freeTile(tile); break ; /* from the switch, which will continue the while */ } } /* Create a dummy top for the layers below this */ if( top->summary & TILESUMMARY_CRISP ) { above = forkTile(top); if(above == XCF_PTR_EMPTY) { return XCF_PTR_EMPTY; } } else { summary_t summary = TILESUMMARY_ALLNULL ; above = newTile(*where); for( i=0; icount; i++ ) if( FULLALPHA(top->pixels[i]) ) { above->pixels[i] = -1 ; summary = 0 ; } else above->pixels[i] = 0 ; above->summary = TILESUMMARY_UPTODATE + TILESUMMARY_CRISP + summary; } below = flattenTopdown(spec, above, nlayers, where); if (below == XCF_PTR_EMPTY) { return XCF_PTR_EMPTY; } if( below->refcount > 1 ) { if (below != top) { return XCF_PTR_EMPTY; } /* This can only happen if 'below' is a copy of 'top' * THROUGH 'above', which in turn means that none of all * this is visible after all. So just free it and return 'top'. */ freeTile(below); return top ; } if (merge_exotic(below,tile,spec->layers[nlayers].mode) != XCF_OK) { return XCF_PTR_EMPTY; } freeTile(tile); top = merge_normal(below,top); return top ; } } } return top ; } static int addBackground(struct FlattenSpec *spec, struct Tile *tile, unsigned ncols) { unsigned i ; if( tileSummary(tile) & TILESUMMARY_ALLFULL ) return XCF_OK; switch( spec->partial_transparency_mode ) { case FORBID_PARTIAL_TRANSPARENCY: if( !(tileSummary(tile) & TILESUMMARY_CRISP) ) { FatalGeneric(102,_("Flattened image has partially transparent pixels")); return XCF_ERROR; } break ; case DISSOLVE_PARTIAL_TRANSPARENCY: dissolveTile(tile); break ; case ALLOW_PARTIAL_TRANSPARENCY: case PARTIAL_TRANSPARENCY_IMPOSSIBLE: break ; } if( spec->default_pixel == CHECKERED_BACKGROUND ) { INIT_SCALETABLE_IF( !(tile->summary & TILESUMMARY_CRISP ) ); for( i=0; icount; i++ ) if( !FULLALPHA(tile->pixels[i]) ) { rgba fillwith = ((i/ncols)^(i%ncols))&8 ? 0x66 : 0x99 ; fillwith = graytable[fillwith] + (255 << ALPHA_SHIFT) ; if( NULLALPHA(tile->pixels[i]) ) tile->pixels[i] = fillwith ; else tile->pixels[i] = composite_one(fillwith,tile->pixels[i]); } tile->summary = TILESUMMARY_UPTODATE + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ; return XCF_OK; } if( !FULLALPHA(spec->default_pixel) ) return XCF_OK; if( tileSummary(tile) & TILESUMMARY_ALLNULL ) { fillTile(tile,spec->default_pixel); } else { INIT_SCALETABLE_IF( !(tile->summary & TILESUMMARY_CRISP) ); for( i=0; icount; i++ ) if( NULLALPHA(tile->pixels[i]) ) tile->pixels[i] = spec->default_pixel ; else if( FULLALPHA(tile->pixels[i]) ) ; else tile->pixels[i] = composite_one(spec->default_pixel,tile->pixels[i]); tile->summary = TILESUMMARY_UPTODATE + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ; } return XCF_OK; } int flattenIncrementally(struct FlattenSpec *spec,lineCallback callback) { rgba *rows[TILE_HEIGHT] ; unsigned i, y, nrows, ncols ; struct rect where ; struct Tile *tile ; static struct Tile toptile ; toptile.count = TILE_HEIGHT * TILE_WIDTH ; fillTile(&toptile,0); for( where.t = spec->dim.c.t; where.t < spec->dim.c.b; where.t=where.b ) { where.b = TILE_TOP(where.t)+TILE_HEIGHT ; if( where.b > spec->dim.c.b ) where.b = spec->dim.c.b ; nrows = where.b - where.t ; for( y = 0; y < nrows ; y++ ) rows[y] = xcfmalloc(4*(spec->dim.c.r-spec->dim.c.l)); for( where.l = spec->dim.c.l; where.l < spec->dim.c.r; where.l=where.r ) { where.r = TILE_LEFT(where.l)+TILE_WIDTH ; if( where.r > spec->dim.c.r ) where.r = spec->dim.c.r ; ncols = where.r - where.l ; toptile.count = ncols * nrows ; toptile.refcount = 2 ; /* For bug checking */ assert( toptile.summary == TILESUMMARY_UPTODATE + TILESUMMARY_ALLNULL + TILESUMMARY_CRISP ); tile = flattenTopdown(spec,&toptile,spec->numLayers,&where) ; if (tile == XCF_PTR_EMPTY) { return XCF_ERROR; } toptile.refcount-- ; /* addBackground may change destructively */ if (addBackground(spec,tile,ncols) != XCF_OK) { return XCF_ERROR; } for( i = 0 ; i < tile->count ; i++ ) if( NULLALPHA(tile->pixels[i]) ) tile->pixels[i] = 0 ; for( y = 0 ; y < nrows ; y++ ) memcpy(rows[y] + (where.l - spec->dim.c.l), tile->pixels + y * ncols, ncols*4); if( tile == &toptile ) { fillTile(&toptile,0); } else { freeTile(tile); } } for( y = 0 ; y < nrows ; y++ ) callback(spec->dim.width,rows[y]); } return XCF_OK; } static rgba **collectPointer ; static void collector(unsigned num,rgba *row) { num += 0; *collectPointer++ = row ; } rgba ** flattenAll(struct FlattenSpec *spec) { rgba **rows = xcfmalloc(spec->dim.height * sizeof(rgba*)); if( verboseFlag ) fprintf(stderr,_("Flattening image ...")); collectPointer = rows ; if (flattenIncrementally(spec,collector) != XCF_OK) { xcffree(rows); collectPointer = XCF_PTR_EMPTY; return XCF_PTR_EMPTY; } if( verboseFlag ) fprintf(stderr,"\n"); return rows ; } void shipoutWithCallback(struct FlattenSpec *spec, rgba **pixels, lineCallback callback) { unsigned i ; for( i = 0; i < spec->dim.height; i++ ) { callback(spec->dim.width,pixels[i]); } xcffree(pixels); } diff --git a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings.cpp b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings.cpp index ad559c25a2..d1899edd4b 100644 --- a/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings.cpp +++ b/plugins/paintops/defaultpaintops/duplicate/kis_duplicateop_settings.cpp @@ -1,261 +1,261 @@ /* * Copyright (c) 2002 Patrick Julien * Copyright (c) 2004-2008 Boudewijn Rempt * Copyright (c) 2004 Clarence Dang * Copyright (c) 2004 Adrian Page * Copyright (c) 2004 Cyrille Berger * * 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 "kis_duplicateop_settings.h" #include "kis_duplicateop_option.h" #include "kis_duplicateop_settings_widget.h" #include #include #include #include #include #include #include #include #include #include #include #include KisDuplicateOpSettings::KisDuplicateOpSettings() : m_isOffsetNotUptodate(false), m_duringPaintingStroke(false) { } KisDuplicateOpSettings::~KisDuplicateOpSettings() { } bool KisDuplicateOpSettings::paintIncremental() { return false; } QString KisDuplicateOpSettings::indirectPaintingCompositeOp() const { return COMPOSITE_COPY; } QPointF KisDuplicateOpSettings::offset() const { return m_offset; } QPointF KisDuplicateOpSettings::position() const { return m_position; } bool KisDuplicateOpSettings::mousePressEvent(const KisPaintInformation &info, Qt::KeyboardModifiers modifiers, KisNodeWSP currentNode) { bool ignoreEvent = true; if (modifiers & Qt::ControlModifier) { if (!m_sourceNode || !(modifiers & Qt::AltModifier)) { m_sourceNode = currentNode; } m_position = info.pos(); m_isOffsetNotUptodate = true; ignoreEvent = false; } else { bool resetOrigin = getBool(DUPLICATE_RESET_SOURCE_POINT); if (m_isOffsetNotUptodate || resetOrigin) { m_offset = info.pos() - m_position; m_isOffsetNotUptodate = false; } m_duringPaintingStroke = true; ignoreEvent = true; } return ignoreEvent; } bool KisDuplicateOpSettings::mouseReleaseEvent() { m_duringPaintingStroke = false; bool ignoreEvent = true; return ignoreEvent; } KisNodeWSP KisDuplicateOpSettings::sourceNode() const { return m_sourceNode; } void KisDuplicateOpSettings::activate() { } void KisDuplicateOpSettings::fromXML(const QDomElement& elt) { // First, call the parent class fromXML to make sure all the // properties are saved to the map KisPaintOpSettings::fromXML(elt); m_offset.setX(KisDomUtils::toDouble(elt.attribute("OffsetX", "0.0"))); m_offset.setY(KisDomUtils::toDouble(elt.attribute("OffsetY", "0.0"))); m_isOffsetNotUptodate = false; } void KisDuplicateOpSettings::toXML(QDomDocument& doc, QDomElement& rootElt) const { // Then call the parent class fromXML KisPropertiesConfiguration::toXML(doc, rootElt); rootElt.setAttribute("OffsetX", QString::number(m_offset.x())); rootElt.setAttribute("OffsetY", QString::number(m_offset.y())); } KisPaintOpSettingsSP KisDuplicateOpSettings::clone() const { KisPaintOpSettingsSP setting = KisBrushBasedPaintOpSettings::clone(); - KisDuplicateOpSettings* s = dynamic_cast(setting.data()); + KisDuplicateOpSettings* s = static_cast(setting.data()); s->m_offset = m_offset; s->m_isOffsetNotUptodate = m_isOffsetNotUptodate; s->m_position = m_position; s->m_sourceNode = m_sourceNode; s->m_duringPaintingStroke = m_duringPaintingStroke; return setting; } QPainterPath KisDuplicateOpSettings::brushOutline(const KisPaintInformation &info, const OutlineMode &mode) { QPainterPath path; OutlineMode forcedMode = mode; if (!forcedMode.isVisible) { forcedMode.isVisible = true; forcedMode.forceCircle = true; } // clone tool should always show an outline path = KisBrushBasedPaintOpSettings::brushOutlineImpl(info, forcedMode, 1.0); QPainterPath copy(path); QRectF rect2 = copy.boundingRect(); bool shouldStayInOrigin = m_isOffsetNotUptodate // the clone brush right now waits for first stroke with a new origin, so stays at origin point || !getBool(DUPLICATE_MOVE_SOURCE_POINT) // the brush always use the same source point, so stays at origin point || (!m_duringPaintingStroke && getBool(DUPLICATE_RESET_SOURCE_POINT)); // during the stroke, with reset Origin selected, outline should stay at origin point if (shouldStayInOrigin) { copy.translate(m_position - info.pos()); } else { copy.translate(-m_offset); } path.addPath(copy); qreal dx = rect2.width() / 4.0; qreal dy = rect2.height() / 4.0; rect2.adjust(dx, dy, -dx, -dy); path.moveTo(rect2.topLeft()); path.lineTo(rect2.bottomRight()); path.moveTo(rect2.topRight()); path.lineTo(rect2.bottomLeft()); return path; } #include #include "kis_paintop_preset.h" #include "kis_paintop_settings_update_proxy.h" #include "kis_standard_uniform_properties_factory.h" QList KisDuplicateOpSettings::uniformProperties(KisPaintOpSettingsSP settings) { QList props = listWeakToStrong(m_uniformProperties); if (props.isEmpty()) { { KisUniformPaintOpPropertyCallback *prop = new KisUniformPaintOpPropertyCallback( KisUniformPaintOpPropertyCallback::Bool, "clone_healing", i18n("Healing"), settings, 0); prop->setReadCallback( [](KisUniformPaintOpProperty *prop) { KisDuplicateOptionProperties option; option.readOptionSetting(prop->settings().data()); prop->setValue(option.duplicate_healing); }); prop->setWriteCallback( [](KisUniformPaintOpProperty *prop) { KisDuplicateOptionProperties option; option.readOptionSetting(prop->settings().data()); option.duplicate_healing = prop->value().toBool(); option.writeOptionSetting(prop->settings().data()); }); QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue())); prop->requestReadValue(); props << toQShared(prop); } { KisUniformPaintOpPropertyCallback *prop = new KisUniformPaintOpPropertyCallback( KisUniformPaintOpPropertyCallback::Bool, "clone_movesource", i18n("Move Source"), settings, 0); prop->setReadCallback( [](KisUniformPaintOpProperty *prop) { KisDuplicateOptionProperties option; option.readOptionSetting(prop->settings().data()); prop->setValue(option.duplicate_move_source_point); }); prop->setWriteCallback( [](KisUniformPaintOpProperty *prop) { KisDuplicateOptionProperties option; option.readOptionSetting(prop->settings().data()); option.duplicate_move_source_point = prop->value().toBool(); option.writeOptionSetting(prop->settings().data()); }); QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue())); prop->requestReadValue(); props << toQShared(prop); } } return KisPaintOpSettings::uniformProperties(settings) + props; } diff --git a/plugins/paintops/experiment/kis_experiment_paintop.cpp b/plugins/paintops/experiment/kis_experiment_paintop.cpp index 88c738243f..54764d8233 100644 --- a/plugins/paintops/experiment/kis_experiment_paintop.cpp +++ b/plugins/paintops/experiment/kis_experiment_paintop.cpp @@ -1,358 +1,355 @@ /* * Copyright (c) 2010-2011 Lukáš Tvrdý * Copyright (c) 2012 Dmitry Kazakov * * 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 "kis_experiment_paintop.h" #include "kis_experiment_paintop_settings.h" #include #include #include #include #include #include #include #include KisExperimentPaintOp::KisExperimentPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image) : KisPaintOp(painter) { Q_UNUSED(image); Q_UNUSED(node); m_firstRun = true; m_experimentOption.readOptionSetting(settings); m_displaceEnabled = m_experimentOption.isDisplacementEnabled; m_displaceCoeff = (m_experimentOption.displacement * 0.01 * 14) + 1; // 1..15 [7 default according alchemy] m_speedEnabled = m_experimentOption.isSpeedEnabled; m_speedMultiplier = (m_experimentOption.speed * 0.01 * 35); // 0..35 [15 default according alchemy] m_smoothingEnabled = m_experimentOption.isSmoothingEnabled; m_smoothingThreshold = m_experimentOption.smoothing; m_useMirroring = painter->hasMirroring(); m_windingFill = m_experimentOption.windingFill; m_hardEdge = m_experimentOption.hardEdge; //Sets the brush to pattern or foregroundColor if (m_experimentOption.fillType == ExperimentFillType::Pattern) { m_fillStyle = KisPainter::FillStylePattern; } else { m_fillStyle = KisPainter::FillStyleForegroundColor; } // Mirror options set with appropriate color, pattern, and fillStyle if (m_useMirroring) { m_originalDevice = source()->createCompositionSourceDevice(); m_originalPainter = new KisPainter(m_originalDevice); m_originalPainter->setCompositeOp(COMPOSITE_COPY); m_originalPainter->setPaintColor(painter->paintColor()); m_originalPainter->setPattern(painter->pattern()); m_originalPainter->setFillStyle(m_fillStyle); - - - } else { m_originalPainter = 0; } } KisExperimentPaintOp::~KisExperimentPaintOp() { delete m_originalPainter; } void KisExperimentPaintOp::paintRegion(const QRegion &changedRegion) { if (m_windingFill) { m_path.setFillRule(Qt::WindingFill); } if (m_useMirroring) { m_originalPainter->setAntiAliasPolygonFill(!m_hardEdge); Q_FOREACH (const QRect & rect, changedRegion.rects()) { m_originalPainter->fillPainterPath(m_path, rect); painter()->renderDabWithMirroringNonIncremental(rect, m_originalDevice); } } else { //Sets options when mirror is not selected painter()->setFillStyle(m_fillStyle); painter()->setCompositeOp(COMPOSITE_COPY); painter()->setAntiAliasPolygonFill(!m_hardEdge); Q_FOREACH (const QRect & rect, changedRegion.rects()) { painter()->fillPainterPath(m_path, rect); } } } QPointF KisExperimentPaintOp::speedCorrectedPosition(const KisPaintInformation& pi1, const KisPaintInformation& pi2) { const qreal fadeFactor = 0.6; QPointF diff = pi2.pos() - pi1.pos(); qreal realLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y()); if (realLength < 0.1) return pi2.pos(); qreal coeff = 0.5 * realLength * m_speedMultiplier; m_savedSpeedCoeff = fadeFactor * m_savedSpeedCoeff + (1 - fadeFactor) * coeff; QPointF newPoint = pi1.pos() + diff * m_savedSpeedCoeff / realLength; m_savedSpeedPoint = fadeFactor * m_savedSpeedPoint + (1 - fadeFactor) * newPoint; return m_savedSpeedPoint; } void KisExperimentPaintOp::paintLine(const KisPaintInformation &pi1, const KisPaintInformation &pi2, KisDistanceInformation *currentDistance) { Q_UNUSED(currentDistance); if (!painter()) return; if (m_firstRun) { m_firstRun = false; m_path.moveTo(pi1.pos()); m_path.lineTo(pi2.pos()); m_center = pi1.pos(); m_savedUpdateDistance = 0; m_lastPaintTime = 0; m_savedSpeedCoeff = 0; m_savedSpeedPoint = m_center; m_savedSmoothingDistance = 0; m_savedSmoothingPoint = m_center; } else { const QPointF pos1 = pi1.pos(); QPointF pos2 = pi2.pos(); if (m_speedEnabled) { pos2 = speedCorrectedPosition(pi1, pi2); } int length = (pos2 - pos1).manhattanLength(); m_savedUpdateDistance += length; if (m_smoothingEnabled) { m_savedSmoothingDistance += length; if (m_savedSmoothingDistance > m_smoothingThreshold) { QPointF pt = (m_savedSmoothingPoint + pos2) * 0.5; // for updates approximate curve with two lines m_savedPoints << m_path.currentPosition(); m_savedPoints << m_savedSmoothingPoint; m_savedPoints << m_savedSmoothingPoint; m_savedPoints << pt; m_path.quadTo(m_savedSmoothingPoint, pt); m_savedSmoothingPoint = pos2; m_savedSmoothingDistance = 0; } } else { m_path.lineTo(pos2); m_savedPoints << pos1; m_savedPoints << pos2; } if (m_displaceEnabled) { if (m_path.elementCount() % 16 == 0) { QRectF bounds = m_path.boundingRect(); m_path = applyDisplace(m_path, m_displaceCoeff - length); bounds |= m_path.boundingRect(); qreal threshold = simplifyThreshold(bounds); m_path = KritaUtils::trySimplifyPath(m_path, threshold); } else { m_path = applyDisplace(m_path, m_displaceCoeff - length); } } /** * Refresh rate at least 25fps */ const int timeThreshold = 40; const int elapsedTime = pi2.currentTime() - m_lastPaintTime; QRect pathBounds = m_path.boundingRect().toRect(); int distanceMetric = qMax(pathBounds.width(), pathBounds.height()); if (elapsedTime > timeThreshold || (!m_displaceEnabled && m_savedUpdateDistance > distanceMetric / 8)) { if (m_displaceEnabled) { /** * Rendering the path with diff'ed rects is up to two * times more efficient for really huge shapes (tested * on 2000+ px shapes), however for smaller ones doing * paths arithmetics eats too much time. That's why we * choose the method on the base of the size of the * shape. */ const int pathSizeThreshold = 128; QRegion changedRegion; if (distanceMetric < pathSizeThreshold) { QRectF changedRect = m_path.boundingRect().toRect() | m_lastPaintedPath.boundingRect().toRect(); changedRect.adjust(-1, -1, 1, 1); changedRegion = changedRect.toRect(); } else { QPainterPath diff1 = m_path - m_lastPaintedPath; QPainterPath diff2 = m_lastPaintedPath - m_path; changedRegion = KritaUtils::splitPath(diff1 | diff2); } paintRegion(changedRegion); m_lastPaintedPath = m_path; } else if (!m_savedPoints.isEmpty()) { QRegion changedRegion = KritaUtils::splitTriangles(m_center, m_savedPoints); paintRegion(changedRegion); } m_savedPoints.clear(); m_savedUpdateDistance = 0; m_lastPaintTime = pi2.currentTime(); } } } KisSpacingInformation KisExperimentPaintOp::paintAt(const KisPaintInformation& info) { return updateSpacingImpl(info); } KisSpacingInformation KisExperimentPaintOp::updateSpacingImpl(const KisPaintInformation &info) const { Q_UNUSED(info); return KisSpacingInformation(1.0); } bool tryMergePoints(QPainterPath &path, const QPointF &startPoint, const QPointF &endPoint, qreal &distance, qreal distanceThreshold, bool lastSegment) { qreal length = (endPoint - startPoint).manhattanLength(); if (lastSegment || length > distanceThreshold) { if (distance != 0) { path.lineTo(startPoint); } distance = 0; return false; } distance += length; if (distance > distanceThreshold) { path.lineTo(endPoint); distance = 0; } return true; } qreal KisExperimentPaintOp::simplifyThreshold(const QRectF &bounds) { qreal maxDimension = qMax(bounds.width(), bounds.height()); return qMax(0.01 * maxDimension, 1.0); } QPointF KisExperimentPaintOp::getAngle(const QPointF& p1, const QPointF& p2, qreal distance) { QPointF diff = p1 - p2; qreal realLength = sqrt(diff.x() * diff.x() + diff.y() * diff.y()); return realLength > 0.5 ? p1 + diff * distance / realLength : p1; } QPainterPath KisExperimentPaintOp::applyDisplace(const QPainterPath& path, int speed) { QPointF lastPoint = path.currentPosition(); QPainterPath newPath; int count = path.elementCount(); int curveElementCounter = 0; QPointF ctrl1; QPointF ctrl2; QPointF endPoint; for (int i = 0; i < count; i++) { QPainterPath::Element e = path.elementAt(i); switch (e.type) { case QPainterPath::MoveToElement: { newPath.moveTo(getAngle(QPointF(e.x, e.y), lastPoint, speed)); break; } case QPainterPath::LineToElement: { newPath.lineTo(getAngle(QPointF(e.x, e.y), lastPoint, speed)); break; } case QPainterPath::CurveToElement: { curveElementCounter = 0; endPoint = getAngle(QPointF(e.x, e.y), lastPoint, speed); break; } case QPainterPath::CurveToDataElement: { curveElementCounter++; if (curveElementCounter == 1) { ctrl1 = getAngle(QPointF(e.x, e.y), lastPoint, speed); } else if (curveElementCounter == 2) { ctrl2 = getAngle(QPointF(e.x, e.y), lastPoint, speed); newPath.cubicTo(ctrl1, ctrl2, endPoint); } break; } } }// for return newPath; } diff --git a/plugins/paintops/experiment/kis_experiment_paintop.h b/plugins/paintops/experiment/kis_experiment_paintop.h index 7f146ffe9b..19dec9b9d8 100644 --- a/plugins/paintops/experiment/kis_experiment_paintop.h +++ b/plugins/paintops/experiment/kis_experiment_paintop.h @@ -1,94 +1,94 @@ /* * Copyright (c) 2010-2011 Lukáš Tvrdý * * 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. */ #ifndef KIS_EXPERIMENT_PAINTOP_H_ #define KIS_EXPERIMENT_PAINTOP_H_ #include #include #include #include "kis_experiment_paintop_settings.h" #include "kis_experimentop_option.h" #include class QPointF; class KisPainter; class KisExperimentPaintOp : public KisPaintOp { public: KisExperimentPaintOp(const KisPaintOpSettingsSP settings, KisPainter *painter, KisNodeSP node, KisImageSP image); ~KisExperimentPaintOp() override; void paintLine(const KisPaintInformation& pi1, const KisPaintInformation& pi2, KisDistanceInformation *currentDistance) override; protected: KisSpacingInformation paintAt(const KisPaintInformation& info) override; KisSpacingInformation updateSpacingImpl(const KisPaintInformation &info) const override; private: void paintRegion(const QRegion &changedRegion); QPointF speedCorrectedPosition(const KisPaintInformation& pi1, const KisPaintInformation& pi2); static qreal simplifyThreshold(const QRectF &bounds); static QPointF getAngle(const QPointF& p1, const QPointF& p2, qreal distance); static QPainterPath applyDisplace(const QPainterPath& path, int speed); - bool m_displaceEnabled; - int m_displaceCoeff; + bool m_displaceEnabled {false}; + int m_displaceCoeff {0}; QPainterPath m_lastPaintedPath; - bool m_windingFill; - bool m_hardEdge; + bool m_windingFill {false}; + bool m_hardEdge {false}; - bool m_speedEnabled; - int m_speedMultiplier; - qreal m_savedSpeedCoeff; + bool m_speedEnabled {false}; + int m_speedMultiplier {1}; + qreal m_savedSpeedCoeff {1.0}; QPointF m_savedSpeedPoint; - bool m_smoothingEnabled; - int m_smoothingThreshold; + bool m_smoothingEnabled {false}; + int m_smoothingThreshold {1}; QPointF m_savedSmoothingPoint; - int m_savedSmoothingDistance; + int m_savedSmoothingDistance {1}; - int m_savedUpdateDistance; + int m_savedUpdateDistance {1}; QVector m_savedPoints; - int m_lastPaintTime; + int m_lastPaintTime {0}; - bool m_firstRun; + bool m_firstRun {true}; QPointF m_center; QPainterPath m_path; ExperimentOption m_experimentOption; - bool m_useMirroring; - KisPainter *m_originalPainter; + bool m_useMirroring {false}; + KisPainter *m_originalPainter {0}; KisPaintDeviceSP m_originalDevice; - KisPainter::FillStyle m_fillStyle; + KisPainter::FillStyle m_fillStyle {KisPainter::FillStyleNone}; }; #endif // KIS_EXPERIMENT_PAINTOP_H_ diff --git a/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp b/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp index 49a8ddfb3a..b7dc342b13 100644 --- a/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp +++ b/plugins/paintops/libpaintop/kis_brush_based_paintop_settings.cpp @@ -1,334 +1,336 @@ /* * Copyright (c) 2010 Sven Langkamp * * 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 "kis_brush_based_paintop_settings.h" #include #include #include "kis_brush_based_paintop_options_widget.h" #include #include "kis_brush_server.h" #include #include "kis_signals_blocker.h" #include "kis_brush_option.h" #include struct BrushReader { BrushReader(const KisBrushBasedPaintOpSettings *parent) : m_parent(parent) { m_option.readOptionSetting(m_parent); } KisBrushSP brush() { return m_option.brush(); } const KisBrushBasedPaintOpSettings *m_parent; KisBrushOptionProperties m_option; }; struct BrushWriter { BrushWriter(KisBrushBasedPaintOpSettings *parent) : m_parent(parent) { m_option.readOptionSetting(m_parent); } ~BrushWriter() { m_option.writeOptionSetting(m_parent); } KisBrushSP brush() { return m_option.brush(); } KisBrushBasedPaintOpSettings *m_parent; KisBrushOptionProperties m_option; }; KisBrushBasedPaintOpSettings::KisBrushBasedPaintOpSettings() : KisOutlineGenerationPolicy(KisCurrentOutlineFetcher::SIZE_OPTION | KisCurrentOutlineFetcher::ROTATION_OPTION | KisCurrentOutlineFetcher::MIRROR_OPTION) { } bool KisBrushBasedPaintOpSettings::paintIncremental() { if (hasProperty("PaintOpAction")) { return (enumPaintActionType)getInt("PaintOpAction", WASH) == BUILDUP; } return true; } KisPaintOpSettingsSP KisBrushBasedPaintOpSettings::clone() const { KisPaintOpSettingsSP _settings = KisOutlineGenerationPolicy::clone(); KisBrushBasedPaintOpSettingsSP settings = dynamic_cast(_settings.data()); settings->m_savedBrush = 0; return settings; } KisBrushSP KisBrushBasedPaintOpSettings::brush() const { KisBrushSP brush = m_savedBrush; if (!brush) { BrushReader w(this); brush = w.brush(); m_savedBrush = brush; } return brush; } QPainterPath KisBrushBasedPaintOpSettings::brushOutlineImpl(const KisPaintInformation &info, const OutlineMode &mode, qreal additionalScale) { QPainterPath path; if (mode.isVisible) { KisBrushSP brush = this->brush(); if (!brush) return path; qreal finalScale = brush->scale() * additionalScale; QPainterPath realOutline = brush->outline(); if (mode.forceCircle) { QPainterPath ellipse; ellipse.addEllipse(realOutline.boundingRect()); realOutline = ellipse; } path = outlineFetcher()->fetchOutline(info, this, realOutline, mode, finalScale, brush->angle()); if (mode.showTiltDecoration) { const QPainterPath tiltLine = makeTiltIndicator(info, realOutline.boundingRect().center(), realOutline.boundingRect().width() * 0.5, 3.0); path.addPath(outlineFetcher()->fetchOutline(info, this, tiltLine, mode, finalScale, 0.0, true, realOutline.boundingRect().center().x(), realOutline.boundingRect().center().y())); } } return path; } QPainterPath KisBrushBasedPaintOpSettings::brushOutline(const KisPaintInformation &info, const OutlineMode &mode) { return brushOutlineImpl(info, mode, 1.0); } bool KisBrushBasedPaintOpSettings::isValid() const { QStringList files = getStringList(KisPaintOpUtils::RequiredBrushFilesListTag); files << getString(KisPaintOpUtils::RequiredBrushFileTag); Q_FOREACH (const QString &file, files) { if (!file.isEmpty()) { KisBrushSP brush = KisBrushServer::instance()->brushServer()->resourceByFilename(file); if (!brush) { return false; } } } return true; } bool KisBrushBasedPaintOpSettings::isLoadable() { return (KisBrushServer::instance()->brushServer()->resources().count() > 0); } void KisBrushBasedPaintOpSettings::setAngle(qreal value) { BrushWriter w(this); if (!w.brush()) return; w.brush()->setAngle(value); } qreal KisBrushBasedPaintOpSettings::angle() { return this->brush()->angle(); } void KisBrushBasedPaintOpSettings::setSpacing(qreal value) { BrushWriter w(this); if (!w.brush()) return; w.brush()->setSpacing(value); } qreal KisBrushBasedPaintOpSettings::spacing() { return this->brush()->spacing(); } void KisBrushBasedPaintOpSettings::setAutoSpacing(bool active, qreal coeff) { BrushWriter w(this); if (!w.brush()) return; w.brush()->setAutoSpacing(active, coeff); } bool KisBrushBasedPaintOpSettings::autoSpacingActive() { return this->brush()->autoSpacingActive(); } qreal KisBrushBasedPaintOpSettings::autoSpacingCoeff() { return this->brush()->autoSpacingCoeff(); } void KisBrushBasedPaintOpSettings::setPaintOpSize(qreal value) { BrushWriter w(this); if (!w.brush()) return; w.brush()->setUserEffectiveSize(value); } qreal KisBrushBasedPaintOpSettings::paintOpSize() const { return this->brush()->userEffectiveSize(); } #include #include "kis_paintop_preset.h" #include "kis_paintop_settings_update_proxy.h" QList KisBrushBasedPaintOpSettings::uniformProperties(KisPaintOpSettingsSP settings) { QList props = listWeakToStrong(m_uniformProperties); if (props.isEmpty()) { { KisIntSliderBasedPaintOpPropertyCallback *prop = new KisIntSliderBasedPaintOpPropertyCallback( KisIntSliderBasedPaintOpPropertyCallback::Int, "angle", "Angle", settings, 0); prop->setRange(0, 360); prop->setReadCallback( [](KisUniformPaintOpProperty *prop) { KisBrushBasedPaintOpSettings *s = dynamic_cast(prop->settings().data()); const qreal angleResult = kisRadiansToDegrees(s->angle()); prop->setValue(angleResult); }); prop->setWriteCallback( [](KisUniformPaintOpProperty *prop) { KisBrushBasedPaintOpSettings *s = dynamic_cast(prop->settings().data()); s->setAngle(kisDegreesToRadians(prop->value().toReal())); }); QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue())); prop->requestReadValue(); props << toQShared(prop); } { KisUniformPaintOpPropertyCallback *prop = new KisUniformPaintOpPropertyCallback( KisUniformPaintOpPropertyCallback::Bool, "auto_spacing", "Auto Spacing", settings, 0); prop->setReadCallback( [](KisUniformPaintOpProperty *prop) { KisBrushBasedPaintOpSettings *s = dynamic_cast(prop->settings().data()); prop->setValue(s->autoSpacingActive()); }); prop->setWriteCallback( [](KisUniformPaintOpProperty *prop) { KisBrushBasedPaintOpSettings *s = dynamic_cast(prop->settings().data()); s->setAutoSpacing(prop->value().toBool(), s->autoSpacingCoeff()); }); QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue())); prop->requestReadValue(); props << toQShared(prop); } { KisDoubleSliderBasedPaintOpPropertyCallback *prop = new KisDoubleSliderBasedPaintOpPropertyCallback( KisDoubleSliderBasedPaintOpPropertyCallback::Double, "spacing", "Spacing", settings, 0); prop->setRange(0.01, 10); prop->setSingleStep(0.01); prop->setExponentRatio(3.0); prop->setReadCallback( [](KisUniformPaintOpProperty *prop) { KisBrushBasedPaintOpSettings *s = dynamic_cast(prop->settings().data()); - - const qreal value = s->autoSpacingActive() ? - s->autoSpacingCoeff() : s->spacing(); - prop->setValue(value); + if (s) { + const qreal value = s->autoSpacingActive() ? + s->autoSpacingCoeff() : s->spacing(); + prop->setValue(value); + } }); prop->setWriteCallback( [](KisUniformPaintOpProperty *prop) { KisBrushBasedPaintOpSettings *s = dynamic_cast(prop->settings().data()); - - if (s->autoSpacingActive()) { - s->setAutoSpacing(true, prop->value().toReal()); - } else { - s->setSpacing(prop->value().toReal()); + if (s) { + if (s->autoSpacingActive()) { + s->setAutoSpacing(true, prop->value().toReal()); + } else { + s->setSpacing(prop->value().toReal()); + } } }); QObject::connect(preset()->updateProxy(), SIGNAL(sigSettingsChanged()), prop, SLOT(requestReadValue())); prop->requestReadValue(); props << toQShared(prop); } } return KisPaintOpSettings::uniformProperties(settings) + props; } void KisBrushBasedPaintOpSettings::onPropertyChanged() { m_savedBrush.clear(); KisOutlineGenerationPolicy::onPropertyChanged(); } diff --git a/plugins/tools/basictools/kis_tool_colorpicker.cc b/plugins/tools/basictools/kis_tool_colorpicker.cc index b40dbfcef1..1ceeb51f54 100644 --- a/plugins/tools/basictools/kis_tool_colorpicker.cc +++ b/plugins/tools/basictools/kis_tool_colorpicker.cc @@ -1,373 +1,373 @@ /* * Copyright (c) 1999 Matthias Elter * Copyright (c) 2002 Patrick Julien * Copyright (c) 2010 Lukáš Tvrdý * Copyright (c) 2018 Emmet & Eoin O'Neill * * 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 "kis_tool_colorpicker.h" #include #include #include "kis_cursor.h" #include "KisDocument.h" #include "kis_canvas2.h" #include "KisReferenceImagesLayer.h" #include "KoCanvasBase.h" #include "kis_random_accessor_ng.h" #include "KoResourceServerProvider.h" #include #include "kis_wrapped_rect.h" #include "kis_tool_utils.h" namespace { // GUI ComboBox index constants const int SAMPLE_MERGED = 0; } KisToolColorPicker::KisToolColorPicker(KoCanvasBase *canvas) : KisTool(canvas, KisCursor::pickerCursor()), m_config(new KisToolUtils::ColorPickerConfig) { setObjectName("tool_colorpicker"); m_isActivated = false; m_optionsWidget = 0; m_pickedColor = KoColor(); } KisToolColorPicker::~KisToolColorPicker() { if (m_isActivated) { m_config->save(m_toolActivationSource == KisTool::DefaultActivation); } } void KisToolColorPicker::paint(QPainter &gc, const KoViewConverter &converter) { Q_UNUSED(gc); Q_UNUSED(converter); } void KisToolColorPicker::activate(ToolActivation activation, const QSet &shapes) { m_isActivated = true; m_toolActivationSource = activation; m_config->load(m_toolActivationSource == KisTool::DefaultActivation); updateOptionWidget(); KisTool::activate(activation, shapes); } void KisToolColorPicker::deactivate() { m_config->save(m_toolActivationSource == KisTool::DefaultActivation); m_isActivated = false; KisTool::deactivate(); } bool KisToolColorPicker::pickColor(const QPointF &pos) { // Timer check. if (m_colorPickerDelayTimer.isActive()) { return false; } else { m_colorPickerDelayTimer.setSingleShot(true); m_colorPickerDelayTimer.start(100); } QScopedPointer> imageLocker; m_pickedColor.setOpacity(0.0); // Pick from reference images. if (m_optionsWidget->cmbSources->currentIndex() == SAMPLE_MERGED) { auto *kisCanvas = dynamic_cast(canvas()); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(kisCanvas, false); KisSharedPtr referenceImageLayer = kisCanvas->imageView()->document()->referenceImagesLayer(); if (referenceImageLayer && kisCanvas->referenceImagesDecoration()->visible()) { QColor color = referenceImageLayer->getPixel(pos); if (color.isValid()) { m_pickedColor.fromQColor(color); } } } if (m_pickedColor.opacityU8() == OPACITY_TRANSPARENT_U8) { if (!currentImage()->bounds().contains(pos.toPoint()) && !currentImage()->wrapAroundModePermitted()) { return false; } KisPaintDeviceSP dev; if (m_optionsWidget->cmbSources->currentIndex() != SAMPLE_MERGED && currentNode() && currentNode()->colorPickSourceDevice()) { dev = currentNode()->colorPickSourceDevice(); } else { imageLocker.reset(new boost::lock_guard(*currentImage())); dev = currentImage()->projection(); } KoColor previousColor = canvas()->resourceManager()->foregroundColor(); KisToolUtils::pickColor(m_pickedColor, dev, pos.toPoint(), &previousColor, m_config->radius, m_config->blend); } if (m_config->updateColor && m_pickedColor.opacityU8() != OPACITY_TRANSPARENT_U8) { KoColor publicColor = m_pickedColor; publicColor.setOpacity(OPACITY_OPAQUE_U8); // Alpha is unwanted for FG and BG colors. if (m_config->toForegroundColor) { canvas()->resourceManager()->setResource(KoCanvasResourceProvider::ForegroundColor, publicColor); } else { canvas()->resourceManager()->setResource(KoCanvasResourceProvider::BackgroundColor, publicColor); } } return true; } void KisToolColorPicker::beginPrimaryAction(KoPointerEvent *event) { bool sampleMerged = m_optionsWidget->cmbSources->currentIndex() == SAMPLE_MERGED; if (!sampleMerged) { if (!currentNode()) { QMessageBox::information(0, i18nc("@title:window", "Krita"), i18n("Cannot pick a color as no layer is active.")); event->ignore(); return; } if (!currentNode()->visible()) { QMessageBox::information(0, i18nc("@title:window", "Krita"), i18n("Cannot pick a color as the active layer is not visible.")); event->ignore(); return; } } QPoint pos = convertToImagePixelCoordFloored(event); setMode(KisTool::PAINT_MODE); bool picked = pickColor(pos); if (!picked) { // Color picking has to start in the visible part of the layer event->ignore(); return; } displayPickedColor(); } void KisToolColorPicker::continuePrimaryAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); QPoint pos = convertToImagePixelCoordFloored(event); pickColor(pos); displayPickedColor(); } #include "kis_canvas2.h" #include "kis_display_color_converter.h" void KisToolColorPicker::endPrimaryAction(KoPointerEvent *event) { Q_UNUSED(event); CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); - if (m_config->addPalette) { - KisSwatch ent; - ent.setColor(m_pickedColor); + if (m_config->addColorToCurrentPalette) { + KisSwatch swatch; + swatch.setColor(m_pickedColor); // We don't ask for a name, too intrusive here KoColorSet *palette = m_palettes.at(m_optionsWidget->cmbPalette->currentIndex()); - palette->add(ent); + palette->add(swatch); if (!palette->save()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Cannot write to palette file %1. Maybe it is read-only.", palette->filename())); } } } struct PickedChannel { QString name; QString valueText; }; void KisToolColorPicker::displayPickedColor() { if (m_pickedColor.data() && m_optionsWidget) { QList channels = m_pickedColor.colorSpace()->channels(); m_optionsWidget->listViewChannels->clear(); QVector pickedChannels; for (int i = 0; i < channels.count(); ++i) { pickedChannels.append(PickedChannel()); } for (int i = 0; i < channels.count(); ++i) { PickedChannel pc; pc.name = channels[i]->name(); if (m_config->normaliseValues) { pc.valueText = m_pickedColor.colorSpace()->normalisedChannelValueText(m_pickedColor.data(), i); } else { pc.valueText = m_pickedColor.colorSpace()->channelValueText(m_pickedColor.data(), i); } pickedChannels[channels[i]->displayPosition()] = pc; } Q_FOREACH (const PickedChannel &pc, pickedChannels) { QTreeWidgetItem *item = new QTreeWidgetItem(m_optionsWidget->listViewChannels); item->setText(0, pc.name); item->setText(1, pc.valueText); } KisCanvas2 *kritaCanvas = dynamic_cast(canvas()); KoColor newColor = kritaCanvas->displayColorConverter()->applyDisplayFiltering(m_pickedColor, Float32BitsColorDepthID); QVector values(4); newColor.colorSpace()->normalisedChannelsValue(newColor.data(), values); for (int i = 0; i < values.size(); i++) { QTreeWidgetItem *item = new QTreeWidgetItem(m_optionsWidget->listViewChannels); item->setText(0, QString("DisplayCh%1").arg(i)); item->setText(1, QString::number(values[i])); } } } QWidget* KisToolColorPicker::createOptionWidget() { m_optionsWidget = new ColorPickerOptionsWidget(0); m_optionsWidget->setObjectName(toolId() + " option widget"); m_optionsWidget->listViewChannels->setSortingEnabled(false); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); // Initialize blend KisSliderSpinBox m_optionsWidget->blend->setRange(0,100); m_optionsWidget->blend->setSuffix(i18n("%")); updateOptionWidget(); connect(m_optionsWidget->cbUpdateCurrentColor, SIGNAL(toggled(bool)), SLOT(slotSetUpdateColor(bool))); connect(m_optionsWidget->cbNormaliseValues, SIGNAL(toggled(bool)), SLOT(slotSetNormaliseValues(bool))); connect(m_optionsWidget->cbPalette, SIGNAL(toggled(bool)), SLOT(slotSetAddPalette(bool))); connect(m_optionsWidget->radius, SIGNAL(valueChanged(int)), SLOT(slotChangeRadius(int))); connect(m_optionsWidget->blend, SIGNAL(valueChanged(int)), SLOT(slotChangeBlend(int))); connect(m_optionsWidget->cmbSources, SIGNAL(currentIndexChanged(int)), SLOT(slotSetColorSource(int))); KoResourceServer *srv = KoResourceServerProvider::instance()->paletteServer(); if (!srv) { return m_optionsWidget; } QList palettes = srv->resources(); Q_FOREACH (KoColorSet *palette, palettes) { if (palette) { m_optionsWidget->cmbPalette->addSqueezedItem(palette->name()); m_palettes.append(palette); } } return m_optionsWidget; } void KisToolColorPicker::updateOptionWidget() { if (!m_optionsWidget) return; m_optionsWidget->cbNormaliseValues->setChecked(m_config->normaliseValues); m_optionsWidget->cbUpdateCurrentColor->setChecked(m_config->updateColor); m_optionsWidget->cmbSources->setCurrentIndex(SAMPLE_MERGED + !m_config->sampleMerged); - m_optionsWidget->cbPalette->setChecked(m_config->addPalette); + m_optionsWidget->cbPalette->setChecked(m_config->addColorToCurrentPalette); m_optionsWidget->radius->setValue(m_config->radius); m_optionsWidget->blend->setValue(m_config->blend); } void KisToolColorPicker::setToForeground(bool newValue) { m_config->toForegroundColor = newValue; emit toForegroundChanged(); } bool KisToolColorPicker::toForeground() const { return m_config->toForegroundColor; } void KisToolColorPicker::slotSetUpdateColor(bool state) { m_config->updateColor = state; } void KisToolColorPicker::slotSetNormaliseValues(bool state) { m_config->normaliseValues = state; displayPickedColor(); } void KisToolColorPicker::slotSetAddPalette(bool state) { - m_config->addPalette = state; + m_config->addColorToCurrentPalette = state; } void KisToolColorPicker::slotChangeRadius(int value) { m_config->radius = value; } void KisToolColorPicker::slotChangeBlend(int value) { m_config->blend = value; } void KisToolColorPicker::slotSetColorSource(int value) { m_config->sampleMerged = value == SAMPLE_MERGED; } void KisToolColorPicker::slotAddPalette(KoResource *resource) { KoColorSet *palette = dynamic_cast(resource); if (palette) { m_optionsWidget->cmbPalette->addSqueezedItem(palette->name()); m_palettes.append(palette); } } diff --git a/plugins/tools/basictools/kis_tool_move.cc b/plugins/tools/basictools/kis_tool_move.cc index 9e2747f23c..a905497b3e 100644 --- a/plugins/tools/basictools/kis_tool_move.cc +++ b/plugins/tools/basictools/kis_tool_move.cc @@ -1,714 +1,714 @@ /* * Copyright (c) 1999 Matthias Elter * 1999 Michael Koch * 2002 Patrick Julien * 2004 Boudewijn Rempt * 2016 Michael Abrahams * * 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 "kis_tool_move.h" #include #include "kis_cursor.h" #include "kis_selection.h" #include "kis_canvas2.h" #include "kis_image.h" #include "kis_tool_utils.h" #include "kis_paint_layer.h" #include "strokes/move_stroke_strategy.h" #include "kis_tool_movetooloptionswidget.h" #include "strokes/move_selection_stroke_strategy.h" #include "kis_resources_snapshot.h" #include "kis_action_registry.h" #include "krita_utils.h" #include #include #include "kis_node_manager.h" #include "kis_selection_manager.h" #include "kis_signals_blocker.h" #include #include "KisMoveBoundsCalculationJob.h" struct KisToolMoveState : KisToolChangesTrackerData, boost::equality_comparable { KisToolMoveState(QPoint _accumulatedOffset) : accumulatedOffset(_accumulatedOffset) {} KisToolChangesTrackerData* clone() const override { return new KisToolMoveState(*this); } bool operator ==(const KisToolMoveState &rhs) { return accumulatedOffset == rhs.accumulatedOffset; } QPoint accumulatedOffset; }; KisToolMove::KisToolMove(KoCanvasBase *canvas) : KisTool(canvas, KisCursor::moveCursor()) , m_updateCursorCompressor(100, KisSignalCompressor::FIRST_ACTIVE) { setObjectName("tool_move"); m_showCoordinatesAction = action("movetool-show-coordinates"); m_showCoordinatesAction = action("movetool-show-coordinates"); connect(&m_updateCursorCompressor, SIGNAL(timeout()), this, SLOT(resetCursorStyle())); m_optionsWidget = new MoveToolOptionsWidget(0, currentImage()->xRes(), toolId()); // See https://bugs.kde.org/show_bug.cgi?id=316896 QWidget *specialSpacer = new QWidget(m_optionsWidget); specialSpacer->setObjectName("SpecialSpacer"); specialSpacer->setFixedSize(0, 0); m_optionsWidget->layout()->addWidget(specialSpacer); m_optionsWidget->setFixedHeight(m_optionsWidget->sizeHint().height()); m_showCoordinatesAction->setChecked(m_optionsWidget->showCoordinates()); m_optionsWidget->slotSetTranslate(m_handlesRect.topLeft() + currentOffset()); connect(m_optionsWidget, SIGNAL(sigSetTranslateX(int)), SLOT(moveBySpinX(int)), Qt::UniqueConnection); connect(m_optionsWidget, SIGNAL(sigSetTranslateY(int)), SLOT(moveBySpinY(int)), Qt::UniqueConnection); connect(m_optionsWidget, SIGNAL(sigRequestCommitOffsetChanges()), this, SLOT(commitChanges()), Qt::UniqueConnection); connect(this, SIGNAL(moveInNewPosition(QPoint)), m_optionsWidget, SLOT(slotSetTranslate(QPoint)), Qt::UniqueConnection); connect(qobject_cast(canvas)->viewManager()->nodeManager(), SIGNAL(sigUiNeedChangeSelectedNodes(KisNodeList)), this, SLOT(slotNodeChanged(KisNodeList)), Qt::UniqueConnection); connect(qobject_cast(canvas)->viewManager()->selectionManager(), SIGNAL(currentSelectionChanged()), this, SLOT(slotSelectionChanged()), Qt::UniqueConnection); } KisToolMove::~KisToolMove() { endStroke(); } void KisToolMove::resetCursorStyle() { KisTool::resetCursorStyle(); if (!isActive()) return; KisImageSP image = this->image(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, currentNode(), canvas()->resourceManager()); KisSelectionSP selection = resources->activeSelection(); KisNodeList nodes = fetchSelectedNodes(moveToolMode(), &m_lastCursorPos, selection); if (nodes.isEmpty()) { canvas()->setCursor(Qt::ForbiddenCursor); } } KisNodeList KisToolMove::fetchSelectedNodes(MoveToolMode mode, const QPoint *pixelPoint, KisSelectionSP selection) { KisNodeList nodes; KisImageSP image = this->image(); if (mode != MoveSelectedLayer && pixelPoint) { const bool wholeGroup = !selection && mode == MoveGroup; KisNodeSP node = KisToolUtils::findNode(image->root(), *pixelPoint, wholeGroup); if (node) { nodes = {node}; } } if (nodes.isEmpty()) { nodes = this->selectedNodes(); KritaUtils::filterContainer(nodes, [](KisNodeSP node) { return node->isEditable(); }); } return nodes; } bool KisToolMove::startStrokeImpl(MoveToolMode mode, const QPoint *pos) { KisNodeSP node; KisImageSP image = this->image(); KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image, currentNode(), canvas()->resourceManager()); KisSelectionSP selection = resources->activeSelection(); KisNodeList nodes = fetchSelectedNodes(mode, pos, selection); if (nodes.size() == 1) { node = nodes.first(); } if (nodes.isEmpty()) { return false; } /** * If the target node has changed, the stroke should be * restarted. Otherwise just continue processing current node. */ if (m_strokeId && !tryEndPreviousStroke(nodes)) { return true; } KisStrokeStrategy *strategy; KisPaintLayerSP paintLayer = node ? dynamic_cast(node.data()) : 0; bool isMoveSelection = false; if (paintLayer && selection && (!selection->selectedRect().isEmpty() && !selection->selectedExactRect().isEmpty())) { MoveSelectionStrokeStrategy *moveStrategy = new MoveSelectionStrokeStrategy(paintLayer, selection, image.data(), image.data()); connect(moveStrategy, SIGNAL(sigHandlesRectCalculated(const QRect&)), SLOT(slotHandlesRectCalculated(const QRect&))); strategy = moveStrategy; isMoveSelection = true; } else { MoveStrokeStrategy *moveStrategy = new MoveStrokeStrategy(nodes, image.data(), image.data()); connect(moveStrategy, SIGNAL(sigHandlesRectCalculated(const QRect&)), SLOT(slotHandlesRectCalculated(const QRect&))); strategy = moveStrategy; } // disable outline feedback until the stroke calcualtes // correct bounding rect m_handlesRect = QRect(); m_strokeId = image->startStroke(strategy); m_currentlyProcessingNodes = nodes; m_accumulatedOffset = QPoint(); if (!isMoveSelection) { m_asyncUpdateHelper.startUpdateStream(image.data(), m_strokeId); } KIS_SAFE_ASSERT_RECOVER(m_changesTracker.isEmpty()) { m_changesTracker.reset(); } commitChanges(); return true; } QPoint KisToolMove::currentOffset() const { return m_accumulatedOffset + m_dragPos - m_dragStart; } void KisToolMove::notifyGuiAfterMove(bool showFloatingMessage) { if (!m_optionsWidget) return; if (m_handlesRect.isEmpty()) return; const QPoint currentTopLeft = m_handlesRect.topLeft() + currentOffset(); KisSignalsBlocker b(m_optionsWidget); emit moveInNewPosition(currentTopLeft); // TODO: fetch this info not from options widget, but from config const bool showCoordinates = m_optionsWidget->showCoordinates(); if (showCoordinates && showFloatingMessage) { - KisCanvas2 *kisCanvas = dynamic_cast(canvas()); + KisCanvas2 *kisCanvas = static_cast(canvas()); kisCanvas->viewManager()-> showFloatingMessage( i18nc("floating message in move tool", "X: %1 px, Y: %2 px", QLocale().toString(currentTopLeft.x()), QLocale().toString(currentTopLeft.y())), QIcon(), 1000, KisFloatingMessage::High); } } bool KisToolMove::tryEndPreviousStroke(const KisNodeList &nodes) { if (!m_strokeId) return false; bool strokeEnded = false; if (!KritaUtils::compareListsUnordered(nodes, m_currentlyProcessingNodes)) { endStroke(); strokeEnded = true; } return strokeEnded; } void KisToolMove::commitChanges() { KIS_SAFE_ASSERT_RECOVER_RETURN(m_strokeId); QSharedPointer newState(new KisToolMoveState(m_accumulatedOffset)); KisToolMoveState *lastState = dynamic_cast(m_changesTracker.lastState().data()); if (lastState && *lastState == *newState) return; m_changesTracker.commitConfig(newState); } void KisToolMove::slotHandlesRectCalculated(const QRect &handlesRect) { m_handlesRect = handlesRect; notifyGuiAfterMove(false); } void KisToolMove::moveDiscrete(MoveDirection direction, bool big) { if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging if (!currentNode()) return; if (!image()) return; if (!currentNode()->isEditable()) return; // Don't move invisible nodes if (startStrokeImpl(MoveSelectedLayer, 0)) { setMode(KisTool::PAINT_MODE); } // Larger movement if "shift" key is pressed. qreal scale = big ? m_optionsWidget->moveScale() : 1.0; qreal moveStep = m_optionsWidget->moveStep() * scale; const QPoint offset = direction == Up ? QPoint( 0, -moveStep) : direction == Down ? QPoint( 0, moveStep) : direction == Left ? QPoint(-moveStep, 0) : QPoint( moveStep, 0) ; m_accumulatedOffset += offset; image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); notifyGuiAfterMove(); commitChanges(); setMode(KisTool::HOVER_MODE); } void KisToolMove::activate(ToolActivation toolActivation, const QSet &shapes) { KisTool::activate(toolActivation, shapes); m_actionConnections.addConnection(action("movetool-move-up"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteUp())); m_actionConnections.addConnection(action("movetool-move-down"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteDown())); m_actionConnections.addConnection(action("movetool-move-left"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteLeft())); m_actionConnections.addConnection(action("movetool-move-right"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteRight())); m_actionConnections.addConnection(action("movetool-move-up-more"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteUpMore())); m_actionConnections.addConnection(action("movetool-move-down-more"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteDownMore())); m_actionConnections.addConnection(action("movetool-move-left-more"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteLeftMore())); m_actionConnections.addConnection(action("movetool-move-right-more"), SIGNAL(triggered(bool)), this, SLOT(slotMoveDiscreteRightMore())); connect(m_showCoordinatesAction, SIGNAL(triggered(bool)), m_optionsWidget, SLOT(setShowCoordinates(bool)), Qt::UniqueConnection); connect(m_optionsWidget, SIGNAL(showCoordinatesChanged(bool)), m_showCoordinatesAction, SLOT(setChecked(bool)), Qt::UniqueConnection); connect(&m_changesTracker, SIGNAL(sigConfigChanged(KisToolChangesTrackerDataSP)), SLOT(slotTrackerChangedConfig(KisToolChangesTrackerDataSP))); slotNodeChanged(this->selectedNodes()); } void KisToolMove::paint(QPainter& gc, const KoViewConverter &converter) { Q_UNUSED(converter); if (m_strokeId && !m_handlesRect.isEmpty()) { QPainterPath handles; handles.addRect(m_handlesRect.translated(currentOffset())); QPainterPath path = pixelToView(handles); paintToolOutline(&gc, path); } } void KisToolMove::deactivate() { m_actionConnections.clear(); disconnect(m_showCoordinatesAction, 0, this, 0); disconnect(m_optionsWidget, 0, this, 0); endStroke(); KisTool::deactivate(); } void KisToolMove::requestStrokeEnd() { endStroke(); } void KisToolMove::requestStrokeCancellation() { cancelStroke(); } void KisToolMove::requestUndoDuringStroke() { if (!m_strokeId) return; if (m_changesTracker.isEmpty()) { cancelStroke(); } else { m_changesTracker.requestUndo(); } } void KisToolMove::beginPrimaryAction(KoPointerEvent *event) { startAction(event, moveToolMode()); } void KisToolMove::continuePrimaryAction(KoPointerEvent *event) { continueAction(event); } void KisToolMove::endPrimaryAction(KoPointerEvent *event) { endAction(event); } void KisToolMove::beginAlternateAction(KoPointerEvent *event, AlternateAction action) { // Ctrl+Right click toggles between moving current layer and moving layer w/ content if (action == PickFgNode || action == PickBgImage) { MoveToolMode mode = moveToolMode(); if (mode == MoveSelectedLayer) { mode = MoveFirstLayer; } else if (mode == MoveFirstLayer) { mode = MoveSelectedLayer; } startAction(event, mode); } else { startAction(event, MoveGroup); } } void KisToolMove::continueAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(action) continueAction(event); } void KisToolMove::endAlternateAction(KoPointerEvent *event, AlternateAction action) { Q_UNUSED(action) endAction(event); } void KisToolMove::mouseMoveEvent(KoPointerEvent *event) { m_lastCursorPos = convertToPixelCoord(event).toPoint(); KisTool::mouseMoveEvent(event); if (moveToolMode() == MoveFirstLayer) { m_updateCursorCompressor.start(); } } void KisToolMove::startAction(KoPointerEvent *event, MoveToolMode mode) { QPoint pos = convertToPixelCoordAndSnap(event).toPoint(); m_dragStart = pos; m_dragPos = pos; if (startStrokeImpl(mode, &pos)) { setMode(KisTool::PAINT_MODE); } else { event->ignore(); m_dragPos = QPoint(); m_dragStart = QPoint(); } qobject_cast(canvas())->updateCanvas(); } void KisToolMove::continueAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); if (!m_strokeId) return; QPoint pos = convertToPixelCoordAndSnap(event).toPoint(); pos = applyModifiers(event->modifiers(), pos); m_dragPos = pos; drag(pos); notifyGuiAfterMove(); qobject_cast(canvas())->updateCanvas(); } void KisToolMove::endAction(KoPointerEvent *event) { CHECK_MODE_SANITY_OR_RETURN(KisTool::PAINT_MODE); setMode(KisTool::HOVER_MODE); if (!m_strokeId) return; QPoint pos = convertToPixelCoordAndSnap(event).toPoint(); pos = applyModifiers(event->modifiers(), pos); drag(pos); m_accumulatedOffset += pos - m_dragStart; m_dragStart = QPoint(); m_dragPos = QPoint(); commitChanges(); notifyGuiAfterMove(); qobject_cast(canvas())->updateCanvas(); } void KisToolMove::drag(const QPoint& newPos) { KisImageWSP image = currentImage(); QPoint offset = m_accumulatedOffset + newPos - m_dragStart; image->addJob(m_strokeId, new MoveStrokeStrategy::Data(offset)); } void KisToolMove::endStroke() { if (!m_strokeId) return; if (m_asyncUpdateHelper.isActive()) { m_asyncUpdateHelper.endUpdateStream(); } KisImageSP image = currentImage(); image->endStroke(m_strokeId); m_strokeId.clear(); m_changesTracker.reset(); m_currentlyProcessingNodes.clear(); m_accumulatedOffset = QPoint(); qobject_cast(canvas())->updateCanvas(); } void KisToolMove::slotTrackerChangedConfig(KisToolChangesTrackerDataSP state) { KIS_SAFE_ASSERT_RECOVER_RETURN(m_strokeId); KisToolMoveState *newState = dynamic_cast(state.data()); KIS_SAFE_ASSERT_RECOVER_RETURN(newState); if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging m_accumulatedOffset = newState->accumulatedOffset; image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); notifyGuiAfterMove(); } void KisToolMove::slotMoveDiscreteLeft() { moveDiscrete(MoveDirection::Left, false); } void KisToolMove::slotMoveDiscreteRight() { moveDiscrete(MoveDirection::Right, false); } void KisToolMove::slotMoveDiscreteUp() { moveDiscrete(MoveDirection::Up, false); } void KisToolMove::slotMoveDiscreteDown() { moveDiscrete(MoveDirection::Down, false); } void KisToolMove::slotMoveDiscreteLeftMore() { moveDiscrete(MoveDirection::Left, true); } void KisToolMove::slotMoveDiscreteRightMore() { moveDiscrete(MoveDirection::Right, true); } void KisToolMove::slotMoveDiscreteUpMore() { moveDiscrete(MoveDirection::Up, true); } void KisToolMove::slotMoveDiscreteDownMore() { moveDiscrete(MoveDirection::Down, true); } void KisToolMove::cancelStroke() { if (!m_strokeId) return; if (m_asyncUpdateHelper.isActive()) { m_asyncUpdateHelper.cancelUpdateStream(); } KisImageSP image = currentImage(); image->cancelStroke(m_strokeId); m_strokeId.clear(); m_changesTracker.reset(); m_currentlyProcessingNodes.clear(); m_accumulatedOffset = QPoint(); notifyGuiAfterMove(); qobject_cast(canvas())->updateCanvas(); } QWidget* KisToolMove::createOptionWidget() { return m_optionsWidget; } KisToolMove::MoveToolMode KisToolMove::moveToolMode() const { if (m_optionsWidget) return m_optionsWidget->mode(); return MoveSelectedLayer; } QPoint KisToolMove::applyModifiers(Qt::KeyboardModifiers modifiers, QPoint pos) { QPoint move = pos - m_dragStart; // Snap to axis if (modifiers & Qt::ShiftModifier) { move = snapToClosestAxis(move); } // "Precision mode" - scale down movement by 1/5 if (modifiers & Qt::AltModifier) { const qreal SCALE_FACTOR = .2; move = SCALE_FACTOR * move; } return m_dragStart + move; } void KisToolMove::moveBySpinX(int newX) { if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging if (!currentNode()->isEditable()) return; // Don't move invisible nodes if (startStrokeImpl(MoveSelectedLayer, 0)) { setMode(KisTool::PAINT_MODE); } m_accumulatedOffset.rx() = newX - m_handlesRect.x(); image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); notifyGuiAfterMove(false); setMode(KisTool::HOVER_MODE); } void KisToolMove::moveBySpinY(int newY) { if (mode() == KisTool::PAINT_MODE) return; // Don't interact with dragging if (!currentNode()->isEditable()) return; // Don't move invisible nodes if (startStrokeImpl(MoveSelectedLayer, 0)) { setMode(KisTool::PAINT_MODE); } m_accumulatedOffset.ry() = newY - m_handlesRect.y(); image()->addJob(m_strokeId, new MoveStrokeStrategy::Data(m_accumulatedOffset)); notifyGuiAfterMove(false); setMode(KisTool::HOVER_MODE); } void KisToolMove::requestHandlesRectUpdate() { KisResourcesSnapshotSP resources = new KisResourcesSnapshot(image(), currentNode(), canvas()->resourceManager()); KisSelectionSP selection = resources->activeSelection(); KisMoveBoundsCalculationJob *job = new KisMoveBoundsCalculationJob(this->selectedNodes(), selection, this); connect(job, SIGNAL(sigCalcualtionFinished(const QRect&)), SLOT(slotHandlesRectCalculated(const QRect &))); KisImageSP image = this->image(); image->addSpontaneousJob(job); notifyGuiAfterMove(false); } void KisToolMove::slotNodeChanged(const KisNodeList &nodes) { if (m_strokeId && !tryEndPreviousStroke(nodes)) { return; } requestHandlesRectUpdate(); } void KisToolMove::slotSelectionChanged() { if (m_strokeId) return; requestHandlesRectUpdate(); } QList KisToolMoveFactory::createActionsImpl() { KisActionRegistry *actionRegistry = KisActionRegistry::instance(); QList actions = KisToolPaintFactoryBase::createActionsImpl(); actions << actionRegistry->makeQAction("movetool-move-up"); actions << actionRegistry->makeQAction("movetool-move-down"); actions << actionRegistry->makeQAction("movetool-move-left"); actions << actionRegistry->makeQAction("movetool-move-right"); actions << actionRegistry->makeQAction("movetool-move-up-more"); actions << actionRegistry->makeQAction("movetool-move-down-more"); actions << actionRegistry->makeQAction("movetool-move-left-more"); actions << actionRegistry->makeQAction("movetool-move-right-more"); actions << actionRegistry->makeQAction("movetool-show-coordinates"); return actions; }