diff --git a/libs/libkis/Document.cpp b/libs/libkis/Document.cpp index 5563ab15d7..8a847f665b 100644 --- a/libs/libkis/Document.cpp +++ b/libs/libkis/Document.cpp @@ -1,993 +1,994 @@ /* * Copyright (c) 2016 Boudewijn Rempt * * 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 "Document.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include "kis_animation_importer.h" #include #include #include #include struct Document::Private { Private() {} QPointer document; }; Document::Document(KisDocument *document, QObject *parent) : QObject(parent) , d(new Private) { d->document = document; } Document::~Document() { delete d; } bool Document::operator==(const Document &other) const { return (d->document == other.d->document); } bool Document::operator!=(const Document &other) const { return !(operator==(other)); } bool Document::batchmode() const { if (!d->document) return false; return d->document->fileBatchMode(); } void Document::setBatchmode(bool value) { if (!d->document) return; d->document->setFileBatchMode(value); } Node *Document::activeNode() const { QList activeNodes; Q_FOREACH(QPointer view, KisPart::instance()->views()) { if (view && view->document() == d->document) { activeNodes << view->currentNode(); } } if (activeNodes.size() > 0) { QList nodes = LibKisUtils::createNodeList(activeNodes, d->document->image()); return nodes.first(); } return 0; } void Document::setActiveNode(Node* value) { if (!value->node()) return; KisMainWindow *mainWin = KisPart::instance()->currentMainwindow(); if (!mainWin) return; KisViewManager *viewManager = mainWin->viewManager(); if (!viewManager) return; if (viewManager->document() != d->document) return; KisNodeManager *nodeManager = viewManager->nodeManager(); if (!nodeManager) return; KisNodeSelectionAdapter *selectionAdapter = nodeManager->nodeSelectionAdapter(); if (!selectionAdapter) return; selectionAdapter->setActiveNode(value->node()); } QList Document::topLevelNodes() const { if (!d->document) return QList(); Node n(d->document->image(), d->document->image()->rootLayer()); return n.childNodes(); } Node *Document::nodeByName(const QString &name) const { if (!d->document) return 0; KisNodeSP node = d->document->image()->rootLayer()->findChildByName(name); return new Node(d->document->image(), node); } QString Document::colorDepth() const { if (!d->document) return ""; return d->document->image()->colorSpace()->colorDepthId().id(); } QString Document::colorModel() const { if (!d->document) return ""; return d->document->image()->colorSpace()->colorModelId().id(); } QString Document::colorProfile() const { if (!d->document) return ""; return d->document->image()->colorSpace()->profile()->name(); } bool Document::setColorProfile(const QString &value) { if (!d->document) return false; if (!d->document->image()) return false; const KoColorProfile *profile = KoColorSpaceRegistry::instance()->profileByName(value); if (!profile) return false; bool retval = d->document->image()->assignImageProfile(profile); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return retval; } bool Document::setColorSpace(const QString &colorModel, const QString &colorDepth, const QString &colorProfile) { if (!d->document) return false; if (!d->document->image()) return false; const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorModel, colorDepth, colorProfile); if (!colorSpace) return false; d->document->image()->convertImageColorSpace(colorSpace, KoColorConversionTransformation::IntentPerceptual, KoColorConversionTransformation::HighQuality | KoColorConversionTransformation::NoOptimization); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return true; } QColor Document::backgroundColor() { if (!d->document) return QColor(); if (!d->document->image()) return QColor(); const KoColor color = d->document->image()->defaultProjectionColor(); return color.toQColor(); } bool Document::setBackgroundColor(const QColor &color) { if (!d->document) return false; if (!d->document->image()) return false; KoColor background = KoColor(color, d->document->image()->colorSpace()); d->document->image()->setDefaultProjectionColor(background); d->document->image()->setModified(); d->document->image()->initialRefreshGraph(); return true; } QString Document::documentInfo() const { QDomDocument doc = KisDocument::createDomDocument("document-info" /*DTD name*/, "document-info" /*tag name*/, "1.1"); doc = d->document->documentInfo()->save(doc); return doc.toString(); } void Document::setDocumentInfo(const QString &document) { KoXmlDocument doc; QString errorMsg; int errorLine, errorColumn; doc.setContent(document, &errorMsg, &errorLine, &errorColumn); d->document->documentInfo()->load(doc); } QString Document::fileName() const { if (!d->document) return QString(); return d->document->url().toLocalFile(); } void Document::setFileName(QString value) { if (!d->document) return; QString mimeType = KisMimeDatabase::mimeTypeForFile(value, false); d->document->setMimeType(mimeType.toLatin1()); d->document->setUrl(QUrl::fromLocalFile(value)); } int Document::height() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->height(); } void Document::setHeight(int value) { if (!d->document) return; if (!d->document->image()) return; resizeImage(d->document->image()->bounds().x(), d->document->image()->bounds().y(), d->document->image()->width(), value); } QString Document::name() const { if (!d->document) return ""; return d->document->documentInfo()->aboutInfo("title"); } void Document::setName(QString value) { if (!d->document) return; d->document->documentInfo()->setAboutInfo("title", value); } int Document::resolution() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return qRound(d->document->image()->xRes() * 72); } void Document::setResolution(int value) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; d->document->image()->setResolution(value / 72.0, value / 72.0); } Node *Document::rootNode() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return new Node(image, image->root()); } Selection *Document::selection() const { if (!d->document) return 0; if (!d->document->image()) return 0; if (!d->document->image()->globalSelection()) return 0; return new Selection(d->document->image()->globalSelection()); } void Document::setSelection(Selection* value) { if (!d->document) return; if (!d->document->image()) return; if (value) { d->document->image()->setGlobalSelection(value->selection()); } else { d->document->image()->setGlobalSelection(0); } } int Document::width() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->width(); } void Document::setWidth(int value) { if (!d->document) return; if (!d->document->image()) return; resizeImage(d->document->image()->bounds().x(), d->document->image()->bounds().y(), value, d->document->image()->height()); } int Document::xOffset() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->bounds().x(); } void Document::setXOffset(int x) { if (!d->document) return; if (!d->document->image()) return; resizeImage(x, d->document->image()->bounds().y(), d->document->image()->width(), d->document->image()->height()); } int Document::yOffset() const { if (!d->document) return 0; KisImageSP image = d->document->image(); if (!image) return 0; return image->bounds().y(); } void Document::setYOffset(int y) { if (!d->document) return; if (!d->document->image()) return; resizeImage(d->document->image()->bounds().x(), y, d->document->image()->width(), d->document->image()->height()); } double Document::xRes() const { if (!d->document) return 0.0; if (!d->document->image()) return 0.0; return d->document->image()->xRes()*72.0; } void Document::setXRes(double xRes) const { if (!d->document) return; if (!d->document->image()) return; d->document->image()->setResolution(xRes/72.0, d->document->image()->yRes()); } double Document::yRes() const { if (!d->document) return 0.0; if (!d->document->image()) return 0.0; return d->document->image()->yRes()*72.0; } void Document::setYRes(double yRes) const { if (!d->document) return; if (!d->document->image()) return; d->document->image()->setResolution(d->document->image()->xRes(), yRes/72.0); } QByteArray Document::pixelData(int x, int y, int w, int h) const { QByteArray ba; if (!d->document) return ba; KisImageSP image = d->document->image(); if (!image) return ba; KisPaintDeviceSP dev = image->projection(); ba.resize(w * h * dev->pixelSize()); dev->readBytes(reinterpret_cast(ba.data()), x, y, w, h); return ba; } bool Document::close() { bool retval = d->document->closeUrl(false); Q_FOREACH(KisView *view, KisPart::instance()->views()) { if (view->document() == d->document) { view->close(); view->closeView(); view->deleteLater(); } } KisPart::instance()->removeDocument(d->document); d->document = 0; return retval; } void Document::crop(int x, int y, int w, int h) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc(x, y, w, h); image->cropImage(rc); } bool Document::exportImage(const QString &filename, const InfoObject &exportConfiguration) { if (!d->document) return false; const QString outputFormatString = KisMimeDatabase::mimeTypeForFile(filename, false); const QByteArray outputFormat = outputFormatString.toLatin1(); return d->document->exportDocumentSync(QUrl::fromLocalFile(filename), outputFormat, exportConfiguration.configuration()); } void Document::flatten() { if (!d->document) return; if (!d->document->image()) return; d->document->image()->flatten(0); } void Document::resizeImage(int x, int y, int w, int h) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc; rc.setX(x); rc.setY(y); rc.setWidth(w); rc.setHeight(h); image->resizeImage(rc); } void Document::scaleImage(int w, int h, int xres, int yres, QString strategy) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; QRect rc = image->bounds(); rc.setWidth(w); rc.setHeight(h); KisFilterStrategy *actualStrategy = KisFilterStrategyRegistry::instance()->get(strategy); if (!actualStrategy) actualStrategy = KisFilterStrategyRegistry::instance()->get("Bicubic"); image->scaleImage(rc.size(), xres/72, yres/72, actualStrategy); } void Document::rotateImage(double radians) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; image->rotateImage(radians); } void Document::shearImage(double angleX, double angleY) { if (!d->document) return; KisImageSP image = d->document->image(); if (!image) return; image->shear(angleX, angleY); } bool Document::save() { if (!d->document) return false; if (d->document->url().isEmpty()) return false; bool retval = d->document->save(true, 0); d->document->waitForSavingToComplete(); return retval; } bool Document::saveAs(const QString &filename) { if (!d->document) return false; const QString outputFormatString = KisMimeDatabase::mimeTypeForFile(filename, false); const QByteArray outputFormat = outputFormatString.toLatin1(); QUrl oldUrl = d->document->url(); d->document->setUrl(QUrl::fromLocalFile(filename)); bool retval = d->document->saveAs(QUrl::fromLocalFile(filename), outputFormat, true); d->document->waitForSavingToComplete(); d->document->setUrl(oldUrl); return retval; } Node* Document::createNode(const QString &name, const QString &nodeType) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); Node *node = 0; if (nodeType.toLower()== "paintlayer") { node = new Node(image, new KisPaintLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "grouplayer") { node = new Node(image, new KisGroupLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "filelayer") { node = new Node(image, new KisFileLayer(image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "filterlayer") { node = new Node(image, new KisAdjustmentLayer(image, name, 0, 0)); } else if (nodeType.toLower() == "filllayer") { node = new Node(image, new KisGeneratorLayer(image, name, 0, 0)); } else if (nodeType.toLower() == "clonelayer") { node = new Node(image, new KisCloneLayer(0, image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "vectorlayer") { node = new Node(image, new KisShapeLayer(d->document->shapeController(), image, name, OPACITY_OPAQUE_U8)); } else if (nodeType.toLower() == "transparencymask") { node = new Node(image, new KisTransparencyMask()); } else if (nodeType.toLower() == "filtermask") { node = new Node(image, new KisFilterMask()); } else if (nodeType.toLower() == "transformmask") { node = new Node(image, new KisTransformMask()); } else if (nodeType.toLower() == "selectionmask") { node = new Node(image, new KisSelectionMask(image)); } return node; } GroupLayer *Document::createGroupLayer(const QString &name) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new GroupLayer(image, name); } FileLayer *Document::createFileLayer(const QString &name, const QString fileName, const QString scalingMethod) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new FileLayer(image, name, this->fileName(), fileName, scalingMethod); } FilterLayer *Document::createFilterLayer(const QString &name, Filter &filter, Selection &selection) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new FilterLayer(image, name, filter, selection); } FillLayer *Document::createFillLayer(const QString &name, const QString generatorName, InfoObject &configuration, Selection &selection) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); KisGeneratorSP generator = KisGeneratorRegistry::instance()->value(generatorName); if (generator) { KisFilterConfigurationSP config = generator->defaultConfiguration(); Q_FOREACH(const QString property, configuration.properties().keys()) { config->setProperty(property, configuration.property(property)); } return new FillLayer(image, name, config, selection); } return 0; } CloneLayer *Document::createCloneLayer(const QString &name, const Node *source) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); KisLayerSP layer = qobject_cast(source->node().data()); return new CloneLayer(image, name, layer); } VectorLayer *Document::createVectorLayer(const QString &name) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new VectorLayer(d->document->shapeController(), image, name); } FilterMask *Document::createFilterMask(const QString &name, Filter &filter, const Node *selection_source) { if (!d->document) return 0; if (!d->document->image()) return 0; if(!selection_source) return 0; KisLayerSP layer = qobject_cast(selection_source->node().data()); if(layer.isNull()) return 0; KisImageSP image = d->document->image(); FilterMask* mask = new FilterMask(image, name, filter); qobject_cast(mask->node().data())->initSelection(layer); return mask; } FilterMask *Document::createFilterMask(const QString &name, Filter &filter, Selection &selection) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); FilterMask* mask = new FilterMask(image, name, filter); qobject_cast(mask->node().data())->setSelection(selection.selection()); return mask; } SelectionMask *Document::createSelectionMask(const QString &name) { if (!d->document) return 0; if (!d->document->image()) return 0; KisImageSP image = d->document->image(); return new SelectionMask(image, name); } QImage Document::projection(int x, int y, int w, int h) const { if (!d->document || !d->document->image()) return QImage(); return d->document->image()->convertToQImage(x, y, w, h, 0); } QImage Document::thumbnail(int w, int h) const { if (!d->document || !d->document->image()) return QImage(); return d->document->generatePreview(QSize(w, h)).toImage(); } void Document::lock() { if (!d->document || !d->document->image()) return; d->document->image()->barrierLock(); } void Document::unlock() { if (!d->document || !d->document->image()) return; d->document->image()->unlock(); } void Document::waitForDone() { if (!d->document || !d->document->image()) return; d->document->image()->waitForDone(); } bool Document::tryBarrierLock() { if (!d->document || !d->document->image()) return false; return d->document->image()->tryBarrierLock(); } bool Document::isIdle() { if (!d->document || !d->document->image()) return false; return d->document->image()->isIdle(); } void Document::refreshProjection() { if (!d->document || !d->document->image()) return; d->document->image()->refreshGraph(); } QList Document::horizontalGuides() const { QList lines; if (!d->document || !d->document->image()) return lines; KisCoordinatesConverter converter; converter.setImage(d->document->image()); QTransform transform = converter.imageToDocumentTransform().inverted(); QList untransformedLines = d->document->guidesConfig().horizontalGuideLines(); for (int i = 0; i< untransformedLines.size(); i++) { qreal line = untransformedLines[i]; lines.append(transform.map(QPointF(line, line)).x()); } return lines; } QList Document::verticalGuides() const { QList lines; if (!d->document || !d->document->image()) return lines; KisCoordinatesConverter converter; converter.setImage(d->document->image()); QTransform transform = converter.imageToDocumentTransform().inverted(); QList untransformedLines = d->document->guidesConfig().verticalGuideLines(); for (int i = 0; i< untransformedLines.size(); i++) { qreal line = untransformedLines[i]; lines.append(transform.map(QPointF(line, line)).y()); } return lines; } bool Document::guidesVisible() const { return d->document->guidesConfig().lockGuides(); } bool Document::guidesLocked() const { return d->document->guidesConfig().showGuides(); } Document *Document::clone() const { if (!d->document) return 0; QPointer clone = d->document->clone(); Document * d = new Document(clone); clone->setParent(d); // It's owned by the document, not KisPart return d; } void Document::setHorizontalGuides(const QList &lines) { if (!d->document) return; KisGuidesConfig config = d->document->guidesConfig(); KisCoordinatesConverter converter; converter.setImage(d->document->image()); QTransform transform = converter.imageToDocumentTransform(); QList transformedLines; for (int i = 0; i< lines.size(); i++) { qreal line = lines[i]; transformedLines.append(transform.map(QPointF(line, line)).x()); } config.setHorizontalGuideLines(transformedLines); d->document->setGuidesConfig(config); } void Document::setVerticalGuides(const QList &lines) { if (!d->document) return; KisGuidesConfig config = d->document->guidesConfig(); KisCoordinatesConverter converter; converter.setImage(d->document->image()); QTransform transform = converter.imageToDocumentTransform(); QList transformedLines; for (int i = 0; i< lines.size(); i++) { qreal line = lines[i]; transformedLines.append(transform.map(QPointF(line, line)).y()); } config.setVerticalGuideLines(transformedLines); d->document->setGuidesConfig(config); } void Document::setGuidesVisible(bool visible) { if (!d->document) return; KisGuidesConfig config = d->document->guidesConfig(); config.setShowGuides(visible); d->document->setGuidesConfig(config); } void Document::setGuidesLocked(bool locked) { if (!d->document) return; KisGuidesConfig config = d->document->guidesConfig(); config.setLockGuides(locked); d->document->setGuidesConfig(config); } bool Document::modified() const { if (!d->document) return false; return d->document->isModified(); } QRect Document::bounds() const { if (!d->document) return QRect(); return d->document->image()->bounds(); } QPointer Document::document() const { return d->document; } /* Animation related function */ bool Document::importAnimation(const QList &files, int firstFrame, int step) { KisView *activeView = KisPart::instance()->currentMainwindow()->activeView(); KoUpdaterPtr updater = 0; if (activeView && d->document->fileBatchMode()) { updater = activeView->viewManager()->createUnthreadedUpdater(i18n("Import frames")); } KisAnimationImporter importer(d->document->image(), updater); - KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step); + KisImportExportErrorCode status = importer.import(files, firstFrame, step); - return (status == KisImportExportFilter::OK); + return status.isOk(); } int Document::framesPerSecond() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->framerate(); } void Document::setFramesPerSecond(int fps) { if (!d->document) return; if (!d->document->image()) return; d->document->image()->animationInterface()->setFramerate(fps); } void Document::setFullClipRangeStartTime(int startTime) { if (!d->document) return; if (!d->document->image()) return; d->document->image()->animationInterface()->setFullClipRangeStartTime(startTime); } int Document::fullClipRangeStartTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->fullClipRange().start(); } void Document::setFullClipRangeEndTime(int endTime) { if (!d->document) return; if (!d->document->image()) return; d->document->image()->animationInterface()->setFullClipRangeEndTime(endTime); } int Document::fullClipRangeEndTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->fullClipRange().end(); } int Document::animationLength() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->totalLength(); } void Document::setPlayBackRange(int start, int stop) { if (!d->document) return; if (!d->document->image()) return; const KisTimeRange newTimeRange = KisTimeRange(start, (stop-start)); d->document->image()->animationInterface()->setPlaybackRange(newTimeRange); } int Document::playBackStartTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->playbackRange().start(); } int Document::playBackEndTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->playbackRange().end(); } int Document::currentTime() { if (!d->document) return false; if (!d->document->image()) return false; return d->document->image()->animationInterface()->currentTime(); } void Document::setCurrentTime(int time) { if (!d->document) return; if (!d->document->image()) return; return d->document->image()->animationInterface()->requestTimeSwitchWithUndo(time); } diff --git a/libs/ui/CMakeLists.txt b/libs/ui/CMakeLists.txt index 0947e7fc5e..1ec57f07b7 100644 --- a/libs/ui/CMakeLists.txt +++ b/libs/ui/CMakeLists.txt @@ -1,611 +1,612 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/qtlockedfile ) include_directories(SYSTEM ${EIGEN3_INCLUDE_DIR} ${OCIO_INCLUDE_DIR} ) add_subdirectory( tests ) if (APPLE) find_library(FOUNDATION_LIBRARY Foundation) find_library(APPKIT_LIBRARY AppKit) endif () set(kritaui_LIB_SRCS canvas/kis_canvas_widget_base.cpp canvas/kis_canvas2.cpp canvas/kis_canvas_updates_compressor.cpp canvas/kis_canvas_controller.cpp canvas/kis_paintop_transformation_connector.cpp canvas/kis_display_color_converter.cpp canvas/kis_display_filter.cpp canvas/kis_exposure_gamma_correction_interface.cpp canvas/kis_tool_proxy.cpp canvas/kis_canvas_decoration.cc canvas/kis_coordinates_converter.cpp canvas/kis_grid_manager.cpp canvas/kis_grid_decoration.cpp canvas/kis_grid_config.cpp canvas/kis_prescaled_projection.cpp canvas/kis_qpainter_canvas.cpp canvas/kis_projection_backend.cpp canvas/kis_update_info.cpp canvas/kis_image_patch.cpp canvas/kis_image_pyramid.cpp canvas/kis_infinity_manager.cpp canvas/kis_change_guides_command.cpp canvas/kis_guides_decoration.cpp canvas/kis_guides_manager.cpp canvas/kis_guides_config.cpp canvas/kis_snap_config.cpp canvas/kis_snap_line_strategy.cpp canvas/KisSnapPointStrategy.cpp canvas/KisSnapPixelStrategy.cpp canvas/KisMirrorAxisConfig.cpp dialogs/kis_about_application.cpp dialogs/kis_dlg_adj_layer_props.cc dialogs/kis_dlg_adjustment_layer.cc dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_generator_layer.cpp dialogs/kis_dlg_file_layer.cpp dialogs/kis_dlg_filter.cpp dialogs/kis_dlg_stroke_selection_properties.cpp dialogs/kis_dlg_image_properties.cc dialogs/kis_dlg_layer_properties.cc dialogs/kis_dlg_preferences.cc dialogs/slider_and_spin_box_sync.cpp dialogs/kis_dlg_blacklist_cleanup.cpp dialogs/kis_dlg_layer_style.cpp dialogs/kis_dlg_png_import.cpp dialogs/kis_dlg_import_image_sequence.cpp dialogs/kis_delayed_save_dialog.cpp dialogs/KisSessionManagerDialog.cpp dialogs/KisNewWindowLayoutDialog.cpp dialogs/KisDlgChangeCloneSource.cpp flake/kis_node_dummies_graph.cpp flake/kis_dummies_facade_base.cpp flake/kis_dummies_facade.cpp flake/kis_node_shapes_graph.cpp flake/kis_node_shape.cpp flake/kis_shape_controller.cpp flake/kis_shape_layer.cc flake/kis_shape_layer_canvas.cpp flake/kis_shape_selection.cpp flake/kis_shape_selection_canvas.cpp flake/kis_shape_selection_model.cpp flake/kis_take_all_shapes_command.cpp brushhud/kis_uniform_paintop_property_widget.cpp brushhud/kis_brush_hud.cpp brushhud/kis_round_hud_button.cpp brushhud/kis_dlg_brush_hud_config.cpp brushhud/kis_brush_hud_properties_list.cpp brushhud/kis_brush_hud_properties_config.cpp kis_aspect_ratio_locker.cpp kis_autogradient.cc kis_bookmarked_configurations_editor.cc kis_bookmarked_configurations_model.cc kis_bookmarked_filter_configurations_model.cc KisPaintopPropertiesBase.cpp kis_canvas_resource_provider.cpp kis_derived_resources.cpp kis_categories_mapper.cpp kis_categorized_list_model.cpp kis_categorized_item_delegate.cpp kis_clipboard.cc kis_config.cc KisOcioConfiguration.cpp kis_control_frame.cpp kis_composite_ops_model.cc kis_paint_ops_model.cpp kis_cursor.cc kis_cursor_cache.cpp kis_custom_pattern.cc kis_file_layer.cpp kis_change_file_layer_command.h kis_safe_document_loader.cpp kis_splash_screen.cpp kis_filter_manager.cc kis_filters_model.cc KisImageBarrierLockerWithFeedback.cpp kis_image_manager.cc kis_image_view_converter.cpp kis_import_catcher.cc kis_layer_manager.cc kis_mask_manager.cc kis_mimedata.cpp kis_node_commands_adapter.cpp kis_node_manager.cpp kis_node_juggler_compressed.cpp kis_node_selection_adapter.cpp kis_node_insertion_adapter.cpp KisNodeDisplayModeAdapter.cpp kis_node_model.cpp kis_node_filter_proxy_model.cpp kis_model_index_converter_base.cpp kis_model_index_converter.cpp kis_model_index_converter_show_all.cpp kis_painting_assistant.cc kis_painting_assistants_decoration.cpp KisDecorationsManager.cpp kis_paintop_box.cc kis_paintop_option.cpp kis_paintop_options_model.cpp kis_paintop_settings_widget.cpp kis_popup_palette.cpp kis_png_converter.cpp kis_preference_set_registry.cpp KisResourceServerProvider.cpp KisResourceBundleServerProvider.cpp KisSelectedShapesProxy.cpp kis_selection_decoration.cc kis_selection_manager.cc KisSelectionActionsAdapter.cpp kis_statusbar.cc kis_zoom_manager.cc kis_favorite_resource_manager.cpp kis_workspace_resource.cpp kis_action.cpp kis_action_manager.cpp KisActionPlugin.cpp kis_canvas_controls_manager.cpp kis_tooltip_manager.cpp kis_multinode_property.cpp kis_stopgradient_editor.cpp KisWelcomePageWidget.cpp KisChangeCloneLayersCommand.cpp kisexiv2/kis_exif_io.cpp kisexiv2/kis_exiv2.cpp kisexiv2/kis_iptc_io.cpp kisexiv2/kis_xmp_io.cpp opengl/kis_opengl.cpp opengl/kis_opengl_canvas2.cpp opengl/kis_opengl_canvas_debugger.cpp opengl/kis_opengl_image_textures.cpp opengl/kis_texture_tile.cpp opengl/kis_opengl_shader_loader.cpp opengl/kis_texture_tile_info_pool.cpp opengl/KisOpenGLUpdateInfoBuilder.cpp opengl/KisOpenGLModeProber.cpp opengl/KisScreenInformationAdapter.cpp kis_fps_decoration.cpp tool/KisToolChangesTracker.cpp tool/KisToolChangesTrackerData.cpp tool/kis_selection_tool_helper.cpp tool/kis_selection_tool_config_widget_helper.cpp tool/kis_rectangle_constraint_widget.cpp tool/kis_shape_tool_helper.cpp tool/kis_tool.cc tool/kis_delegated_tool_policies.cpp tool/kis_tool_freehand.cc tool/kis_speed_smoother.cpp tool/kis_painting_information_builder.cpp tool/kis_stabilized_events_sampler.cpp tool/kis_tool_freehand_helper.cpp tool/kis_tool_multihand_helper.cpp tool/kis_figure_painting_tool_helper.cpp tool/kis_tool_paint.cc tool/kis_tool_shape.cc tool/kis_tool_ellipse_base.cpp tool/kis_tool_rectangle_base.cpp tool/kis_tool_polyline_base.cpp tool/kis_tool_utils.cpp tool/kis_resources_snapshot.cpp tool/kis_smoothing_options.cpp tool/KisStabilizerDelayedPaintHelper.cpp tool/KisStrokeSpeedMonitor.cpp tool/strokes/freehand_stroke.cpp tool/strokes/KisStrokeEfficiencyMeasurer.cpp tool/strokes/kis_painter_based_stroke_strategy.cpp tool/strokes/kis_filter_stroke_strategy.cpp tool/strokes/kis_color_picker_stroke_strategy.cpp tool/strokes/KisFreehandStrokeInfo.cpp tool/strokes/KisMaskedFreehandStrokePainter.cpp tool/strokes/KisMaskingBrushRenderer.cpp tool/strokes/KisMaskingBrushCompositeOpFactory.cpp tool/strokes/move_stroke_strategy.cpp tool/KisSelectionToolFactoryBase.cpp tool/KisToolPaintFactoryBase.cpp widgets/kis_cmb_composite.cc widgets/kis_cmb_contour.cpp widgets/kis_cmb_gradient.cpp widgets/kis_paintop_list_widget.cpp widgets/kis_cmb_idlist.cc widgets/kis_color_space_selector.cc widgets/kis_advanced_color_space_selector.cc widgets/kis_cie_tongue_widget.cpp widgets/kis_tone_curve_widget.cpp widgets/kis_curve_widget.cpp widgets/kis_custom_image_widget.cc widgets/kis_image_from_clipboard_widget.cpp widgets/kis_double_widget.cc widgets/kis_filter_selector_widget.cc widgets/kis_gradient_chooser.cc widgets/kis_iconwidget.cc widgets/kis_mask_widgets.cpp widgets/kis_meta_data_merge_strategy_chooser_widget.cc widgets/kis_multi_bool_filter_widget.cc widgets/kis_multi_double_filter_widget.cc widgets/kis_multi_integer_filter_widget.cc widgets/kis_multipliers_double_slider_spinbox.cpp widgets/kis_paintop_presets_popup.cpp widgets/kis_tool_options_popup.cpp widgets/kis_paintop_presets_chooser_popup.cpp widgets/kis_paintop_presets_save.cpp widgets/kis_paintop_preset_icon_library.cpp widgets/kis_pattern_chooser.cc widgets/kis_preset_chooser.cpp widgets/kis_progress_widget.cpp widgets/kis_selection_options.cc widgets/kis_scratch_pad.cpp widgets/kis_scratch_pad_event_filter.cpp widgets/kis_preset_selector_strip.cpp widgets/kis_slider_spin_box.cpp widgets/KisSelectionPropertySlider.cpp widgets/kis_size_group.cpp widgets/kis_size_group_p.cpp widgets/kis_wdg_generator.cpp widgets/kis_workspace_chooser.cpp widgets/kis_categorized_list_view.cpp widgets/kis_widget_chooser.cpp widgets/kis_tool_button.cpp widgets/kis_floating_message.cpp widgets/kis_lod_availability_widget.cpp widgets/kis_color_label_selector_widget.cpp widgets/kis_color_filter_combo.cpp widgets/kis_elided_label.cpp widgets/kis_stopgradient_slider_widget.cpp widgets/kis_preset_live_preview_view.cpp widgets/KisScreenColorPicker.cpp widgets/KoDualColorButton.cpp widgets/KoStrokeConfigWidget.cpp widgets/KoFillConfigWidget.cpp widgets/KisLayerStyleAngleSelector.cpp widgets/KisMemoryReportButton.cpp KisPaletteEditor.cpp dialogs/KisDlgPaletteEditor.cpp widgets/KisNewsWidget.cpp widgets/KisGamutMaskToolbar.cpp utils/kis_document_aware_spin_box_unit_manager.cpp utils/KisSpinBoxSplineUnitConverter.cpp input/kis_input_manager.cpp input/kis_input_manager_p.cpp input/kis_extended_modifiers_mapper.cpp input/kis_abstract_input_action.cpp input/kis_tool_invocation_action.cpp input/kis_pan_action.cpp input/kis_alternate_invocation_action.cpp input/kis_rotate_canvas_action.cpp input/kis_zoom_action.cpp input/kis_change_frame_action.cpp input/kis_gamma_exposure_action.cpp input/kis_show_palette_action.cpp input/kis_change_primary_setting_action.cpp input/kis_abstract_shortcut.cpp input/kis_native_gesture_shortcut.cpp input/kis_single_action_shortcut.cpp input/kis_stroke_shortcut.cpp input/kis_shortcut_matcher.cpp input/kis_select_layer_action.cpp input/KisQtWidgetsTweaker.cpp input/KisInputActionGroup.cpp operations/kis_operation.cpp operations/kis_operation_configuration.cpp operations/kis_operation_registry.cpp operations/kis_operation_ui_factory.cpp operations/kis_operation_ui_widget.cpp operations/kis_filter_selection_operation.cpp actions/kis_selection_action_factories.cpp actions/KisPasteActionFactory.cpp actions/KisTransformToolActivationCommand.cpp input/kis_touch_shortcut.cpp kis_document_undo_store.cpp kis_gui_context_command.cpp kis_gui_context_command_p.cpp input/kis_tablet_debugger.cpp input/kis_input_profile_manager.cpp input/kis_input_profile.cpp input/kis_shortcut_configuration.cpp input/config/kis_input_configuration_page.cpp input/config/kis_edit_profiles_dialog.cpp input/config/kis_input_profile_model.cpp input/config/kis_input_configuration_page_item.cpp input/config/kis_action_shortcuts_model.cpp input/config/kis_input_type_delegate.cpp input/config/kis_input_mode_delegate.cpp input/config/kis_input_button.cpp input/config/kis_input_editor_delegate.cpp input/config/kis_mouse_input_editor.cpp input/config/kis_wheel_input_editor.cpp input/config/kis_key_input_editor.cpp processing/fill_processing_visitor.cpp kis_asl_layer_style_serializer.cpp kis_psd_layer_style_resource.cpp canvas/kis_mirror_axis.cpp kis_abstract_perspective_grid.cpp KisApplication.cpp KisAutoSaveRecoveryDialog.cpp KisDetailsPane.cpp KisDocument.cpp KisCloneDocumentStroke.cpp kis_node_view_color_scheme.cpp KisImportExportFilter.cpp KisFilterEntry.cpp KisImportExportManager.cpp KisImportExportUtils.cpp kis_async_action_feedback.cpp KisMainWindow.cpp KisOpenPane.cpp KisPart.cpp KisPrintJob.cpp KisTemplate.cpp KisTemplateCreateDia.cpp KisTemplateGroup.cpp KisTemplates.cpp KisTemplatesPane.cpp KisTemplateTree.cpp KisUndoActionsUpdateManager.cpp KisView.cpp KisImportExportErrorCode.cpp + KisImportExportAdditionalChecks.cpp thememanager.cpp kis_mainwindow_observer.cpp KisViewManager.cpp kis_mirror_manager.cpp qtlockedfile/qtlockedfile.cpp qtsingleapplication/qtlocalpeer.cpp qtsingleapplication/qtsingleapplication.cpp KisResourceBundle.cpp KisResourceBundleManifest.cpp kis_md5_generator.cpp KisApplicationArguments.cpp KisNetworkAccessManager.cpp KisMultiFeedRSSModel.cpp KisRemoteFileFetcher.cpp KisSaveGroupVisitor.cpp KisWindowLayoutResource.cpp KisWindowLayoutManager.cpp KisSessionResource.cpp KisReferenceImagesDecoration.cpp KisReferenceImage.cpp flake/KisReferenceImagesLayer.cpp flake/KisReferenceImagesLayer.h KisMouseClickEater.cpp ) if(WIN32) # Private headers are needed for: # * KisDlgCustomTabletResolution # * KisScreenInformationAdapter include_directories(SYSTEM ${Qt5Gui_PRIVATE_INCLUDE_DIRS}) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} qtlockedfile/qtlockedfile_win.cpp ) if (NOT USE_QT_TABLET_WINDOWS) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} input/wintab/kis_tablet_support_win.cpp input/wintab/kis_screen_size_choice_dialog.cpp input/wintab/kis_tablet_support_win8.cpp ) else() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} dialogs/KisDlgCustomTabletResolution.cpp ) endif() endif() set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} kis_animation_frame_cache.cpp kis_animation_cache_populator.cpp KisAsyncAnimationRendererBase.cpp KisAsyncAnimationCacheRenderer.cpp KisAsyncAnimationFramesSavingRenderer.cpp dialogs/KisAsyncAnimationRenderDialogBase.cpp dialogs/KisAsyncAnimationCacheRenderDialog.cpp dialogs/KisAsyncAnimationFramesSaveDialog.cpp canvas/kis_animation_player.cpp kis_animation_importer.cpp KisSyncedAudioPlayback.cpp KisFrameDataSerializer.cpp KisFrameCacheStore.cpp KisFrameCacheSwapper.cpp KisAbstractFrameCacheSwapper.cpp KisInMemoryFrameCacheSwapper.cpp input/wintab/drawpile_tablettester/tablettester.cpp input/wintab/drawpile_tablettester/tablettest.cpp ) if (UNIX) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} qtlockedfile/qtlockedfile_unix.cpp ) endif() if(APPLE) set(kritaui_LIB_SRCS ${kritaui_LIB_SRCS} osx.mm ) endif() ki18n_wrap_ui(kritaui_LIB_SRCS widgets/KoFillConfigWidget.ui widgets/KoStrokeConfigWidget.ui forms/wdgdlgpngimport.ui forms/wdgfullscreensettings.ui forms/wdgautogradient.ui forms/wdggeneralsettings.ui forms/wdgperformancesettings.ui forms/wdggenerators.ui forms/wdgbookmarkedconfigurationseditor.ui forms/wdgapplyprofile.ui forms/wdgcustompattern.ui forms/wdglayerproperties.ui forms/wdgcolorsettings.ui forms/wdgtabletsettings.ui forms/wdgcolorspaceselector.ui forms/wdgcolorspaceselectoradvanced.ui forms/wdgdisplaysettings.ui forms/kis_previewwidgetbase.ui forms/kis_matrix_widget.ui forms/wdgselectionoptions.ui forms/wdggeometryoptions.ui forms/wdgnewimage.ui forms/wdgimageproperties.ui forms/wdgmaskfromselection.ui forms/wdgmasksource.ui forms/wdgfilterdialog.ui forms/wdgmetadatamergestrategychooser.ui forms/wdgpaintoppresets.ui forms/wdgpaintopsettings.ui forms/wdgdlggeneratorlayer.ui forms/wdgdlgfilelayer.ui forms/wdgfilterselector.ui forms/wdgfilternodecreation.ui forms/wdgmultipliersdoublesliderspinbox.ui forms/wdgnodequerypatheditor.ui forms/wdgpresetselectorstrip.ui forms/wdgsavebrushpreset.ui forms/wdgpreseticonlibrary.ui forms/wdgdlgblacklistcleanup.ui forms/wdgrectangleconstraints.ui forms/wdgimportimagesequence.ui forms/wdgstrokeselectionproperties.ui forms/KisDetailsPaneBase.ui forms/KisOpenPaneBase.ui forms/wdgstopgradienteditor.ui forms/wdgsessionmanager.ui forms/wdgnewwindowlayout.ui forms/KisWelcomePage.ui forms/WdgDlgPaletteEditor.ui forms/KisNewsPage.ui forms/wdgGamutMaskToolbar.ui forms/wdgchangeclonesource.ui brushhud/kis_dlg_brush_hud_config.ui dialogs/kis_delayed_save_dialog.ui input/config/kis_input_configuration_page.ui input/config/kis_edit_profiles_dialog.ui input/config/kis_input_configuration_page_item.ui input/config/kis_mouse_input_editor.ui input/config/kis_wheel_input_editor.ui input/config/kis_key_input_editor.ui layerstyles/wdgBevelAndEmboss.ui layerstyles/wdgblendingoptions.ui layerstyles/WdgColorOverlay.ui layerstyles/wdgContour.ui layerstyles/wdgdropshadow.ui layerstyles/WdgGradientOverlay.ui layerstyles/wdgInnerGlow.ui layerstyles/wdglayerstyles.ui layerstyles/WdgPatternOverlay.ui layerstyles/WdgSatin.ui layerstyles/WdgStroke.ui layerstyles/wdgstylesselector.ui layerstyles/wdgTexture.ui layerstyles/wdgKisLayerStyleAngleSelector.ui wdgsplash.ui input/wintab/kis_screen_size_choice_dialog.ui input/wintab/drawpile_tablettester/tablettest.ui ) if(WIN32) if(USE_QT_TABLET_WINDOWS) ki18n_wrap_ui(kritaui_LIB_SRCS dialogs/KisDlgCustomTabletResolution.ui ) else() ki18n_wrap_ui(kritaui_LIB_SRCS input/wintab/kis_screen_size_choice_dialog.ui ) endif() endif() add_library(kritaui SHARED ${kritaui_HEADERS_MOC} ${kritaui_LIB_SRCS} ) generate_export_header(kritaui BASE_NAME kritaui) target_link_libraries(kritaui KF5::CoreAddons KF5::Completion KF5::I18n KF5::ItemViews Qt5::Network kritaimpex kritacolor kritaimage kritalibbrush kritawidgets kritawidgetutils ${PNG_LIBRARIES} LibExiv2::LibExiv2 ) if (HAVE_QT_MULTIMEDIA) target_link_libraries(kritaui Qt5::Multimedia) endif() if (NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${X11_X11_LIB} ${X11_Xinput_LIB} ${XCB_LIBRARIES}) endif() if(APPLE) target_link_libraries(kritaui ${FOUNDATION_LIBRARY}) target_link_libraries(kritaui ${APPKIT_LIBRARY}) endif () target_link_libraries(kritaui ${OPENEXR_LIBRARIES}) # Add VSync disable workaround if(NOT WIN32 AND NOT APPLE) target_link_libraries(kritaui ${CMAKE_DL_LIBS} Qt5::X11Extras) endif() if(X11_FOUND) target_link_libraries(kritaui Qt5::X11Extras ${X11_LIBRARIES}) endif() target_include_directories(kritaui PUBLIC $ $ $ $ $ $ $ ) set_target_properties(kritaui PROPERTIES VERSION ${GENERIC_KRITA_LIB_VERSION} SOVERSION ${GENERIC_KRITA_LIB_SOVERSION} ) install(TARGETS kritaui ${INSTALL_TARGETS_DEFAULT_ARGS}) if (APPLE) install(FILES osx.stylesheet DESTINATION ${DATA_INSTALL_DIR}/krita) endif () diff --git a/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp b/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp index 539a7cb4db..b4b6c8f510 100644 --- a/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp +++ b/libs/ui/KisAsyncAnimationFramesSavingRenderer.cpp @@ -1,127 +1,127 @@ /* * 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 "KisAsyncAnimationFramesSavingRenderer.h" #include "kis_image.h" #include "kis_paint_device.h" #include "KisImportExportFilter.h" #include "KisPart.h" #include "KisDocument.h" #include "kis_time_range.h" #include "kis_paint_layer.h" struct KisAsyncAnimationFramesSavingRenderer::Private { Private(KisImageSP image, const KisTimeRange &_range, int _sequenceNumberingOffset, KisPropertiesConfigurationSP _exportConfiguration) : savingDoc(KisPart::instance()->createDocument()), range(_range), sequenceNumberingOffset(_sequenceNumberingOffset), exportConfiguration(_exportConfiguration) { savingDoc->setInfiniteAutoSaveInterval(); savingDoc->setFileBatchMode(true); KisImageSP savingImage = new KisImage(savingDoc->createUndoStore(), image->bounds().width(), image->bounds().height(), image->colorSpace(), QString()); savingImage->setResolution(image->xRes(), image->yRes()); savingDoc->setCurrentImage(savingImage); KisPaintLayer* paintLayer = new KisPaintLayer(savingImage, "paint device", 255); savingImage->addNode(paintLayer, savingImage->root(), KisLayerSP(0)); savingDevice = paintLayer->paintDevice(); } QScopedPointer savingDoc; KisPaintDeviceSP savingDevice; KisTimeRange range; int sequenceNumberingOffset = 0; QString filenamePrefix; QString filenameSuffix; QByteArray outputMimeType; KisPropertiesConfigurationSP exportConfiguration; }; KisAsyncAnimationFramesSavingRenderer::KisAsyncAnimationFramesSavingRenderer(KisImageSP image, const QString &fileNamePrefix, const QString &fileNameSuffix, const QByteArray &outputMimeType, const KisTimeRange &range, const int sequenceNumberingOffset, KisPropertiesConfigurationSP exportConfiguration) : m_d(new Private(image, range, sequenceNumberingOffset, exportConfiguration)) { m_d->filenamePrefix = fileNamePrefix; m_d->filenameSuffix = fileNameSuffix; m_d->outputMimeType = outputMimeType; connect(this, SIGNAL(sigCompleteRegenerationInternal(int)), SLOT(notifyFrameCompleted(int))); connect(this, SIGNAL(sigCancelRegenerationInternal(int)), SLOT(notifyFrameCancelled(int))); } KisAsyncAnimationFramesSavingRenderer::~KisAsyncAnimationFramesSavingRenderer() { } void KisAsyncAnimationFramesSavingRenderer::frameCompletedCallback(int frame, const QRegion &requestedRegion) { KisImageSP image = requestedImage(); if (!image) return; KIS_SAFE_ASSERT_RECOVER (requestedRegion == image->bounds()) { emit sigCancelRegenerationInternal(frame); return; } m_d->savingDevice->makeCloneFromRough(image->projection(), image->bounds()); - KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK; + KisImportExportErrorCode status = ImportExportCodes::OK; QString frameNumber = QString("%1").arg(frame + m_d->sequenceNumberingOffset, 4, 10, QChar('0')); QString filename = m_d->filenamePrefix + frameNumber + m_d->filenameSuffix; if (!m_d->savingDoc->exportDocumentSync(QUrl::fromLocalFile(filename), m_d->outputMimeType, m_d->exportConfiguration)) { - status = KisImportExportFilter::InternalError; + status = ImportExportCodes::InternalError; } - if (status == KisImportExportFilter::OK) { + if (status.isOk()) { emit sigCompleteRegenerationInternal(frame); } else { emit sigCancelRegenerationInternal(frame); } } void KisAsyncAnimationFramesSavingRenderer::frameCancelledCallback(int frame) { notifyFrameCancelled(frame); } diff --git a/libs/ui/KisDocument.cpp b/libs/ui/KisDocument.cpp index 859b082e3a..391c8c368f 100644 --- a/libs/ui/KisDocument.cpp +++ b/libs/ui/KisDocument.cpp @@ -1,1996 +1,2005 @@ /* This file is part of the Krita project * * Copyright (C) 2014 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 "KisMainWindow.h" // XXX: remove #include // XXX: remove #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Krita Image #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_layer_utils.h" // Local #include "KisViewManager.h" #include "kis_clipboard.h" #include "widgets/kis_custom_image_widget.h" #include "canvas/kis_canvas2.h" #include "flake/kis_shape_controller.h" #include "kis_statusbar.h" #include "widgets/kis_progress_widget.h" #include "kis_canvas_resource_provider.h" #include "KisResourceServerProvider.h" #include "kis_node_manager.h" #include "KisPart.h" #include "KisApplication.h" #include "KisDocument.h" #include "KisImportExportManager.h" #include "KisView.h" #include "kis_grid_config.h" #include "kis_guides_config.h" #include "kis_image_barrier_lock_adapter.h" #include "KisReferenceImagesLayer.h" #include #include "kis_config_notifier.h" #include "kis_async_action_feedback.h" #include "KisCloneDocumentStroke.h" #include // Define the protocol used here for embedded documents' URL // This used to "store" but QUrl didn't like it, // so let's simply make it "tar" ! #define STORE_PROTOCOL "tar" // The internal path is a hack to make QUrl happy and for document children #define INTERNAL_PROTOCOL "intern" #define INTERNAL_PREFIX "intern:/" // Warning, keep it sync in koStore.cc #include using namespace std; namespace { constexpr int errorMessageTimeout = 5000; constexpr int successMessageTimeout = 1000; } /********************************************************** * * KisDocument * **********************************************************/ //static QString KisDocument::newObjectName() { static int s_docIFNumber = 0; QString name; name.setNum(s_docIFNumber++); name.prepend("document_"); return name; } class UndoStack : public KUndo2Stack { public: UndoStack(KisDocument *doc) : KUndo2Stack(doc), m_doc(doc) { } void setIndex(int idx) override { KisImageWSP image = this->image(); image->requestStrokeCancellation(); if(image->tryBarrierLock()) { KUndo2Stack::setIndex(idx); image->unlock(); } } void notifySetIndexChangedOneCommand() override { KisImageWSP image = this->image(); image->unlock(); /** * Some very weird commands may emit blocking signals to * the GUI (e.g. KisGuiContextCommand). Here is the best thing * we can do to avoid the deadlock */ while(!image->tryBarrierLock()) { QApplication::processEvents(); } } void undo() override { KisImageWSP image = this->image(); image->requestUndoDuringStroke(); if (image->tryUndoUnfinishedLod0Stroke() == UNDO_OK) { return; } if(image->tryBarrierLock()) { KUndo2Stack::undo(); image->unlock(); } } void redo() override { KisImageWSP image = this->image(); if(image->tryBarrierLock()) { KUndo2Stack::redo(); image->unlock(); } } private: KisImageWSP image() { KisImageWSP currentImage = m_doc->image(); Q_ASSERT(currentImage); return currentImage; } private: KisDocument *m_doc; }; class Q_DECL_HIDDEN KisDocument::Private { public: Private(KisDocument *q) : docInfo(new KoDocumentInfo(q)) // deleted by QObject , importExportManager(new KisImportExportManager(q)) // deleted manually , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) // deleted by QObject , m_bAutoDetectedMime(false) , modified(false) , readwrite(true) , firstMod(QDateTime::currentDateTime()) , lastMod(firstMod) , nserver(new KisNameServer(1)) , imageIdleWatcher(2000 /*ms*/) , globalAssistantsColor(KisConfig(true).defaultAssistantsColor()) , savingLock(&savingMutex) , batchMode(false) { if (QLocale().measurementSystem() == QLocale::ImperialSystem) { unit = KoUnit::Inch; } else { unit = KoUnit::Centimeter; } } Private(const Private &rhs, KisDocument *q) : docInfo(new KoDocumentInfo(*rhs.docInfo, q)) , unit(rhs.unit) , importExportManager(new KisImportExportManager(q)) , mimeType(rhs.mimeType) , outputMimeType(rhs.outputMimeType) , autoSaveTimer(new QTimer(q)) , undoStack(new UndoStack(q)) , guidesConfig(rhs.guidesConfig) , mirrorAxisConfig(rhs.mirrorAxisConfig) , m_bAutoDetectedMime(rhs.m_bAutoDetectedMime) , m_url(rhs.m_url) , m_file(rhs.m_file) , modified(rhs.modified) , readwrite(rhs.readwrite) , firstMod(rhs.firstMod) , lastMod(rhs.lastMod) , nserver(new KisNameServer(*rhs.nserver)) , preActivatedNode(0) // the node is from another hierarchy! , imageIdleWatcher(2000 /*ms*/) , assistants(rhs.assistants) // WARNING: assistants should not store pointers to the document! , globalAssistantsColor(rhs.globalAssistantsColor) , paletteList(rhs.paletteList) , gridConfig(rhs.gridConfig) , savingLock(&savingMutex) , batchMode(rhs.batchMode) { // TODO: clone assistants } ~Private() { // Don't delete m_d->shapeController because it's in a QObject hierarchy. delete nserver; } KoDocumentInfo *docInfo = 0; KoUnit unit; KisImportExportManager *importExportManager = 0; // The filter-manager to use when loading/saving [for the options] QByteArray mimeType; // The actual mimetype of the document QByteArray outputMimeType; // The mimetype to use when saving QTimer *autoSaveTimer; QString lastErrorMessage; // see openFile() QString lastWarningMessage; int autoSaveDelay = 300; // in seconds, 0 to disable. bool modifiedAfterAutosave = false; bool isAutosaving = false; bool disregardAutosaveFailure = false; int autoSaveFailureCount = 0; KUndo2Stack *undoStack = 0; KisGuidesConfig guidesConfig; KisMirrorAxisConfig mirrorAxisConfig; bool m_bAutoDetectedMime = false; // whether the mimetype in the arguments was detected by the part itself QUrl m_url; // local url - the one displayed to the user. QString m_file; // Local file - the only one the part implementation should deal with. QMutex savingMutex; bool modified = false; bool readwrite = false; QDateTime firstMod; QDateTime lastMod; KisNameServer *nserver; KisImageSP image; KisImageSP savingImage; KisNodeWSP preActivatedNode; KisShapeController* shapeController = 0; KoShapeController* koShapeController = 0; KisIdleWatcher imageIdleWatcher; QScopedPointer imageIdleConnection; QList assistants; QColor globalAssistantsColor; KisSharedPtr referenceImagesLayer; QList paletteList; KisGridConfig gridConfig; StdLockableWrapper savingLock; bool modifiedWhileSaving = false; QScopedPointer backgroundSaveDocument; QPointer savingUpdater; - QFuture childSavingFuture; + QFuture childSavingFuture; KritaUtils::ExportFileJob backgroundSaveJob; bool isRecovered = false; bool batchMode { false }; void setImageAndInitIdleWatcher(KisImageSP _image) { image = _image; imageIdleWatcher.setTrackedImage(image); if (image) { imageIdleConnection.reset( new KisSignalAutoConnection( &imageIdleWatcher, SIGNAL(startedIdleMode()), image.data(), SLOT(explicitRegenerateLevelOfDetail()))); } } class StrippedSafeSavingLocker; }; class KisDocument::Private::StrippedSafeSavingLocker { public: StrippedSafeSavingLocker(QMutex *savingMutex, KisImageSP image) : m_locked(false) , m_image(image) , m_savingLock(savingMutex) , m_imageLock(image, true) { /** * Initial try to lock both objects. Locking the image guards * us from any image composition threads running in the * background, while savingMutex guards us from entering the * saving code twice by autosave and main threads. * * Since we are trying to lock multiple objects, so we should * do it in a safe manner. */ m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; if (!m_locked) { m_image->requestStrokeEnd(); QApplication::processEvents(); // one more try... m_locked = std::try_lock(m_imageLock, m_savingLock) < 0; } } ~StrippedSafeSavingLocker() { if (m_locked) { m_imageLock.unlock(); m_savingLock.unlock(); } } bool successfullyLocked() const { return m_locked; } private: bool m_locked; KisImageSP m_image; StdLockableWrapper m_savingLock; KisImageBarrierLockAdapter m_imageLock; }; KisDocument::KisDocument() : d(new Private(this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(newObjectName()); // preload the krita resources KisResourceServerProvider::instance(); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); } KisDocument::KisDocument(const KisDocument &rhs) : QObject(), d(new Private(*rhs.d, this)) { connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), SLOT(slotConfigChanged())); connect(d->undoStack, SIGNAL(cleanChanged(bool)), this, SLOT(slotUndoStackCleanChanged(bool))); connect(d->autoSaveTimer, SIGNAL(timeout()), this, SLOT(slotAutoSave())); setObjectName(rhs.objectName()); d->shapeController = new KisShapeController(this, d->nserver), d->koShapeController = new KoShapeController(0, d->shapeController), slotConfigChanged(); // clone the image with keeping the GUIDs of the layers intact // NOTE: we expect the image to be locked! setCurrentImage(rhs.image()->clone(true), false); if (rhs.d->preActivatedNode) { // since we clone uuid's, we can use them for lacating new // nodes. Otherwise we would need to use findSymmetricClone() d->preActivatedNode = KisLayerUtils::findNodeByUuid(d->image->root(), rhs.d->preActivatedNode->uuid()); } } KisDocument::~KisDocument() { // wait until all the pending operations are in progress waitForSavingToComplete(); /** * Push a timebomb, which will try to release the memory after * the document has been deleted */ KisPaintDevice::createMemoryReleaseObject()->deleteLater(); d->autoSaveTimer->disconnect(this); d->autoSaveTimer->stop(); delete d->importExportManager; // Despite being QObject they needs to be deleted before the image delete d->shapeController; delete d->koShapeController; if (d->image) { d->image->notifyAboutToBeDeleted(); /** * WARNING: We should wait for all the internal image jobs to * finish before entering KisImage's destructor. The problem is, * while execution of KisImage::~KisImage, all the weak shared * pointers pointing to the image enter an inconsistent * state(!). The shared counter is already zero and destruction * has started, but the weak reference doesn't know about it, * because KisShared::~KisShared hasn't been executed yet. So all * the threads running in background and having weak pointers will * enter the KisImage's destructor as well. */ d->image->requestStrokeCancellation(); d->image->waitForDone(); // clear undo commands that can still point to the image d->undoStack->clear(); d->image->waitForDone(); KisImageWSP sanityCheckPointer = d->image; Q_UNUSED(sanityCheckPointer); // The following line trigger the deletion of the image d->image.clear(); // check if the image has actually been deleted KIS_SAFE_ASSERT_RECOVER_NOOP(!sanityCheckPointer.isValid()); } delete d; } bool KisDocument::reload() { // XXX: reimplement! return false; } KisDocument *KisDocument::clone() { return new KisDocument(*this); } bool KisDocument::exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { QFileInfo filePathInfo(job.filePath); if (filePathInfo.exists() && !filePathInfo.isWritable()) { - slotCompleteSavingDocument(job, - KisImportExportFilter::CreationError, + slotCompleteSavingDocument(job, ImportExportCodes::NoAccessToWrite, i18n("%1 cannot be written to. Please save under a different name.", job.filePath)); + //return ImportExportCodes::NoAccessToWrite; return false; } KisConfig cfg(true); if (cfg.backupFile() && filePathInfo.exists()) { QString backupDir; switch(cfg.readEntry("backupfilelocation", 0)) { case 1: backupDir = QStandardPaths::writableLocation(QStandardPaths::HomeLocation); break; case 2: backupDir = QStandardPaths::writableLocation(QStandardPaths::TempLocation); break; default: // Do nothing: the empty string is user file location break; } int numOfBackupsKept = cfg.readEntry("numberofbackupfiles", 1); QString suffix = cfg.readEntry("backupfilesuffix", "~"); if (numOfBackupsKept == 1) { KBackup::simpleBackupFile(job.filePath, backupDir, suffix); } else if (numOfBackupsKept > 2) { KBackup::numberedBackupFile(job.filePath, backupDir, suffix, numOfBackupsKept); } } - KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); + //KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!job.mimeType.isEmpty(), false); + if (job.mimeType.isEmpty()) { + + KisImportExportErrorCode error = ImportExportCodes::FileFormatIncorrect; + slotCompleteSavingDocument(job, error, error.errorMessage()); + return false; + + } const QString actionName = job.flags & KritaUtils::SaveIsExporting ? i18n("Exporting Document...") : i18n("Saving Document..."); + ENTER_FUNCTION() << "second!"; bool started = initiateSavingInBackground(actionName, - this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), + this, SLOT(slotCompleteSavingDocument(KritaUtils::ExportFileJob, KisImportExportErrorCode ,QString)), job, exportConfiguration); - + ENTER_FUNCTION() << "started? " << started; if (!started) { emit canceled(QString()); } return started; } bool KisDocument::exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; SaveFlags flags = SaveIsExporting; if (showWarnings) { flags |= SaveShowWarnings; } KisUsageLogger::log(QString("Exporting Document: %1 as %2. %3 * %4 pixels, %5 layers, %6 frames, %7 framerate. Export configuration: %8") .arg(url.toLocalFile()) .arg(QString::fromLatin1(mimeType)) .arg(d->image->width()) .arg(d->image->height()) .arg(d->image->nlayers()) .arg(d->image->animationInterface()->totalLength()) .arg(d->image->animationInterface()->framerate()) .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration")); return exportDocumentImpl(KritaUtils::ExportFileJob(url.toLocalFile(), mimeType, flags), exportConfiguration); } bool KisDocument::saveAs(const QUrl &_url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { using namespace KritaUtils; KisUsageLogger::log(QString("Saving Document %9 as %1 (mime: %2). %3 * %4 pixels, %5 layers. %6 frames, %7 framerate. Export configuration: %8") .arg(_url.toLocalFile()) .arg(QString::fromLatin1(mimeType)) .arg(d->image->width()) .arg(d->image->height()) .arg(d->image->nlayers()) .arg(d->image->animationInterface()->totalLength()) .arg(d->image->animationInterface()->framerate()) .arg(exportConfiguration ? exportConfiguration->toXML() : "No configuration") .arg(url().toLocalFile())); return exportDocumentImpl(ExportFileJob(_url.toLocalFile(), mimeType, showWarnings ? SaveShowWarnings : SaveNone), exportConfiguration); } bool KisDocument::save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { return saveAs(url(), mimeType(), showWarnings, exportConfiguration); } QByteArray KisDocument::serializeToNativeByteArray() { QByteArray byteArray; QBuffer buffer(&byteArray); QScopedPointer filter(KisImportExportManager::filterForMimeType(nativeFormatMimeType(), KisImportExportManager::Export)); filter->setBatchMode(true); filter->setMimeType(nativeFormatMimeType()); Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return byteArray; } d->savingImage = d->image; - if (filter->convert(this, &buffer) != KisImportExportFilter::OK) { + if (!filter->convert(this, &buffer).isOk()) { qWarning() << "serializeToByteArray():: Could not export to our native format"; } return byteArray; } -void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) +void KisDocument::slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage) { - if (status == KisImportExportFilter::UserCancelled) + if (status.isCancelled()) return; const QString fileName = QFileInfo(job.filePath).fileName(); - if (status != KisImportExportFilter::OK) { + ENTER_FUNCTION() << "status = " << status << " Message " << status.errorMessage(); + + if (!status.isOk()) { emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during saving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); if (!fileBatchMode()) { const QString filePath = job.filePath; QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not save %1\nReason: %2", filePath, exportErrorToUserMessage(status, errorMessage))); } } else { if (!(job.flags & KritaUtils::SaveIsExporting)) { const QString existingAutoSaveBaseName = localFilePath(); const bool wasRecovered = isRecovered(); setUrl(QUrl::fromLocalFile(job.filePath)); setLocalFilePath(job.filePath); setMimeType(job.mimeType); updateEditingTime(true); if (!d->modifiedWhileSaving) { /** * If undo stack is already clean/empty, it doesn't emit any * signals, so we might forget update document modified state * (which was set, e.g. while recovering an autosave file) */ if (d->undoStack->isClean()) { setModified(false); } else { d->undoStack->setClean(); } } setRecovered(false); removeAutoSaveFiles(existingAutoSaveBaseName, wasRecovered); } emit completed(); emit sigSavingFinished(); emit statusBarMessage(i18n("Finished saving %1", fileName), successMessageTimeout); } } QByteArray KisDocument::mimeType() const { return d->mimeType; } void KisDocument::setMimeType(const QByteArray & mimeType) { d->mimeType = mimeType; } bool KisDocument::fileBatchMode() const { return d->batchMode; } void KisDocument::setFileBatchMode(const bool batchMode) { d->batchMode = batchMode; } KisDocument* KisDocument::lockAndCloneForSaving() { // force update of all the asynchronous nodes before cloning QApplication::processEvents(); KisLayerUtils::forceAllDelayedNodesUpdate(d->image->root()); KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { if (!window->viewManager()->blockUntilOperationsFinished(d->image)) { return 0; } } } Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return 0; } return new KisDocument(*this); } bool KisDocument::exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration) { { /** * The caller guarantees that noone else uses the document (usually, * it is a temporary docuent created specifically for exporting), so * we don't need to copy or lock the document. Instead we should just * ensure the barrier lock is synced and then released. */ Private::StrippedSafeSavingLocker locker(&d->savingMutex, d->image); if (!locker.successfullyLocked()) { return false; } } d->savingImage = d->image; const QString fileName = url.toLocalFile(); - KisImportExportFilter::ConversionStatus status = + KisImportExportErrorCode status = d->importExportManager-> exportDocument(fileName, fileName, mimeType, false, exportConfiguration); d->savingImage = 0; - return status == KisImportExportFilter::OK; + return status.isOk(); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration) { return initiateSavingInBackground(actionName, receiverObject, receiverMethod, job, exportConfiguration, std::unique_ptr()); } bool KisDocument::initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument) { KIS_ASSERT_RECOVER_RETURN_VALUE(job.isValid(), false); QScopedPointer clonedDocument; if (!optionalClonedDocument) { clonedDocument.reset(lockAndCloneForSaving()); } else { clonedDocument.reset(optionalClonedDocument.release()); } // we block saving until the current saving is finished! if (!clonedDocument || !d->savingMutex.tryLock()) { return false; } auto waitForImage = [] (KisImageSP image) { KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { window->viewManager()->blockUntilOperationsFinishedForced(image); } } }; { KisNodeSP newRoot = clonedDocument->image()->root(); KIS_SAFE_ASSERT_RECOVER(!KisLayerUtils::hasDelayedNodeWithUpdates(newRoot)) { KisLayerUtils::forceAllDelayedNodesUpdate(newRoot); waitForImage(clonedDocument->image()); } } KIS_SAFE_ASSERT_RECOVER(clonedDocument->image()->isIdle()) { waitForImage(clonedDocument->image()); } KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveDocument, false); KIS_ASSERT_RECOVER_RETURN_VALUE(!d->backgroundSaveJob.isValid(), false); d->backgroundSaveDocument.reset(clonedDocument.take()); d->backgroundSaveJob = job; d->modifiedWhileSaving = false; if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = true; } connect(d->backgroundSaveDocument.data(), - SIGNAL(sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus,QString)), + SIGNAL(sigBackgroundSavingFinished(KisImportExportErrorCode, QString)), this, - SLOT(slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus,QString))); + SLOT(slotChildCompletedSavingInBackground(KisImportExportErrorCode, QString))); - connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), + connect(this, SIGNAL(sigCompleteBackgroundSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString)), receiverObject, receiverMethod, Qt::UniqueConnection); bool started = d->backgroundSaveDocument->startExportInBackground(actionName, job.filePath, job.filePath, job.mimeType, job.flags & KritaUtils::SaveShowWarnings, exportConfiguration); if (!started) { // the state should have been deinitialized in slotChildCompletedSavingInBackground() KIS_SAFE_ASSERT_RECOVER (!d->backgroundSaveDocument && !d->backgroundSaveJob.isValid()) { d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); d->backgroundSaveJob = KritaUtils::ExportFileJob(); } } return started; } -void KisDocument::slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) +void KisDocument::slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage) { KIS_SAFE_ASSERT_RECOVER(!d->savingMutex.tryLock()) { d->savingMutex.unlock(); return; } KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveDocument); if (d->backgroundSaveJob.flags & KritaUtils::SaveInAutosaveMode) { d->backgroundSaveDocument->d->isAutosaving = false; } d->backgroundSaveDocument.take()->deleteLater(); d->savingMutex.unlock(); KIS_SAFE_ASSERT_RECOVER_RETURN(d->backgroundSaveJob.isValid()); const KritaUtils::ExportFileJob job = d->backgroundSaveJob; d->backgroundSaveJob = KritaUtils::ExportFileJob(); KisUsageLogger::log(QString("Completed saving %1 (mime: %2). Result: %3") .arg(job.filePath) .arg(QString::fromLatin1(job.mimeType)) - .arg(status != KisImportExportFilter::OK ? exportErrorToUserMessage(status, errorMessage) : "OK")); + .arg(!status.isOk() ? exportErrorToUserMessage(status, errorMessage) : "OK")); emit sigCompleteBackgroundSaving(job, status, errorMessage); } void KisDocument::slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument) { if (!d->modified || !d->modifiedAfterAutosave) return; const QString autoSaveFileName = generateAutoSaveFileName(localFilePath()); emit statusBarMessage(i18n("Autosaving... %1", autoSaveFileName), successMessageTimeout); const bool hadClonedDocument = bool(optionalClonedDocument); bool started = false; if (d->image->isIdle() || hadClonedDocument) { started = initiateSavingInBackground(i18n("Autosaving..."), - this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob,KisImportExportFilter::ConversionStatus,QString)), + this, SLOT(slotCompleteAutoSaving(KritaUtils::ExportFileJob, KisImportExportErrorCode, QString)), KritaUtils::ExportFileJob(autoSaveFileName, nativeFormatMimeType(), KritaUtils::SaveIsExporting | KritaUtils::SaveInAutosaveMode), 0, std::move(optionalClonedDocument)); } else { emit statusBarMessage(i18n("Autosaving postponed: document is busy..."), errorMessageTimeout); } if (!started && !hadClonedDocument && d->autoSaveFailureCount >= 3) { KisCloneDocumentStroke *stroke = new KisCloneDocumentStroke(this); connect(stroke, SIGNAL(sigDocumentCloned(KisDocument*)), this, SLOT(slotInitiateAsyncAutosaving(KisDocument*)), Qt::BlockingQueuedConnection); KisStrokeId strokeId = d->image->startStroke(stroke); d->image->endStroke(strokeId); setInfiniteAutoSaveInterval(); } else if (!started) { setEmergencyAutoSaveInterval(); } else { d->modifiedAfterAutosave = false; } } void KisDocument::slotAutoSave() { slotAutoSaveImpl(std::unique_ptr()); } void KisDocument::slotInitiateAsyncAutosaving(KisDocument *clonedDocument) { slotAutoSaveImpl(std::unique_ptr(clonedDocument)); } -void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage) +void KisDocument::slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage) { Q_UNUSED(job); const QString fileName = QFileInfo(job.filePath).fileName(); - if (status != KisImportExportFilter::OK) { + if (!status.isOk()) { setEmergencyAutoSaveInterval(); emit statusBarMessage(i18nc("%1 --- failing file name, %2 --- error message", "Error during autosaving %1: %2", fileName, exportErrorToUserMessage(status, errorMessage)), errorMessageTimeout); } else { KisConfig cfg(true); d->autoSaveDelay = cfg.autoSaveInterval(); if (!d->modifiedWhileSaving) { d->autoSaveTimer->stop(); // until the next change d->autoSaveFailureCount = 0; } else { setNormalAutoSaveInterval(); } emit statusBarMessage(i18n("Finished autosaving %1", fileName), successMessageTimeout); } } bool KisDocument::startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { d->savingImage = d->image; KisMainWindow *window = KisPart::instance()->currentMainwindow(); if (window) { if (window->viewManager()) { d->savingUpdater = window->viewManager()->createThreadedUpdater(actionName); d->importExportManager->setUpdater(d->savingUpdater); } } - KisImportExportFilter::ConversionStatus initializationStatus; + KisImportExportErrorCode initializationStatus(ImportExportCodes::OK); d->childSavingFuture = d->importExportManager->exportDocumentAsyc(location, realLocation, mimeType, initializationStatus, showWarnings, exportConfiguration); - if (initializationStatus != KisImportExportFilter::ConversionStatus::OK) { + if (!initializationStatus.isOk()) { if (d->savingUpdater) { d->savingUpdater->cancel(); } d->savingImage.clear(); - emit sigBackgroundSavingFinished(initializationStatus, this->errorMessage()); + emit sigBackgroundSavingFinished(initializationStatus, initializationStatus.errorMessage()); return false; } - typedef QFutureWatcher StatusWatcher; + typedef QFutureWatcher StatusWatcher; StatusWatcher *watcher = new StatusWatcher(); watcher->setFuture(d->childSavingFuture); connect(watcher, SIGNAL(finished()), SLOT(finishExportInBackground())); connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); return true; } void KisDocument::finishExportInBackground() { KIS_SAFE_ASSERT_RECOVER(d->childSavingFuture.isFinished()) { - emit sigBackgroundSavingFinished(KisImportExportFilter::InternalError, ""); + emit sigBackgroundSavingFinished(ImportExportCodes::InternalError, ""); return; } - KisImportExportFilter::ConversionStatus status = + KisImportExportErrorCode status = d->childSavingFuture.result(); - const QString errorMessage = this->errorMessage(); + const QString errorMessage = status.errorMessage(); d->savingImage.clear(); - d->childSavingFuture = QFuture(); + d->childSavingFuture = QFuture(); d->lastErrorMessage.clear(); if (d->savingUpdater) { d->savingUpdater->setProgress(100); } emit sigBackgroundSavingFinished(status, errorMessage); } void KisDocument::setReadWrite(bool readwrite) { d->readwrite = readwrite; setNormalAutoSaveInterval(); Q_FOREACH (KisMainWindow *mainWindow, KisPart::instance()->mainWindows()) { mainWindow->setReadWrite(readwrite); } } void KisDocument::setAutoSaveDelay(int delay) { if (isReadWrite() && delay > 0) { d->autoSaveTimer->start(delay * 1000); } else { d->autoSaveTimer->stop(); } } void KisDocument::setNormalAutoSaveInterval() { setAutoSaveDelay(d->autoSaveDelay); d->autoSaveFailureCount = 0; } void KisDocument::setEmergencyAutoSaveInterval() { const int emergencyAutoSaveInterval = 10; /* sec */ setAutoSaveDelay(emergencyAutoSaveInterval); d->autoSaveFailureCount++; } void KisDocument::setInfiniteAutoSaveInterval() { setAutoSaveDelay(-1); } KoDocumentInfo *KisDocument::documentInfo() const { return d->docInfo; } bool KisDocument::isModified() const { return d->modified; } QPixmap KisDocument::generatePreview(const QSize& size) { KisImageSP image = d->image; if (d->savingImage) image = d->savingImage; if (image) { QRect bounds = image->bounds(); QSize newSize = bounds.size(); newSize.scale(size, Qt::KeepAspectRatio); QPixmap px = QPixmap::fromImage(image->convertToQImage(newSize, 0)); if (px.size() == QSize(0,0)) { px = QPixmap(newSize); QPainter gc(&px); QBrush checkBrush = QBrush(KisCanvasWidgetBase::createCheckersImage(newSize.width() / 5)); gc.fillRect(px.rect(), checkBrush); gc.end(); } return px; } return QPixmap(size); } QString KisDocument::generateAutoSaveFileName(const QString & path) const { QString retval; // Using the extension allows to avoid relying on the mime magic when opening const QString extension (".kra"); QString prefix = KisConfig(true).readEntry("autosavefileshidden") ? QString(".") : QString(); QRegularExpression autosavePattern1("^\\..+-autosave.kra$"); QRegularExpression autosavePattern2("^.+-autosave.kra$"); QFileInfo fi(path); QString dir = fi.absolutePath(); QString filename = fi.fileName(); if (path.isEmpty() || autosavePattern1.match(filename).hasMatch() || autosavePattern2.match(filename).hasMatch() || !fi.isWritable()) { // Never saved? #ifdef Q_OS_WIN // On Windows, use the temp location (https://bugs.kde.org/show_bug.cgi?id=314921) retval = QString("%1%2%7%3-%4-%5-autosave%6").arg(QDir::tempPath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix); #else // On Linux, use a temp file in $HOME then. Mark it with the pid so two instances don't overwrite each other's autosave file retval = QString("%1%2%7%3-%4-%5-autosave%6").arg(QDir::homePath()).arg(QDir::separator()).arg("krita").arg(qApp->applicationPid()).arg(objectName()).arg(extension).arg(prefix); #endif } else { retval = QString("%1%2%5%3-autosave%4").arg(dir).arg(QDir::separator()).arg(filename).arg(extension).arg(prefix); } //qDebug() << "generateAutoSaveFileName() for path" << path << ":" << retval; return retval; } bool KisDocument::importDocument(const QUrl &_url) { bool ret; dbgUI << "url=" << _url.url(); // open... ret = openUrl(_url); // reset url & m_file (kindly? set by KisParts::openUrl()) to simulate a // File --> Import if (ret) { dbgUI << "success, resetting url"; resetURL(); setTitleModified(); } return ret; } bool KisDocument::openUrl(const QUrl &_url, OpenFlags flags) { if (!_url.isLocalFile()) { return false; } dbgUI << "url=" << _url.url(); d->lastErrorMessage.clear(); // Reimplemented, to add a check for autosave files and to improve error reporting if (!_url.isValid()) { d->lastErrorMessage = i18n("Malformed URL\n%1", _url.url()); // ## used anywhere ? return false; } QUrl url(_url); bool autosaveOpened = false; if (url.isLocalFile() && !fileBatchMode()) { QString file = url.toLocalFile(); QString asf = generateAutoSaveFileName(file); if (QFile::exists(asf)) { KisApplication *kisApp = static_cast(qApp); kisApp->hideSplashScreen(); //dbgUI <<"asf=" << asf; // ## TODO compare timestamps ? int res = QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("An autosaved file exists for this document.\nDo you want to open the autosaved file instead?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes); switch (res) { case QMessageBox::Yes : url.setPath(asf); autosaveOpened = true; break; case QMessageBox::No : QFile::remove(asf); break; default: // Cancel return false; } } } bool ret = openUrlInternal(url); if (autosaveOpened || flags & RecoveryFile) { setReadWrite(true); // enable save button setModified(true); setRecovered(true); } else { if (ret) { if (!(flags & DontAddToRecent)) { KisPart::instance()->addRecentURLToAllMainWindows(_url); } // Detect readonly local-files; remote files are assumed to be writable QFileInfo fi(url.toLocalFile()); setReadWrite(fi.isWritable()); } setRecovered(false); } return ret; } class DlgLoadMessages : public KoDialog { public: DlgLoadMessages(const QString &title, const QString &message, const QStringList &warnings) { setWindowTitle(title); setWindowIcon(KisIconUtils::loadIcon("warning")); QWidget *page = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(page); QHBoxLayout *hlayout = new QHBoxLayout(); QLabel *labelWarning= new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hlayout->addWidget(labelWarning); hlayout->addWidget(new QLabel(message)); layout->addLayout(hlayout); QTextBrowser *browser = new QTextBrowser(); QString warning = "

"; if (warnings.size() == 1) { warning += " Reason:

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); browser->setMinimumHeight(200); browser->setMinimumWidth(400); layout->addWidget(browser); setMainWidget(page); setButtons(KoDialog::Ok); resize(minimumSize()); } }; bool KisDocument::openFile() { //dbgUI <<"for" << localFilePath(); if (!QFile::exists(localFilePath())) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath())); return false; } QString filename = localFilePath(); QString typeName = mimeType(); if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(filename); } //qDebug() << "mimetypes 4:" << typeName; // Allow to open backup files, don't keep the mimetype application/x-trash. if (typeName == "application/x-trash") { QString path = filename; while (path.length() > 0) { path.chop(1); typeName = KisMimeDatabase::mimeTypeForFile(path); //qDebug() << "\t" << path << typeName; if (!typeName.isEmpty()) { break; } } //qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName; } dbgUI << localFilePath() << "type:" << typeName; KisMainWindow *window = KisPart::instance()->currentMainwindow(); KoUpdaterPtr updater; if (window && window->viewManager()) { updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document")); d->importExportManager->setUpdater(updater); } - KisImportExportFilter::ConversionStatus status; - - status = d->importExportManager->importDocument(localFilePath(), typeName); + KisImportExportErrorCode status = d->importExportManager->importDocument(localFilePath(), typeName); - if (status != KisImportExportFilter::OK) { + if (!status.isOk()) { if (window && window->viewManager()) { updater->cancel(); } - QString msg = KisImportExportFilter::conversionStatusString(status); + QString msg = status.errorMessage(); if (!msg.isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()), errorMessage().split("\n") + warningMessage().split("\n")); dlg.exec(); } return false; } else if (!warningMessage().isEmpty()) { DlgLoadMessages dlg(i18nc("@title:window", "Krita"), i18n("There were problems opening %1.", prettyPathOrUrl()), warningMessage().split("\n")); dlg.exec(); setUrl(QUrl()); } setMimeTypeAfterLoading(typeName); emit sigLoadingFinished(); undoStack()->clear(); return true; } // shared between openFile and koMainWindow's "create new empty document" code void KisDocument::setMimeTypeAfterLoading(const QString& mimeType) { d->mimeType = mimeType.toLatin1(); d->outputMimeType = d->mimeType; } bool KisDocument::loadNativeFormat(const QString & file_) { return openUrl(QUrl::fromLocalFile(file_)); } void KisDocument::setModified(bool mod) { if (mod) { updateEditingTime(false); } if (d->isAutosaving) // ignore setModified calls due to autosaving return; if ( !d->readwrite && d->modified ) { errKrita << "Can't set a read-only document to 'modified' !" << endl; return; } //dbgUI<<" url:" << url.path(); //dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod))); d->firstMod = now; } else if (firstModDelta > 60 || forceStoreElapsed) { d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta)); d->firstMod = now; } d->lastMod = now; } QString KisDocument::prettyPathOrUrl() const { QString _url(url().toDisplayString()); #ifdef Q_OS_WIN if (url().isLocalFile()) { _url = QDir::toNativeSeparators(_url); } #endif return _url; } // Get caption from document info (title(), in about page) QString KisDocument::caption() const { QString c; const QString _url(url().fileName()); // if URL is empty...it is probably an unsaved file if (_url.isEmpty()) { c = " [" + i18n("Not Saved") + "] "; } else { c = _url; // Fall back to document URL } return c; } void KisDocument::setTitleModified() { emit titleModified(caption(), isModified()); } QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const { return createDomDocument("krita", tagName, version); } //static QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version) { QDomImplementation impl; QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version); QDomDocumentType dtype = impl.createDocumentType(tagName, QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version), url); // The namespace URN doesn't need to include the version number. QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName); QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype); doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement()); return doc; } bool KisDocument::isNativeFormat(const QByteArray& mimetype) const { if (mimetype == nativeFormatMimeType()) return true; return extraNativeMimeTypes().contains(mimetype); } void KisDocument::setErrorMessage(const QString& errMsg) { d->lastErrorMessage = errMsg; } QString KisDocument::errorMessage() const { return d->lastErrorMessage; } void KisDocument::setWarningMessage(const QString& warningMsg) { d->lastWarningMessage = warningMsg; } QString KisDocument::warningMessage() const { return d->lastWarningMessage; } void KisDocument::removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered) { //qDebug() << "removeAutoSaveFiles"; // Eliminate any auto-save file QString asf = generateAutoSaveFileName(autosaveBaseName); // the one in the current dir //qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf); if (QFile::exists(asf)) { //qDebug() << "\tremoving autosavefile" << asf; QFile::remove(asf); } asf = generateAutoSaveFileName(QString()); // and the one in $HOME //qDebug() << "Autsavefile in $home" << asf; if (QFile::exists(asf)) { //qDebug() << "\tremoving autsavefile 2" << asf; QFile::remove(asf); } QList expressions; expressions << QRegularExpression("^\\..+-autosave.kra$") << QRegularExpression("^.+-autosave.kra$"); Q_FOREACH(const QRegularExpression &rex, expressions) { if (wasRecovered && !autosaveBaseName.isEmpty() && rex.match(QFileInfo(autosaveBaseName).fileName()).hasMatch() && QFile::exists(autosaveBaseName)) { QFile::remove(autosaveBaseName); } } } KoUnit KisDocument::unit() const { return d->unit; } void KisDocument::setUnit(const KoUnit &unit) { if (d->unit != unit) { d->unit = unit; emit unitChanged(unit); } } KUndo2Stack *KisDocument::undoStack() { return d->undoStack; } KisImportExportManager *KisDocument::importExportManager() const { return d->importExportManager; } void KisDocument::addCommand(KUndo2Command *command) { if (command) d->undoStack->push(command); } void KisDocument::beginMacro(const KUndo2MagicString & text) { d->undoStack->beginMacro(text); } void KisDocument::endMacro() { d->undoStack->endMacro(); } void KisDocument::slotUndoStackCleanChanged(bool value) { setModified(!value); } void KisDocument::slotConfigChanged() { KisConfig cfg(true); if (!d->undoStack->isClean() && d->undoStack->undoLimit() != cfg.undoStackLimit()) { d->undoStack->clear(); d->undoStack->setUndoLimit(cfg.undoStackLimit()); } d->autoSaveDelay = cfg.autoSaveInterval(); setNormalAutoSaveInterval(); } void KisDocument::clearUndoHistory() { d->undoStack->clear(); } KisGridConfig KisDocument::gridConfig() const { return d->gridConfig; } void KisDocument::setGridConfig(const KisGridConfig &config) { d->gridConfig = config; } QList &KisDocument::paletteList() { return d->paletteList; } void KisDocument::setPaletteList(const QList &paletteList) { d->paletteList = paletteList; } const KisGuidesConfig& KisDocument::guidesConfig() const { return d->guidesConfig; } void KisDocument::setGuidesConfig(const KisGuidesConfig &data) { if (d->guidesConfig == data) return; d->guidesConfig = data; emit sigGuidesConfigChanged(d->guidesConfig); } const KisMirrorAxisConfig& KisDocument::mirrorAxisConfig() const { return d->mirrorAxisConfig; } void KisDocument::setMirrorAxisConfig(const KisMirrorAxisConfig &config) { if (d->mirrorAxisConfig == config) { return; } d->mirrorAxisConfig = config; setModified(true); emit sigMirrorAxisConfigChanged(); } void KisDocument::resetURL() { setUrl(QUrl()); setLocalFilePath(QString()); } KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const { return new KoDocumentInfoDlg(parent, docInfo); } bool KisDocument::isReadWrite() const { return d->readwrite; } QUrl KisDocument::url() const { return d->m_url; } bool KisDocument::closeUrl(bool promptToSave) { if (promptToSave) { if ( isReadWrite() && isModified()) { Q_FOREACH (KisView *view, KisPart::instance()->views()) { if (view && view->document() == this) { if (!view->queryClose()) { return false; } } } } } // Not modified => ok and delete temp file. d->mimeType = QByteArray(); // It always succeeds for a read-only part, // but the return value exists for reimplementations // (e.g. pressing cancel for a modified read-write part) return true; } void KisDocument::setUrl(const QUrl &url) { d->m_url = url; } QString KisDocument::localFilePath() const { return d->m_file; } void KisDocument::setLocalFilePath( const QString &localFilePath ) { d->m_file = localFilePath; } bool KisDocument::openUrlInternal(const QUrl &url) { if ( !url.isValid() ) { return false; } if (d->m_bAutoDetectedMime) { d->mimeType = QByteArray(); d->m_bAutoDetectedMime = false; } QByteArray mimetype = d->mimeType; if ( !closeUrl() ) { return false; } d->mimeType = mimetype; setUrl(url); d->m_file.clear(); if (d->m_url.isLocalFile()) { d->m_file = d->m_url.toLocalFile(); bool ret; // set the mimetype only if it was not already set (for example, by the host application) if (d->mimeType.isEmpty()) { // get the mimetype of the file // using findByUrl() to avoid another string -> url conversion QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile()); d->mimeType = mime.toLocal8Bit(); d->m_bAutoDetectedMime = true; } setUrl(d->m_url); ret = openFile(); if (ret) { emit completed(); } else { emit canceled(QString()); } return ret; } return false; } bool KisDocument::newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace* cs, const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle, int numberOfLayers, const QString &description, const double imageResolution) { Q_ASSERT(cs); KisImageSP image; if (!cs) return false; QApplication::setOverrideCursor(Qt::BusyCursor); image = new KisImage(createUndoStore(), width, height, cs, name); Q_CHECK_PTR(image); connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); image->setResolution(imageResolution, imageResolution); image->assignImageProfile(cs->profile()); documentInfo()->setAboutInfo("title", name); documentInfo()->setAboutInfo("abstract", description); KisLayerSP layer; if (bgStyle == KisConfig::RASTER_LAYER || bgStyle == KisConfig::FILL_LAYER) { KoColor strippedAlpha = bgColor; strippedAlpha.setOpacity(OPACITY_OPAQUE_U8); if (bgStyle == KisConfig::RASTER_LAYER) { layer = new KisPaintLayer(image.data(), "Background", OPACITY_OPAQUE_U8, cs);; layer->paintDevice()->setDefaultPixel(strippedAlpha); } else if (bgStyle == KisConfig::FILL_LAYER) { KisFilterConfigurationSP filter_config = KisGeneratorRegistry::instance()->get("color")->defaultConfiguration(); filter_config->setProperty("color", strippedAlpha.toQColor()); layer = new KisGeneratorLayer(image.data(), "Background Fill", filter_config, image->globalSelection()); } layer->setOpacity(bgColor.opacityU8()); if (numberOfLayers > 1) { //Lock bg layer if others are present. layer->setUserLocked(true); } } else { // KisConfig::CANVAS_COLOR (needs an unlocked starting layer). image->setDefaultProjectionColor(bgColor); layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs); } Q_CHECK_PTR(layer); image->addNode(layer.data(), image->rootLayer().data()); layer->setDirty(QRect(0, 0, width, height)); setCurrentImage(image); for(int i = 1; i < numberOfLayers; ++i) { KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs); image->addNode(layer, image->root(), i); layer->setDirty(QRect(0, 0, width, height)); } KisConfig cfg(false); cfg.defImageWidth(width); cfg.defImageHeight(height); cfg.defImageResolution(imageResolution); cfg.defColorModel(image->colorSpace()->colorModelId().id()); cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id()); cfg.defColorProfile(image->colorSpace()->profile()->name()); KisUsageLogger::log(i18n("Created image \"%1\", %2 * %3 pixels, %4 dpi. Color model: %6 %5 (%7). Layers: %8" , name , width, height , imageResolution * 72.0 , image->colorSpace()->colorModelId().name(), image->colorSpace()->colorDepthId().name() , image->colorSpace()->profile()->name() , numberOfLayers)); QApplication::restoreOverrideCursor(); return true; } bool KisDocument::isSaving() const { const bool result = d->savingMutex.tryLock(); if (result) { d->savingMutex.unlock(); } return !result; } void KisDocument::waitForSavingToComplete() { if (isSaving()) { KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0); f.waitForMutex(&d->savingMutex); } } KoShapeControllerBase *KisDocument::shapeController() const { return d->shapeController; } KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const { return d->shapeController->shapeForNode(layer); } QList KisDocument::assistants() const { return d->assistants; } void KisDocument::setAssistants(const QList &value) { d->assistants = value; } KisSharedPtr KisDocument::referenceImagesLayer() const { return d->referenceImagesLayer.data(); } void KisDocument::setReferenceImagesLayer(KisSharedPtr layer, bool updateImage) { if (d->referenceImagesLayer) { d->referenceImagesLayer->disconnect(this); } if (updateImage) { if (layer) { d->image->addNode(layer); } else { d->image->removeNode(d->referenceImagesLayer); } } d->referenceImagesLayer = layer; if (d->referenceImagesLayer) { connect(d->referenceImagesLayer, SIGNAL(sigUpdateCanvas(QRectF)), this, SIGNAL(sigReferenceImagesChanged())); } } void KisDocument::setPreActivatedNode(KisNodeSP activatedNode) { d->preActivatedNode = activatedNode; } KisNodeSP KisDocument::preActivatedNode() const { return d->preActivatedNode; } KisImageWSP KisDocument::image() const { return d->image; } KisImageSP KisDocument::savingImage() const { return d->savingImage; } void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate) { if (d->image) { // Disconnect existing sig/slot connections d->image->setUndoStore(new KisDumbUndoStore()); d->image->disconnect(this); d->shapeController->setImage(0); d->image = 0; } if (!image) return; d->setImageAndInitIdleWatcher(image); d->image->setUndoStore(new KisDocumentUndoStore(this)); d->shapeController->setImage(image); setModified(false); connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection); if (forceInitialUpdate) { d->image->initialRefreshGraph(); } } void KisDocument::hackPreliminarySetImage(KisImageSP image) { KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image); d->setImageAndInitIdleWatcher(image); d->shapeController->setImage(image); } void KisDocument::setImageModified() { setModified(true); } KisUndoStore* KisDocument::createUndoStore() { return new KisDocumentUndoStore(this); } bool KisDocument::isAutosaving() const { return d->isAutosaving; } -QString KisDocument::exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage) +QString KisDocument::exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage) { - return errorMessage.isEmpty() ? KisImportExportFilter::conversionStatusString(status) : errorMessage; + return errorMessage.isEmpty() ? status.errorMessage() : errorMessage; } void KisDocument::setAssistantsGlobalColor(QColor color) { d->globalAssistantsColor = color; } QColor KisDocument::assistantsGlobalColor() { return d->globalAssistantsColor; } QRectF KisDocument::documentBounds() const { QRectF bounds = d->image->bounds(); if (d->referenceImagesLayer) { bounds |= d->referenceImagesLayer->boundingImageRect(); } return bounds; } diff --git a/libs/ui/KisDocument.h b/libs/ui/KisDocument.h index 01e5f59938..2ccdbfeb64 100644 --- a/libs/ui/KisDocument.h +++ b/libs/ui/KisDocument.h @@ -1,667 +1,667 @@ /* This file is part of the Krita project * * Copyright (C) 2014 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 KISDOCUMENT_H #define KISDOCUMENT_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kritaui_export.h" #include class QString; class KUndo2Command; class KoUnit; class KoColor; class KoColorSpace; class KoShapeControllerBase; class KoShapeLayer; class KoStore; class KoOdfReadStore; class KoDocumentInfo; class KoDocumentInfoDlg; class KisImportExportManager; class KisUndoStore; class KisPart; class KisGridConfig; class KisGuidesConfig; class KisMirrorAxisConfig; class QDomDocument; class KisReferenceImagesLayer; #define KIS_MIME_TYPE "application/x-krita" /** * The %Calligra document class * * This class provides some functionality each %Calligra document should have. * * @short The %Calligra document class */ class KRITAUI_EXPORT KisDocument : public QObject, public KoDocumentBase { Q_OBJECT protected: explicit KisDocument(); /** * @brief KisDocument makes a deep copy of the document \p rhs. * The caller *must* ensure that the image is properly * locked and is in consistent state before asking for * cloning. * @param rhs the source document to copy from */ explicit KisDocument(const KisDocument &rhs); public: enum OpenFlag { None = 0, DontAddToRecent = 0x1, RecoveryFile = 0x2 }; Q_DECLARE_FLAGS(OpenFlags, OpenFlag) /** * Destructor. * * The destructor does not delete any attached KisView objects and it does not * delete the attached widget as returned by widget(). */ ~KisDocument() override; /** * @brief reload Reloads the document from the original url * @return the result of loading the document */ bool reload(); /** * @brief creates a clone of the document and returns it. Please make sure that you * hold all the necessary locks on the image before asking for a clone! */ KisDocument* clone(); /** * @brief openUrl Open an URL * @param url The URL to open * @param flags Control specific behavior * @return success status */ bool openUrl(const QUrl &url, OpenFlags flags = None); /** * Opens the document given by @p url, without storing the URL * in the KisDocument. * Call this instead of openUrl() to implement KisMainWindow's * File --> Import feature. * * @note This will call openUrl(). To differentiate this from an ordinary * Open operation (in any reimplementation of openUrl() or openFile()) * call isImporting(). */ bool importDocument(const QUrl &url); /** * Saves the document as @p url without changing the state of the * KisDocument (URL, modified flag etc.). Call this instead of * KisParts::ReadWritePart::saveAs() to implement KisMainWindow's * File --> Export feature. */ bool exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings = false, KisPropertiesConfigurationSP exportConfiguration = 0); /** * Exports he document is a synchronous way. The caller must ensure that the * image is not accessed by any other actors, because the exporting happens * without holding the image lock. */ bool exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration = 0); private: bool exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration); public: /** * @brief Sets whether the document can be edited or is read only. * * This recursively applied to all child documents and * KisView::updateReadWrite is called for every attached * view. */ void setReadWrite(bool readwrite = true); /** * To be preferred when a document exists. It is fast when calling * it multiple times since it caches the result that readNativeFormatMimeType() * delivers. * This comes from the X-KDE-NativeMimeType key in the .desktop file. */ static QByteArray nativeFormatMimeType() { return KIS_MIME_TYPE; } /// Checks whether a given mimetype can be handled natively. bool isNativeFormat(const QByteArray& mimetype) const; /// Returns a list of the mimetypes considered "native", i.e. which can /// be saved by KisDocument without a filter, in *addition* to the main one static QStringList extraNativeMimeTypes() { return QStringList() << KIS_MIME_TYPE; } /** * Returns the actual mimetype of the document */ QByteArray mimeType() const override; /** * @brief Sets the mime type for the document. * * When choosing "save as" this is also the mime type * selected by default. */ void setMimeType(const QByteArray & mimeType) override; /** * @return true if file operations should inhibit the option dialog */ bool fileBatchMode() const; /** * @param batchMode if true, do not show the option dialog for file operations. */ void setFileBatchMode(const bool batchMode); /** * Sets the error message to be shown to the user (use i18n()!) * when loading or saving fails. * If you asked the user about something and they chose "Cancel", */ void setErrorMessage(const QString& errMsg); /** * Return the last error message. Usually KisDocument takes care of * showing it; this method is mostly provided for non-interactive use. */ QString errorMessage() const; /** * Sets the warning message to be shown to the user (use i18n()!) * when loading or saving fails. */ void setWarningMessage(const QString& warningMsg); /** * Return the last warning message set by loading or saving. Warnings * mean that the document could not be completely loaded, but the errors * were not absolutely fatal. */ QString warningMessage() const; /** * @brief Generates a preview picture of the document * @note The preview is used in the File Dialog and also to create the Thumbnail */ QPixmap generatePreview(const QSize& size); /** * Tells the document that its title has been modified, either because * the modified status changes (this is done by setModified() ) or * because the URL or the document-info's title changed. */ void setTitleModified(); /** * @brief Sets the document to empty. * * Used after loading a template * (which is not empty, but not the user's input). * * @see isEmpty() */ void setEmpty(bool empty = true); /** * Return a correctly created QDomDocument for this KisDocument, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * @param tagName the name of the tag for the root element * @param version the DTD version (usually the application's version). */ QDomDocument createDomDocument(const QString& tagName, const QString& version) const; /** * Return a correctly created QDomDocument for an old (1.3-style) %Calligra document, * including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element. * This static method can be used e.g. by filters. * @param appName the app's instance name, e.g. words, kspread, kpresenter etc. * @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter. * @param version the DTD version (usually the application's version). */ static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version); /** * Loads a document in the native format from a given URL. * Reimplement if your native format isn't XML. * * @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter */ bool loadNativeFormat(const QString & file); /** * Set standard autosave interval that is set by a config file */ void setNormalAutoSaveInterval(); /** * Set emergency interval that autosave uses when the image is busy, * by default it is 10 sec */ void setEmergencyAutoSaveInterval(); /** * Disable autosave */ void setInfiniteAutoSaveInterval(); /** * @return the information concerning this document. * @see KoDocumentInfo */ KoDocumentInfo *documentInfo() const; /** * Performs a cleanup of unneeded backup files */ void removeAutoSaveFiles(const QString &autosaveBaseName, bool wasRecovered); /** * Returns true if this document or any of its internal child documents are modified. */ bool isModified() const override; /** * @return caption of the document * * Caption is of the form "[title] - [url]", * built out of the document info (title) and pretty-printed * document URL. * If the title is not present, only the URL it returned. */ QString caption() const; /** * Sets the document URL to empty URL * KParts doesn't allow this, but %Calligra apps have e.g. templates * After using loadNativeFormat on a template, one wants * to set the url to QUrl() */ void resetURL(); /** * @internal (public for KisMainWindow) */ void setMimeTypeAfterLoading(const QString& mimeType); /** * Returns the unit used to display all measures/distances. */ KoUnit unit() const; /** * Sets the unit used to display all measures/distances. */ void setUnit(const KoUnit &unit); KisGridConfig gridConfig() const; void setGridConfig(const KisGridConfig &config); /// returns the guides data for this document. const KisGuidesConfig& guidesConfig() const; void setGuidesConfig(const KisGuidesConfig &data); const KisMirrorAxisConfig& mirrorAxisConfig() const; void setMirrorAxisConfig(const KisMirrorAxisConfig& config); QList &paletteList(); void setPaletteList(const QList &paletteList); void clearUndoHistory(); /** * Sets the modified flag on the document. This means that it has * to be saved or not before deleting it. */ void setModified(bool _mod); void setRecovered(bool value); bool isRecovered() const; void updateEditingTime(bool forceStoreElapsed); /** * Returns the global undo stack */ KUndo2Stack *undoStack(); /** * @brief importExportManager gives access to the internal import/export manager * @return the document's import/export manager */ KisImportExportManager *importExportManager() const; /** * @brief serializeToNativeByteArray daves the document into a .kra file wtitten * to a memory-based byte-array * @return a byte array containing the .kra file */ QByteArray serializeToNativeByteArray(); /** * @brief isInSaving shown if the document has any (background) saving process or not * @return true if there is some saving in action */ bool isInSaving() const; public Q_SLOTS: /** * Adds a command to the undo stack and executes it by calling the redo() function. * @param command command to add to the undo stack */ void addCommand(KUndo2Command *command); /** * Begins recording of a macro command. At the end endMacro needs to be called. * @param text command description */ void beginMacro(const KUndo2MagicString &text); /** * Ends the recording of a macro command. */ void endMacro(); Q_SIGNALS: /** * This signal is emitted when the unit is changed by setUnit(). * It is common to connect views to it, in order to change the displayed units * (e.g. in the rulers) */ void unitChanged(const KoUnit &unit); /** * Emitted e.g. at the beginning of a save operation * This is emitted by KisDocument and used by KisView to display a statusbar message */ void statusBarMessage(const QString& text, int timeout = 0); /** * Emitted e.g. at the end of a save operation * This is emitted by KisDocument and used by KisView to clear the statusbar message */ void clearStatusBarMessage(); /** * Emitted when the document is modified */ void modified(bool); void titleModified(const QString &caption, bool isModified); void sigLoadingFinished(); void sigSavingFinished(); void sigGuidesConfigChanged(const KisGuidesConfig &config); - void sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); + void sigBackgroundSavingFinished(KisImportExportErrorCode status, const QString &errorMessage); - void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); + void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage); void sigReferenceImagesChanged(); void sigMirrorAxisConfigChanged(); private Q_SLOTS: void finishExportInBackground(); - void slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); - void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); + void slotChildCompletedSavingInBackground(KisImportExportErrorCode status, const QString &errorMessage); + void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage); - void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage); + void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportErrorCode status, const QString &errorMessage); void slotInitiateAsyncAutosaving(KisDocument *clonedDocument); private: friend class KisPart; friend class SafeSavingLocker; bool initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration, std::unique_ptr &&optionalClonedDocument); bool initiateSavingInBackground(const QString actionName, const QObject *receiverObject, const char *receiverMethod, const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration); bool startExportInBackground(const QString &actionName, const QString &location, const QString &realLocation, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); /** * Activate/deactivate/configure the autosave feature. * @param delay in seconds, 0 to disable */ void setAutoSaveDelay(int delay); /** * Generate a name for the document. */ QString newObjectName(); QString generateAutoSaveFileName(const QString & path) const; /** * Loads a document * * Applies a filter if necessary, and calls loadNativeFormat in any case * You should not have to reimplement, except for very special cases. * * NOTE: this method also creates a new KisView instance! * * This method is called from the KReadOnlyPart::openUrl method. */ bool openFile(); public: bool isAutosaving() const override; public: QString localFilePath() const override; void setLocalFilePath( const QString &localFilePath ); KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const; bool isReadWrite() const; QUrl url() const override; void setUrl(const QUrl &url) override; bool closeUrl(bool promptToSave = true); bool saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfigration = 0); /** * Create a new image that has this document as a parent and * replace the current image with this image. */ bool newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace * cs, const KoColor &bgColor, KisConfig::BackgroundStyle bgStyle, int numberOfLayers, const QString &imageDescription, const double imageResolution); bool isSaving() const; void waitForSavingToComplete(); KisImageWSP image() const; /** * @brief savingImage provides a detached, shallow copy of the original image that must be used when saving. * Any strokes in progress will not be applied to this image, so the result might be missing some data. On * the other hand, it won't block. * * @return a shallow copy of the original image, or 0 is saving is not in progress */ KisImageSP savingImage() const; /** * Set the current image to the specified image and turn undo on. */ void setCurrentImage(KisImageSP image, bool forceInitialUpdate = true); /** * Set the image of the document preliminary, before the document * has completed loading. Some of the document items (shapes) may want * to access image properties (bounds and resolution), so we should provide * it to them even before the entire image is loaded. * * Right now, the only use by KoShapeRegistry::createShapeFromOdf(), remove * after it is deprecated. */ void hackPreliminarySetImage(KisImageSP image); KisUndoStore* createUndoStore(); /** * The shape controller matches internal krita image layers with * the flake shape hierarchy. */ KoShapeControllerBase * shapeController() const; KoShapeLayer* shapeForNode(KisNodeSP layer) const; /** * Set the list of nodes that was marked as currently active. Used *only* * for saving loading. Never use it for tools or processing. */ void setPreActivatedNode(KisNodeSP activatedNode); /** * @return the node that was set as active during loading. Used *only* * for saving loading. Never use it for tools or processing. */ KisNodeSP preActivatedNode() const; /// @return the list of assistants associated with this document QList assistants() const; /// @replace the current list of assistants with @param value void setAssistants(const QList &value); void setAssistantsGlobalColor(QColor color); QColor assistantsGlobalColor(); /** * Get existing reference images layer or null if none exists. */ KisSharedPtr referenceImagesLayer() const; void setReferenceImagesLayer(KisSharedPtr layer, bool updateImage); bool save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration); /** * Return the bounding box of the image and associated elements (e.g. reference images) */ QRectF documentBounds() const; Q_SIGNALS: void completed(); void canceled(const QString &); private Q_SLOTS: void setImageModified(); void slotAutoSave(); void slotUndoStackCleanChanged(bool value); void slotConfigChanged(); private: /** * @brief try to clone the image. This method handles all the locking for you. If locking * has failed, no cloning happens * @return cloned document on success, null otherwise */ KisDocument *lockAndCloneForSaving(); - QString exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage); + QString exportErrorToUserMessage(KisImportExportErrorCode status, const QString &errorMessage); QString prettyPathOrUrl() const; bool openUrlInternal(const QUrl &url); void slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument); class Private; Private *const d; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KisDocument::OpenFlags) Q_DECLARE_METATYPE(KisDocument*) #endif diff --git a/libs/ui/KisImageBuilderResult.h b/libs/ui/KisImageBuilderResult.h index bbdab8e5d7..cbe8f74a25 100644 --- a/libs/ui/KisImageBuilderResult.h +++ b/libs/ui/KisImageBuilderResult.h @@ -1,44 +1,46 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * 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_IMAGEBUILDER_RESULT_H #define KIS_IMAGEBUILDER_RESULT_H /** * Image import/export plugins can use these results to report about success or failure. */ enum KisImageBuilder_Result { KisImageBuilder_RESULT_FAILURE = -400, KisImageBuilder_RESULT_NOT_EXIST = -300, KisImageBuilder_RESULT_NOT_LOCAL = -200, KisImageBuilder_RESULT_BAD_FETCH = -100, KisImageBuilder_RESULT_INVALID_ARG = -50, KisImageBuilder_RESULT_OK = 0, KisImageBuilder_RESULT_PROGRESS = 1, KisImageBuilder_RESULT_CANCEL = 50, KisImageBuilder_RESULT_EMPTY = 100, KisImageBuilder_RESULT_BUSY = 150, KisImageBuilder_RESULT_NO_URI = 200, KisImageBuilder_RESULT_UNSUPPORTED = 300, KisImageBuilder_RESULT_INTR = 400, KisImageBuilder_RESULT_PATH = 500, KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE = 600 }; + + #endif diff --git a/plugins/impex/gif/kis_gif_import.h b/libs/ui/KisImportExportAdditionalChecks.cpp similarity index 59% copy from plugins/impex/gif/kis_gif_import.h copy to libs/ui/KisImportExportAdditionalChecks.cpp index 7b9d9f90bf..fa698a310c 100644 --- a/plugins/impex/gif/kis_gif_import.h +++ b/libs/ui/KisImportExportAdditionalChecks.cpp @@ -1,37 +1,40 @@ /* - * Copyright (c) 2018 Boudewijn Rempt + * Copyright (c) 2019 Agata Cacko * * 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_GIF_IMPORT_H_ -#define _KIS_GIF_IMPORT_H_ +#include "KisImportExportAdditionalChecks.h" +#include -#include -#include +bool KisImportExportAdditionalChecks::isFileWritable(QString filepath) +{ + QFileInfo finfo(filepath); + return finfo.isWritable(); +} + +bool KisImportExportAdditionalChecks::isFileReadable(QString filepath) +{ + QFileInfo finfo(filepath); + return finfo.isReadable(); +} -class KisGIFImport : public KisImportExportFilter +bool KisImportExportAdditionalChecks::doesFileExist(QString filepath) { - Q_OBJECT -public: - KisGIFImport(QObject *parent, const QVariantList &); - ~KisGIFImport() override; -public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; -}; + return QFile::exists(filepath); +} -#endif diff --git a/plugins/impex/pdf/kis_pdf_import.h b/libs/ui/KisImportExportAdditionalChecks.h similarity index 61% copy from plugins/impex/pdf/kis_pdf_import.h copy to libs/ui/KisImportExportAdditionalChecks.h index df387515ff..007c1c1a50 100644 --- a/plugins/impex/pdf/kis_pdf_import.h +++ b/libs/ui/KisImportExportAdditionalChecks.h @@ -1,37 +1,38 @@ /* - * Copyright (c) 2006 Cyrille Berger + * Copyright (c) 2019 Agata Cacko * * 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_IMPORT_EXPORT_ADDITIONAL_CHECKS_H +#define KIS_IMPORT_EXPORT_ADDITIONAL_CHECKS_H -#ifndef KIS_PDF_IMPORT_H -#define KIS_PDF_IMPORT_H +#include +#include -#include - -#include - -class KisPDFImport : public KisImportExportFilter +class KRITAUI_EXPORT KisImportExportAdditionalChecks { - Q_OBJECT -public: - KisPDFImport(QObject *parent, const QVariantList &); - virtual ~KisPDFImport(); + public: - virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0); + + static bool isFileWritable(QString filepath); + static bool isFileReadable(QString filepath); + static bool doesFileExist(QString filepath); }; -#endif + + + +#endif // #ifndef KIS_IMPORT_EXPORT_ADDITIONAL_CHECKS_H diff --git a/libs/ui/KisImportExportErrorCode.cpp b/libs/ui/KisImportExportErrorCode.cpp index 5874351653..8fbb022d77 100644 --- a/libs/ui/KisImportExportErrorCode.cpp +++ b/libs/ui/KisImportExportErrorCode.cpp @@ -1,189 +1,224 @@ /* * Copyright (c) 2019 Agata Cacko * * 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 "KisImportExportErrorCode.h" #include #include KisImportExportComplexError::KisImportExportComplexError(QFileDevice::FileError error) : m_error(error) { } QString KisImportExportComplexError::qtErrorMessage() const { // Error descriptions in most cases taken from https://doc.qt.io/qt-5/qfiledevice.html QString unspecifiedError = i18n("An unspecified error occurred."); switch (m_error) { case QFileDevice::FileError::NoError : // Returning this file error may mean that something is wrong in our code. // Successful operation should return ImportExportCodes::OK instead. return i18n("The action has been completed successfully."); case QFileDevice::FileError::ReadError : return i18n("An error occurred when reading from the file."); case QFileDevice::FileError::WriteError : return i18n("An error occurred when writing to the file."); case QFileDevice::FileError::FatalError : return i18n("A fatal error occurred."); case QFileDevice::FileError::ResourceError : return i18n("Out of resources (e.g. out of memory)."); case QFileDevice::FileError::OpenError : return i18n("The file could not be opened."); case QFileDevice::FileError::AbortError : return i18n("The operation was aborted."); case QFileDevice::FileError::TimeOutError : return i18n("A timeout occurred."); case QFileDevice::FileError::UnspecifiedError : return unspecifiedError; case QFileDevice::FileError::RemoveError : return i18n("The file could not be removed."); case QFileDevice::FileError::RenameError : return i18n("The file could not be renamed."); case QFileDevice::FileError::PositionError : return i18n("The position in the file could not be changed."); case QFileDevice::FileError::ResizeError : return i18n("The file could not be resized."); case QFileDevice::FileError::PermissionsError : return i18n("Permission denied. Krita is not allowed to read or write to the file."); case QFileDevice::FileError::CopyError : return i18n("The file could not be copied."); } return unspecifiedError; } +KisImportExportErrorCannotRead::KisImportExportErrorCannotRead() : KisImportExportComplexError(QFileDevice::FileError()) { } KisImportExportErrorCannotRead::KisImportExportErrorCannotRead(QFileDevice::FileError error) : KisImportExportComplexError(error) { KIS_ASSERT_RECOVER_NOOP(error != QFileDevice::NoError); } QString KisImportExportErrorCannotRead::errorMessage() const { return i18n("Cannot open file for reading. Reason: %1", qtErrorMessage()); } +bool KisImportExportErrorCannotRead::operator==(KisImportExportErrorCannotRead other) +{ + return other.m_error == m_error; +} + + + +KisImportExportErrorCannotWrite::KisImportExportErrorCannotWrite() : KisImportExportComplexError(QFileDevice::FileError()) { } KisImportExportErrorCannotWrite::KisImportExportErrorCannotWrite(QFileDevice::FileError error) : KisImportExportComplexError(error) { KIS_ASSERT_RECOVER_NOOP(error != QFileDevice::NoError); } QString KisImportExportErrorCannotWrite::errorMessage() const { return i18n("Cannot open file for writing. Reason: %1", qtErrorMessage()); } -KisImportExportErrorCode::KisImportExportErrorCode() : errorFieldUsed(None), cannotRead(QFileDevice::FileError()), cannotWrite(QFileDevice::FileError()) { } +bool KisImportExportErrorCannotWrite::operator==(KisImportExportErrorCannotWrite other) +{ + return other.m_error == m_error; +} + + -KisImportExportErrorCode::KisImportExportErrorCode(ImportExportCodes::ErrorCodeID id) : errorFieldUsed(CodeId), codeId(id), cannotRead(QFileDevice::FileError()), cannotWrite(QFileDevice::FileError()) { } -KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotRead error) : errorFieldUsed(CannotRead), cannotRead(error), cannotWrite(QFileDevice::FileError()) { } -KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotWrite error) : errorFieldUsed(CannotWrite), cannotRead(QFileDevice::FileError()), cannotWrite(error) { } +KisImportExportErrorCode::KisImportExportErrorCode() : errorFieldUsed(None), cannotRead(), cannotWrite() { } +KisImportExportErrorCode::KisImportExportErrorCode(ImportExportCodes::ErrorCodeID id) : errorFieldUsed(CodeId), codeId(id), cannotRead(), cannotWrite() { } +KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotRead error) : errorFieldUsed(CannotRead), cannotRead(error), cannotWrite() { } +KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotWrite error) : errorFieldUsed(CannotWrite), cannotRead(), cannotWrite(error) { } bool KisImportExportErrorCode::isOk() const { // if cannotRead or cannotWrite is "NoError", it means that something is wrong in our code return errorFieldUsed == CodeId && codeId == ImportExportCodes::OK; } bool KisImportExportErrorCode::isCancelled() const { return errorFieldUsed == CodeId && codeId == ImportExportCodes::Cancelled; } bool KisImportExportErrorCode::isInternalError() const { return errorFieldUsed == CodeId && codeId == ImportExportCodes::InternalError; } QString KisImportExportErrorCode::errorMessage() const { QString internal = i18n("Unexpected error. Please contact developers."); + QString unknown = i18n("Unknown error."); if (errorFieldUsed == CannotRead) { return cannotRead.errorMessage(); } else if (errorFieldUsed == CannotWrite) { return cannotWrite.errorMessage(); } else if (errorFieldUsed == CodeId) { switch (codeId) { // Reading case ImportExportCodes::FileNotExist: return i18n("The file doesn't exists."); case ImportExportCodes::NoAccessToRead: return i18n("Permission denied: Krita is not allowed to read the file."); case ImportExportCodes::FileFormatIncorrect: return i18n("The file format cannot be parsed."); case ImportExportCodes::FormatFeaturesUnsupported: return i18n("The file format contains unsupported features."); case ImportExportCodes::FormatColorSpaceUnsupported: return i18n("The file format contains unsupported color space."); + case ImportExportCodes::ErrorWhileReading: + return unknown; // Writing case ImportExportCodes::CannotCreateFile: return i18n("The file cannot be created."); case ImportExportCodes::NoAccessToWrite: return i18n("Permission denied: Krita is not allowed to write to the file."); case ImportExportCodes::InsufficientMemory: return i18n("There is not enough memory left to save the file."); + case ImportExportCodes::ErrorWhileWriting: + return unknown; // Both case ImportExportCodes::Cancelled: return i18n("The action was cancelled by the user."); // Other case ImportExportCodes::Failure: - return i18n("Unknown error."); + return unknown; case ImportExportCodes::InternalError: return internal; // OK case ImportExportCodes::OK: return i18n("The action has been completed successfully."); default: return internal; } } return internal; // errorFieldUsed = None } +bool KisImportExportErrorCode::operator==(KisImportExportErrorCode errorCode) +{ + if (errorFieldUsed != errorCode.errorFieldUsed) { + return false; + } + if (errorFieldUsed == CodeId) { + return codeId == errorCode.codeId; + } + if (errorFieldUsed == CannotRead) { + return cannotRead == errorCode.cannotRead; + } + return cannotWrite == errorCode.cannotWrite; +} + + QDebug operator<<(QDebug d, const KisImportExportErrorCode& errorCode) { switch(errorCode.errorFieldUsed) { case KisImportExportErrorCode::None: d << "None of the error fields is in use."; break; case KisImportExportErrorCode::CannotRead: d << "Cannot read: " << errorCode.cannotRead.m_error; break; case KisImportExportErrorCode::CannotWrite: - d << "Cannot read: " << errorCode.cannotRead.m_error; + d << "Cannot write: " << errorCode.cannotRead.m_error; break; case KisImportExportErrorCode::CodeId: d << "Error code = " << errorCode.codeId; } d << " " << errorCode.errorMessage(); return d; } diff --git a/libs/ui/KisImportExportErrorCode.h b/libs/ui/KisImportExportErrorCode.h index bef2d6395a..7f9f5d0e02 100644 --- a/libs/ui/KisImportExportErrorCode.h +++ b/libs/ui/KisImportExportErrorCode.h @@ -1,141 +1,163 @@ /* * Copyright (c) 2019 Agata Cacko * * 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_IMPORT_EXPORT_ERROR_CODES_H #define KIS_IMPORT_EXPORT_ERROR_CODES_H #include #include #include #include #include namespace ImportExportCodes { enum KRITAUI_EXPORT ErrorCodeID { InternalError, // error that shouldn't happen; only inside ASSERTS // Reading FileNotExist, // there is no file with that name in that location, NoAccessToRead, // Krita has no reading access to the file, ErrorWhileReading, // there was an error that occured during reading, FileFormatIncorrect, // file format cannot be parsed, FormatFeaturesUnsupported, // file format can be parsed, but some features are unsupported, FormatColorSpaceUnsupported, // file format can be parsed, but color space of the image is unsupported // Writing CannotCreateFile, // file cannot be created NoAccessToWrite, // Krita has no writing access to the file ErrorWhileWriting, // there was an error that occured during writing (can be insufficient memory, too, just we don't know) InsufficientMemory, // there is not enough memory left // Both Cancelled, // cancelled by a user // Other Failure, // unspecified error // OK OK, // everything went ok }; }; struct KisImportExportErrorCode; struct KRITAUI_EXPORT KisImportExportComplexError { virtual QString errorMessage() const = 0; KisImportExportComplexError(QFileDevice::FileError error); friend QDebug operator<<(QDebug d, const KisImportExportErrorCode& errorCode); + + protected: QString qtErrorMessage() const; QFileDevice::FileError m_error; virtual ~KisImportExportComplexError() {} }; struct KRITAUI_EXPORT KisImportExportErrorCannotWrite : KisImportExportComplexError { KisImportExportErrorCannotWrite(QFileDevice::FileError error); QString errorMessage() const override; ~KisImportExportErrorCannotWrite() { } + + bool operator==(KisImportExportErrorCannotWrite other); + +private: + KisImportExportErrorCannotWrite(); + + //friend KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotWrite code); + friend class KisImportExportErrorCode; + }; struct KRITAUI_EXPORT KisImportExportErrorCannotRead : KisImportExportComplexError { KisImportExportErrorCannotRead(QFileDevice::FileError error); QString errorMessage() const override; ~KisImportExportErrorCannotRead() { } + + bool operator==(KisImportExportErrorCannotRead other); + +private: + KisImportExportErrorCannotRead(); + + //friend KisImportExportErrorCode::KisImportExportErrorCode(KisImportExportErrorCannotRead code); + friend class KisImportExportErrorCode; + }; struct KRITAUI_EXPORT KisImportExportErrorCode { public: // required by kis_async_action_feedback KisImportExportErrorCode(); KisImportExportErrorCode(ImportExportCodes::ErrorCodeID code); KisImportExportErrorCode(KisImportExportErrorCannotRead code); KisImportExportErrorCode(KisImportExportErrorCannotWrite code); QString errorMessage() const; bool isOk() const; bool isCancelled() const; bool isInternalError() const; friend QDebug operator<<(QDebug d, const KisImportExportErrorCode& errorCode); + bool operator==(KisImportExportErrorCode errorCode); + private: enum ErrorFieldUsed { None, CodeId, CannotRead, CannotWrite }; ErrorFieldUsed errorFieldUsed; ImportExportCodes::ErrorCodeID codeId; KisImportExportErrorCannotRead cannotRead; KisImportExportErrorCannotWrite cannotWrite; }; KRITAUI_EXPORT QDebug operator<<(QDebug d, const KisImportExportErrorCode& errorCode); #endif // KIS_IMPORT_EXPORT_ERROR_CODES_H diff --git a/libs/ui/KisImportExportFilter.h b/libs/ui/KisImportExportFilter.h index 8fa8b4d961..48282d157b 100644 --- a/libs/ui/KisImportExportFilter.h +++ b/libs/ui/KisImportExportFilter.h @@ -1,183 +1,185 @@ /* 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); /** * 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 ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) = 0; + 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; } 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); private: KisImportExportFilter(const KisImportExportFilter& rhs); KisImportExportFilter& operator=(const KisImportExportFilter& rhs); class Private; Private *const d; }; #endif diff --git a/libs/ui/KisImportExportManager.cpp b/libs/ui/KisImportExportManager.cpp index e5ac96de10..33c5a40ea7 100644 --- a/libs/ui/KisImportExportManager.cpp +++ b/libs/ui/KisImportExportManager.cpp @@ -1,699 +1,687 @@ /* * 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 "KisImportExportManager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "KisImportExportFilter.h" #include "KisDocument.h" #include #include #include "kis_painter.h" #include "kis_guides_config.h" #include "kis_grid_config.h" #include "kis_popup_button.h" #include #include "kis_async_action_feedback.h" #include "KisReferenceImagesLayer.h" // static cache for import and export mimetypes QStringList KisImportExportManager::m_importMimeTypes; QStringList KisImportExportManager::m_exportMimeTypes; class Q_DECL_HIDDEN KisImportExportManager::Private { public: KoUpdaterPtr updater; QString cachedExportFilterMimeType; QSharedPointer cachedExportFilter; }; struct KisImportExportManager::ConversionResult { ConversionResult() { } - ConversionResult(const QFuture &futureStatus) + ConversionResult(const QFuture &futureStatus) : m_isAsync(true), m_futureStatus(futureStatus) { } - ConversionResult(KisImportExportFilter::ConversionStatus status) + ConversionResult(KisImportExportErrorCode status) : m_isAsync(false), m_status(status) { } bool isAsync() const { return m_isAsync; } - QFuture futureStatus() const { + QFuture futureStatus() const { // if the result is not async, then it means some failure happened, // just return a cancelled future - KIS_SAFE_ASSERT_RECOVER_NOOP(m_isAsync || m_status != KisImportExportFilter::OK); + KIS_SAFE_ASSERT_RECOVER_NOOP(m_isAsync || !m_status.isOk()); return m_futureStatus; } - KisImportExportFilter::ConversionStatus status() const { + KisImportExportErrorCode status() const { return m_status; } - void setStatus(KisImportExportFilter::ConversionStatus value) { + void setStatus(KisImportExportErrorCode value) { m_status = value; } private: bool m_isAsync = false; - QFuture m_futureStatus; - KisImportExportFilter::ConversionStatus m_status = KisImportExportFilter::UsageError; + QFuture m_futureStatus; + KisImportExportErrorCode m_status = ImportExportCodes::InternalError; }; KisImportExportManager::KisImportExportManager(KisDocument* document) : m_document(document) , d(new Private) { } KisImportExportManager::~KisImportExportManager() { delete d; } -KisImportExportFilter::ConversionStatus KisImportExportManager::importDocument(const QString& location, const QString& mimeType) +KisImportExportErrorCode KisImportExportManager::importDocument(const QString& location, const QString& mimeType) { ConversionResult result = convert(Import, location, location, mimeType, false, 0, false); - KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), KisImportExportFilter::UsageError); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError); return result.status(); } -KisImportExportFilter::ConversionStatus KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) +KisImportExportErrorCode KisImportExportManager::exportDocument(const QString& location, const QString& realLocation, const QByteArray& mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, false); - KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), KisImportExportFilter::UsageError); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(!result.isAsync(), ImportExportCodes::InternalError); return result.status(); } -QFuture KisImportExportManager::exportDocumentAsyc(const QString &location, const QString &realLocation, const QByteArray &mimeType, KisImportExportFilter::ConversionStatus &status, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) +QFuture KisImportExportManager::exportDocumentAsyc(const QString &location, const QString &realLocation, const QByteArray &mimeType, + KisImportExportErrorCode &status, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration) { ConversionResult result = convert(Export, location, realLocation, mimeType, showWarnings, exportConfiguration, true); KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(result.isAsync() || - result.status() != KisImportExportFilter::OK, QFuture()); + !result.status().isOk(), QFuture()); status = result.status(); return result.futureStatus(); } // The static method to figure out to which parts of the // graph this mimetype has a connection to. QStringList KisImportExportManager::supportedMimeTypes(Direction direction) { // Find the right mimetype by the extension QSet mimeTypes; // mimeTypes << KisDocument::nativeFormatMimeType() << "application/x-krita-paintoppreset" << "image/openraster"; if (direction == KisImportExportManager::Import) { if (m_importMimeTypes.isEmpty()) { QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); Q_FOREACH(const QString &mimetype, json.value("X-KDE-Import").toString().split(",", QString::SkipEmptyParts)) { //qDebug() << "Adding import mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader; mimeTypes << mimetype; } } qDeleteAll(list); m_importMimeTypes = mimeTypes.toList(); } return m_importMimeTypes; } else if (direction == KisImportExportManager::Export) { if (m_exportMimeTypes.isEmpty()) { QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); Q_FOREACH(const QString &mimetype, json.value("X-KDE-Export").toString().split(",", QString::SkipEmptyParts)) { //qDebug() << "Adding export mimetype" << mimetype << KisMimeDatabase::descriptionForMimeType(mimetype) << "from plugin" << loader; mimeTypes << mimetype; } } qDeleteAll(list); m_exportMimeTypes = mimeTypes.toList(); } return m_exportMimeTypes; } return QStringList(); } KisImportExportFilter *KisImportExportManager::filterForMimeType(const QString &mimetype, KisImportExportManager::Direction direction) { int weight = -1; KisImportExportFilter *filter = 0; QListlist = KoJsonTrader::instance()->query("Krita/FileFilter", ""); Q_FOREACH(QPluginLoader *loader, list) { QJsonObject json = loader->metaData().value("MetaData").toObject(); QString directionKey = direction == Export ? "X-KDE-Export" : "X-KDE-Import"; if (json.value(directionKey).toString().split(",", QString::SkipEmptyParts).contains(mimetype)) { KLibFactory *factory = qobject_cast(loader->instance()); if (!factory) { warnUI << loader->errorString(); continue; } QObject* obj = factory->create(0); if (!obj || !obj->inherits("KisImportExportFilter")) { delete obj; continue; } KisImportExportFilter *f = qobject_cast(obj); if (!f) { delete obj; continue; } int w = json.value("X-KDE-Weight").toInt(); if (w > weight) { delete filter; filter = f; f->setObjectName(loader->fileName()); weight = w; } } } qDeleteAll(list); if (filter) { filter->setMimeType(mimetype); } return filter; } bool KisImportExportManager::batchMode(void) const { return m_document->fileBatchMode(); } void KisImportExportManager::setUpdater(KoUpdaterPtr updater) { d->updater = updater; } QString KisImportExportManager::askForAudioFileName(const QString &defaultDir, QWidget *parent) { KoFileDialog dialog(parent, KoFileDialog::ImportFiles, "ImportAudio"); if (!defaultDir.isEmpty()) { dialog.setDefaultDir(defaultDir); } QStringList mimeTypes; mimeTypes << "audio/mpeg"; mimeTypes << "audio/ogg"; mimeTypes << "audio/vorbis"; mimeTypes << "audio/vnd.wave"; mimeTypes << "audio/flac"; dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@titile:window", "Open Audio")); return dialog.filename(); } KisImportExportManager::ConversionResult KisImportExportManager::convert(KisImportExportManager::Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync) { // export configuration is supported for export only KIS_SAFE_ASSERT_RECOVER_NOOP(direction == Export || !bool(exportConfiguration)); QString typeName = mimeType; if (typeName.isEmpty()) { typeName = KisMimeDatabase::mimeTypeForFile(location, direction == KisImportExportManager::Export ? false : true); } QSharedPointer filter; /** * Fetching a filter from the registry is a really expensive operation, * because it blocks all the threads. Cache the filter if possible. */ if (direction == KisImportExportManager::Export && d->cachedExportFilter && d->cachedExportFilterMimeType == typeName) { filter = d->cachedExportFilter; } else { filter = toQShared(filterForMimeType(typeName, direction)); if (direction == Export) { d->cachedExportFilter = filter; d->cachedExportFilterMimeType = typeName; } } if (!filter) { - return KisImportExportFilter::FilterCreationError; + return KisImportExportErrorCode(ImportExportCodes::FileFormatIncorrect); } filter->setFilename(location); filter->setRealFilename(realLocation); filter->setBatchMode(batchMode()); filter->setMimeType(typeName); if (!d->updater.isNull()) { // WARNING: The updater is not guaranteed to be persistent! If you ever want // to add progress reporting to "Save also as .kra", make sure you create // a separate KoProgressUpdater for that! // WARNING2: the failsafe completion of the updater happens in the destructor // the filter. filter->setUpdater(d->updater); } QByteArray from, to; if (direction == Export) { from = m_document->nativeFormatMimeType(); to = typeName.toLatin1(); } else { from = typeName.toLatin1(); to = m_document->nativeFormatMimeType(); } KIS_ASSERT_RECOVER_RETURN_VALUE( direction == Import || direction == Export, - KisImportExportFilter::BadConversionGraph); + KisImportExportErrorCode(ImportExportCodes::InternalError)); // "bad conversion graph" - ConversionResult result = KisImportExportFilter::OK; + ConversionResult result = KisImportExportErrorCode(ImportExportCodes::OK); if (direction == Import) { KisUsageLogger::log(QString("Importing %1 to %2. Location: %3. Real location: %4. Batchmode: %5") .arg(QString::fromLatin1(from)) .arg(QString::fromLatin1(to)) .arg(location) .arg(realLocation) .arg(batchMode())); // async importing is not yet supported! KIS_SAFE_ASSERT_RECOVER_NOOP(!isAsync); if (0 && !batchMode()) { KisAsyncActionFeedback f(i18n("Opening document..."), 0); result = f.runAction(std::bind(&KisImportExportManager::doImport, this, location, filter)); } else { result = doImport(location, filter); } } else /* if (direction == Export) */ { if (!exportConfiguration) { exportConfiguration = filter->lastSavedConfiguration(from, to); } if (exportConfiguration) { fillStaticExportConfigurationProperties(exportConfiguration); } bool alsoAsKra = false; bool askUser = askUserAboutExportConfiguration(filter, exportConfiguration, from, to, batchMode(), showWarnings, &alsoAsKra); if (!batchMode() && !askUser) { - return KisImportExportFilter::UserCancelled; + return KisImportExportErrorCode(ImportExportCodes::Cancelled); } KisUsageLogger::log(QString("Converting from %1 to %2. Location: %3. Real location: %4. Batchmode: %5. Configuration: %6") .arg(QString::fromLatin1(from)) .arg(QString::fromLatin1(to)) .arg(location) .arg(realLocation) .arg(batchMode()) .arg(exportConfiguration ? exportConfiguration->toXML() : "none")); if (isAsync) { result = QtConcurrent::run(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra)); // we should explicitly report that the exporting has been initiated - result.setStatus(KisImportExportFilter::OK); + result.setStatus(ImportExportCodes::OK); } else if (!batchMode()) { KisAsyncActionFeedback f(i18n("Saving document..."), 0); result = f.runAction(std::bind(&KisImportExportManager::doExport, this, location, filter, exportConfiguration, alsoAsKra)); } else { result = doExport(location, filter, exportConfiguration, alsoAsKra); } if (exportConfiguration && !batchMode() && showWarnings) { KisConfig(false).setExportConfiguration(typeName, exportConfiguration); } } return result; } void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration, KisImageSP image) { KisPaintDeviceSP dev = image->projection(); const KoColorSpace* cs = dev->colorSpace(); const bool isThereAlpha = KisPainter::checkDeviceHasTransparency(image->projection()); exportConfiguration->setProperty(KisImportExportFilter::ImageContainsTransparencyTag, isThereAlpha); exportConfiguration->setProperty(KisImportExportFilter::ColorModelIDTag, cs->colorModelId().id()); exportConfiguration->setProperty(KisImportExportFilter::ColorDepthIDTag, cs->colorDepthId().id()); const bool sRGB = (cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive) && !cs->profile()->name().contains(QLatin1String("g10"))); exportConfiguration->setProperty(KisImportExportFilter::sRGBTag, sRGB); } void KisImportExportManager::fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration) { return fillStaticExportConfigurationProperties(exportConfiguration, m_document->image()); } bool KisImportExportManager::askUserAboutExportConfiguration( QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, const QByteArray &from, const QByteArray &to, const bool batchMode, const bool showWarnings, bool *alsoAsKra) { // prevents the animation renderer from running this code const QString mimeUserDescription = KisMimeDatabase::descriptionForMimeType(to); QStringList warnings; QStringList errors; { KisPreExportChecker checker; checker.check(m_document->image(), filter->exportChecks()); warnings = checker.warnings(); errors = checker.errors(); } KisConfigWidget *wdg = 0; if (QThread::currentThread() == qApp->thread()) { wdg = filter->createConfigurationWidget(0, from, to); } // Extra checks that cannot be done by the checker, because the checker only has access to the image. if (!m_document->assistants().isEmpty() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains assistants. The assistants will not be saved.")); } if (m_document->referenceImagesLayer() && m_document->referenceImagesLayer()->shapeCount() > 0 && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains reference images. The reference images will not be saved.")); } if (m_document->guidesConfig().hasGuides() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains guides. The guides will not be saved.")); } if (!m_document->gridConfig().isDefault() && to != m_document->nativeFormatMimeType()) { warnings.append(i18nc("image conversion warning", "The image contains a custom grid configuration. The configuration will not be saved.")); } if (!batchMode && !errors.isEmpty()) { QString error = "

" + i18n("Error: cannot save this image as a %1.", mimeUserDescription) + " Reasons:

" + "

    "; Q_FOREACH(const QString &w, errors) { error += "\n
  • " + w + "
  • "; } error += "
"; QMessageBox::critical(KisPart::instance()->currentMainwindow(), i18nc("@title:window", "Krita: Export Error"), error); return false; } if (!batchMode && (wdg || !warnings.isEmpty())) { KoDialog dlg; dlg.setButtons(KoDialog::Ok | KoDialog::Cancel); dlg.setWindowTitle(mimeUserDescription); QWidget *page = new QWidget(&dlg); QVBoxLayout *layout = new QVBoxLayout(page); if (showWarnings && !warnings.isEmpty()) { QHBoxLayout *hLayout = new QHBoxLayout(); QLabel *labelWarning = new QLabel(); labelWarning->setPixmap(KisIconUtils::loadIcon("warning").pixmap(32, 32)); hLayout->addWidget(labelWarning); KisPopupButton *bn = new KisPopupButton(0); bn->setText(i18nc("Keep the extra space at the end of the sentence, please", "Warning: saving as %1 will lose information from your image. ", mimeUserDescription)); hLayout->addWidget(bn); layout->addLayout(hLayout); QTextBrowser *browser = new QTextBrowser(); browser->setMinimumWidth(bn->width()); bn->setPopupWidget(browser); QString warning = "

" + i18n("You will lose information when saving this image as a %1.", mimeUserDescription); if (warnings.size() == 1) { warning += " Reason:

"; } else { warning += " Reasons:

"; } warning += "

    "; Q_FOREACH(const QString &w, warnings) { warning += "\n
  • " + w + "
  • "; } warning += "
"; browser->setHtml(warning); } if (wdg) { QGroupBox *box = new QGroupBox(i18n("Options")); QVBoxLayout *boxLayout = new QVBoxLayout(box); wdg->setConfiguration(exportConfiguration); boxLayout->addWidget(wdg); layout->addWidget(box); } QCheckBox *chkAlsoAsKra = 0; if (showWarnings && !warnings.isEmpty()) { chkAlsoAsKra = new QCheckBox(i18n("Also save your image as a Krita file.")); chkAlsoAsKra->setChecked(KisConfig(true).readEntry("AlsoSaveAsKra", false)); layout->addWidget(chkAlsoAsKra); } dlg.setMainWidget(page); dlg.resize(dlg.minimumSize()); if (showWarnings || wdg) { if (!dlg.exec()) { return false; } } *alsoAsKra = false; if (chkAlsoAsKra) { KisConfig(false).writeEntry("AlsoSaveAsKra", chkAlsoAsKra->isChecked()); *alsoAsKra = chkAlsoAsKra->isChecked(); } if (wdg) { *exportConfiguration = *wdg->configuration(); } } return true; } -KisImportExportFilter::ConversionStatus KisImportExportManager::doImport(const QString &location, QSharedPointer filter) +KisImportExportErrorCode KisImportExportManager::doImport(const QString &location, QSharedPointer filter) { QFile file(location); if (!file.exists()) { - return KisImportExportFilter::FileNotFound; + return ImportExportCodes::FileNotExist; } if (filter->supportsIO() && !file.open(QFile::ReadOnly)) { - return KisImportExportFilter::FileNotFound; + return KisImportExportErrorCode(KisImportExportErrorCannotRead(file.error())); } - KisImportExportFilter::ConversionStatus status = - filter->convert(m_document, &file, KisPropertiesConfigurationSP()); + KisImportExportErrorCode status = filter->convert(m_document, &file, KisPropertiesConfigurationSP()); if (file.isOpen()) { file.close(); } return status; } -KisImportExportFilter::ConversionStatus KisImportExportManager::doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra) +KisImportExportErrorCode KisImportExportManager::doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra) { - KisImportExportFilter::ConversionStatus status = + KisImportExportErrorCode status = doExportImpl(location, filter, exportConfiguration); - if (alsoAsKra && status == KisImportExportFilter::OK) { + if (alsoAsKra && status.isOk()) { QString kraLocation = location + ".kra"; QByteArray mime = m_document->nativeFormatMimeType(); QSharedPointer filter( filterForMimeType(QString::fromLatin1(mime), Export)); KIS_SAFE_ASSERT_RECOVER_NOOP(filter); if (filter) { filter->setFilename(kraLocation); KisPropertiesConfigurationSP kraExportConfiguration = filter->lastSavedConfiguration(mime, mime); status = doExportImpl(kraLocation, filter, kraExportConfiguration); } else { - status = KisImportExportFilter::FilterCreationError; + status = ImportExportCodes::FileFormatIncorrect; } } return status; } // Temporary workaround until QTBUG-57299 is fixed. #ifndef Q_OS_WIN #define USE_QSAVEFILE #endif -KisImportExportFilter::ConversionStatus KisImportExportManager::doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration) +KisImportExportErrorCode KisImportExportManager::doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration) { #ifdef USE_QSAVEFILE QSaveFile file(location); file.setDirectWriteFallback(true); if (filter->supportsIO() && !file.open(QFile::WriteOnly)) { #else QFileInfo fi(location); QTemporaryFile file(fi.absolutePath() + ".XXXXXX.kra"); if (filter->supportsIO() && !file.open()) { #endif - QString error = file.errorString(); - if (error.isEmpty()) { - error = i18n("Could not open %1 for writing.", location); - } - m_document->setErrorMessage(error); + KisImportExportErrorCannotWrite result(file.error()); #ifdef USE_QSAVEFILE file.cancelWriting(); #endif - return KisImportExportFilter::CreationError; + return result; } - KisImportExportFilter::ConversionStatus status = filter->convert(m_document, &file, exportConfiguration); + KisImportExportErrorCode status = filter->convert(m_document, &file, exportConfiguration); if (filter->supportsIO()) { - if (status != KisImportExportFilter::OK) { + if (!status.isOk()) { #ifdef USE_QSAVEFILE file.cancelWriting(); #endif } else { #ifdef USE_QSAVEFILE if (!file.commit()) { qWarning() << "Could not commit QSaveFile"; - QString error = file.errorString(); - if (error.isEmpty()) { - error = i18n("Could not write to %1.", location); - } - if (m_document->errorMessage().isEmpty()) { - m_document->setErrorMessage(error); - } - status = KisImportExportFilter::CreationError; + status = KisImportExportErrorCannotWrite(file.error()); } #else file.flush(); file.close(); QFile target(location); if (target.exists()) { // There should already be a .kra~ backup target.remove(); } if (!file.copy(location)) { file.setAutoRemove(false); - m_document->setErrorMessage(i18n("Could not copy %1 to its final location %2", file.fileName(), location)); - return KisImportExportFilter::CreationError; + return KisImportExportErrorCannotWrite(file.error()); } #endif } } return status; } #include diff --git a/libs/ui/KisImportExportManager.h b/libs/ui/KisImportExportManager.h index 9e8785608b..4a6a358736 100644 --- a/libs/ui/KisImportExportManager.h +++ b/libs/ui/KisImportExportManager.h @@ -1,158 +1,158 @@ /* * 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 KIS_IMPORT_EXPORT_MANAGER_H #define KIS_IMPORT_EXPORT_MANAGER_H #include #include #include #include #include "KisImportExportFilter.h" #include "kritaui_export.h" class KisDocument; class KoProgressUpdater; template class QFuture; /** * @brief The class managing all the filters. * * This class manages all filters for a %Calligra application. Normally * you will not have to use it, since KisMainWindow takes care of loading * and saving documents. * * @ref KisFilter * * @author Kalle Dalheimer * @author Torben Weis * @author Werner Trobin */ class KRITAUI_EXPORT KisImportExportManager : public QObject { Q_OBJECT public: /** * This enum is used to distinguish the import/export cases */ enum Direction { Import = 1, Export = 2 }; /** * Create a filter manager for a document */ explicit KisImportExportManager(KisDocument *document); public: ~KisImportExportManager() override; /** * Imports the specified document and returns the resultant filename * (most likely some file in /tmp). * @p path can be either a URL or a filename. * @p documentMimeType gives importDocument a hint about what type * the document may be. It can be left empty. * * @return status signals the success/error of the conversion. * If the QString which is returned isEmpty() and the status is OK, * then we imported the file directly into the document. */ - KisImportExportFilter::ConversionStatus importDocument(const QString &location, const QString &mimeType); + KisImportExportErrorCode importDocument(const QString &location, const QString &mimeType); /** * @brief Exports the given file/document to the specified URL/mimetype. * * If @p mimeType is empty, then the closest matching Calligra part is searched * and when the method returns @p mimeType contains this mimetype. * Oh, well, export is a C++ keyword ;) */ - KisImportExportFilter::ConversionStatus exportDocument(const QString &location, const QString& realLocation, const QByteArray &mimeType, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0); + KisImportExportErrorCode exportDocument(const QString &location, const QString& realLocation, const QByteArray &mimeType, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0); - QFuture exportDocumentAsyc(const QString &location, const QString& realLocation, const QByteArray &mimeType, KisImportExportFilter::ConversionStatus &status, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0); + QFuture exportDocumentAsyc(const QString &location, const QString& realLocation, const QByteArray &mimeType, KisImportExportErrorCode &status, bool showWarnings = true, KisPropertiesConfigurationSP exportConfiguration = 0); ///@name Static API //@{ /** * Suitable for passing to KoFileDialog::setMimeTypeFilters. The default mime * gets set by the "users" of this method, as we do not have enough * information here. * Optionally, @p extraNativeMimeTypes are added after the native mimetype. */ static QStringList supportedMimeTypes(Direction direction); /** * @brief filterForMimeType loads the relevant import/export plugin and returns it. The caller * is responsible for deleting it! * @param mimetype the mimetype we want to import/export. If there's more than one plugin, the one * with the highest weight as defined in the json description will be taken * @param direction import or export * @return a pointer to the filter plugin or 0 if none could be found */ static KisImportExportFilter *filterForMimeType(const QString &mimetype, Direction direction); /** * Fill necessary information for the export filter into the properties, e.g. if the image has * transparency or has sRGB profile. */ static void fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration, KisImageSP image); /** * Get if the filter manager is batch mode (true) * or in interactive mode (true) */ bool batchMode(void) const; void setUpdater(KoUpdaterPtr updater); static QString askForAudioFileName(const QString &defaultDir, QWidget *parent); private: struct ConversionResult; ConversionResult convert(Direction direction, const QString &location, const QString& realLocation, const QString &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfiguration, bool isAsync); void fillStaticExportConfigurationProperties(KisPropertiesConfigurationSP exportConfiguration); bool askUserAboutExportConfiguration(QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, const QByteArray &from, const QByteArray &to, bool batchMode, const bool showWarnings, bool *alsoAsKra); - KisImportExportFilter::ConversionStatus doImport(const QString &location, QSharedPointer filter); + KisImportExportErrorCode doImport(const QString &location, QSharedPointer filter); - KisImportExportFilter::ConversionStatus doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra); - KisImportExportFilter::ConversionStatus doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration); + KisImportExportErrorCode doExport(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration, bool alsoAsKra); + KisImportExportErrorCode doExportImpl(const QString &location, QSharedPointer filter, KisPropertiesConfigurationSP exportConfiguration); // Private API KisImportExportManager(const KisImportExportManager& rhs); KisImportExportManager &operator=(const KisImportExportManager& rhs); KisDocument *m_document; /// A static cache for the availability checks of filters static QStringList m_importMimeTypes; static QStringList m_exportMimeTypes; class Private; Private * const d; }; #endif // __KO_FILTER_MANAGER_H__ diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp index a0e42f34f2..a4ecc63e48 100644 --- a/libs/ui/KisMainWindow.cpp +++ b/libs/ui/KisMainWindow.cpp @@ -1,2652 +1,2652 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2000-2006 David Faure Copyright (C) 2007, 2009 Thomas zander Copyright (C) 2010 Benjamin Port 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 "KisMainWindow.h" #include // qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_selection_manager.h" #include "kis_icon_utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KoDockFactoryBase.h" #include "KoDocumentInfoDlg.h" #include "KoDocumentInfo.h" #include "KoFileDialog.h" #include #include #include #include #include #include "KoToolDocker.h" #include "KoToolBoxDocker_p.h" #include #include #include #include #include #include #include #include "dialogs/kis_about_application.h" #include "dialogs/kis_delayed_save_dialog.h" #include "dialogs/kis_dlg_preferences.h" #include "kis_action.h" #include "kis_action_manager.h" #include "KisApplication.h" #include "kis_canvas2.h" #include "kis_canvas_controller.h" #include "kis_canvas_resource_provider.h" #include "kis_clipboard.h" #include "kis_config.h" #include "kis_config_notifier.h" #include "kis_custom_image_widget.h" #include #include "kis_group_layer.h" #include "kis_image_from_clipboard_widget.h" #include "kis_image.h" #include #include "KisImportExportManager.h" #include "kis_mainwindow_observer.h" #include "kis_memory_statistics_server.h" #include "kis_node.h" #include "KisOpenPane.h" #include "kis_paintop_box.h" #include "KisPart.h" #include "KisPrintJob.h" #include "KisResourceServerProvider.h" #include "kis_signal_compressor_with_param.h" #include "kis_statusbar.h" #include "KisView.h" #include "KisViewManager.h" #include "thememanager.h" #include "kis_animation_importer.h" #include "dialogs/kis_dlg_import_image_sequence.h" #include #include "KisWindowLayoutManager.h" #include #include "KisWelcomePageWidget.h" #include #include #include class ToolDockerFactory : public KoDockFactoryBase { public: ToolDockerFactory() : KoDockFactoryBase() { } QString id() const override { return "sharedtooldocker"; } QDockWidget* createDockWidget() override { KoToolDocker* dockWidget = new KoToolDocker(); return dockWidget; } DockPosition defaultDockPosition() const override { return DockRight; } }; class Q_DECL_HIDDEN KisMainWindow::Private { public: Private(KisMainWindow *parent, QUuid id) : q(parent) , id(id) , dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent)) , windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent)) , documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent)) , workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent)) , welcomePage(new KisWelcomePageWidget(parent)) , widgetStack(new QStackedWidget(parent)) , mdiArea(new QMdiArea(parent)) , windowMapper(new QSignalMapper(parent)) , documentMapper(new QSignalMapper(parent)) { if (id.isNull()) this->id = QUuid::createUuid(); widgetStack->addWidget(welcomePage); widgetStack->addWidget(mdiArea); mdiArea->setTabsMovable(true); mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder); } ~Private() { qDeleteAll(toolbarList); } KisMainWindow *q {0}; QUuid id; KisViewManager *viewManager {0}; QPointer activeView; QList toolbarList; bool firstTime {true}; bool windowSizeDirty {false}; bool readOnly {false}; KisAction *showDocumentInfo {0}; KisAction *saveAction {0}; KisAction *saveActionAs {0}; // KisAction *printAction; // KisAction *printActionPreview; // KisAction *exportPdf {0}; KisAction *importAnimation {0}; KisAction *closeAll {0}; // KisAction *reloadFile; KisAction *importFile {0}; KisAction *exportFile {0}; KisAction *undo {0}; KisAction *redo {0}; KisAction *newWindow {0}; KisAction *close {0}; KisAction *mdiCascade {0}; KisAction *mdiTile {0}; KisAction *mdiNextWindow {0}; KisAction *mdiPreviousWindow {0}; KisAction *toggleDockers {0}; KisAction *toggleDockerTitleBars {0}; KisAction *fullScreenMode {0}; KisAction *showSessionManager {0}; KisAction *expandingSpacers[2]; KActionMenu *dockWidgetMenu; KActionMenu *windowMenu; KActionMenu *documentMenu; KActionMenu *workspaceMenu; KHelpMenu *helpMenu {0}; KRecentFilesAction *recentFiles {0}; KoResourceModel *workspacemodel {0}; QScopedPointer undoActionsUpdateManager; QString lastExportLocation; QMap dockWidgetsMap; QByteArray dockerStateBeforeHiding; KoToolDocker *toolOptionsDocker {0}; QCloseEvent *deferredClosingEvent {0}; Digikam::ThemeManager *themeManager {0}; KisWelcomePageWidget *welcomePage {0}; QStackedWidget *widgetStack {0}; QMdiArea *mdiArea; QMdiSubWindow *activeSubWindow {0}; QSignalMapper *windowMapper; QSignalMapper *documentMapper; QByteArray lastExportedFormat; QScopedPointer > tabSwitchCompressor; QMutex savingEntryMutex; KConfigGroup windowStateConfig; QUuid workspaceBorrowedBy; KisSignalAutoConnectionsStore screenConnectionsStore; KisActionManager * actionManager() { return viewManager->actionManager(); } QTabBar* findTabBarHACK() { QObjectList objects = mdiArea->children(); Q_FOREACH (QObject *object, objects) { QTabBar *bar = qobject_cast(object); if (bar) { return bar; } } return 0; } }; KisMainWindow::KisMainWindow(QUuid uuid) : KXmlGuiWindow() , d(new Private(this, uuid)) { auto rserver = KisResourceServerProvider::instance()->workspaceServer(); QSharedPointer adapter(new KoResourceServerAdapter(rserver)); d->workspacemodel = new KoResourceModel(adapter, this); connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); }); d->viewManager = new KisViewManager(this, actionCollection()); KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this); d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow"); setStandardToolBarMenuEnabled(true); setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North); setDockNestingEnabled(true); qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events #ifdef Q_OS_MACOS setUnifiedTitleAndToolBarOnMac(true); #endif connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts())); connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons())); connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu())); connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu())); connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged())); actionCollection()->addAssociatedWidget(this); KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, false); // Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created. KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true); KoToolBoxFactory toolBoxFactory; QDockWidget *toolbox = createDockWidget(&toolBoxFactory); toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable); KisConfig cfg(true); if (cfg.toolOptionsInDocker()) { ToolDockerFactory toolDockerFactory; d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory)); d->toolOptionsDocker->toggleViewAction()->setEnabled(true); } QMap dockwidgetActions; dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction(); Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) { KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker); QDockWidget *dw = createDockWidget(factory); dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction(); } if (d->toolOptionsDocker) { dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction(); } connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*,QList >)), this, SLOT(newOptionWidgets(KoCanvasController*,QList >))); Q_FOREACH (QString title, dockwidgetActions.keys()) { d->dockWidgetMenu->addAction(dockwidgetActions[title]); } Q_FOREACH (QDockWidget *wdg, dockWidgets()) { if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) { wdg->setVisible(true); } } Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) { observer->setObservedCanvas(0); KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer); if (mainwindowObserver) { mainwindowObserver->setViewManager(d->viewManager); } } // Load all the actions from the tool plugins Q_FOREACH(KoToolFactoryBase *toolFactory, KoToolRegistry::instance()->values()) { toolFactory->createActions(actionCollection()); } d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); d->mdiArea->setTabPosition(QTabWidget::North); d->mdiArea->setTabsClosable(true); // Tab close button override // Windows just has a black X, and Ubuntu has a dark x that is hard to read // just switch this icon out for all OSs so it is easier to see d->mdiArea->setStyleSheet("QTabBar::close-button { image: url(:/pics/broken-preset.png) }"); setCentralWidget(d->widgetStack); d->widgetStack->setCurrentIndex(0); connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated())); connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*))); createActions(); // the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist d->welcomePage->setMainWindow(this); setAutoSaveSettings(d->windowStateConfig, false); subWindowActivated(); updateWindowMenu(); if (isHelpMenuEnabled() && !d->helpMenu) { // workaround for KHelpMenu (or rather KAboutData::applicationData()) internally // not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard // not having the app version preset // fixed hopefully in KF5 5.22.0, patch pending QGuiApplication *app = qApp; KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion()); aboutData.setOrganizationDomain(app->organizationDomain().toUtf8()); d->helpMenu = new KHelpMenu(this, aboutData, false); // workaround-less version: // d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false); // The difference between using KActionCollection->addAction() is that // these actions do not get tied to the MainWindow. What does this all do? KActionCollection *actions = d->viewManager->actionCollection(); QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents); QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis); QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug); QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage); QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp); QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE); if (helpContentsAction) { actions->addAction(helpContentsAction->objectName(), helpContentsAction); } if (whatsThisAction) { actions->addAction(whatsThisAction->objectName(), whatsThisAction); } if (reportBugAction) { actions->addAction(reportBugAction->objectName(), reportBugAction); } if (switchLanguageAction) { actions->addAction(switchLanguageAction->objectName(), switchLanguageAction); } if (aboutAppAction) { actions->addAction(aboutAppAction->objectName(), aboutAppAction); } if (aboutKdeAction) { actions->addAction(aboutKdeAction->objectName(), aboutKdeAction); } connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication())); } // KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves QAction *helpAction = actionCollection()->action("help_contents"); helpAction->disconnect(); connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual())); #if 0 //check for colliding shortcuts QSet existingShortcuts; Q_FOREACH (QAction* action, actionCollection()->actions()) { if(action->shortcut() == QKeySequence(0)) { continue; } dbgKrita << "shortcut " << action->text() << " " << action->shortcut(); Q_ASSERT(!existingShortcuts.contains(action->shortcut())); existingShortcuts.insert(action->shortcut()); } #endif configChanged(); // If we have customized the toolbars, load that first setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui")); setXMLFile(":/kxmlgui5/krita4.xmlgui"); guiFactory()->addClient(this); // Create and plug toolbar list for Settings menu QList toolbarList; Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) { KToolBar * toolBar = ::qobject_cast(it); toolBar->setMovable(KisConfig(true).readEntry("LockAllDockerPanels", false)); if (toolBar) { if (toolBar->objectName() == "BrushesAndStuff") { toolBar->setEnabled(false); } KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this); actionCollection()->addAction(toolBar->objectName().toUtf8(), act); act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle()))); connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool))); act->setChecked(!toolBar->isHidden()); toolbarList.append(act); } else { warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!"; } } KToolBar::setToolBarsLocked(KisConfig(true).readEntry("LockAllDockerPanels", false)); plugActionList("toolbarlist", toolbarList); d->toolbarList = toolbarList; applyToolBarLayout(); d->viewManager->updateGUI(); d->viewManager->updateIcons(); QTimer::singleShot(1000, this, SLOT(checkSanity())); { using namespace std::placeholders; // For _1 placeholder std::function callback( std::bind(&KisMainWindow::switchTab, this, _1)); d->tabSwitchCompressor.reset( new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE)); } if (cfg.readEntry("CanvasOnlyActive", false)) { QString currentWorkspace = cfg.readEntry("CurrentWorkspace", "Default"); KoResourceServer * rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = rserver->resourceByName(currentWorkspace); if (workspace) { restoreWorkspace(workspace); } cfg.writeEntry("CanvasOnlyActive", false); menuBar()->setVisible(true); } this->winId(); // Ensures the native window has been created. QWindow *window = this->windowHandle(); connect(window, SIGNAL(screenChanged(QScreen *)), this, SLOT(windowScreenChanged(QScreen *))); } KisMainWindow::~KisMainWindow() { // Q_FOREACH (QAction *ac, actionCollection()->actions()) { // QAction *action = qobject_cast(ac); // if (action) { // qDebug() << "", "").replace("", "") // << "\n\ticonText=" << action->iconText().replace("&", "&") // << "\n\tshortcut=" << action->shortcut().toString() // << "\n\tisCheckable=" << QString((action->isChecked() ? "true" : "false")) // << "\n\tstatusTip=" << action->statusTip() // << "\n/>\n" ; // } // else { // dbgKrita << "Got a non-qaction:" << ac->objectName(); // } // } // The doc and view might still exist (this is the case when closing the window) KisPart::instance()->removeMainWindow(this); delete d->viewManager; delete d; } QUuid KisMainWindow::id() const { return d->id; } void KisMainWindow::addView(KisView *view) { if (d->activeView == view) return; if (d->activeView) { d->activeView->disconnect(this); } // register the newly created view in the input manager viewManager()->inputManager()->addTrackedCanvas(view->canvasBase()); showView(view); updateCaption(); emit restoringDone(); if (d->activeView) { connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified())); connect(d->viewManager->statusBar(), SIGNAL(memoryStatusUpdated()), this, SLOT(updateCaption())); } } void KisMainWindow::notifyChildViewDestroyed(KisView *view) { /** * If we are the last view of the window, Qt will not activate another tab * before destroying tab/window. In ths case we should clear oll the dangling * pointers manually by setting the current view to null */ viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase()); if (view->canvasBase() == viewManager()->canvasBase()) { viewManager()->setCurrentView(0); } } void KisMainWindow::showView(KisView *imageView) { if (imageView && activeView() != imageView) { // XXX: find a better way to initialize this! imageView->setViewManager(d->viewManager); imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager()); imageView->slotLoadingFinished(); QMdiSubWindow *subwin = d->mdiArea->addSubWindow(imageView); imageView->setSubWindow(subwin); subwin->setAttribute(Qt::WA_DeleteOnClose, true); connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu())); KisConfig cfg(true); subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setWindowIcon(qApp->windowIcon()); /** * Hack alert! * * Here we explicitly request KoToolManager to emit all the tool * activation signals, to reinitialize the tool options docker. * * That is needed due to a design flaw we have in the * initialization procedure. The tool in the KoToolManager is * initialized in KisView::setViewManager() calls, which * happens early enough. During this call the tool manager * requests KoCanvasControllerWidget to emit the signal to * update the widgets in the tool docker. *But* at that moment * of time the view is not yet connected to the main window, * because it happens in KisViewManager::setCurrentView a bit * later. This fact makes the widgets updating signals be lost * and never reach the tool docker. * * So here we just explicitly call the tool activation stub. */ KoToolManager::instance()->initializeCurrentToolForCanvas(); if (d->mdiArea->subWindowList().size() == 1) { imageView->showMaximized(); } else { imageView->show(); } // No, no, no: do not try to call this _before_ the show() has // been called on the view; only when that has happened is the // opengl context active, and very bad things happen if we tell // the dockers to update themselves with a view if the opengl // context is not active. setActiveView(imageView); updateWindowMenu(); updateCaption(); } } void KisMainWindow::slotPreferences() { if (KisDlgPreferences::editPreferences()) { KisConfigNotifier::instance()->notifyConfigChanged(); KisConfigNotifier::instance()->notifyPixelGridModeChanged(); KisImageConfigNotifier::instance()->notifyConfigChanged(); // XXX: should this be changed for the views in other windows as well? Q_FOREACH (QPointer koview, KisPart::instance()->views()) { KisViewManager *view = qobject_cast(koview); if (view) { // Update the settings for all nodes -- they don't query // KisConfig directly because they need the settings during // compositing, and they don't connect to the config notifier // because nodes are not QObjects (because only one base class // can be a QObject). KisNode* node = dynamic_cast(view->image()->rootLayer().data()); node->updateSettings(); } } updateWindowMenu(); d->viewManager->showHideScrollbars(); } } void KisMainWindow::slotThemeChanged() { // save theme changes instantly KConfigGroup group( KSharedConfig::openConfig(), "theme"); group.writeEntry("Theme", d->themeManager->currentThemeName()); // reload action icons! Q_FOREACH (QAction *action, actionCollection()->actions()) { KisIconUtils::updateIcon(action); } if (d->mdiArea) { d->mdiArea->setPalette(qApp->palette()); for (int i=0; imdiArea->subWindowList().size(); i++) { QMdiSubWindow *window = d->mdiArea->subWindowList().at(i); if (window) { window->setPalette(qApp->palette()); KisView *view = qobject_cast(window->widget()); if (view) { view->slotThemeChanged(qApp->palette()); } } } } emit themeChanged(); } void KisMainWindow::updateReloadFileAction(KisDocument *doc) { Q_UNUSED(doc); // d->reloadFile->setEnabled(doc && !doc->url().isEmpty()); } void KisMainWindow::setReadWrite(bool readwrite) { d->saveAction->setEnabled(readwrite); d->importFile->setEnabled(readwrite); d->readOnly = !readwrite; updateCaption(); } void KisMainWindow::addRecentURL(const QUrl &url) { // Add entry to recent documents list // (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.) if (!url.isEmpty()) { bool ok = true; if (url.isLocalFile()) { QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile(); const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp"); for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the tmp resource } } const QStringList templateDirs = KoResourcePaths::findDirs("templates"); for (QStringList::ConstIterator it = templateDirs.begin() ; ok && it != templateDirs.end() ; ++it) { if (path.contains(*it)) { ok = false; // it's in the templates directory. break; } } } if (ok) { d->recentFiles->addUrl(url); } saveRecentFiles(); } } void KisMainWindow::saveRecentFiles() { // Save list of recent files KSharedConfigPtr config = KSharedConfig::openConfig(); d->recentFiles->saveEntries(config->group("RecentFiles")); config->sync(); // Tell all windows to reload their list, after saving // Doesn't work multi-process, but it's a start Q_FOREACH (KisMainWindow *mw, KisPart::instance()->mainWindows()) { if (mw != this) { mw->reloadRecentFileList(); } } } QList KisMainWindow::recentFilesUrls() { return d->recentFiles->urls(); } void KisMainWindow::clearRecentFiles() { d->recentFiles->clear(); } void KisMainWindow::reloadRecentFileList() { d->recentFiles->loadEntries(KSharedConfig::openConfig()->group("RecentFiles")); } void KisMainWindow::updateCaption() { if (!d->mdiArea->activeSubWindow()) { updateCaption(QString(), false); } else if (d->activeView && d->activeView->document() && d->activeView->image()){ KisDocument *doc = d->activeView->document(); QString caption(doc->caption()); if (d->readOnly) { caption += " [" + i18n("Write Protected") + "] "; } if (doc->isRecovered()) { caption += " [" + i18n("Recovered") + "] "; } // show the file size for the document KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0); if (m_fileSizeStats.imageSize) { caption += QString(" (").append( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")"); } updateCaption(caption, doc->isModified()); if (!doc->url().fileName().isEmpty()) { d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName())); } else { d->saveAction->setToolTip(i18n("Save")); } } } void KisMainWindow::updateCaption(const QString &caption, bool modified) { QString versionString = KritaVersionWrapper::versionString(true); QString title = caption; if (!title.contains(QStringLiteral("[*]"))) { // append the placeholder so that the modified mechanism works title.append(QStringLiteral(" [*]")); } if (d->mdiArea->activeSubWindow()) { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) d->mdiArea->activeSubWindow()->setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else d->mdiArea->activeSubWindow()->setWindowTitle(title); #endif d->mdiArea->activeSubWindow()->setWindowModified(modified); } else { #if defined(KRITA_ALPHA) || defined (KRITA_BETA) || defined (KRITA_RC) setWindowTitle(QString("%1: %2").arg(versionString).arg(title)); #else setWindowTitle(title); #endif } setWindowModified(modified); } KisView *KisMainWindow::activeView() const { if (d->activeView) { return d->activeView; } return 0; } bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags) { if (!QFile(url.toLocalFile()).exists()) { if (!(flags & BatchMode)) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url())); } d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list saveRecentFiles(); return false; } return openDocumentInternal(url, flags); } bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags) { if (!url.isLocalFile()) { qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url; return false; } KisDocument *newdoc = KisPart::instance()->createDocument(); if (flags & BatchMode) { newdoc->setFileBatchMode(true); } d->firstTime = true; connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); connect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); KisDocument::OpenFlags openFlags = KisDocument::None; if (flags & RecoveryFile) { openFlags |= KisDocument::RecoveryFile; } bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url); if (!openRet) { delete newdoc; return false; } KisPart::instance()->addDocument(newdoc); updateReloadFileAction(newdoc); if (!QFileInfo(url.toLocalFile()).isWritable()) { setReadWrite(false); } return true; } void KisMainWindow::showDocument(KisDocument *document) { Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) { KisView *view = qobject_cast(subwindow->widget()); KIS_SAFE_ASSERT_RECOVER_NOOP(view); if (view) { if (view->document() == document) { setActiveSubWindow(subwindow); return; } } } addViewAndNotifyLoadingCompleted(document); } KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document) { showWelcomeScreen(false); // see workaround in function header KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this); addView(view); emit guiLoadingFinished(); return view; } QStringList KisMainWindow::showOpenFileDialog(bool isImporting) { KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument"); dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)); dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images")); return dialog.filenames(); } // Separate from openDocument to handle async loading (remote URLs) void KisMainWindow::slotLoadCompleted() { KisDocument *newdoc = qobject_cast(sender()); if (newdoc && newdoc->image()) { addViewAndNotifyLoadingCompleted(newdoc); disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(newdoc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); emit loadCompleted(); } } void KisMainWindow::slotLoadCanceled(const QString & errMsg) { dbgUI << "KisMainWindow::slotLoadCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); // ... can't delete the document, it's the one who emitted the signal... KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotLoadCanceled(QString))); } void KisMainWindow::slotSaveCanceled(const QString &errMsg) { dbgUI << "KisMainWindow::slotSaveCanceled"; if (!errMsg.isEmpty()) // empty when canceled by user QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg); slotSaveCompleted(); } void KisMainWindow::slotSaveCompleted() { dbgUI << "KisMainWindow::slotSaveCompleted"; KisDocument* doc = qobject_cast(sender()); Q_ASSERT(doc); disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); disconnect(doc, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); if (d->deferredClosingEvent) { KXmlGuiWindow::closeEvent(d->deferredClosingEvent); } } bool KisMainWindow::hackIsSaving() const { StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); return !l.owns_lock(); } bool KisMainWindow::installBundle(const QString &fileName) const { QFileInfo from(fileName); QFileInfo to(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); if (to.exists()) { QFile::remove(to.canonicalFilePath()); } return QFile::copy(fileName, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName()); } bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting) { if (!document) { return true; } /** * Make sure that we cannot enter this method twice! * * The lower level functions may call processEvents() so * double-entry is quite possible to achieve. Here we try to lock * the mutex, and if it is failed, just cancel saving. */ StdLockableWrapper wrapper(&d->savingEntryMutex); std::unique_lock> l(wrapper, std::try_to_lock); if (!l.owns_lock()) return false; // no busy wait for saving because it is dangerous! KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this); dlg.blockIfImageIsBusy(); if (dlg.result() == KisDelayedSaveDialog::Rejected) { return false; } else if (dlg.result() == KisDelayedSaveDialog::Ignored) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("You are saving a file while the image is " "still rendering. The saved file may be " "incomplete or corrupted.\n\n" "Please select a location where the original " "file will not be overridden!")); saveas = true; } if (document->isRecovered()) { saveas = true; } if (document->url().isEmpty()) { saveas = true; } connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted())); connect(document, SIGNAL(canceled(QString)), this, SLOT(slotSaveCanceled(QString))); QByteArray nativeFormat = document->nativeFormatMimeType(); QByteArray oldMimeFormat = document->mimeType(); QUrl suggestedURL = document->url(); QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export); if (!mimeFilter.contains(oldMimeFormat)) { dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat; // --- don't setOutputMimeType in case the user cancels the Save As // dialog and then tries to just plain Save --- // suggest a different filename extension (yes, we fortunately don't all live in a world of magic :)) QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).baseName(); if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first(); suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename); suggestedURL.setPath(suggestedURL.path() + suggestedFilename); } // force the user to choose outputMimeType saveas = true; } bool ret = false; if (document->url().isEmpty() || isExporting || saveas) { // if you're just File/Save As'ing to change filter options you // don't want to be reminded about overwriting files etc. bool justChangingFilterOptions = false; KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs"); dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As")); //qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType()); if (isExporting && !d->lastExportLocation.isEmpty()) { // Use the location where we last exported to, if it's set, as the opening location for the file dialog QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath(); // If the document doesn't have a filename yet, use the title QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).baseName(); // Use the last mimetype we exported to by default QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat; QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,"); // Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true); dialog.setMimeTypeFilters(mimeFilter, proposedMimeType); } else { // Get the last used location for saving KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString proposedPath = group.readEntry("SaveAs", ""); // if that is empty, get the last used location for loading if (proposedPath.isEmpty()) { proposedPath = group.readEntry("OpenDocument", ""); } // If that is empty, too, use the Pictures location. if (proposedPath.isEmpty()) { proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); } // But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise // open the location where the document currently is. dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true); // If exporting, default to all supported file types if user is exporting QByteArray default_mime_type = ""; if (!isExporting) { // otherwise use the document's mimetype, or if that is empty, kra, which is the savest. default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType(); } dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type)); } QUrl newURL = QUrl::fromUserInput(dialog.filename()); if (newURL.isLocalFile()) { QString fn = newURL.toLocalFile(); if (QFileInfo(fn).completeSuffix().isEmpty()) { fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first()); newURL = QUrl::fromLocalFile(fn); } } if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) { QString fn = newURL.toLocalFile(); QFileInfo info(fn); document->documentInfo()->setAboutInfo("title", info.baseName()); } QByteArray outputFormat = nativeFormat; QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), false); outputFormat = outputFormatString.toLatin1(); if (!isExporting) { justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType()); } else { QString path = QFileInfo(d->lastExportLocation).absolutePath(); QString filename = QFileInfo(document->url().toLocalFile()).baseName(); justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path) && (QFileInfo(newURL.toLocalFile()).baseName() == filename) && (outputFormat == d->lastExportedFormat); } bool bOk = true; if (newURL.isEmpty()) { bOk = false; } if (bOk) { bool wantToSave = true; // don't change this line unless you know what you're doing :) if (!justChangingFilterOptions) { if (!document->isNativeFormat(outputFormat)) wantToSave = true; } if (wantToSave) { if (!isExporting) { // Save As ret = document->saveAs(newURL, outputFormat, true); if (ret) { dbgUI << "Successful Save As!"; KisPart::instance()->addRecentURLToAllMainWindows(newURL); setReadWrite(true); } else { dbgUI << "Failed Save As!"; + } } else { // Export ret = document->exportDocument(newURL, outputFormat); if (ret) { d->lastExportLocation = newURL.toLocalFile(); d->lastExportedFormat = outputFormat; } } } // if (wantToSave) { else ret = false; } // if (bOk) { else ret = false; } else { // saving // We cannot "export" into the currently // opened document. We are not Gimp. KIS_ASSERT_RECOVER_NOOP(!isExporting); // be sure document has the correct outputMimeType! if (document->isModified()) { ret = document->save(true, 0); } if (!ret) { dbgUI << "Failed Save!"; } } updateReloadFileAction(document); updateCaption(); return ret; } void KisMainWindow::undo() { if (activeView()) { activeView()->document()->undoStack()->undo(); } } void KisMainWindow::redo() { if (activeView()) { activeView()->document()->undoStack()->redo(); } } void KisMainWindow::closeEvent(QCloseEvent *e) { if (!KisPart::instance()->closingSession()) { QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only"); if ((action) && (action->isChecked())) { action->setChecked(false); } // Save session when last window is closed if (KisPart::instance()->mainwindowCount() == 1) { bool closeAllowed = KisPart::instance()->closeSession(); if (!closeAllowed) { e->setAccepted(false); return; } } } d->mdiArea->closeAllSubWindows(); QList childrenList = d->mdiArea->subWindowList(); if (childrenList.isEmpty()) { d->deferredClosingEvent = e; saveWindowState(true); } else { e->setAccepted(false); } } void KisMainWindow::saveWindowSettings() { KSharedConfigPtr config = KSharedConfig::openConfig(); if (d->windowSizeDirty ) { dbgUI << "KisMainWindow::saveWindowSettings"; KConfigGroup group = d->windowStateConfig; KWindowConfig::saveWindowSize(windowHandle(), group); config->sync(); d->windowSizeDirty = false; } if (!d->activeView || d->activeView->document()) { // Save toolbar position into the config file of the app, under the doc's component name KConfigGroup group = d->windowStateConfig; saveMainWindowSettings(group); // Save collapsible state of dock widgets for (QMap::const_iterator i = d->dockWidgetsMap.constBegin(); i != d->dockWidgetsMap.constEnd(); ++i) { if (i.value()->widget()) { KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key()); dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden()); dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool()); dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value())); dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x()); dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y()); dockGroup.writeEntry("width", (int) i.value()->widget()->width()); dockGroup.writeEntry("height", (int) i.value()->widget()->height()); } } } KSharedConfig::openConfig()->sync(); resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down } void KisMainWindow::resizeEvent(QResizeEvent * e) { d->windowSizeDirty = true; KXmlGuiWindow::resizeEvent(e); } void KisMainWindow::setActiveView(KisView* view) { d->activeView = view; updateCaption(); if (d->undoActionsUpdateManager) { d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0); } d->viewManager->setCurrentView(view); KisWindowLayoutManager::instance()->activeDocumentChanged(view->document()); } void KisMainWindow::dragMove(QDragMoveEvent * event) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) { qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!"; } if (tabBar && tabBar->isVisible()) { QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos())); if (tabBar->rect().contains(pos)) { const int tabIndex = tabBar->tabAt(pos); if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) { d->tabSwitchCompressor->start(tabIndex); } } else if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } } void KisMainWindow::dragLeave() { if (d->tabSwitchCompressor->isActive()) { d->tabSwitchCompressor->stop(); } } void KisMainWindow::switchTab(int index) { QTabBar *tabBar = d->findTabBarHACK(); if (!tabBar) return; tabBar->setCurrentIndex(index); } void KisMainWindow::showWelcomeScreen(bool show) { d->widgetStack->setCurrentIndex(!show); } void KisMainWindow::slotFileNew() { const QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import); KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/")); startupWidget->setWindowModality(Qt::WindowModal); startupWidget->setWindowTitle(i18n("Create new document")); KisConfig cfg(true); int w = cfg.defImageWidth(); int h = cfg.defImageHeight(); const double resolution = cfg.defImageResolution(); const QString colorModel = cfg.defColorModel(); const QString colorDepth = cfg.defaultColorDepth(); const QString colorProfile = cfg.defColorProfile(); CustomDocumentWidgetItem item; item.widget = new KisCustomImageWidget(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.icon = "document-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); QSize sz = KisClipboard::instance()->clipSize(); if (sz.isValid() && sz.width() != 0 && sz.height() != 0) { w = sz.width(); h = sz.height(); } item.widget = new KisImageFromClipboard(startupWidget, w, h, resolution, colorModel, colorDepth, colorProfile, i18n("Unnamed")); item.title = i18n("Create from Clipboard"); item.icon = "tab-new"; startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon); // calls deleteLater connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*))); // calls deleteLater connect(startupWidget, SIGNAL(openTemplate(QUrl)), KisPart::instance(), SLOT(openTemplate(QUrl))); startupWidget->exec(); // Cancel calls deleteLater... } void KisMainWindow::slotImportFile() { dbgUI << "slotImportFile()"; slotFileOpen(true); } void KisMainWindow::slotFileOpen(bool isImporting) { QStringList urls = showOpenFileDialog(isImporting); if (urls.isEmpty()) return; Q_FOREACH (const QString& url, urls) { if (!url.isEmpty()) { OpenFlags flags = isImporting ? Import : None; bool res = openDocument(QUrl::fromLocalFile(url), flags); if (!res) { warnKrita << "Loading" << url << "failed"; } } } } void KisMainWindow::slotFileOpenRecent(const QUrl &url) { (void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None); } void KisMainWindow::slotFileSave() { if (saveDocument(d->activeView->document(), false, false)) { emit documentSaved(); } } void KisMainWindow::slotFileSaveAs() { if (saveDocument(d->activeView->document(), true, false)) { emit documentSaved(); } } void KisMainWindow::slotExportFile() { if (saveDocument(d->activeView->document(), true, true)) { emit documentSaved(); } } void KisMainWindow::slotShowSessionManager() { KisPart::instance()->showSessionManager(); } KoCanvasResourceProvider *KisMainWindow::resourceManager() const { return d->viewManager->canvasResourceProvider()->resourceManager(); } int KisMainWindow::viewCount() const { return d->mdiArea->subWindowList().size(); } const KConfigGroup &KisMainWindow::windowStateConfig() const { return d->windowStateConfig; } void KisMainWindow::saveWindowState(bool restoreNormalState) { if (restoreNormalState) { QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only"); if (showCanvasOnly && showCanvasOnly->isChecked()) { showCanvasOnly->setChecked(false); } d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64()); d->windowStateConfig.writeEntry("State", saveState().toBase64()); if (!d->dockerStateBeforeHiding.isEmpty()) { restoreState(d->dockerStateBeforeHiding); } statusBar()->setVisible(true); menuBar()->setVisible(true); saveWindowSettings(); } else { saveMainWindowSettings(d->windowStateConfig); } } bool KisMainWindow::restoreWorkspaceState(const QByteArray &state) { QByteArray oldState = saveState(); // needed because otherwise the layout isn't correctly restored in some situations Q_FOREACH (QDockWidget *dock, dockWidgets()) { dock->toggleViewAction()->setEnabled(true); dock->hide(); } bool success = KXmlGuiWindow::restoreState(state); if (!success) { KXmlGuiWindow::restoreState(oldState); return false; } return success; } bool KisMainWindow::restoreWorkspace(KisWorkspaceResource *workspace) { bool success = restoreWorkspaceState(workspace->dockerState()); if (activeKisView()) { activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace); } return success; } QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other) { QByteArray currentWorkspace = saveState(); if (!d->workspaceBorrowedBy.isNull()) { if (other->id() == d->workspaceBorrowedBy) { // We're swapping our original workspace back d->workspaceBorrowedBy = QUuid(); return currentWorkspace; } else { // Get our original workspace back before swapping with a third window KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy); if (borrower) { QByteArray originalLayout = borrower->borrowWorkspace(this); borrower->restoreWorkspaceState(currentWorkspace); d->workspaceBorrowedBy = other->id(); return originalLayout; } } } d->workspaceBorrowedBy = other->id(); return currentWorkspace; } void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b) { QByteArray workspaceA = a->borrowWorkspace(b); QByteArray workspaceB = b->borrowWorkspace(a); a->restoreWorkspaceState(workspaceB); b->restoreWorkspaceState(workspaceA); } KisViewManager *KisMainWindow::viewManager() const { return d->viewManager; } void KisMainWindow::slotDocumentInfo() { if (!d->activeView->document()) return; KoDocumentInfo *docInfo = d->activeView->document()->documentInfo(); if (!docInfo) return; KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo); if (dlg->exec()) { if (dlg->isDocumentSaved()) { d->activeView->document()->setModified(false); } else { d->activeView->document()->setModified(true); } d->activeView->document()->setTitleModified(); } delete dlg; } bool KisMainWindow::slotFileCloseAll() { Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { if (subwin) { if(!subwin->close()) return false; } } updateCaption(); return true; } void KisMainWindow::slotFileQuit() { KisPart::instance()->closeSession(); } void KisMainWindow::slotFilePrint() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; applyDefaultSettings(printJob->printer()); QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this ); if (printDialog && printDialog->exec() == QDialog::Accepted) { printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point); printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()), activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())), QPrinter::Inch); printJob->startPrinting(KisPrintJob::DeleteWhenDone); } else { delete printJob; } delete printDialog; } void KisMainWindow::slotFilePrintPreview() { if (!activeView()) return; KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return; /* Sets the startPrinting() slot to be blocking. The Qt print-preview dialog requires the printing to be completely blocking and only return when the full document has been printed. By default the KisPrintingDialog is non-blocking and multithreading, setting blocking to true will allow it to be used in the preview dialog */ printJob->setProperty("blocking", true); QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this); printJob->setParent(preview); // will take care of deleting the job connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting())); preview->exec(); delete preview; } KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName) { if (!activeView()) return 0; if (!activeView()->document()) return 0; KoPageLayout pageLayout; pageLayout.width = 0; pageLayout.height = 0; pageLayout.topMargin = 0; pageLayout.bottomMargin = 0; pageLayout.leftMargin = 0; pageLayout.rightMargin = 0; if (pdfFileName.isEmpty()) { KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs"); QString defaultDir = group.readEntry("SavePdfDialog"); if (defaultDir.isEmpty()) defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); QUrl startUrl = QUrl::fromLocalFile(defaultDir); KisDocument* pDoc = d->activeView->document(); /** if document has a file name, take file name and replace extension with .pdf */ if (pDoc && pDoc->url().isValid()) { startUrl = pDoc->url(); QString fileName = startUrl.toLocalFile(); fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" ); startUrl = startUrl.adjusted(QUrl::RemoveFilename); startUrl.setPath(startUrl.path() + fileName ); } QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout)); layoutDlg->setWindowModality(Qt::WindowModal); if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) { delete layoutDlg; return 0; } pageLayout = layoutDlg->pageLayout(); delete layoutDlg; KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument"); dialog.setCaption(i18n("Export as PDF")); dialog.setDefaultDir(startUrl.toLocalFile()); dialog.setMimeTypeFilters(QStringList() << "application/pdf"); QUrl url = QUrl::fromUserInput(dialog.filename()); pdfFileName = url.toLocalFile(); if (pdfFileName.isEmpty()) return 0; } KisPrintJob *printJob = activeView()->createPrintJob(); if (printJob == 0) return 0; if (isHidden()) { printJob->setProperty("noprogressdialog", true); } applyDefaultSettings(printJob->printer()); // TODO for remote files we have to first save locally and then upload. printJob->printer().setOutputFileName(pdfFileName); printJob->printer().setDocName(pdfFileName); printJob->printer().setColorMode(QPrinter::Color); if (pageLayout.format == KoPageFormat::CustomSize) { printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter); } else { printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format)); } printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter); switch (pageLayout.orientation) { case KoPageFormat::Portrait: printJob->printer().setOrientation(QPrinter::Portrait); break; case KoPageFormat::Landscape: printJob->printer().setOrientation(QPrinter::Landscape); break; } //before printing check if the printer can handle printing if (!printJob->canPrint()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file")); } printJob->startPrinting(KisPrintJob::DeleteWhenDone); return printJob; } void KisMainWindow::importAnimation() { if (!activeView()) return; KisDocument *document = activeView()->document(); if (!document) return; KisDlgImportImageSequence dlg(this, document); if (dlg.exec() == QDialog::Accepted) { QStringList files = dlg.files(); int firstFrame = dlg.firstFrame(); int step = dlg.step(); KoUpdaterPtr updater = !document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0; KisAnimationImporter importer(document->image(), updater); - KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step); - - if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) { - QString msg = KisImportExportFilter::conversionStatusString(status); + KisImportExportErrorCode status = importer.import(files, firstFrame, step); + if (!status.isOk() && !status.isInternalError()) { + QString msg = status.errorMessage(); if (!msg.isEmpty()) QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg)); } activeView()->canvasBase()->refetchDataFromImage(); } } void KisMainWindow::slotConfigureToolbars() { saveWindowState(); KEditToolBar edit(factory(), this); connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig())); (void) edit.exec(); applyToolBarLayout(); } void KisMainWindow::slotNewToolbarConfig() { applyMainWindowSettings(d->windowStateConfig); KXMLGUIFactory *factory = guiFactory(); Q_UNUSED(factory); // Check if there's an active view if (!d->activeView) return; plugActionList("toolbarlist", d->toolbarList); applyToolBarLayout(); } void KisMainWindow::slotToolbarToggled(bool toggle) { //dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true; // The action (sender) and the toolbar have the same name KToolBar * bar = toolBar(sender()->objectName()); if (bar) { if (toggle) { bar->show(); } else { bar->hide(); } if (d->activeView && d->activeView->document()) { saveWindowState(); } } else warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!"; } void KisMainWindow::viewFullscreen(bool fullScreen) { KisConfig cfg(false); cfg.setFullscreenMode(fullScreen); if (fullScreen) { setWindowState(windowState() | Qt::WindowFullScreen); // set } else { setWindowState(windowState() & ~Qt::WindowFullScreen); // reset } } void KisMainWindow::setMaxRecentItems(uint _number) { d->recentFiles->setMaxItems(_number); } void KisMainWindow::slotReloadFile() { KisDocument* document = d->activeView->document(); if (!document || document->url().isEmpty()) return; if (document->isModified()) { bool ok = QMessageBox::question(this, i18nc("@title:window", "Krita"), i18n("You will lose all changes made since your last save\n" "Do you want to continue?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes; if (!ok) return; } QUrl url = document->url(); saveWindowSettings(); if (!document->reload()) { QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document")); } return; } QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory) { QDockWidget* dockWidget = 0; bool lockAllDockers = KisConfig(true).readEntry("LockAllDockerPanels", false); if (!d->dockWidgetsMap.contains(factory->id())) { dockWidget = factory->createDockWidget(); // It is quite possible that a dock factory cannot create the dock; don't // do anything in that case. if (!dockWidget) { warnKrita << "Could not create docker for" << factory->id(); return 0; } dockWidget->setFont(KoDockRegistry::dockFont()); dockWidget->setObjectName(factory->id()); dockWidget->setParent(this); if (lockAllDockers) { if (dockWidget->titleBarWidget()) { dockWidget->titleBarWidget()->setVisible(false); } dockWidget->setFeatures(QDockWidget::NoDockWidgetFeatures); } if (dockWidget->widget() && dockWidget->widget()->layout()) dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1); Qt::DockWidgetArea side = Qt::RightDockWidgetArea; bool visible = true; switch (factory->defaultDockPosition()) { case KoDockFactoryBase::DockTornOff: dockWidget->setFloating(true); // position nicely? break; case KoDockFactoryBase::DockTop: side = Qt::TopDockWidgetArea; break; case KoDockFactoryBase::DockLeft: side = Qt::LeftDockWidgetArea; break; case KoDockFactoryBase::DockBottom: side = Qt::BottomDockWidgetArea; break; case KoDockFactoryBase::DockRight: side = Qt::RightDockWidgetArea; break; case KoDockFactoryBase::DockMinimized: default: side = Qt::RightDockWidgetArea; visible = false; } KConfigGroup group = d->windowStateConfig.group("DockWidget " + factory->id()); side = static_cast(group.readEntry("DockArea", static_cast(side))); if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea; addDockWidget(side, dockWidget); if (!visible) { dockWidget->hide(); } d->dockWidgetsMap.insert(factory->id(), dockWidget); } else { dockWidget = d->dockWidgetsMap[factory->id()]; } #ifdef Q_OS_MACOS dockWidget->setAttribute(Qt::WA_MacSmallSize, true); #endif dockWidget->setFont(KoDockRegistry::dockFont()); connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts())); return dockWidget; } void KisMainWindow::forceDockTabFonts() { Q_FOREACH (QObject *child, children()) { if (child->inherits("QTabBar")) { ((QTabBar *)child)->setFont(KoDockRegistry::dockFont()); } } } QList KisMainWindow::dockWidgets() const { return d->dockWidgetsMap.values(); } QDockWidget* KisMainWindow::dockWidget(const QString &id) { if (!d->dockWidgetsMap.contains(id)) return 0; return d->dockWidgetsMap[id]; } QList KisMainWindow::canvasObservers() const { QList observers; Q_FOREACH (QDockWidget *docker, dockWidgets()) { KoCanvasObserverBase *observer = dynamic_cast(docker); if (observer) { observers << observer; } else { warnKrita << docker << "is not a canvas observer"; } } return observers; } void KisMainWindow::toggleDockersVisibility(bool visible) { if (!visible) { d->dockerStateBeforeHiding = saveState(); Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if (dw->isVisible()) { dw->hide(); } } } } else { restoreState(d->dockerStateBeforeHiding); } } void KisMainWindow::slotDocumentTitleModified() { updateCaption(); updateReloadFileAction(d->activeView ? d->activeView->document() : 0); } void KisMainWindow::subWindowActivated() { bool enabled = (activeKisView() != 0); d->mdiCascade->setEnabled(enabled); d->mdiNextWindow->setEnabled(enabled); d->mdiPreviousWindow->setEnabled(enabled); d->mdiTile->setEnabled(enabled); d->close->setEnabled(enabled); d->closeAll->setEnabled(enabled); setActiveSubWindow(d->mdiArea->activeSubWindow()); Q_FOREACH (QToolBar *tb, toolBars()) { if (tb->objectName() == "BrushesAndStuff") { tb->setEnabled(enabled); } } /** * Qt has a weirdness, it has hardcoded shortcuts added to an action * in the window menu. We need to reset the shortcuts for that menu * to nothing, otherwise the shortcuts cannot be made configurable. * * See: https://bugs.kde.org/show_bug.cgi?id=352205 * https://bugs.kde.org/show_bug.cgi?id=375524 * https://bugs.kde.org/show_bug.cgi?id=398729 */ QMdiSubWindow *subWindow = d->mdiArea->currentSubWindow(); if (subWindow) { QMenu *menu = subWindow->systemMenu(); if (menu && menu->actions().size() == 8) { Q_FOREACH (QAction *action, menu->actions()) { action->setShortcut(QKeySequence()); } menu->actions().last()->deleteLater(); } } updateCaption(); d->actionManager()->updateGUI(); } void KisMainWindow::windowFocused() { /** * Notify selection manager so that it could update selection mask overlay */ if (viewManager() && viewManager()->selectionManager()) { viewManager()->selectionManager()->selectionChanged(); } KisPart *kisPart = KisPart::instance(); KisWindowLayoutManager *layoutManager = KisWindowLayoutManager::instance(); if (!layoutManager->primaryWorkspaceFollowsFocus()) return; QUuid primary = layoutManager->primaryWindowId(); if (primary.isNull()) return; if (d->id == primary) { if (!d->workspaceBorrowedBy.isNull()) { KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy); if (!borrower) return; swapWorkspaces(this, borrower); } } else { if (d->workspaceBorrowedBy == primary) return; KisMainWindow *primaryWindow = kisPart->windowById(primary); if (!primaryWindow) return; swapWorkspaces(this, primaryWindow); } } void KisMainWindow::updateWindowMenu() { QMenu *menu = d->windowMenu->menu(); menu->clear(); menu->addAction(d->newWindow); menu->addAction(d->documentMenu); QMenu *docMenu = d->documentMenu->menu(); docMenu->clear(); QFontMetrics fontMetrics = docMenu->fontMetrics(); int fileStringWidth = int(QApplication::desktop()->screenGeometry(this).width() * .40f); Q_FOREACH (QPointer doc, KisPart::instance()->documents()) { if (doc) { QString title = fontMetrics.elidedText(doc->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth); if (title.isEmpty() && doc->image()) { title = doc->image()->objectName(); } QAction *action = docMenu->addAction(title); action->setIcon(qApp->windowIcon()); connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map())); d->documentMapper->setMapping(action, doc); } } menu->addAction(d->workspaceMenu); QMenu *workspaceMenu = d->workspaceMenu->menu(); workspaceMenu->clear(); auto workspaces = KisResourceServerProvider::instance()->workspaceServer()->resources(); auto m_this = this; for (auto &w : workspaces) { auto action = workspaceMenu->addAction(w->name()); connect(action, &QAction::triggered, this, [=]() { m_this->restoreWorkspace(w); }); } workspaceMenu->addSeparator(); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")), &QAction::triggered, this, [&]() { QString extensions = d->workspacemodel->extensions(); QStringList mimeTypes; for(const QString &suffix : extensions.split(":")) { mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix); } KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument"); dialog.setMimeTypeFilters(mimeTypes); dialog.setCaption(i18nc("@title:window", "Choose File to Add")); QString filename = dialog.filename(); d->workspacemodel->importResourceFile(filename); }); connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")), &QAction::triggered, [=]() { QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."), i18nc("@label:textbox", "Name:")); if (name.isEmpty()) return; auto rserver = KisResourceServerProvider::instance()->workspaceServer(); KisWorkspaceResource* workspace = new KisWorkspaceResource(""); workspace->setDockerState(m_this->saveState()); d->viewManager->canvasResourceProvider()->notifySavingWorkspace(workspace); workspace->setValid(true); QString saveLocation = rserver->saveLocation(); bool newName = false; if(name.isEmpty()) { newName = true; name = i18n("Workspace"); } QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension()); int i = 1; while (fileInfo.exists()) { fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension()); i++; } workspace->setFilename(fileInfo.filePath()); if(newName) { name = i18n("Workspace %1", i); } workspace->setName(name); rserver->addResource(workspace); }); // TODO: What to do about delete? // workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace...")); menu->addSeparator(); menu->addAction(d->close); menu->addAction(d->closeAll); if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) { menu->addSeparator(); menu->addAction(d->mdiTile); menu->addAction(d->mdiCascade); } menu->addSeparator(); menu->addAction(d->mdiNextWindow); menu->addAction(d->mdiPreviousWindow); menu->addSeparator(); QList windows = d->mdiArea->subWindowList(); for (int i = 0; i < windows.size(); ++i) { QPointerchild = qobject_cast(windows.at(i)->widget()); if (child && child->document()) { QString text; if (i < 9) { text = i18n("&%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } else { text = i18n("%1 %2", i + 1, fontMetrics.elidedText(child->document()->url().toDisplayString(QUrl::PreferLocalFile), Qt::ElideMiddle, fileStringWidth)); } QAction *action = menu->addAction(text); action->setIcon(qApp->windowIcon()); action->setCheckable(true); action->setChecked(child == activeKisView()); connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map())); d->windowMapper->setMapping(action, windows.at(i)); } } bool showMdiArea = windows.count( ) > 0; if (!showMdiArea) { showWelcomeScreen(true); // see workaround in function in header // keep the recent file list updated when going back to welcome screen reloadRecentFileList(); d->welcomePage->populateRecentDocuments(); } // enable/disable the toolbox docker if there are no documents open Q_FOREACH (QObject* widget, children()) { if (widget->inherits("QDockWidget")) { QDockWidget* dw = static_cast(widget); if ( dw->objectName() == "ToolBox") { dw->setEnabled(showMdiArea); } } } updateCaption(); } void KisMainWindow::setActiveSubWindow(QWidget *window) { if (!window) return; QMdiSubWindow *subwin = qobject_cast(window); //dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow; if (subwin && subwin != d->activeSubWindow) { KisView *view = qobject_cast(subwin->widget()); //dbgKrita << "\t" << view << activeView(); if (view && view != activeView()) { d->mdiArea->setActiveSubWindow(subwin); setActiveView(view); } d->activeSubWindow = subwin; } updateWindowMenu(); d->actionManager()->updateGUI(); } void KisMainWindow::configChanged() { KisConfig cfg(true); QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView); d->mdiArea->setViewMode(viewMode); Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) { subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL())); /** * Dirty workaround for a bug in Qt (checked on Qt 5.6.1): * * If you make a window "Show on top" and then switch to the tabbed mode * the window will contiue to be painted in its initial "mid-screen" * position. It will persist here until you explicitly switch to its tab. */ if (viewMode == QMdiArea::TabbedView) { Qt::WindowFlags oldFlags = subwin->windowFlags(); Qt::WindowFlags flags = oldFlags; flags &= ~Qt::WindowStaysOnTopHint; flags &= ~Qt::WindowStaysOnBottomHint; if (flags != oldFlags) { subwin->setWindowFlags(flags); subwin->showMaximized(); } } } KConfigGroup group( KSharedConfig::openConfig(), "theme"); d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark")); d->actionManager()->updateGUI(); QString s = cfg.getMDIBackgroundColor(); KoColor c = KoColor::fromXML(s); QBrush brush(c.toQColor()); d->mdiArea->setBackground(brush); QString backgroundImage = cfg.getMDIBackgroundImage(); if (backgroundImage != "") { QImage image(backgroundImage); QBrush brush(image); d->mdiArea->setBackground(brush); } d->mdiArea->update(); } KisView* KisMainWindow::newView(QObject *document) { KisDocument *doc = qobject_cast(document); KisView *view = addViewAndNotifyLoadingCompleted(doc); d->actionManager()->updateGUI(); return view; } void KisMainWindow::newWindow() { KisMainWindow *mainWindow = KisPart::instance()->createMainWindow(); mainWindow->initializeGeometry(); mainWindow->show(); } void KisMainWindow::closeCurrentWindow() { if (d->mdiArea->currentSubWindow()) { d->mdiArea->currentSubWindow()->close(); d->actionManager()->updateGUI(); } } void KisMainWindow::checkSanity() { // print error if the lcms engine is not available if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) { // need to wait 1 event since exiting here would not work. m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer(); if (rserver->resources().isEmpty()) { m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now."); m_dieOnError = true; QTimer::singleShot(0, this, SLOT(showErrorAndDie())); return; } } void KisMainWindow::showErrorAndDie() { QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage); if (m_dieOnError) { exit(10); } } void KisMainWindow::showAboutApplication() { KisAboutApplication dlg(this); dlg.exec(); } QPointer KisMainWindow::activeKisView() { if (!d->mdiArea) return 0; QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow(); //dbgKrita << "activeKisView" << activeSubWindow; if (!activeSubWindow) return 0; return qobject_cast(activeSubWindow->widget()); } void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList) { KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController()); bool isOurOwnView = false; Q_FOREACH (QPointer view, KisPart::instance()->views()) { if (view && view->canvasController() == controller) { isOurOwnView = view->mainWindow() == this; } } if (!isOurOwnView) return; Q_FOREACH (QWidget *w, optionWidgetList) { #ifdef Q_OS_MACOS w->setAttribute(Qt::WA_MacSmallSize, true); #endif w->setFont(KoDockRegistry::dockFont()); } if (d->toolOptionsDocker) { d->toolOptionsDocker->setOptionWidgets(optionWidgetList); } else { d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList); } } void KisMainWindow::applyDefaultSettings(QPrinter &printer) { if (!d->activeView) return; QString title = d->activeView->document()->documentInfo()->aboutInfo("title"); if (title.isEmpty()) { QFileInfo info(d->activeView->document()->url().fileName()); title = info.baseName(); } if (title.isEmpty()) { // #139905 title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(), QLocale().toString(QDate::currentDate(), QLocale::ShortFormat)); } printer.setDocName(title); } void KisMainWindow::createActions() { KisActionManager *actionManager = d->actionManager(); actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew())); actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen())); actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit())); actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars())); d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool))); d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection()); connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles())); KSharedConfigPtr configPtr = KSharedConfig::openConfig(); d->recentFiles->loadEntries(configPtr->group("RecentFiles")); d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave())); d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE); d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs())); d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint())); // d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE); // d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview())); // d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo())); d->undo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo())); d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE); d->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo)); d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0); // d->exportPdf = actionManager->createAction("file_export_pdf"); // connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf())); d->importAnimation = actionManager->createAction("file_import_animation"); connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation())); d->closeAll = actionManager->createAction("file_close_all"); connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll())); // d->reloadFile = actionManager->createAction("file_reload_file"); // d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED); // connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile())); d->importFile = actionManager->createAction("file_import_file"); connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile())); d->exportFile = actionManager->createAction("file_export_file"); connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile())); /* The following entry opens the document information dialog. Since the action is named so it intends to show data this entry should not have a trailing ellipses (...). */ d->showDocumentInfo = actionManager->createAction("file_documentinfo"); connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo())); d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this)); d->themeManager->registerThemeActions(actionCollection()); connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); connect(d->themeManager, SIGNAL(signalThemeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors())); d->toggleDockers = actionManager->createAction("view_toggledockers"); KisConfig(true).showDockers(true); d->toggleDockers->setChecked(true); connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool))); actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu); actionCollection()->addAction("window", d->windowMenu); d->mdiCascade = actionManager->createAction("windows_cascade"); connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows())); d->mdiTile = actionManager->createAction("windows_tile"); connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows())); d->mdiNextWindow = actionManager->createAction("windows_next"); connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow())); d->mdiPreviousWindow = actionManager->createAction("windows_previous"); connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow())); d->newWindow = actionManager->createAction("view_newwindow"); connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow())); d->close = actionManager->createStandardAction(KStandardAction::Close, this, SLOT(closeCurrentWindow())); d->showSessionManager = actionManager->createAction("file_sessions"); connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager())); actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences())); for (int i = 0; i < 2; i++) { d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer")); d->expandingSpacers[i]->setDefaultWidget(new QWidget(this)); d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]); } } void KisMainWindow::applyToolBarLayout() { const bool isPlastiqueStyle = style()->objectName() == "plastique"; Q_FOREACH (KToolBar *toolBar, toolBars()) { toolBar->layout()->setSpacing(4); if (isPlastiqueStyle) { toolBar->setContentsMargins(0, 0, 0, 2); } //Hide text for buttons with an icon in the toolbar Q_FOREACH (QAction *ac, toolBar->actions()){ if (ac->icon().pixmap(QSize(1,1)).isNull() == false){ ac->setPriority(QAction::LowPriority); }else { ac->setIcon(QIcon()); } } } } void KisMainWindow::initializeGeometry() { // if the user didn's specify the geometry on the command line (does anyone do that still?), // we first figure out some good default size and restore the x,y position. See bug 285804Z. KConfigGroup cfg = d->windowStateConfig; QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray())); if (!restoreGeometry(geom)) { const int scnum = QApplication::desktop()->screenNumber(parentWidget()); QRect desk = QApplication::desktop()->availableGeometry(scnum); // if the desktop is virtual then use virtual screen size if (QApplication::desktop()->isVirtualDesktop()) { desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum)); } quint32 x = desk.x(); quint32 y = desk.y(); quint32 w = 0; quint32 h = 0; // Default size -- maximize on small screens, something useful on big screens const int deskWidth = desk.width(); if (deskWidth > 1024) { // a nice width, and slightly less than total available // height to compensate for the window decs w = (deskWidth / 3) * 2; h = (desk.height() / 3) * 2; } else { w = desk.width(); h = desk.height(); } x += (desk.width() - w) / 2; y += (desk.height() - h) / 2; move(x,y); setGeometry(geometry().x(), geometry().y(), w, h); } d->fullScreenMode->setChecked(isFullScreen()); } void KisMainWindow::showManual() { QDesktopServices::openUrl(QUrl("https://docs.krita.org")); } void KisMainWindow::windowScreenChanged(QScreen *screen) { emit screenChanged(); d->screenConnectionsStore.clear(); d->screenConnectionsStore.addConnection(screen, SIGNAL(physicalDotsPerInchChanged(qreal)), this, SIGNAL(screenChanged())); } #include diff --git a/libs/ui/kis_animation_importer.cpp b/libs/ui/kis_animation_importer.cpp index f43cb43677..a4c69aab8a 100644 --- a/libs/ui/kis_animation_importer.cpp +++ b/libs/ui/kis_animation_importer.cpp @@ -1,130 +1,130 @@ /* * 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_animation_importer.h" #include #include "KoColorSpace.h" #include #include #include "KisPart.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_undo_adapter.h" #include "kis_paint_layer.h" #include "kis_group_layer.h" #include "kis_raster_keyframe_channel.h" #include "commands/kis_image_layer_add_command.h" struct KisAnimationImporter::Private { KisImageSP image; KisDocument *document; bool stop; KoUpdaterPtr updater; }; KisAnimationImporter::KisAnimationImporter(KisImageSP image, KoUpdaterPtr updater) : m_d(new Private()) { m_d->document = 0; m_d->image = image; m_d->stop = false; m_d->updater = updater; } KisAnimationImporter::KisAnimationImporter(KisDocument* document) : m_d(new Private()) { m_d->document= document; m_d->image = document->image(); m_d->stop = false; } KisAnimationImporter::~KisAnimationImporter() {} -KisImportExportFilter::ConversionStatus KisAnimationImporter::import(QStringList files, int firstFrame, int step) +KisImportExportErrorCode KisAnimationImporter::import(QStringList files, int firstFrame, int step) { Q_ASSERT(step > 0); m_d->image->lock(); KisUndoAdapter *undo = m_d->image->undoAdapter(); undo->beginMacro(kundo2_i18n("Import animation")); QScopedPointer importDoc(KisPart::instance()->createDocument()); importDoc->setFileBatchMode(true); - KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK; + KisImportExportErrorCode status = ImportExportCodes::OK; int frame = firstFrame; int filesProcessed = 0; if (m_d->updater) { m_d->updater->setRange(0, files.size()); } KisRasterKeyframeChannel *contentChannel = 0; Q_FOREACH(QString file, files) { bool successfullyLoaded = importDoc->openUrl(QUrl::fromLocalFile(file), KisDocument::DontAddToRecent); if (!successfullyLoaded) { - status = KisImportExportFilter::InternalError; + status = ImportExportCodes::InternalError; break; } if (frame == firstFrame) { const KoColorSpace *cs = importDoc->image()->colorSpace(); KisPaintLayerSP paintLayer = new KisPaintLayer(m_d->image, m_d->image->nextLayerName(), OPACITY_OPAQUE_U8, cs); undo->addCommand(new KisImageLayerAddCommand(m_d->image, paintLayer, m_d->image->rootLayer(), m_d->image->rootLayer()->childCount())); paintLayer->enableAnimation(); contentChannel = qobject_cast(paintLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true)); } if (m_d->updater) { if (m_d->updater->interrupted()) { m_d->stop = true; } else { m_d->updater->setValue(filesProcessed + 1); // the updater doesn't call that automatically, // it is "threaded" by default qApp->processEvents(); } } if (m_d->stop) { - status = KisImportExportFilter::ProgressCancelled; + status = ImportExportCodes::Cancelled; break; } contentChannel->importFrame(frame, importDoc->image()->projection(), NULL); frame += step; filesProcessed++; } undo->endMacro(); m_d->image->unlock(); return status; } void KisAnimationImporter::cancel() { m_d->stop = true; } diff --git a/libs/ui/kis_animation_importer.h b/libs/ui/kis_animation_importer.h index 565db0ecd7..22514080d4 100644 --- a/libs/ui/kis_animation_importer.h +++ b/libs/ui/kis_animation_importer.h @@ -1,48 +1,49 @@ /* * 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. */ #ifndef KIS_ANIMATION_IMPORTER_H #define KIS_ANIMATION_IMPORTER_H #include "kis_types.h" #include "kritaui_export.h" #include +#include class KisDocument; class KisMainWindow; class KRITAUI_EXPORT KisAnimationImporter : public QObject { Q_OBJECT public: KisAnimationImporter(KisImageSP image, KoUpdaterPtr updater = 0); KisAnimationImporter(KisDocument* document); ~KisAnimationImporter() override; - KisImportExportFilter::ConversionStatus import(QStringList files, int firstFrame, int step); + KisImportExportErrorCode import(QStringList files, int firstFrame, int step); private Q_SLOTS: void cancel(); private: struct Private; QScopedPointer m_d; }; #endif diff --git a/libs/ui/kis_async_action_feedback.cpp b/libs/ui/kis_async_action_feedback.cpp index 255e758d48..e5af581bc2 100644 --- a/libs/ui/kis_async_action_feedback.cpp +++ b/libs/ui/kis_async_action_feedback.cpp @@ -1,87 +1,87 @@ /* * Copyright (c) 2016 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_async_action_feedback.h" #include #include struct KisAsyncActionFeedback::Private { QScopedPointer progress; }; KisAsyncActionFeedback::KisAsyncActionFeedback(const QString &message, QWidget *parent) : m_d(new Private) { m_d->progress.reset(new QProgressDialog(message, "", 0, 0, parent)); m_d->progress->setWindowModality(Qt::ApplicationModal); m_d->progress->setCancelButton(0); m_d->progress->setMinimumDuration(1000); m_d->progress->setValue(0); // disable close button m_d->progress->setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowTitleHint); } KisAsyncActionFeedback::~KisAsyncActionFeedback() { } template T runActionImpl(std::function func) { QFuture result = QtConcurrent::run(func); QFutureWatcher watcher; watcher.setFuture(result); while (watcher.isRunning()) { qApp->processEvents(); } watcher.waitForFinished(); return watcher.result(); } -KisImportExportFilter::ConversionStatus KisAsyncActionFeedback::runAction(std::function func) +KisImportExportErrorCode KisAsyncActionFeedback::runAction(std::function func) { return runActionImpl(func); } void KisAsyncActionFeedback::runVoidAction(std::function func) { QFuture result = QtConcurrent::run(func); QFutureWatcher watcher; watcher.setFuture(result); while (watcher.isRunning()) { qApp->processEvents(); } watcher.waitForFinished(); } void KisAsyncActionFeedback::waitForMutex(QMutex *mutex) { while (!mutex->tryLock()) { qApp->processEvents(); } mutex->unlock(); } diff --git a/libs/ui/kis_async_action_feedback.h b/libs/ui/kis_async_action_feedback.h index a615a45585..59e070fd84 100644 --- a/libs/ui/kis_async_action_feedback.h +++ b/libs/ui/kis_async_action_feedback.h @@ -1,44 +1,44 @@ /* * Copyright (c) 2016 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 __KIS_ASYNC_ACTION_FEEDBACK_H #define __KIS_ASYNC_ACTION_FEEDBACK_H #include #include #include "KisImportExportFilter.h" class QWidget; class QMutex; class KisAsyncActionFeedback { public: KisAsyncActionFeedback(const QString &message, QWidget *parent); ~KisAsyncActionFeedback(); - KisImportExportFilter::ConversionStatus runAction(std::function func); + KisImportExportErrorCode runAction(std::function func); void runVoidAction(std::function func); void waitForMutex(QMutex *mutex); private: struct Private; const QScopedPointer m_d; }; #endif /* __KIS_ASYNC_ACTION_FEEDBACK_H */ diff --git a/libs/ui/kis_mimedata.cpp b/libs/ui/kis_mimedata.cpp index dec24428e1..e30f77c7bf 100644 --- a/libs/ui/kis_mimedata.cpp +++ b/libs/ui/kis_mimedata.cpp @@ -1,471 +1,471 @@ /* * Copyright (c) 2011 Boudewijn Rempt * * 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 "kis_mimedata.h" #include "kis_config.h" #include "kis_node.h" #include "kis_paint_device.h" #include "kis_shared_ptr.h" #include "kis_image.h" #include "kis_layer.h" #include "kis_shape_layer.h" #include "kis_paint_layer.h" #include "KisDocument.h" #include "kis_shape_controller.h" #include "KisPart.h" #include "kis_layer_utils.h" #include "kis_node_insertion_adapter.h" #include "kis_dummies_facade_base.h" #include "kis_node_dummies_graph.h" #include "KisImportExportManager.h" #include "KisImageBarrierLockerWithFeedback.h" #include #include #include #include #include #include #include #include #include #include #include #include KisMimeData::KisMimeData(QList nodes, KisImageSP image, bool forceCopy) : QMimeData() , m_nodes(nodes) , m_forceCopy(forceCopy) , m_image(image) { Q_ASSERT(m_nodes.size() > 0); } void KisMimeData::deepCopyNodes() { KisNodeList newNodes; { KisImageBarrierLockerWithFeedbackAllowNull locker(m_image); Q_FOREACH (KisNodeSP node, m_nodes) { newNodes << node->clone(); } } m_nodes = newNodes; m_image = 0; } QList KisMimeData::nodes() const { return m_nodes; } QStringList KisMimeData::formats () const { QStringList f = QMimeData::formats(); if (m_nodes.size() > 0) { f << "application/x-krita-node" << "application/x-krita-node-url" << "application/x-qt-image" << "application/zip" << "application/x-krita-node-internal-pointer"; } return f; } KisDocument *createDocument(QList nodes, KisImageSP srcImage) { KisDocument *doc = KisPart::instance()->createDocument(); QRect rc; Q_FOREACH (KisNodeSP node, nodes) { rc |= node->exactBounds(); } KisImageSP image = new KisImage(0, rc.width(), rc.height(), nodes.first()->colorSpace(), nodes.first()->name()); image->setAllowMasksOnRootNode(true); { KisImageBarrierLockerWithFeedbackAllowNull locker(srcImage); Q_FOREACH (KisNodeSP node, nodes) { image->addNode(node->clone()); } } doc->setCurrentImage(image); return doc; } QByteArray serializeToByteArray(QList nodes, KisImageSP srcImage) { QScopedPointer doc(createDocument(nodes, srcImage)); QByteArray result = doc->serializeToNativeByteArray(); // avoid a sanity check failure caused by the fact that the image outlives // the document (and it does) doc->setCurrentImage(0); return result; } QVariant KisMimeData::retrieveData(const QString &mimetype, QVariant::Type preferredType) const { /** * HACK ALERT: * * Sometimes Qt requests the data *after* destruction of Krita, * we cannot load the nodes in that case, because we need signals * and timers. So we just skip serializing. */ if (!QApplication::instance()) return QVariant(); Q_ASSERT(m_nodes.size() > 0); if (mimetype == "application/x-qt-image") { KisConfig cfg(true); KisDocument *doc = createDocument(m_nodes, m_image); return doc->image()->projection()->convertToQImage(cfg.displayProfile(QApplication::desktop()->screenNumber(qApp->activeWindow())), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } else if (mimetype == "application/x-krita-node" || mimetype == "application/zip") { QByteArray ba = serializeToByteArray(m_nodes, m_image); return ba; } else if (mimetype == "application/x-krita-node-url") { QByteArray ba = serializeToByteArray(m_nodes, m_image); QString temporaryPath = QDir::tempPath() + QDir::separator() + QString("krita_tmp_dnd_layer_%1_%2.kra") .arg(QApplication::applicationPid()) .arg(qrand()); QFile file(temporaryPath); file.open(QFile::WriteOnly); file.write(ba); file.flush(); file.close(); return QUrl::fromLocalFile(temporaryPath).toEncoded(); } else if (mimetype == "application/x-krita-node-internal-pointer") { QDomDocument doc("krita_internal_node_pointer"); QDomElement root = doc.createElement("pointer"); root.setAttribute("application_pid", (qint64)QApplication::applicationPid()); root.setAttribute("force_copy", m_forceCopy); root.setAttribute("image_pointer_value", (qint64)m_image.data()); doc.appendChild(root); Q_FOREACH (KisNodeSP node, m_nodes) { QDomElement element = doc.createElement("node"); element.setAttribute("pointer_value", (qint64)node.data()); root.appendChild(element); } return doc.toByteArray(); } else { return QMimeData::retrieveData(mimetype, preferredType); } } void KisMimeData::initializeExternalNode(KisNodeSP *node, KisImageWSP image, KisShapeController *shapeController) { // adjust the link to a correct image object (*node)->setImage(image); KisShapeLayer *shapeLayer = dynamic_cast(node->data()); if (shapeLayer) { // attach the layer to a new shape controller KisShapeLayer *shapeLayer2 = new KisShapeLayer(*shapeLayer, shapeController); *node = shapeLayer2; } } QList KisMimeData::tryLoadInternalNodes(const QMimeData *data, KisImageSP image, KisShapeController *shapeController, bool /* IN-OUT */ ©Node) { QList nodes; bool forceCopy = false; KisImageSP sourceImage; // Qt 4.7 and Qt 5.5 way const KisMimeData *mimedata = qobject_cast(data); if (mimedata) { nodes = mimedata->nodes(); forceCopy = mimedata->m_forceCopy; sourceImage = mimedata->m_image; } // Qt 4.8 way if (nodes.isEmpty() && data->hasFormat("application/x-krita-node-internal-pointer")) { QByteArray nodeXml = data->data("application/x-krita-node-internal-pointer"); QDomDocument doc; doc.setContent(nodeXml); QDomElement element = doc.documentElement(); qint64 pid = element.attribute("application_pid").toLongLong(); forceCopy = element.attribute("force_copy").toInt(); qint64 imagePointerValue = element.attribute("image_pointer_value").toLongLong(); sourceImage = reinterpret_cast(imagePointerValue); if (pid == QApplication::applicationPid()) { QDomNode n = element.firstChild(); while (!n.isNull()) { QDomElement e = n.toElement(); if (!e.isNull()) { qint64 pointerValue = e.attribute("pointer_value").toLongLong(); if (pointerValue) { nodes << reinterpret_cast(pointerValue); } } n = n.nextSibling(); } } } if (!nodes.isEmpty() && (forceCopy || copyNode || sourceImage != image)) { KisImageBarrierLockerWithFeedbackAllowNull locker(sourceImage); QList clones; Q_FOREACH (KisNodeSP node, nodes) { node = node->clone(); if ((forceCopy || copyNode) && sourceImage == image) { KisLayerUtils::addCopyOfNameTag(node); } initializeExternalNode(&node, image, shapeController); clones << node; } nodes = clones; copyNode = true; } return nodes; } QList KisMimeData::loadNodes(const QMimeData *data, const QRect &imageBounds, const QPoint &preferredCenter, bool forceRecenter, KisImageWSP image, KisShapeController *shapeController) { bool alwaysRecenter = false; QList nodes; if (data->hasFormat("application/x-krita-node")) { KisDocument *tempDoc = KisPart::instance()->createDocument(); QByteArray ba = data->data("application/x-krita-node"); QBuffer buf(&ba); KisImportExportFilter *filter = tempDoc->importExportManager()->filterForMimeType(tempDoc->nativeFormatMimeType(), KisImportExportManager::Import); filter->setBatchMode(true); - bool result = (filter->convert(tempDoc, &buf) == KisImportExportFilter::OK); + bool result = (filter->convert(tempDoc, &buf).isOk()); if (result) { KisImageWSP tempImage = tempDoc->image(); Q_FOREACH (KisNodeSP node, tempImage->root()->childNodes(QStringList(), KoProperties())) { tempImage->removeNode(node); initializeExternalNode(&node, image, shapeController); nodes << node; } } delete filter; delete tempDoc; } if (nodes.isEmpty() && data->hasFormat("application/x-krita-node-url")) { QByteArray ba = data->data("application/x-krita-node-url"); KisDocument *tempDoc = KisPart::instance()->createDocument(); Q_ASSERT(QUrl::fromEncoded(ba).isLocalFile()); bool result = tempDoc->openUrl(QUrl::fromEncoded(ba)); if (result) { KisImageWSP tempImage = tempDoc->image(); Q_FOREACH (KisNodeSP node, tempImage->root()->childNodes(QStringList(), KoProperties())) { tempImage->removeNode(node); initializeExternalNode(&node, image, shapeController); nodes << node; } } delete tempDoc; QFile::remove(QUrl::fromEncoded(ba).toLocalFile()); } if (nodes.isEmpty() && data->hasImage()) { QImage qimage = qvariant_cast(data->imageData()); KisPaintDeviceSP device = new KisPaintDevice(KoColorSpaceRegistry::instance()->rgb8()); device->convertFromQImage(qimage, 0); if (image) { nodes << new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, device); } alwaysRecenter = true; } if (!nodes.isEmpty()) { Q_FOREACH (KisNodeSP node, nodes) { QRect bounds = node->projection()->exactBounds(); if (alwaysRecenter || forceRecenter || (!imageBounds.contains(bounds) && !imageBounds.intersects(bounds))) { QPoint pt = preferredCenter - bounds.center(); node->setX(pt.x()); node->setY(pt.y()); } } } return nodes; } QMimeData* KisMimeData::mimeForLayers(const KisNodeList &nodes, KisImageSP image, bool forceCopy) { KisNodeList inputNodes = nodes; KisNodeList sortedNodes; KisLayerUtils::sortMergableNodes(image->root(), inputNodes, sortedNodes); if (sortedNodes.isEmpty()) return 0; KisMimeData* data = new KisMimeData(sortedNodes, image, forceCopy); return data; } QMimeData* KisMimeData::mimeForLayersDeepCopy(const KisNodeList &nodes, KisImageSP image, bool forceCopy) { KisNodeList inputNodes = nodes; KisNodeList sortedNodes; KisLayerUtils::sortMergableNodes(image->root(), inputNodes, sortedNodes); if (sortedNodes.isEmpty()) return 0; KisMimeData* data = new KisMimeData(sortedNodes, image, forceCopy); data->deepCopyNodes(); return data; } bool nodeAllowsAsChild(KisNodeSP parent, KisNodeList nodes) { bool result = true; Q_FOREACH (KisNodeSP node, nodes) { if (!parent->allowAsChild(node)) { result = false; break; } } return result; } bool correctNewNodeLocation(KisNodeList nodes, KisNodeDummy* &parentDummy, KisNodeDummy* &aboveThisDummy) { KisNodeSP parentNode = parentDummy->node(); bool result = true; if(!nodeAllowsAsChild(parentDummy->node(), nodes)) { aboveThisDummy = parentDummy; parentDummy = parentDummy->parent(); result = (!parentDummy) ? false : correctNewNodeLocation(nodes, parentDummy, aboveThisDummy); } return result; } KisNodeList KisMimeData::loadNodesFast( const QMimeData *data, KisImageSP image, KisShapeController *shapeController, bool ©Node) { QList nodes = KisMimeData::tryLoadInternalNodes(data, image, shapeController, copyNode /* IN-OUT */); if (nodes.isEmpty()) { QRect imageBounds = image->bounds(); nodes = KisMimeData::loadNodes(data, imageBounds, imageBounds.center(), false, image, shapeController); /** * Don't try to move a node originating from another image, * just copy it. */ copyNode = true; } return nodes; } bool KisMimeData::insertMimeLayers(const QMimeData *data, KisImageSP image, KisShapeController *shapeController, KisNodeDummy *parentDummy, KisNodeDummy *aboveThisDummy, bool copyNode, KisNodeInsertionAdapter *nodeInsertionAdapter) { QList nodes = loadNodesFast(data, image, shapeController, copyNode /* IN-OUT */); if (nodes.isEmpty()) return false; bool result = true; if (!correctNewNodeLocation(nodes, parentDummy, aboveThisDummy)) { return false; } KIS_ASSERT_RECOVER(nodeInsertionAdapter) { return false; } Q_ASSERT(parentDummy); KisNodeSP aboveThisNode = aboveThisDummy ? aboveThisDummy->node() : 0; if (copyNode) { nodeInsertionAdapter->addNodes(nodes, parentDummy->node(), aboveThisNode); } else { Q_ASSERT(nodes.first()->graphListener() == image.data()); nodeInsertionAdapter->moveNodes(nodes, parentDummy->node(), aboveThisNode); } return result; } diff --git a/libs/ui/kis_png_converter.cpp b/libs/ui/kis_png_converter.cpp index 5a9c9b0896..bd2440242b 100644 --- a/libs/ui/kis_png_converter.cpp +++ b/libs/ui/kis_png_converter.cpp @@ -1,1393 +1,1394 @@ /* * Copyright (c) 2005-2007 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_png_converter.h" // A big thank to Glenn Randers-Pehrson for his wonderful // documentation of libpng available at // http://www.libpng.org/pub/png/libpng-1.2.5-manual.html #ifndef PNG_MAX_UINT // Removed in libpng 1.4 #define PNG_MAX_UINT PNG_UINT_31_MAX #endif #include // WORDS_BIGENDIAN #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dialogs/kis_dlg_png_import.h" #include "kis_clipboard.h" #include #include "kis_undo_stores.h" +#include + namespace { int getColorTypeforColorSpace(const KoColorSpace * cs , bool alpha) { QString id = cs->id(); if (id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16") { return alpha ? PNG_COLOR_TYPE_GRAY_ALPHA : PNG_COLOR_TYPE_GRAY; } if (id == "RGBA" || id == "RGBA16") { return alpha ? PNG_COLOR_TYPE_RGB_ALPHA : PNG_COLOR_TYPE_RGB; } return -1; } bool colorSpaceIdSupported(const QString &id) { return id == "RGBA" || id == "RGBA16" || id == "GRAYA" || id == "GRAYAU16" || id == "GRAYA16"; } QPair getColorSpaceForColorType(int color_type, int color_nb_bits) { QPair r; if (color_type == PNG_COLOR_TYPE_PALETTE) { r.first = RGBAColorModelID.id(); r.second = Integer8BitsColorDepthID.id(); } else { if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { r.first = GrayAColorModelID.id(); } else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_RGB) { r.first = RGBAColorModelID.id(); } if (color_nb_bits == 16) { r.second = Integer16BitsColorDepthID.id(); } else if (color_nb_bits <= 8) { r.second = Integer8BitsColorDepthID.id(); } } return r; } void fillText(png_text* p_text, const char* key, QString& text) { p_text->compression = PNG_TEXT_COMPRESSION_zTXt; p_text->key = const_cast(key); char* textc = new char[text.length()+1]; strcpy(textc, text.toLatin1()); p_text->text = textc; p_text->text_length = text.length() + 1; } long formatStringList(char *string, const size_t length, const char *format, va_list operands) { int n = vsnprintf(string, length, format, operands); if (n < 0) string[length-1] = '\0'; return((long) n); } long formatString(char *string, const size_t length, const char *format, ...) { long n; va_list operands; va_start(operands, format); n = (long) formatStringList(string, length, format, operands); va_end(operands); return(n); } void writeRawProfile(png_struct *ping, png_info *ping_info, QString profile_type, QByteArray profile_data) { png_textp text; png_uint_32 allocated_length, description_length; const uchar hex[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; dbgFile << "Writing Raw profile: type=" << profile_type << ", length=" << profile_data.length() << endl; text = (png_textp) png_malloc(ping, (png_uint_32) sizeof(png_text)); description_length = profile_type.length(); allocated_length = (png_uint_32)(profile_data.length() * 2 + (profile_data.length() >> 5) + 20 + description_length); text[0].text = (png_charp) png_malloc(ping, allocated_length); QString key = QLatin1Literal("Raw profile type ") + profile_type.toLatin1(); QByteArray keyData = key.toLatin1(); text[0].key = keyData.data(); uchar* sp = (uchar*)profile_data.data(); png_charp dp = text[0].text; *dp++ = '\n'; memcpy(dp, profile_type.toLatin1().constData(), profile_type.length()); dp += description_length; *dp++ = '\n'; formatString(dp, allocated_length - strlen(text[0].text), "%8lu ", profile_data.length()); dp += 8; for (long i = 0; i < (long) profile_data.length(); i++) { if (i % 36 == 0) *dp++ = '\n'; *(dp++) = (char) hex[((*sp >> 4) & 0x0f)]; *(dp++) = (char) hex[((*sp++) & 0x0f)]; } *dp++ = '\n'; *dp = '\0'; text[0].text_length = (png_size_t)(dp - text[0].text); text[0].compression = -1; if (text[0].text_length <= allocated_length) png_set_text(ping, ping_info, text, 1); png_free(ping, text[0].text); png_free(ping, text); } QByteArray png_read_raw_profile(png_textp text) { QByteArray profile; static const unsigned char unhex[103] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13, 14, 15 }; png_charp sp = text[0].text + 1; /* look for newline */ while (*sp != '\n') sp++; /* look for length */ while (*sp == '\0' || *sp == ' ' || *sp == '\n') sp++; png_uint_32 length = (png_uint_32) atol(sp); while (*sp != ' ' && *sp != '\n') sp++; if (length == 0) { return profile; } profile.resize(length); /* copy profile, skipping white space and column 1 "=" signs */ unsigned char *dp = (unsigned char*)profile.data(); png_uint_32 nibbles = length * 2; for (png_uint_32 i = 0; i < nibbles; i++) { while (*sp < '0' || (*sp > '9' && *sp < 'a') || *sp > 'f') { if (*sp == '\0') { return QByteArray(); } sp++; } if (i % 2 == 0) *dp = (unsigned char)(16 * unhex[(int) *sp++]); else (*dp++) += unhex[(int) *sp++]; } return profile; } void decode_meta_data(png_textp text, KisMetaData::Store* store, QString type, int headerSize) { dbgFile << "Decoding " << type << " " << text[0].key; KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value(type); Q_ASSERT(exifIO); QByteArray rawProfile = png_read_raw_profile(text); if (headerSize > 0) { rawProfile.remove(0, headerSize); } if (rawProfile.size() > 0) { QBuffer buffer; buffer.setData(rawProfile); exifIO->loadFrom(store, &buffer); } else { dbgFile << "Decoding failed"; } } } KisPNGConverter::KisPNGConverter(KisDocument *doc, bool batchMode) { // Q_ASSERT(doc); // Q_ASSERT(adapter); m_doc = doc; m_stop = false; m_max_row = 0; m_image = 0; m_batchMode = batchMode; } KisPNGConverter::~KisPNGConverter() { } class KisPNGReadStream { public: KisPNGReadStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) { } int nextValue() { if (m_posinc == 0) { m_posinc = 8; m_buf++; } m_posinc -= m_depth; return (((*m_buf) >> (m_posinc)) & ((1 << m_depth) - 1)); } private: quint32 m_posinc, m_depth; quint8* m_buf; }; class KisPNGWriteStream { public: KisPNGWriteStream(quint8* buf, quint32 depth) : m_posinc(8), m_depth(depth), m_buf(buf) { *m_buf = 0; } void setNextValue(int v) { if (m_posinc == 0) { m_posinc = 8; m_buf++; *m_buf = 0; } m_posinc -= m_depth; *m_buf = (v << m_posinc) | *m_buf; } private: quint32 m_posinc, m_depth; quint8* m_buf; }; class KisPNGReaderAbstract { public: KisPNGReaderAbstract(png_structp _png_ptr, int _width, int _height) : png_ptr(_png_ptr), width(_width), height(_height) {} virtual ~KisPNGReaderAbstract() {} virtual png_bytep readLine() = 0; protected: png_structp png_ptr; int width, height; }; class KisPNGReaderLineByLine : public KisPNGReaderAbstract { public: KisPNGReaderLineByLine(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height) { png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr); row_pointer = new png_byte[rowbytes]; } ~KisPNGReaderLineByLine() override { delete[] row_pointer; } png_bytep readLine() override { png_read_row(png_ptr, row_pointer, 0); return row_pointer; } private: png_bytep row_pointer; }; class KisPNGReaderFullImage : public KisPNGReaderAbstract { public: KisPNGReaderFullImage(png_structp _png_ptr, png_infop info_ptr, int _width, int _height) : KisPNGReaderAbstract(_png_ptr, _width, _height), y(0) { row_pointers = new png_bytep[height]; png_uint_32 rowbytes = png_get_rowbytes(png_ptr, info_ptr); for (int i = 0; i < height; i++) { row_pointers[i] = new png_byte[rowbytes]; } png_read_image(png_ptr, row_pointers); } ~KisPNGReaderFullImage() override { for (int i = 0; i < height; i++) { delete[] row_pointers[i]; } delete[] row_pointers; } png_bytep readLine() override { return row_pointers[y++]; } private: png_bytepp row_pointers; int y; }; static void _read_fn(png_structp png_ptr, png_bytep data, png_size_t length) { QIODevice *in = (QIODevice *)png_get_io_ptr(png_ptr); while (length) { int nr = in->read((char*)data, length); if (nr <= 0) { png_error(png_ptr, "Read Error"); return; } length -= nr; } } static void _write_fn(png_structp png_ptr, png_bytep data, png_size_t length) { QIODevice* out = (QIODevice*)png_get_io_ptr(png_ptr); uint nr = out->write((char*)data, length); if (nr != length) { png_error(png_ptr, "Write Error"); return; } } static void _flush_fn(png_structp png_ptr) { Q_UNUSED(png_ptr); } -KisImageBuilder_Result KisPNGConverter::buildImage(QIODevice* iod) +KisImportExportErrorCode KisPNGConverter::buildImage(QIODevice* iod) { dbgFile << "Start decoding PNG File"; png_byte signature[8]; iod->peek((char*)signature, 8); #if PNG_LIBPNG_VER < 10400 if (!png_check_sig(signature, 8)) { #else if (png_sig_cmp(signature, 0, 8) != 0) { #endif iod->close(); - return (KisImageBuilder_RESULT_BAD_FETCH); + return (ImportExportCodes::FileFormatIncorrect); } // Initialize the internal structures png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); if (!png_ptr) { iod->close(); } png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_read_struct(&png_ptr, (png_infopp)0, (png_infopp)0); iod->close(); - return (KisImageBuilder_RESULT_FAILURE); + return (ImportExportCodes::Failure); } png_infop end_info = png_create_info_struct(png_ptr); if (!end_info) { png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)0); iod->close(); - return (KisImageBuilder_RESULT_FAILURE); + return (ImportExportCodes::Failure); } // Catch errors if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); iod->close(); - return (KisImageBuilder_RESULT_FAILURE); + return (ImportExportCodes::Failure); } // Initialize the special png_set_read_fn(png_ptr, iod, _read_fn); #if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED) png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); #endif // read all PNG info up to image data png_read_info(png_ptr, info_ptr); if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_GRAY && png_get_bit_depth(png_ptr, info_ptr) < 8) { png_set_expand(png_ptr); } if (png_get_color_type(png_ptr, info_ptr) == PNG_COLOR_TYPE_PALETTE && png_get_bit_depth(png_ptr, info_ptr) < 8) { png_set_packing(png_ptr); } if (png_get_color_type(png_ptr, info_ptr) != PNG_COLOR_TYPE_PALETTE && (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))) { png_set_expand(png_ptr); } png_read_update_info(png_ptr, info_ptr); // Read information about the png png_uint_32 width, height; int color_nb_bits, color_type, interlace_type; png_get_IHDR(png_ptr, info_ptr, &width, &height, &color_nb_bits, &color_type, &interlace_type, 0, 0); dbgFile << "width = " << width << " height = " << height << " color_nb_bits = " << color_nb_bits << " color_type = " << color_type << " interlace_type = " << interlace_type << endl; // swap byteorder on little endian machines. #ifndef WORDS_BIGENDIAN if (color_nb_bits > 8) png_set_swap(png_ptr); #endif // Determine the colorspace QPair csName = getColorSpaceForColorType(color_type, color_nb_bits); if (csName.first.isEmpty()) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); iod->close(); - return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; + return ImportExportCodes::FormatColorSpaceUnsupported; } bool hasalpha = (color_type == PNG_COLOR_TYPE_RGB_ALPHA || color_type == PNG_COLOR_TYPE_GRAY_ALPHA); // Read image profile png_charp profile_name; #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5 png_bytep profile_data; #else png_charp profile_data; #endif int compression_type; png_uint_32 proflen; // Get the various optional chunks // https://www.w3.org/TR/PNG/#11cHRM #if defined(PNG_cHRM_SUPPORTED) double whitePointX, whitePointY; double redX, redY; double greenX, greenY; double blueX, blueY; png_get_cHRM(png_ptr,info_ptr, &whitePointX, &whitePointY, &redX, &redY, &greenX, &greenY, &blueX, &blueY); dbgFile << "cHRM:" << whitePointX << whitePointY << redX << redY << greenX << greenY << blueX << blueY; #endif // https://www.w3.org/TR/PNG/#11gAMA #if defined(PNG_GAMMA_SUPPORTED) double gamma; png_get_gAMA(png_ptr, info_ptr, &gamma); dbgFile << "gAMA" << gamma; #endif // https://www.w3.org/TR/PNG/#11sRGB #if defined(PNG_sRGB_SUPPORTED) int sRGBIntent; png_get_sRGB(png_ptr, info_ptr, &sRGBIntent); dbgFile << "sRGB" << sRGBIntent; #endif bool fromBlender = false; png_text* text_ptr; int num_comments; png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments); for (int i = 0; i < num_comments; i++) { QString key = QString(text_ptr[i].key).toLower(); if (key == "file") { QString relatedFile = text_ptr[i].text; if (relatedFile.contains(".blend", Qt::CaseInsensitive)){ fromBlender=true; } } } bool loadedImageIsHDR = false; const KoColorProfile* profile = 0; if (png_get_iCCP(png_ptr, info_ptr, &profile_name, &compression_type, &profile_data, &proflen)) { QByteArray profile_rawdata; // XXX: Hardcoded for icc type -- is that correct for us? profile_rawdata.resize(proflen); memcpy(profile_rawdata.data(), profile_data, proflen); profile = KoColorSpaceRegistry::instance()->createColorProfile(csName.first, csName.second, profile_rawdata); Q_CHECK_PTR(profile); if (profile) { // dbgFile << "profile name: " << profile->productName() << " profile description: " << profile->productDescription() << " information sur le produit: " << profile->productInfo(); 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 } } loadedImageIsHDR = strcmp(profile_name, "ITUR_2100_PQ_FULL") == 0; } else { dbgFile << "no embedded profile, will use the default profile"; if (color_nb_bits == 16 && !fromBlender && !qAppName().toLower().contains("test") && !m_batchMode) { KisConfig cfg(true); quint32 behaviour = cfg.pasteBehaviour(); if (behaviour == PASTE_ASK) { KisDlgPngImport dlg(m_path, csName.first, csName.second); KisCursorOverrideHijacker hijacker; Q_UNUSED(hijacker); dlg.exec(); if (!dlg.profile().isEmpty()) { profile = KoColorSpaceRegistry::instance()->profileByName(dlg.profile()); } } } dbgFile << "no embedded profile, will use the default profile"; } const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(csName.first, csName.second); // 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 " << csName.first << " " << csName.second; profile = 0; } // Retrieve a pointer to the colorspace KoColorConversionTransformation* transform = 0; const KoColorSpace* cs = 0; if (loadedImageIsHDR && csName.first == RGBAColorModelID.id() && csName.second == Integer16BitsColorDepthID.id()) { const KoColorSpace *p2020PQCS = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Integer16BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->p2020PQProfile()); cs = p2020PQCS; } else if (profile && profile->isSuitableForOutput()) { dbgFile << "image has embedded profile: " << profile->name() << "\n"; cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile); } else { if (csName.first == RGBAColorModelID.id()) { cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "sRGB-elle-V2-srgbtrc.icc"); } else if (csName.first == GrayAColorModelID.id()) { cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, "Gray-D50-elle-V2-srgbtrc.icc"); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, 0); } //TODO: two fixes : one tell the user about the problem and ask for a solution, and two once the kocolorspace include KoColorTransformation, use that instead of hacking a lcms transformation // Create the cmsTransform if needed if (profile) { transform = KoColorSpaceRegistry::instance()->colorSpace(csName.first, csName.second, profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } } if (cs == 0) { png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); - return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; + return ImportExportCodes::FormatColorSpaceUnsupported; } // Creating the KisImageSP if (m_image == 0) { KisUndoStore *store = m_doc ? m_doc->createUndoStore() : new KisSurrogateUndoStore(); m_image = new KisImage(store, width, height, cs, "built image"); } // Read resolution int unit_type; png_uint_32 x_resolution, y_resolution; png_get_pHYs(png_ptr, info_ptr, &x_resolution, &y_resolution, &unit_type); if (x_resolution > 0 && y_resolution > 0 && unit_type == PNG_RESOLUTION_METER) { m_image->setResolution((double) POINT_TO_CM(x_resolution) / 100.0, (double) POINT_TO_CM(y_resolution) / 100.0); // It is the "invert" macro because we convert from pointer-per-inchs to points } double coeff = quint8_MAX / (double)(pow((double)2, color_nb_bits) - 1); KisPaintLayerSP layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), UCHAR_MAX); // Read comments/texts... png_get_text(png_ptr, info_ptr, &text_ptr, &num_comments); if (m_doc) { KoDocumentInfo * info = m_doc->documentInfo(); dbgFile << "There are " << num_comments << " comments in the text"; for (int i = 0; i < num_comments; i++) { QString key = QString(text_ptr[i].key).toLower(); dbgFile << "key: " << text_ptr[i].key << ", containing: " << text_ptr[i].text << ": " << (key == "raw profile type exif " ? "isExif" : "something else"); if (key == "title") { info->setAboutInfo("title", text_ptr[i].text); } else if (key == "description") { info->setAboutInfo("comment", text_ptr[i].text); } else if (key == "author") { info->setAuthorInfo("creator", text_ptr[i].text); } else if (key.contains("raw profile type exif")) { decode_meta_data(text_ptr + i, layer->metaData(), "exif", 6); } else if (key.contains("raw profile type iptc")) { decode_meta_data(text_ptr + i, layer->metaData(), "iptc", 14); } else if (key.contains("raw profile type xmp")) { decode_meta_data(text_ptr + i, layer->metaData(), "xmp", 0); } else if (key == "version") { m_image->addAnnotation(new KisAnnotation("kpp_version", "version", QByteArray(text_ptr[i].text))); } else if (key == "preset") { m_image->addAnnotation(new KisAnnotation("kpp_preset", "preset", QByteArray(text_ptr[i].text))); } } } // Read image data QScopedPointer reader; try { if (interlace_type == PNG_INTERLACE_ADAM7) { reader.reset(new KisPNGReaderFullImage(png_ptr, info_ptr, width, height)); } else { reader.reset(new KisPNGReaderLineByLine(png_ptr, info_ptr, width, height)); } } catch (const std::bad_alloc& e) { // new png_byte[] may raise such an exception if the image // is invalid / to large. dbgFile << "bad alloc: " << e.what(); // Free only the already allocated png_byte instances. png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); - return (KisImageBuilder_RESULT_FAILURE); + return (ImportExportCodes::Failure); } // Read the palette if the file is indexed png_colorp palette ; int num_palette; if (color_type == PNG_COLOR_TYPE_PALETTE) { png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette); } // Read the transparency palette quint8 palette_alpha[256]; memset(palette_alpha, 255, 256); if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) { if (color_type == PNG_COLOR_TYPE_PALETTE) { png_bytep alpha_ptr; int num_alpha; png_get_tRNS(png_ptr, info_ptr, &alpha_ptr, &num_alpha, 0); for (int i = 0; i < num_alpha; ++i) { palette_alpha[i] = alpha_ptr[i]; } } } for (png_uint_32 y = 0; y < height; y++) { KisHLineIteratorSP it = layer -> paintDevice() -> createHLineIteratorNG(0, y, width); png_bytep row_pointer = reader->readLine(); switch (color_type) { case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_GRAY_ALPHA: if (color_nb_bits == 16) { quint16 *src = reinterpret_cast(row_pointer); do { quint16 *d = reinterpret_cast(it->rawData()); d[0] = *(src++); if (hasalpha) { d[1] = *(src++); } else { d[1] = quint16_MAX; } if (transform) transform->transformInPlace(reinterpret_cast(d), reinterpret_cast(d), 1); } while (it->nextPixel()); } else { KisPNGReadStream stream(row_pointer, color_nb_bits); do { quint8 *d = it->rawData(); d[0] = (quint8)(stream.nextValue() * coeff); if (hasalpha) { d[1] = (quint8)(stream.nextValue() * coeff); } else { d[1] = UCHAR_MAX; } if (transform) transform->transformInPlace(d, d, 1); } while (it->nextPixel()); } // FIXME:should be able to read 1 and 4 bits depth and scale them to 8 bits" break; case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB_ALPHA: if (color_nb_bits == 16) { quint16 *src = reinterpret_cast(row_pointer); do { quint16 *d = reinterpret_cast(it->rawData()); d[2] = *(src++); d[1] = *(src++); d[0] = *(src++); if (hasalpha) d[3] = *(src++); else d[3] = quint16_MAX; if (transform) transform->transformInPlace(reinterpret_cast(d), reinterpret_cast(d), 1); } while (it->nextPixel()); } else { KisPNGReadStream stream(row_pointer, color_nb_bits); do { quint8 *d = it->rawData(); d[2] = (quint8)(stream.nextValue() * coeff); d[1] = (quint8)(stream.nextValue() * coeff); d[0] = (quint8)(stream.nextValue() * coeff); if (hasalpha) d[3] = (quint8)(stream.nextValue() * coeff); else d[3] = UCHAR_MAX; if (transform) transform->transformInPlace(d, d, 1); } while (it->nextPixel()); } break; case PNG_COLOR_TYPE_PALETTE: { KisPNGReadStream stream(row_pointer, color_nb_bits); do { quint8 *d = it->rawData(); quint8 index = stream.nextValue(); quint8 alpha = palette_alpha[ index ]; if (alpha == 0) { memset(d, 0, 4); } else { png_color c = palette[ index ]; d[2] = c.red; d[1] = c.green; d[0] = c.blue; d[3] = alpha; } } while (it->nextPixel()); } break; default: - return KisImageBuilder_RESULT_UNSUPPORTED; + return ImportExportCodes::FormatFeaturesUnsupported; } } m_image->addNode(layer.data(), m_image->rootLayer().data()); png_read_end(png_ptr, end_info); iod->close(); // Freeing memory png_destroy_read_struct(&png_ptr, &info_ptr, &end_info); - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } -KisImageBuilder_Result KisPNGConverter::buildImage(const QString &filename) +KisImportExportErrorCode KisPNGConverter::buildImage(const QString &filename) { m_path = filename; QFile fp(filename); if (fp.exists()) { if (!fp.open(QIODevice::ReadOnly)) { dbgFile << "Failed to open PNG File"; - return (KisImageBuilder_RESULT_FAILURE); + return (ImportExportCodes::FileFormatIncorrect); } return buildImage(&fp); } - return (KisImageBuilder_RESULT_NOT_EXIST); + return (ImportExportCodes::FileNotExist); } KisImageSP KisPNGConverter::image() { return m_image; } bool KisPNGConverter::saveDeviceToStore(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP dev, KoStore *store, KisMetaData::Store* metaData) { if (store->open(filename)) { KoStoreDevice io(store); if (!io.open(QIODevice::WriteOnly)) { dbgFile << "Could not open for writing:" << filename; return false; } KisPNGConverter pngconv(0); vKisAnnotationSP_it annotIt = 0; KisMetaData::Store* metaDataStore = 0; if (metaData) { metaDataStore = new KisMetaData::Store(*metaData); } KisPNGOptions options; options.compression = 0; options.interlace = false; options.tryToSaveAsIndexed = false; options.alpha = true; options.saveSRGBProfile = false; if (dev->colorSpace()->id() != "RGBA") { dev = new KisPaintDevice(*dev.data()); dev->convertTo(KoColorSpaceRegistry::instance()->rgb8()); } - bool success = pngconv.buildFile(&io, imageRect, xRes, yRes, dev, annotIt, annotIt, options, metaDataStore); - if (success != KisImageBuilder_RESULT_OK) { + KisImportExportErrorCode success = pngconv.buildFile(&io, imageRect, xRes, yRes, dev, annotIt, annotIt, options, metaDataStore); + if (!success.isOk()) { dbgFile << "Saving PNG failed:" << filename; delete metaDataStore; return false; } delete metaDataStore; io.close(); if (!store->close()) { return false; } } else { dbgFile << "Opening of data file failed :" << filename; return false; } return true; } -KisImageBuilder_Result KisPNGConverter::buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData) +KisImportExportErrorCode KisPNGConverter::buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData) { dbgFile << "Start writing PNG File " << filename; // Open a QIODevice for writing QFile fp (filename); if (!fp.open(QIODevice::WriteOnly)) { dbgFile << "Failed to open PNG File for writing"; - return (KisImageBuilder_RESULT_FAILURE); + return (KisImportExportErrorCannotWrite(fp.error())); } - KisImageBuilder_Result result = buildFile(&fp, imageRect, xRes, yRes, device, annotationsStart, annotationsEnd, options, metaData); + KisImportExportErrorCode result = buildFile(&fp, imageRect, xRes, yRes, device, annotationsStart, annotationsEnd, options, metaData); return result; } -KisImageBuilder_Result KisPNGConverter::buildFile(QIODevice* iodevice, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData) +KisImportExportErrorCode KisPNGConverter::buildFile(QIODevice* iodevice, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData) { - if (!device) - return KisImageBuilder_RESULT_INVALID_ARG; + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(device, ImportExportCodes::InternalError); if (!options.alpha) { KisPaintDeviceSP tmp = new KisPaintDevice(device->colorSpace()); KoColor c(options.transparencyFillColor, device->colorSpace()); tmp->fill(imageRect, c); KisPainter gc(tmp); gc.bitBlt(imageRect.topLeft(), device, imageRect); gc.end(); device = tmp; } if (device->colorSpace()->colorDepthId() == Float16BitsColorDepthID || device->colorSpace()->colorDepthId() == Float32BitsColorDepthID || device->colorSpace()->colorDepthId() == Float64BitsColorDepthID || options.saveAsHDR) { const KoColorSpace *dstCS = KoColorSpaceRegistry::instance()->colorSpace( device->colorSpace()->colorModelId().id(), Integer16BitsColorDepthID.id(), device->colorSpace()->profile()); if (options.saveAsHDR) { dstCS = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Integer16BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->p2020PQProfile()); } KisPaintDeviceSP tmp = new KisPaintDevice(device->colorSpace()); tmp->makeCloneFromRough(device, imageRect); tmp->convertTo(dstCS); device = tmp; } KIS_SAFE_ASSERT_RECOVER(!options.saveAsHDR || !options.forceSRGB) { options.forceSRGB = false; } KIS_SAFE_ASSERT_RECOVER(!options.saveAsHDR || !options.tryToSaveAsIndexed) { options.tryToSaveAsIndexed = false; } QStringList colormodels = QStringList() << RGBAColorModelID.id() << GrayAColorModelID.id(); if (options.forceSRGB || !colormodels.contains(device->colorSpace()->colorModelId().id())) { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), device->colorSpace()->colorDepthId().id(), "sRGB built-in - (lcms internal)"); device = new KisPaintDevice(*device); device->convertTo(cs); } // Initialize structures png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0); if (!png_ptr) { - return (KisImageBuilder_RESULT_FAILURE); + return (ImportExportCodes::Failure); } #if defined(PNG_SKIP_sRGB_CHECK_PROFILE) && defined(PNG_SET_OPTION_SUPPORTED) png_set_option(png_ptr, PNG_SKIP_sRGB_CHECK_PROFILE, PNG_OPTION_ON); #endif #ifdef PNG_READ_CHECK_FOR_INVALID_INDEX_SUPPORTED png_set_check_for_invalid_index(png_ptr, 0); #endif png_infop info_ptr = png_create_info_struct(png_ptr); if (!info_ptr) { png_destroy_write_struct(&png_ptr, (png_infopp)0); - return (KisImageBuilder_RESULT_FAILURE); + return (ImportExportCodes::Failure); } // If an error occurs during writing, libpng will jump here if (setjmp(png_jmpbuf(png_ptr))) { png_destroy_write_struct(&png_ptr, &info_ptr); - return (KisImageBuilder_RESULT_FAILURE); + return (ImportExportCodes::Failure); } // Initialize the writing // png_init_io(png_ptr, fp); // Setup the progress function // XXX: Implement progress updating -- png_set_write_status_fn(png_ptr, progress);" // setProgressTotalSteps(100/*height*/); /* set the zlib compression level */ png_set_compression_level(png_ptr, options.compression); png_set_write_fn(png_ptr, (void*)iodevice, _write_fn, _flush_fn); /* set other zlib parameters */ png_set_compression_mem_level(png_ptr, 8); png_set_compression_strategy(png_ptr, Z_DEFAULT_STRATEGY); png_set_compression_window_bits(png_ptr, 15); png_set_compression_method(png_ptr, 8); png_set_compression_buffer_size(png_ptr, 8192); int color_nb_bits = 8 * device->pixelSize() / device->channelCount(); int color_type = getColorTypeforColorSpace(device->colorSpace(), options.alpha); Q_ASSERT(color_type > -1); // Try to compute a table of color if the colorspace is RGB8f QScopedArrayPointer palette; int num_palette = 0; if (!options.alpha && options.tryToSaveAsIndexed && KoID(device->colorSpace()->id()) == KoID("RGBA")) { // png doesn't handle indexed images and alpha, and only have indexed for RGB8 palette.reset(new png_color[255]); KisSequentialIterator it(device, imageRect); bool toomuchcolor = false; while (it.nextPixel()) { const quint8* c = it.oldRawData(); bool findit = false; for (int i = 0; i < num_palette; i++) { if (palette[i].red == c[2] && palette[i].green == c[1] && palette[i].blue == c[0]) { findit = true; break; } } if (!findit) { if (num_palette == 255) { toomuchcolor = true; break; } palette[num_palette].red = c[2]; palette[num_palette].green = c[1]; palette[num_palette].blue = c[0]; num_palette++; } } if (!toomuchcolor) { dbgFile << "Found a palette of " << num_palette << " colors"; color_type = PNG_COLOR_TYPE_PALETTE; if (num_palette <= 2) { color_nb_bits = 1; } else if (num_palette <= 4) { color_nb_bits = 2; } else if (num_palette <= 16) { color_nb_bits = 4; } else { color_nb_bits = 8; } } else { palette.reset(); } } int interlacetype = options.interlace ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE; - KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(color_type >= 0, KisImageBuilder_RESULT_FAILURE); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(color_type >= 0, ImportExportCodes::Failure); png_set_IHDR(png_ptr, info_ptr, imageRect.width(), imageRect.height(), color_nb_bits, color_type, interlacetype, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); // set sRGB only if the profile is sRGB -- http://www.w3.org/TR/PNG/#11sRGB says sRGB and iCCP should not both be present bool sRGB = device->colorSpace()->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive); /* * This automatically writes the correct gamma and chroma chunks along with the sRGB chunk, but firefox's * color management is bugged, so once you give it any incentive to start color managing an sRGB image it * will turn, for example, a nice desaturated rusty red into bright poppy red. So this is disabled for now. */ /*if (!options.saveSRGBProfile && sRGB) { png_set_sRGB_gAMA_and_cHRM(png_ptr, info_ptr, PNG_sRGB_INTENT_PERCEPTUAL); }*/ /** TODO: Firefox still opens the image incorrectly if there is gAMA+cHRM tags * present. According to the standard it should use iCCP tag with higher priority, * but it doesn't: * * "When the iCCP chunk is present, PNG decoders that recognize it and are capable * of colour management [ICC] shall ignore the gAMA and cHRM chunks and use * the iCCP chunk instead and interpret it according to [ICC-1] and [ICC-1A]" */ #if 0 if (options.saveAsHDR) { // https://www.w3.org/TR/PNG/#11gAMA #if defined(PNG_GAMMA_SUPPORTED) // the values are set in accurdance of HDR-PNG standard: // https://www.w3.org/TR/png-hdr-pq/ png_set_gAMA_fixed(png_ptr, info_ptr, 15000); dbgFile << "gAMA" << "(Rec 2100)"; #endif #if defined PNG_cHRM_SUPPORTED png_set_cHRM_fixed(png_ptr, info_ptr, 31270, 32900, // white point 70800, 29200, // red 17000, 79700, // green 13100, 4600 // blue ); dbgFile << "cHRM" << "(Rec 2100)"; #endif } #endif // we should ensure we don't access non-existing palette object - KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(palette || color_type != PNG_COLOR_TYPE_PALETTE, KisImageBuilder_RESULT_FAILURE); + KIS_SAFE_ASSERT_RECOVER_RETURN_VALUE(palette || color_type != PNG_COLOR_TYPE_PALETTE, ImportExportCodes::Failure); // set the palette if (color_type == PNG_COLOR_TYPE_PALETTE) { png_set_PLTE(png_ptr, info_ptr, palette.data(), num_palette); } // Save annotation vKisAnnotationSP_it it = annotationsStart; while (it != annotationsEnd) { if (!(*it) || (*it)->type().isEmpty()) { dbgFile << "Warning: empty annotation"; it++; continue; } dbgFile << "Trying to store annotation of type " << (*it) -> type() << " of size " << (*it) -> annotation() . size(); if ((*it) -> type().startsWith(QString("krita_attribute:"))) { // // Attribute // XXX: it should be possible to save krita_attributes in the \"CHUNKs\"" dbgFile << "cannot save this annotation : " << (*it) -> type(); } else if ((*it)->type() == "kpp_version" || (*it)->type() == "kpp_preset" ) { dbgFile << "Saving preset information " << (*it)->description(); png_textp text = (png_textp) png_malloc(png_ptr, (png_uint_32) sizeof(png_text)); QByteArray keyData = (*it)->description().toLatin1(); text[0].key = keyData.data(); text[0].text = (char*)(*it)->annotation().data(); text[0].text_length = (*it)->annotation().size(); text[0].compression = -1; png_set_text(png_ptr, info_ptr, text, 1); png_free(png_ptr, text); } it++; } // Save the color profile const KoColorProfile* colorProfile = device->colorSpace()->profile(); QByteArray colorProfileData = colorProfile->rawData(); if (!sRGB || options.saveSRGBProfile) { #if PNG_LIBPNG_VER_MAJOR >= 1 && PNG_LIBPNG_VER_MINOR >= 5 const char *typeString = !options.saveAsHDR ? "icc" : "ITUR_2100_PQ_FULL"; png_set_iCCP(png_ptr, info_ptr, (png_const_charp)typeString, PNG_COMPRESSION_TYPE_BASE, (png_const_bytep)colorProfileData.constData(), colorProfileData . size()); #else // older version of libpng has a problem with constness on the parameters char typeStringICC[] = "icc"; char typeStringHDR[] = "ITUR_2100_PQ_FULL"; char *typeString = !options.saveAsHDR ? typeStringICC : typeStringHDR; png_set_iCCP(png_ptr, info_ptr, typeString, PNG_COMPRESSION_TYPE_BASE, colorProfileData.data(), colorProfileData . size()); #endif } // save comments from the document information // warning: according to the official png spec, the keys need to be capitalized! if (m_doc) { png_text texts[4]; int nbtexts = 0; KoDocumentInfo * info = m_doc->documentInfo(); QString title = info->aboutInfo("title"); if (!title.isEmpty() && options.storeMetaData) { fillText(texts + nbtexts, "Title", title); nbtexts++; } QString abstract = info->aboutInfo("subject"); if (abstract.isEmpty()) { abstract = info->aboutInfo("abstract"); } if (!abstract.isEmpty() && options.storeMetaData) { QString keywords = info->aboutInfo("keyword"); if (!keywords.isEmpty()) { abstract = abstract + " keywords: " + keywords; } fillText(texts + nbtexts, "Description", abstract); nbtexts++; } QString license = info->aboutInfo("license"); if (!license.isEmpty() && options.storeMetaData) { fillText(texts + nbtexts, "Copyright", license); nbtexts++; } QString author = info->authorInfo("creator"); if (!author.isEmpty() && options.storeAuthor) { if (!info->authorContactInfo().isEmpty()) { QString contact = info->authorContactInfo().at(0); if (!contact.isEmpty()) { author = author+"("+contact+")"; } } fillText(texts + nbtexts, "Author", author); nbtexts++; } png_set_text(png_ptr, info_ptr, texts, nbtexts); } // Save metadata following imagemagick way // Save exif if (metaData && !metaData->empty()) { 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); writeRawProfile(png_ptr, info_ptr, "exif", buffer.data()); } // 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(); writeRawProfile(png_ptr, info_ptr, "iptc", buffer.data()); } // 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::NoHeader); dbgFile << "XMP information size is" << buffer.data().size(); writeRawProfile(png_ptr, info_ptr, "xmp", buffer.data()); } } #if 0 // Unimplemented? // Save resolution int unit_type; png_uint_32 x_resolution, y_resolution; #endif png_set_pHYs(png_ptr, info_ptr, CM_TO_POINT(xRes) * 100.0, CM_TO_POINT(yRes) * 100.0, PNG_RESOLUTION_METER); // It is the "invert" macro because we convert from pointer-per-inchs to points // Save the information to the file png_write_info(png_ptr, info_ptr); png_write_flush(png_ptr); // swap byteorder on little endian machines. #ifndef WORDS_BIGENDIAN if (color_nb_bits > 8) png_set_swap(png_ptr); #endif // Write the PNG // png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, 0); struct RowPointersStruct { RowPointersStruct(const QSize &size, int pixelSize) : numRows(size.height()) { rows = new png_byte*[numRows]; for (int i = 0; i < numRows; i++) { rows[i] = new png_byte[size.width() * pixelSize]; } } ~RowPointersStruct() { for (int i = 0; i < numRows; i++) { delete[] rows[i]; } delete[] rows; } const int numRows = 0; png_byte** rows = 0; }; // Fill the data structure RowPointersStruct rowPointers(imageRect.size(), device->pixelSize()); int row = 0; for (int y = imageRect.y(); y < imageRect.y() + imageRect.height(); y++, row++) { KisHLineConstIteratorSP it = device->createHLineConstIteratorNG(imageRect.x(), y, imageRect.width()); switch (color_type) { case PNG_COLOR_TYPE_GRAY: case PNG_COLOR_TYPE_GRAY_ALPHA: if (color_nb_bits == 16) { quint16 *dst = reinterpret_cast(rowPointers.rows[row]); do { const quint16 *d = reinterpret_cast(it->oldRawData()); *(dst++) = d[0]; if (options.alpha) *(dst++) = d[1]; } while (it->nextPixel()); } else { quint8 *dst = rowPointers.rows[row]; do { const quint8 *d = it->oldRawData(); *(dst++) = d[0]; if (options.alpha) *(dst++) = d[1]; } while (it->nextPixel()); } break; case PNG_COLOR_TYPE_RGB: case PNG_COLOR_TYPE_RGB_ALPHA: if (color_nb_bits == 16) { quint16 *dst = reinterpret_cast(rowPointers.rows[row]); do { const quint16 *d = reinterpret_cast(it->oldRawData()); *(dst++) = d[2]; *(dst++) = d[1]; *(dst++) = d[0]; if (options.alpha) *(dst++) = d[3]; } while (it->nextPixel()); } else { quint8 *dst = rowPointers.rows[row]; do { const quint8 *d = it->oldRawData(); *(dst++) = d[2]; *(dst++) = d[1]; *(dst++) = d[0]; if (options.alpha) *(dst++) = d[3]; } while (it->nextPixel()); } break; case PNG_COLOR_TYPE_PALETTE: { quint8 *dst = rowPointers.rows[row]; KisPNGWriteStream writestream(dst, color_nb_bits); do { const quint8 *d = it->oldRawData(); int i; for (i = 0; i < num_palette; i++) { if (palette[i].red == d[2] && palette[i].green == d[1] && palette[i].blue == d[0]) { break; } } writestream.setNextValue(i); } while (it->nextPixel()); } break; default: - return KisImageBuilder_RESULT_UNSUPPORTED; + return ImportExportCodes::FormatColorSpaceUnsupported; } } png_write_image(png_ptr, rowPointers.rows); // Writing is over png_write_end(png_ptr, info_ptr); // Free memory png_destroy_write_struct(&png_ptr, &info_ptr); - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } void KisPNGConverter::cancel() { m_stop = true; } void KisPNGConverter::progress(png_structp png_ptr, png_uint_32 row_number, int pass) { if (png_ptr == 0 || row_number > PNG_MAX_UINT || pass > 7) return; // setProgress(row_number); } bool KisPNGConverter::isColorSpaceSupported(const KoColorSpace *cs) { return colorSpaceIdSupported(cs->id()); } diff --git a/libs/ui/kis_png_converter.h b/libs/ui/kis_png_converter.h index 0842928991..e6bb466631 100644 --- a/libs/ui/kis_png_converter.h +++ b/libs/ui/kis_png_converter.h @@ -1,146 +1,146 @@ /* * Copyright (c) 2005, 2007 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. */ #ifndef _KIS_PNG_CONVERTER_H_ #define _KIS_PNG_CONVERTER_H_ #include #include #include #include "kis_types.h" #include "kis_global.h" #include "kis_annotation.h" #include -#include +#include class KoStore; class KisDocument; class KoColorSpace; namespace KisMetaData { class Filter; class Store; } struct KisPNGOptions { KisPNGOptions() : compression(0) , interlace(false) , alpha(true) , exif(true) , iptc(true) , xmp(true) , tryToSaveAsIndexed(true) , saveSRGBProfile(false) , forceSRGB(false) , storeMetaData(false) , storeAuthor(false) , saveAsHDR(false) , transparencyFillColor(Qt::white) {} int compression; bool interlace; bool alpha; bool exif; bool iptc; bool xmp; bool tryToSaveAsIndexed; bool saveSRGBProfile; bool forceSRGB; bool storeMetaData; bool storeAuthor; bool saveAsHDR; QList filters; QColor transparencyFillColor; }; /** * This class allows to import/export a PNG from either a file or a QIODevice. */ // XXX_PROGRESS (pass KoUpdater to the png converter) class KRITAUI_EXPORT KisPNGConverter : public QObject { Q_OBJECT public: /** * Initialize the converter. * @param doc the KisDocument related to the image, can be null if you don't have a KisDocument * @param batchMode whether to use the batch mode */ KisPNGConverter(KisDocument *doc, bool batchMode = false); ~KisPNGConverter() override; public: /** * Load an image from an URL. If the image is not on a local drive, the image is first downloaded to a * temporary location. * @param filename the file name of the image */ - KisImageBuilder_Result buildImage(const QString &filename); + KisImportExportErrorCode buildImage(const QString &filename); /** * Load an image from a QIODevice. * @param iod device to access the data */ - KisImageBuilder_Result buildImage(QIODevice* iod); + KisImportExportErrorCode buildImage(QIODevice* iod); /** * Save a layer to a PNG * @param filename the name of the destination file * @param imageRect the image rectangle to save * @param xRes resolution along x axis * @param yRes resolution along y axis * @param device the paint device to save * @param annotationsStart an iterator on the first annotation * @param annotationsEnd an iterator on the last annotation * @param options PNG formatting options * @param metaData image metadata */ - KisImageBuilder_Result buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData); - KisImageBuilder_Result buildFile(QIODevice*, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData); + KisImportExportErrorCode buildFile(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData); + KisImportExportErrorCode buildFile(QIODevice*, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP device, vKisAnnotationSP_it annotationsStart, vKisAnnotationSP_it annotationsEnd, KisPNGOptions options, KisMetaData::Store* metaData); /** * Retrieve the constructed image */ KisImageSP image(); /** * @brief saveDeviceToStore saves the given paint device to the KoStore. If the device is not 8 bits sRGB, it will be converted to 8 bits sRGB. * @return true if the saving succeeds */ static bool saveDeviceToStore(const QString &filename, const QRect &imageRect, const qreal xRes, const qreal yRes, KisPaintDeviceSP dev, KoStore *store, KisMetaData::Store* metaData = 0); static bool isColorSpaceSupported(const KoColorSpace *cs); public Q_SLOTS: virtual void cancel(); private: void progress(png_structp png_ptr, png_uint_32 row_number, int pass); private: png_uint_32 m_max_row; KisImageSP m_image; KisDocument *m_doc; bool m_stop; bool m_batchMode; QString m_path; }; #endif diff --git a/plugins/extensions/animationrenderer/AnimationRenderer.cpp b/plugins/extensions/animationrenderer/AnimationRenderer.cpp index 8bbb553aa6..718941a02e 100644 --- a/plugins/extensions/animationrenderer/AnimationRenderer.cpp +++ b/plugins/extensions/animationrenderer/AnimationRenderer.cpp @@ -1,206 +1,206 @@ /* * Copyright (c) 2016 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 "AnimationRenderer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include "DlgAnimationRenderer.h" #include #include "video_saver.h" #include "KisAnimationRenderingOptions.h" K_PLUGIN_FACTORY_WITH_JSON(AnimaterionRendererFactory, "kritaanimationrenderer.json", registerPlugin();) AnimaterionRenderer::AnimaterionRenderer(QObject *parent, const QVariantList &) : KisActionPlugin(parent) { // Shows the big dialog KisAction *action = createAction("render_animation"); action->setActivationFlags(KisAction::IMAGE_HAS_ANIMATION); connect(action, SIGNAL(triggered()), this, SLOT(slotRenderAnimation())); // Re-renders the image sequence as defined in the last render action = createAction("render_animation_again"); action->setActivationFlags(KisAction::IMAGE_HAS_ANIMATION); connect(action, SIGNAL(triggered()), this, SLOT(slotRenderSequenceAgain())); } AnimaterionRenderer::~AnimaterionRenderer() { } void AnimaterionRenderer::slotRenderAnimation() { KisImageWSP image = viewManager()->image(); if (!image) return; if (!image->animationInterface()->hasAnimation()) return; KisDocument *doc = viewManager()->document(); DlgAnimationRenderer dlgAnimationRenderer(doc, viewManager()->mainWindow()); dlgAnimationRenderer.setCaption(i18n("Render Animation")); if (dlgAnimationRenderer.exec() == QDialog::Accepted) { KisAnimationRenderingOptions encoderOptions = dlgAnimationRenderer.getEncoderOptions(); renderAnimationImpl(doc, encoderOptions); } } void AnimaterionRenderer::slotRenderSequenceAgain() { KisImageWSP image = viewManager()->image(); if (!image) return; if (!image->animationInterface()->hasAnimation()) return; KisDocument *doc = viewManager()->document(); KisConfig cfg(true); KisPropertiesConfigurationSP settings = cfg.exportConfiguration("ANIMATION_EXPORT"); KisAnimationRenderingOptions encoderOptions; encoderOptions.fromProperties(settings); renderAnimationImpl(doc, encoderOptions); } void AnimaterionRenderer::renderAnimationImpl(KisDocument *doc, KisAnimationRenderingOptions encoderOptions) { const QString frameMimeType = encoderOptions.frameMimeType; const QString framesDirectory = encoderOptions.resolveAbsoluteFramesDirectory(); const QString extension = KisMimeDatabase::suffixesForMimeType(frameMimeType).first(); const QString baseFileName = QString("%1/%2.%3").arg(framesDirectory) .arg(encoderOptions.basename) .arg(extension); /** * The dialog should ensure that the size of the video is even */ KIS_SAFE_ASSERT_RECOVER( !((encoderOptions.width & 0x1 || encoderOptions.height & 0x1) && (encoderOptions.videoMimeType == "video/mp4" || encoderOptions.videoMimeType == "video/x-matroska"))) { encoderOptions.width = encoderOptions.width + (encoderOptions.width & 0x1); encoderOptions.height = encoderOptions.height + (encoderOptions.height & 0x1); } const QSize scaledSize = doc->image()->bounds().size().scaled( encoderOptions.width, encoderOptions.height, Qt::KeepAspectRatio); if ((scaledSize.width() & 0x1 || scaledSize.height() & 0x1) && (encoderOptions.videoMimeType == "video/mp4" || encoderOptions.videoMimeType == "video/x-matroska")) { QString m = "Mastroska (.mkv)"; if (encoderOptions.videoMimeType == "video/mp4") { m = "Mpeg4 (.mp4)"; } qWarning() << m <<"requires width and height to be even, resize and try again!"; doc->setErrorMessage(i18n("%1 requires width and height to be even numbers. Please resize or crop the image before exporting.", m)); QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", doc->errorMessage())); return; } const bool batchMode = false; // TODO: fetch correctly! KisAsyncAnimationFramesSaveDialog exporter(doc->image(), KisTimeRange::fromTime(encoderOptions.firstFrame, encoderOptions.lastFrame), baseFileName, encoderOptions.sequenceStart, encoderOptions.frameExportConfig); exporter.setBatchMode(batchMode); KisAsyncAnimationFramesSaveDialog::Result result = exporter.regenerateRange(viewManager()->mainWindow()->viewManager()); // the folder could have been read-only or something else could happen if (encoderOptions.shouldEncodeVideo && result == KisAsyncAnimationFramesSaveDialog::RenderComplete) { const QString savedFilesMask = exporter.savedFilesMask(); const QString resultFile = encoderOptions.resolveAbsoluteVideoFilePath(); KIS_SAFE_ASSERT_RECOVER_NOOP(QFileInfo(resultFile).isAbsolute()) { const QFileInfo info(resultFile); QDir dir(info.absolutePath()); if (!dir.exists()) { dir.mkpath(info.absolutePath()); } KIS_SAFE_ASSERT_RECOVER_NOOP(dir.exists()); } - KisImportExportFilter::ConversionStatus res; + KisImportExportErrorCode res; QFile fi(resultFile); if (!fi.open(QIODevice::WriteOnly)) { qWarning() << "Could not open" << fi.fileName() << "for writing!"; - doc->setErrorMessage(i18n("Could not open %1 for writing!", fi.fileName())); - res = KisImportExportFilter::CreationError; + res = KisImportExportErrorCannotWrite(fi.error()); } else { fi.close(); } QScopedPointer encoder(new VideoSaver(doc, batchMode)); res = encoder->convert(doc, savedFilesMask, encoderOptions, batchMode); - if (res != KisImportExportFilter::OK) { - QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", doc->errorMessage())); + if (!res.isOk()) { + QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not render animation:\n%1", res.errorMessage())); } if (encoderOptions.shouldDeleteSequence) { QDir d(framesDirectory); QStringList sequenceFiles = d.entryList(QStringList() << encoderOptions.basename + "*." + extension, QDir::Files); Q_FOREACH(const QString &f, sequenceFiles) { d.remove(f); } } } else if (result == KisAsyncAnimationFramesSaveDialog::RenderFailed) { viewManager()->mainWindow()->viewManager()->showFloatingMessage(i18n("Failed to render animation frames!"), QIcon()); } } #include "AnimationRenderer.moc" diff --git a/plugins/extensions/animationrenderer/video_saver.cpp b/plugins/extensions/animationrenderer/video_saver.cpp index 7c0d8f7840..2394d54e2b 100644 --- a/plugins/extensions/animationrenderer/video_saver.cpp +++ b/plugins/extensions/animationrenderer/video_saver.cpp @@ -1,342 +1,331 @@ /* * Copyright (c) 2016 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 "video_saver.h" #include #include #include #include #include #include #include #include #include #include #include "kis_config.h" #include "KisAnimationRenderingOptions.h" #include #include #include #include #include #include #include #include "KisPart.h" class KisFFMpegProgressWatcher : public QObject { Q_OBJECT public: KisFFMpegProgressWatcher(QFile &progressFile, int totalFrames) : m_progressFile(progressFile), m_totalFrames(totalFrames) { connect(&m_progressWatcher, SIGNAL(fileChanged(QString)), SLOT(slotFileChanged())); m_progressWatcher.addPath(m_progressFile.fileName()); } private Q_SLOTS: void slotFileChanged() { int currentFrame = -1; bool isEnded = false; while(!m_progressFile.atEnd()) { QString line = QString(m_progressFile.readLine()).remove(QChar('\n')); QStringList var = line.split("="); if (var[0] == "frame") { currentFrame = var[1].toInt(); } else if (var[0] == "progress") { isEnded = var[1] == "end"; } } if (isEnded) { emit sigProgressChanged(100); emit sigProcessingFinished(); } else { emit sigProgressChanged(100 * currentFrame / m_totalFrames); } } Q_SIGNALS: void sigProgressChanged(int percent); void sigProcessingFinished(); private: QFileSystemWatcher m_progressWatcher; QFile &m_progressFile; int m_totalFrames; }; class KisFFMpegRunner { public: KisFFMpegRunner(const QString &ffmpegPath) : m_cancelled(false), m_ffmpegPath(ffmpegPath) {} public: - KisImageBuilder_Result runFFMpeg(const QStringList &specialArgs, + KisImportExportErrorCode runFFMpeg(const QStringList &specialArgs, const QString &actionName, const QString &logPath, int totalFrames) { dbgFile << "runFFMpeg: specialArgs" << specialArgs << "actionName" << actionName << "logPath" << logPath << "totalFrames" << totalFrames; QTemporaryFile progressFile(QDir::tempPath() + QDir::separator() + "KritaFFmpegProgress.XXXXXX"); progressFile.open(); m_process.setStandardOutputFile(logPath); m_process.setProcessChannelMode(QProcess::MergedChannels); QStringList args; args << "-v" << "debug" << "-nostdin" << "-progress" << progressFile.fileName() << specialArgs; qDebug() << "\t" << m_ffmpegPath << args.join(" "); m_cancelled = false; m_process.start(m_ffmpegPath, args); return waitForFFMpegProcess(actionName, progressFile, m_process, totalFrames); } void cancel() { m_cancelled = true; m_process.kill(); } private: - KisImageBuilder_Result waitForFFMpegProcess(const QString &message, + KisImportExportErrorCode waitForFFMpegProcess(const QString &message, QFile &progressFile, QProcess &ffmpegProcess, int totalFrames) { KisFFMpegProgressWatcher watcher(progressFile, totalFrames); QProgressDialog progress(message, "", 0, 0, KisPart::instance()->currentMainwindow()); progress.setWindowModality(Qt::ApplicationModal); progress.setCancelButton(0); progress.setMinimumDuration(0); progress.setValue(0); progress.setRange(0, 100); QEventLoop loop; loop.connect(&watcher, SIGNAL(sigProcessingFinished()), SLOT(quit())); loop.connect(&ffmpegProcess, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(quit())); loop.connect(&ffmpegProcess, SIGNAL(error(QProcess::ProcessError)), SLOT(quit())); loop.connect(&watcher, SIGNAL(sigProgressChanged(int)), &progress, SLOT(setValue(int))); if (ffmpegProcess.state() != QProcess::NotRunning) { loop.exec(); // wait for some erroneous case ffmpegProcess.waitForFinished(5000); } - KisImageBuilder_Result retval = KisImageBuilder_RESULT_OK; + KisImportExportErrorCode retval = ImportExportCodes::OK; if (ffmpegProcess.state() != QProcess::NotRunning) { // sorry... ffmpegProcess.kill(); - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; } else if (m_cancelled) { - retval = KisImageBuilder_RESULT_CANCEL; + retval = ImportExportCodes::Cancelled; } else if (ffmpegProcess.exitCode()) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; } return retval; } private: QProcess m_process; bool m_cancelled; QString m_ffmpegPath; }; VideoSaver::VideoSaver(KisDocument *doc, bool batchMode) : m_image(doc->image()) , m_doc(doc) , m_batchMode(batchMode) { } VideoSaver::~VideoSaver() { } KisImageSP VideoSaver::image() { return m_image; } -KisImageBuilder_Result VideoSaver::encode(const QString &savedFilesMask, const KisAnimationRenderingOptions &options) +KisImportExportErrorCode VideoSaver::encode(const QString &savedFilesMask, const KisAnimationRenderingOptions &options) { if (!QFileInfo(options.ffmpegPath).exists()) { m_doc->setErrorMessage(i18n("ffmpeg could not be found at %1", options.ffmpegPath)); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::Failure; } - KisImageBuilder_Result result = KisImageBuilder_RESULT_OK; + KisImportExportErrorCode resultOuter = ImportExportCodes::OK; KisImageAnimationInterface *animation = m_image->animationInterface(); const int sequenceNumberingOffset = options.sequenceStart; const KisTimeRange clipRange(sequenceNumberingOffset + options.firstFrame, sequenceNumberingOffset + options.lastFrame); // export dimensions could be off a little bit, so the last force option tweaks the pixels for the export to work const QString exportDimensions = QString("scale=w=") .append(QString::number(options.width)) .append(":h=") .append(QString::number(options.height)) .append(":force_original_aspect_ratio=decrease"); const QString resultFile = options.resolveAbsoluteVideoFilePath(); const QDir videoDir(QFileInfo(resultFile).absolutePath()); const QFileInfo info(resultFile); const QString suffix = info.suffix().toLower(); const QString palettePath = videoDir.filePath("palette.png"); const QStringList additionalOptionsList = options.customFFMpegOptions.split(' ', QString::SkipEmptyParts); QScopedPointer runner(new KisFFMpegRunner(options.ffmpegPath)); if (suffix == "gif") { { QStringList args; args << "-r" << QString::number(options.frameRate) << "-start_number" << QString::number(clipRange.start()) << "-i" << savedFilesMask << "-vf" << "palettegen" << "-y" << palettePath; - KisImageBuilder_Result result = + KisImportExportErrorCode result = runner->runFFMpeg(args, i18n("Fetching palette..."), videoDir.filePath("log_generate_palette_gif.log"), clipRange.duration()); - if (result != KisImageBuilder_RESULT_OK) { + if (!result.isOk()) { return result; } } { QStringList args; args << "-r" << QString::number(options.frameRate) << "-start_number" << QString::number(clipRange.start()) << "-i" << savedFilesMask << "-i" << palettePath << "-lavfi" << "[0:v][1:v] paletteuse" << "-y" << resultFile; // if we are exporting out at a different image size, we apply scaling filter if (m_image->width() != options.width || m_image->height() != options.height) { args << "-vf" << exportDimensions; } dbgFile << "savedFilesMask" << savedFilesMask << "start" << QString::number(clipRange.start()) << "duration" << clipRange.duration(); - KisImageBuilder_Result result = + KisImportExportErrorCode result = runner->runFFMpeg(args, i18n("Encoding frames..."), videoDir.filePath("log_encode_gif.log"), clipRange.duration()); - if (result != KisImageBuilder_RESULT_OK) { + if (!result.isOk()) { return result; } } } else { QStringList args; args << "-r" << QString::number(options.frameRate) << "-start_number" << QString::number(clipRange.start()) << "-i" << savedFilesMask; QFileInfo audioFileInfo = animation->audioChannelFileName(); if (options.includeAudio && audioFileInfo.exists()) { const int msecStart = clipRange.start() * 1000 / animation->framerate(); const int msecDuration = clipRange.duration() * 1000 / animation->framerate(); const QTime startTime = QTime::fromMSecsSinceStartOfDay(msecStart); const QTime durationTime = QTime::fromMSecsSinceStartOfDay(msecDuration); const QString ffmpegTimeFormat("H:m:s.zzz"); args << "-ss" << startTime.toString(ffmpegTimeFormat); args << "-t" << durationTime.toString(ffmpegTimeFormat); args << "-i" << audioFileInfo.absoluteFilePath(); } // if we are exporting out at a different image size, we apply scaling filter // export options HAVE to go after input options, so make sure this is after the audio import if (m_image->width() != options.width || m_image->height() != options.height) { args << "-vf" << exportDimensions; } args << additionalOptionsList << "-y" << resultFile; - result = runner->runFFMpeg(args, i18n("Encoding frames..."), + resultOuter = runner->runFFMpeg(args, i18n("Encoding frames..."), videoDir.filePath("log_encode.log"), clipRange.duration()); } - return result; + return resultOuter; } -KisImportExportFilter::ConversionStatus VideoSaver::convert(KisDocument *document, const QString &savedFilesMask, const KisAnimationRenderingOptions &options, bool batchMode) +KisImportExportErrorCode VideoSaver::convert(KisDocument *document, const QString &savedFilesMask, const KisAnimationRenderingOptions &options, bool batchMode) { VideoSaver videoSaver(document, batchMode); - KisImageBuilder_Result res = videoSaver.encode(savedFilesMask, options); - - if (res == KisImageBuilder_RESULT_OK) { - return KisImportExportFilter::OK; - - } else if (res == KisImageBuilder_RESULT_CANCEL) { - return KisImportExportFilter::ProgressCancelled; - - }else { - document->setErrorMessage(i18n("FFMpeg failed to convert the image sequence. Check the logfile in your output directory for more information.")); - } - - return KisImportExportFilter::InternalError; + KisImportExportErrorCode res = videoSaver.encode(savedFilesMask, options); + return res; } #include "video_saver.moc" diff --git a/plugins/extensions/animationrenderer/video_saver.h b/plugins/extensions/animationrenderer/video_saver.h index 9f2fd15751..e9cb05e217 100644 --- a/plugins/extensions/animationrenderer/video_saver.h +++ b/plugins/extensions/animationrenderer/video_saver.h @@ -1,73 +1,73 @@ /* * Copyright (c) 2016 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 VIDEO_SAVER_H_ #define VIDEO_SAVER_H_ #include #include "kis_types.h" #include "KisImageBuilderResult.h" #include class KisFFMpegRunner; /* The KisImageBuilder_Result definitions come from kis_png_converter.h here */ class KisDocument; class KisAnimationRenderingOptions; class VideoSaver : public QObject { Q_OBJECT public: /** * @brief VideoSaver * This is the object that takes an animation document and config and tells ffmpeg * to render it. Log files are generated here too. * @param doc the document to use for rendering. * @param ffmpegPath the path to the ffmpeg executable. * @param batchMode whether Krita is in batchmde and we can thus not show gui widgets. */ VideoSaver(KisDocument* doc, bool batchMode); ~VideoSaver() override; /** * @brief image * @return get the image used by this exporter. */ KisImageSP image(); /** * @brief encode the main encoding function. * This in turn calls runFFMpeg, which is a private function inside this class. * @param filename the filename to which to render the animation. * @param configuration the configuration * @return whether it is successful or had another failure. */ - KisImageBuilder_Result encode(const QString &savedFilesMask, const KisAnimationRenderingOptions &options); + KisImportExportErrorCode encode(const QString &savedFilesMask, const KisAnimationRenderingOptions &options); - static KisImportExportFilter::ConversionStatus convert(KisDocument *document, const QString &savedFilesMask, const KisAnimationRenderingOptions &options, bool batchMode); + static KisImportExportErrorCode convert(KisDocument *document, const QString &savedFilesMask, const KisAnimationRenderingOptions &options, bool batchMode); private: KisImageSP m_image; KisDocument* m_doc; bool m_batchMode; }; #endif diff --git a/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp b/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp index 1e2049502b..b99a1b89cf 100644 --- a/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp +++ b/plugins/filters/fastcolortransfer/kis_wdg_fastcolortransfer.cpp @@ -1,144 +1,144 @@ /* * This file is part of Krita * * Copyright (c) 2006 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_wdg_fastcolortransfer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_wdgfastcolortransfer.h" KisWdgFastColorTransfer::KisWdgFastColorTransfer(QWidget * parent) : KisConfigWidget(parent) { m_widget = new Ui_WdgFastColorTransfer(); m_widget->setupUi(this); m_widget->fileNameURLRequester->setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import)); connect(m_widget->fileNameURLRequester, SIGNAL(textChanged(QString)), this, SIGNAL(sigConfigurationItemChanged())); } KisWdgFastColorTransfer::~KisWdgFastColorTransfer() { delete m_widget; } void KisWdgFastColorTransfer::setConfiguration(const KisPropertiesConfigurationSP config) { QVariant value; if (config->getProperty("filename", value)) { widget()->fileNameURLRequester->setFileName(value.toString()); } } KisPropertiesConfigurationSP KisWdgFastColorTransfer::configuration() const { KisFilterConfigurationSP config = new KisFilterConfiguration("colortransfer", 1); QString fileName = this->widget()->fileNameURLRequester->fileName(); if (fileName.isEmpty()) return config; KisPaintDeviceSP ref; dbgPlugins << "Use as reference file : " << fileName; KisDocument *d = KisPart::instance()->createDocument(); KisImportExportManager manager(d); - KisImportExportFilter::ConversionStatus status = manager.importDocument(fileName, QString()); - dbgPlugins << "import returned status" << status; + KisImportExportErrorCode status = manager.importDocument(fileName, QString()); + dbgPlugins << "import returned status" << status.errorMessage(); KisImageWSP importedImage = d->image(); if (importedImage) { ref = importedImage->projection(); } if (!ref) { dbgPlugins << "No reference image was specified."; delete d; return config; } // Convert ref to LAB const KoColorSpace* labCS = KoColorSpaceRegistry::instance()->lab16(); if (!labCS) { dbgPlugins << "The LAB colorspace is not available."; delete d; return config; } dbgPlugins << "convert ref to lab"; ref->convertTo(labCS, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); // Compute the means and sigmas of ref double meanL_ref = 0., meanA_ref = 0., meanB_ref = 0.; double sigmaL_ref = 0., sigmaA_ref = 0., sigmaB_ref = 0.; KisSequentialConstIterator refIt(ref, importedImage->bounds()); while (refIt.nextPixel()) { const quint16* data = reinterpret_cast(refIt.oldRawData()); quint32 L = data[0]; quint32 A = data[1]; quint32 B = data[2]; meanL_ref += L; meanA_ref += A; meanB_ref += B; sigmaL_ref += L * L; sigmaA_ref += A * A; sigmaB_ref += B * B; } double totalSize = 1. / (importedImage->width() * importedImage->height()); meanL_ref *= totalSize; meanA_ref *= totalSize; meanB_ref *= totalSize; sigmaL_ref *= totalSize; sigmaA_ref *= totalSize; sigmaB_ref *= totalSize; dbgPlugins << totalSize << "" << meanL_ref << "" << meanA_ref << "" << meanB_ref << "" << sigmaL_ref << "" << sigmaA_ref << "" << sigmaB_ref; config->setProperty("filename", fileName); config->setProperty("meanL", meanL_ref); config->setProperty("meanA", meanA_ref); config->setProperty("meanB", meanB_ref); config->setProperty("sigmaL", sigmaL_ref); config->setProperty("sigmaA", sigmaA_ref); config->setProperty("sigmaB", sigmaB_ref); delete d; return config; } diff --git a/plugins/impex/brush/CMakeLists.txt b/plugins/impex/brush/CMakeLists.txt index ffce44e4d0..3f44e9e589 100644 --- a/plugins/impex/brush/CMakeLists.txt +++ b/plugins/impex/brush/CMakeLists.txt @@ -1,26 +1,28 @@ +add_subdirectory(tests) + set(kritabrushexport_PART_SRCS kis_brush_export.cpp KisAnimatedBrushAnnotation.cpp ) ki18n_wrap_ui(kritabrushexport_PART_SRCS wdg_export_gih.ui) add_library(kritabrushexport MODULE ${kritabrushexport_PART_SRCS}) target_link_libraries(kritabrushexport kritalibbrush kritalibpaintop kritaui kritaimpex) install(TARGETS kritabrushexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) set(kritabrushimport_PART_SRCS kis_brush_import.cpp KisAnimatedBrushAnnotation.cpp ) ki18n_wrap_ui(kritabrushimport_PART_SRCS ) add_library(kritabrushimport MODULE ${kritabrushimport_PART_SRCS}) target_link_libraries(kritabrushimport kritalibbrush kritaui) install(TARGETS kritabrushimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_brush.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/brush/kis_brush_export.cpp b/plugins/impex/brush/kis_brush_export.cpp index 74705909d2..755b2015b9 100644 --- a/plugins/impex/brush/kis_brush_export.cpp +++ b/plugins/impex/brush/kis_brush_export.cpp @@ -1,228 +1,228 @@ /* * Copyright (c) 2016 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_brush_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct KisBrushExportOptions { qreal spacing; bool mask; int brushStyle; int selectionMode; QString name; }; K_PLUGIN_FACTORY_WITH_JSON(KisBrushExportFactory, "krita_brush_export.json", registerPlugin();) KisBrushExport::KisBrushExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisBrushExport::~KisBrushExport() { } -KisImportExportFilter::ConversionStatus KisBrushExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisBrushExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { // XXX: Loading the parasite itself was commented out -- needs investigation // KisAnnotationSP annotation = document->savingImage()->annotation("ImagePipe Parasite"); // KisPipeBrushParasite parasite; // if (annotation) { // QBuffer buf(const_cast(&annotation->annotation())); // buf.open(QBuffer::ReadOnly); // parasite.loadFromDevice(&buf); // buf.close(); // } KisBrushExportOptions exportOptions; if (document->savingImage()->dynamicPropertyNames().contains("brushspacing")) { exportOptions.spacing = document->savingImage()->property("brushspacing").toFloat(); } else { exportOptions.spacing = configuration->getInt("spacing"); } if (!configuration->getString("name").isEmpty()) { exportOptions.name = configuration->getString("name"); } else { exportOptions.name = document->savingImage()->objectName(); } exportOptions.mask = configuration->getBool("mask"); exportOptions.selectionMode = configuration->getInt("selectionMode"); exportOptions.brushStyle = configuration->getInt("brushStyle"); KisGbrBrush *brush = 0; if (mimeType() == "image/x-gimp-brush") { brush = new KisGbrBrush(filename()); } else if (mimeType() == "image/x-gimp-brush-animated") { brush = new KisImagePipeBrush(filename()); } else { - return KisImportExportFilter::BadMimeType; + return ImportExportCodes::FileFormatIncorrect; } qApp->processEvents(); // For vector layers to be updated QRect rc = document->savingImage()->bounds(); brush->setName(exportOptions.name); brush->setSpacing(exportOptions.spacing); brush->setUseColorAsMask(exportOptions.mask); KisImagePipeBrush *pipeBrush = dynamic_cast(brush); if (pipeBrush) { // Create parasite. XXX: share with KisCustomBrushWidget QVector< QVector > devices; devices.push_back(QVector()); KoProperties properties; properties.setProperty("visible", true); QList layers = document->savingImage()->root()->childNodes(QStringList("KisLayer"), properties); Q_FOREACH (KisNodeSP node, layers) { devices[0].push_back(node->projection().data()); } QVector modes; switch (exportOptions.selectionMode) { case 0: modes.push_back(KisParasite::Constant); break; case 1: modes.push_back(KisParasite::Random); break; case 2: modes.push_back(KisParasite::Incremental); break; case 3: modes.push_back(KisParasite::Pressure); break; case 4: modes.push_back(KisParasite::Angular); break; default: modes.push_back(KisParasite::Incremental); } KisPipeBrushParasite parasite; // XXX: share code with KisImagePipeBrush, when we figure out how to support more gih features parasite.dim = devices.count(); // XXX Change for multidim! : parasite.ncells = devices.at(0).count(); parasite.rank[0] = parasite.ncells; // ### This can mask some bugs, be careful here in the future parasite.selection[0] = modes.at(0); // XXX needs movement! parasite.setBrushesCount(); pipeBrush->setParasite(parasite); pipeBrush->setDevices(devices, rc.width(), rc.height()); } else { QImage image = document->savingImage()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); image.save("~/bla.png"); brush->setImage(image); brush->setBrushTipImage(image); } brush->setWidth(rc.width()); brush->setHeight(rc.height()); if (brush->saveToDevice(io)) { - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } else { - return KisImportExportFilter::CreationError; + return ImportExportCodes::Failure; } } KisPropertiesConfigurationSP KisBrushExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("spacing", 1.0); cfg->setProperty("name", ""); cfg->setProperty("mask", true); cfg->setProperty("selectionMode", 0); cfg->setProperty("brushStyle", 0); return cfg; } KisConfigWidget *KisBrushExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &to) const { KisWdgOptionsBrush *wdg = new KisWdgOptionsBrush(parent); if (to == "image/x-gimp-brush") { wdg->groupBox->setVisible(false); } else if (to == "image/x-gimp-brush-animated") { wdg->groupBox->setVisible(true); } return wdg; } void KisBrushExport::initializeCapabilities() { QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "Gimp Brushes"); if (mimeType() == "image/x-gimp-brush-animated") { addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); } } void KisWdgOptionsBrush::setConfiguration(const KisPropertiesConfigurationSP cfg) { spacingWidget->setSpacing(false, cfg->getDouble("spacing")); nameLineEdit->setText(cfg->getString("name")); colorAsMask->setChecked(cfg->getBool("mask")); brushStyle->setCurrentIndex(cfg->getInt("brushStyle")); cmbSelectionMode->setCurrentIndex(cfg->getInt("selectionMode")); } KisPropertiesConfigurationSP KisWdgOptionsBrush::configuration() const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("spacing", spacingWidget->spacing()); cfg->setProperty("name", nameLineEdit->text()); cfg->setProperty("mask", colorAsMask->isChecked()); cfg->setProperty("selectionMode", cmbSelectionMode->currentIndex()); cfg->setProperty("brushStyle", brushStyle->currentIndex()); return cfg; } #include "kis_brush_export.moc" diff --git a/plugins/impex/brush/kis_brush_export.h b/plugins/impex/brush/kis_brush_export.h index 57c5f1449f..681c343ef7 100644 --- a/plugins/impex/brush/kis_brush_export.h +++ b/plugins/impex/brush/kis_brush_export.h @@ -1,68 +1,68 @@ /* * Copyright (c) 2016 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. */ #ifndef _KIS_Brush_EXPORT_H_ #define _KIS_Brush_EXPORT_H_ #include #include #include #include #include class KisWdgOptionsBrush : public KisConfigWidget, public Ui::WdgExportGih { Q_OBJECT public: KisWdgOptionsBrush(QWidget *parent) : KisConfigWidget(parent) { setupUi(this); connect(this->brushStyle, SIGNAL(currentIndexChanged(int)), SLOT(enableSelectionMedthod(int))); } void setConfiguration(const KisPropertiesConfigurationSP cfg) override; KisPropertiesConfigurationSP configuration() const override; public Q_SLOTS: void enableSelectionMedthod(int value) { if (value == 0) { cmbSelectionMode->setEnabled(false); } else { cmbSelectionMode->setEnabled(true); } } }; class KisBrushExport : public KisImportExportFilter { Q_OBJECT public: KisBrushExport(QObject *parent, const QVariantList &); ~KisBrushExport() override; - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/brush/kis_brush_import.cpp b/plugins/impex/brush/kis_brush_import.cpp index fb681bf25e..56bc545c11 100644 --- a/plugins/impex/brush/kis_brush_import.cpp +++ b/plugins/impex/brush/kis_brush_import.cpp @@ -1,123 +1,123 @@ /* * Copyright (c) 2016 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_brush_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KisBrushImportFactory, "krita_brush_import.json", registerPlugin();) KisBrushImport::KisBrushImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisBrushImport::~KisBrushImport() { } -KisImportExportFilter::ConversionStatus KisBrushImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisBrushImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { KisBrush *brush; if (mimeType() == "image/x-gimp-brush") { brush = new KisGbrBrush(filename()); } else if (mimeType() == "image/x-gimp-brush-animated") { brush = new KisImagePipeBrush(filename()); } else { - return KisImportExportFilter::BadMimeType; + return ImportExportCodes::FileFormatIncorrect; } if (!brush->loadFromDevice(io)) { delete brush; - return KisImportExportFilter::InvalidFormat; + return ImportExportCodes::FileFormatIncorrect; } if (!brush->valid()) { delete brush; - return KisImportExportFilter::InvalidFormat; + return ImportExportCodes::FileFormatIncorrect;; } const KoColorSpace *colorSpace = 0; if (brush->hasColor()) { colorSpace = KoColorSpaceRegistry::instance()->rgb8(); } else { colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), ""); } KisImageSP image = new KisImage(document->createUndoStore(), brush->width(), brush->height(), colorSpace, brush->name()); image->setProperty("brushspacing", brush->spacing()); KisImagePipeBrush *pipeBrush = dynamic_cast(brush); if (pipeBrush) { QVector brushes = pipeBrush->brushes(); for(int i = brushes.size(); i > 0; i--) { KisGbrBrush *subbrush = brushes.at(i - 1); const KoColorSpace *subColorSpace = 0; if (brush->hasColor()) { subColorSpace = KoColorSpaceRegistry::instance()->rgb8(); } else { subColorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), ""); } KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255, subColorSpace); layer->paintDevice()->convertFromQImage(subbrush->brushTipImage(), 0, 0, 0); image->addNode(layer, image->rootLayer()); } KisAnnotationSP ann = new KisAnimatedBrushAnnotation(pipeBrush->parasite()); image->addAnnotation(ann); } else { KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255, colorSpace); layer->paintDevice()->convertFromQImage(brush->brushTipImage(), 0, 0, 0); image->addNode(layer, image->rootLayer(), 0); } document->setCurrentImage(image); delete brush; - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include "kis_brush_import.moc" diff --git a/plugins/impex/brush/kis_brush_import.h b/plugins/impex/brush/kis_brush_import.h index ee4f870acd..86b91a20a6 100644 --- a/plugins/impex/brush/kis_brush_import.h +++ b/plugins/impex/brush/kis_brush_import.h @@ -1,37 +1,37 @@ /* * Copyright (c) 2016 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. */ #ifndef _KIS_Brush_IMPORT_H_ #define _KIS_Brush_IMPORT_H_ #include #include class KisBrushImport : public KisImportExportFilter { Q_OBJECT public: KisBrushImport(QObject *parent, const QVariantList &); ~KisBrushImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/brush/tests/CMakeLists.txt b/plugins/impex/brush/tests/CMakeLists.txt new file mode 100644 index 0000000000..d150e0f759 --- /dev/null +++ b/plugins/impex/brush/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisBrushTest.cpp + TEST_NAME KisBrushTest + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/brush/tests/KisBrushTest.cpp similarity index 63% copy from plugins/impex/svg/tests/kis_svg_test.cpp copy to plugins/impex/brush/tests/KisBrushTest.cpp index 6ca66688f8..32877619f1 100644 --- a/plugins/impex/svg/tests/kis_svg_test.cpp +++ b/plugins/impex/brush/tests/KisBrushTest.cpp @@ -1,40 +1,57 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * 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_svg_test.h" +#include "KisBrushTest.h" #include #include -#include - #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif -void KisSvgTest::testFiles() +const QString BrushMimetype = "image/x-gimp-brush"; + + + +void KisBrushTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), BrushMimetype); +} + + +void KisBrushTest::testExportToReadonly() { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50); + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), BrushMimetype); } -KISTEST_MAIN(KisSvgTest) + +void KisBrushTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), BrushMimetype); +} + + + +KISTEST_MAIN(KisBrushTest) + diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/brush/tests/KisBrushTest.h similarity index 73% copy from plugins/impex/jpeg/tests/kis_jpeg_test.h copy to plugins/impex/brush/tests/KisBrushTest.h index 5c19b0cda6..0defe86df7 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/brush/tests/KisBrushTest.h @@ -1,31 +1,35 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * 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_JPEG_TEST_H_ -#define _KIS_JPEG_TEST_H_ +#ifndef _KIS_BRUSH_TEST_H_ +#define _KIS_BRUSH_TEST_H_ #include -class KisJpegTest : public QObject +class KisBrushTest : public QObject { Q_OBJECT private Q_SLOTS: - void testFiles(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; -#endif +#endif // _KIS_BRUSH_TEST_H_ + diff --git a/plugins/impex/brush/tests/data/incorrectFormatFile.txt b/plugins/impex/brush/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/brush/tests/data/readonlyFile.txt b/plugins/impex/brush/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/brush/tests/data/writeonlyFile.txt b/plugins/impex/brush/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/csv/csv_loader.cpp b/plugins/impex/csv/csv_loader.cpp index 6feedd40bc..7ebf6d03fb 100644 --- a/plugins/impex/csv/csv_loader.cpp +++ b/plugins/impex/csv/csv_loader.cpp @@ -1,489 +1,488 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * 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 "csv_loader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "csv_read_line.h" #include "csv_layer_record.h" CSVLoader::CSVLoader(KisDocument *doc, bool batchMode) : m_image(0) , m_doc(doc) , m_batchMode(batchMode) , m_stop(false) { } CSVLoader::~CSVLoader() { } -KisImageBuilder_Result CSVLoader::decode(QIODevice *io, const QString &filename) +KisImportExportErrorCode CSVLoader::decode(QIODevice *io, const QString &filename) { QString field; int idx; int frame = 0; QString projName; int width = 0; int height = 0; int frameCount = 1; float framerate = 24.0; float pixelRatio = 1.0; int projNameIdx = -1; int widthIdx = -1; int heightIdx = -1; int frameCountIdx = -1; int framerateIdx = -1; int pixelRatioIdx = -1; QVector layers; QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); idx = filename.lastIndexOf(QRegExp("[\\/]")); QString base = (idx == -1) ? QString() : filename.left(idx + 1); //include separator QString path = filename; if (path.right(4).toUpper() == ".CSV") path = path.left(path.size() - 4); //according to the QT docs, the slash is a universal directory separator path.append(".frames/"); - KisImageBuilder_Result retval = KisImageBuilder_RESULT_OK; + KisImportExportErrorCode retval = ImportExportCodes::OK; dbgFile << "pos:" << io->pos(); CSVReadLine readLine; QScopedPointer importDoc(KisPart::instance()->createDocument()); importDoc->setInfiniteAutoSaveInterval(); importDoc->setFileBatchMode(true); KisView *setView(0); if (!m_batchMode) { // TODO: use other systems of progress reporting (KisViewManager::createUnthreadedUpdater() // //show the statusbar message even if no view // Q_FOREACH (KisView* view, KisPart::instance()->views()) { // if (view && view->document() == m_doc) { // setView = view; // break; // } // } // if (!setView) { // QStatusBar *sb = KisPart::instance()->currentMainwindow()->statusBar(); // if (sb) { // sb->showMessage(i18n("Loading CSV file...")); // } // } else { // emit m_doc->statusBarMessage(i18n("Loading CSV file...")); // } // emit m_doc->sigProgress(0); // connect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); } int step = 0; do { qApp->processEvents(); if (m_stop) { - retval = KisImageBuilder_RESULT_CANCEL; + retval = ImportExportCodes::Cancelled; break; } if ((idx = readLine.nextLine(io)) <= 0) { if ((idx < 0) ||(step < 5)) - retval = KisImageBuilder_RESULT_FAILURE; - + retval = ImportExportCodes::FileFormatIncorrect; break; } field = readLine.nextField(); //first field of the line if (field.isNull()) continue; //empty row switch (step) { case 0 : //skip first row step = 1; break; case 1 : //scene header names step = 2; for (idx = 0; !field.isNull(); idx++) { if (field == "Project Name") { projNameIdx = idx; } else if (field == "Width") { widthIdx = idx; } else if (field == "Height") { heightIdx = idx; } else if (field == "Frame Count") { frameCountIdx = idx; } else if (field == "Frame Rate") { framerateIdx = idx; } else if (field == "Pixel Aspect Ratio") { pixelRatioIdx = idx; } field= readLine.nextField(); } break; case 2 : //scene header values step= 3; for (idx= 0; !field.isNull(); idx++) { if (idx == projNameIdx) { projName = field; } else if (idx == widthIdx) { width = field.toInt(); } else if (idx == heightIdx) { height = field.toInt(); } else if (idx == frameCountIdx) { frameCount = field.toInt(); if (frameCount < 1) frameCount= 1; } else if (idx == framerateIdx) { framerate = field.toFloat(); } else if (idx == pixelRatioIdx) { pixelRatio = field.toFloat(); } field= readLine.nextField(); } if ((width < 1) || (height < 1)) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } retval = createNewImage(width, height, pixelRatio, projName.isNull() ? filename : projName); break; case 3 : //create level headers if (field[0] != '#') break; for (; !(field = readLine.nextField()).isNull(); ) { CSVLayerRecord* layerRecord = new CSVLayerRecord(); layers.append(layerRecord); } readLine.rewind(); field = readLine.nextField(); step = 4; Q_FALLTHROUGH(); case 4 : //level header if (field == "#Layers") { //layer name for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->name = field; break; } if (field == "#Density") { //layer opacity for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->density = field.toFloat(); break; } if (field == "#Blending") { //layer blending mode for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->blending = field; break; } if (field == "#Visible") { //layer visibility for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->visible = field.toInt(); break; } if (field == "#Folder") { //CSV 1.1 folder location for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) layers.at(idx)->path = validPath(field, base); break; } if ((field.size() < 2) || (field[0] != '#') || !field[1].isDigit()) break; step = 5; Q_FALLTHROUGH(); case 5 : //frames if ((field.size() < 2) || (field[0] != '#') || !field[1].isDigit()) break; for (idx = 0; !(field = readLine.nextField()).isNull() && (idx < layers.size()); idx++) { CSVLayerRecord* layer = layers.at(idx); if (layer->last != field) { if (!m_batchMode) { //emit m_doc->sigProgress((frame * layers.size() + idx) * 100 / // (frameCount * layers.size())); } retval = setLayer(layer, importDoc.data(), path); layer->last = field; layer->frame = frame; } } frame++; break; } - } while (retval == KisImageBuilder_RESULT_OK); + } while (retval.isOk()); //finish the layers - if (retval == KisImageBuilder_RESULT_OK) { + if (retval.isOk()) { if (m_image) { KisImageAnimationInterface *animation = m_image->animationInterface(); if (frame > frameCount) frameCount = frame; animation->setFullClipRange(KisTimeRange::fromTime(0,frameCount - 1)); animation->setFramerate((int)framerate); } for (idx = 0; idx < layers.size(); idx++) { CSVLayerRecord* layer = layers.at(idx); //empty layers without any pictures are dropped if ((layer->frame > 0) || !layer->last.isEmpty()) { retval = setLayer(layer, importDoc.data(), path); - if (retval != KisImageBuilder_RESULT_OK) + if (!retval.isOk()) break; } } } if (m_image) { //insert the existing layers by the right order for (idx = layers.size() - 1; idx >= 0; idx--) { CSVLayerRecord* layer = layers.at(idx); if (layer->layer) { m_image->addNode(layer->layer, m_image->root()); } } m_image->unlock(); } qDeleteAll(layers); io->close(); if (!m_batchMode) { // disconnect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); // emit m_doc->sigProgress(100); if (!setView) { QStatusBar *sb = KisPart::instance()->currentMainwindow()->statusBar(); if (sb) { sb->clearMessage(); } } else { emit m_doc->clearStatusBarMessage(); } } QApplication::restoreOverrideCursor(); return retval; } QString CSVLoader::convertBlending(const QString &blending) { if (blending == "Color") return COMPOSITE_OVER; if (blending == "Behind") return COMPOSITE_BEHIND; if (blending == "Erase") return COMPOSITE_ERASE; // "Shade" if (blending == "Light") return COMPOSITE_LINEAR_LIGHT; if (blending == "Colorize") return COMPOSITE_COLORIZE; if (blending == "Hue") return COMPOSITE_HUE; if (blending == "Add") return COMPOSITE_ADD; if (blending == "Sub") return COMPOSITE_INVERSE_SUBTRACT; if (blending == "Multiply") return COMPOSITE_MULT; if (blending == "Screen") return COMPOSITE_SCREEN; // "Replace" // "Substitute" if (blending == "Difference") return COMPOSITE_DIFF; if (blending == "Divide") return COMPOSITE_DIVIDE; if (blending == "Overlay") return COMPOSITE_OVERLAY; if (blending == "Light2") return COMPOSITE_DODGE; if (blending == "Shade2") return COMPOSITE_BURN; if (blending == "HardLight") return COMPOSITE_HARD_LIGHT; if (blending == "SoftLight") return COMPOSITE_SOFT_LIGHT_PHOTOSHOP; if (blending == "GrainExtract") return COMPOSITE_GRAIN_EXTRACT; if (blending == "GrainMerge") return COMPOSITE_GRAIN_MERGE; if (blending == "Sub2") return COMPOSITE_SUBTRACT; if (blending == "Darken") return COMPOSITE_DARKEN; if (blending == "Lighten") return COMPOSITE_LIGHTEN; if (blending == "Saturation") return COMPOSITE_SATURATION; return COMPOSITE_OVER; } QString CSVLoader::validPath(const QString &path,const QString &base) { //replace Windows directory separators with the universal / QString tryPath= QString(path).replace(QString("\\"), QString("/")); int i = tryPath.lastIndexOf("/"); if (i == (tryPath.size() - 1)) tryPath= tryPath.left(i); //remove the ending separator if exists if (QFileInfo(tryPath).isDir()) return tryPath.append("/"); QString scan(tryPath); i = -1; while ((i= (scan.lastIndexOf("/",i) - 1)) > 0) { //avoid testing if the next level will be the default xxxx.layers folder if ((i >= 6) && (scan.mid(i - 6, 7) == ".layers")) continue; tryPath= QString(base).append(scan.mid(i + 2)); //base already ending with a / if (QFileInfo(tryPath).isDir()) return tryPath.append("/"); } return QString(); //NULL string } -KisImageBuilder_Result CSVLoader::setLayer(CSVLayerRecord* layer, KisDocument *importDoc, const QString &path) +KisImportExportErrorCode CSVLoader::setLayer(CSVLayerRecord* layer, KisDocument *importDoc, const QString &path) { bool result = true; if (layer->channel == 0) { //create a new document layer float opacity = layer->density; if (opacity > 1.0) opacity = 1.0; else if (opacity < 0.0) opacity = 0.0; const KoColorSpace* cs = m_image->colorSpace(); const QString layerName = (layer->name).isEmpty() ? m_image->nextLayerName() : layer->name; KisPaintLayer* paintLayer = new KisPaintLayer(m_image, layerName, (quint8)(opacity * OPACITY_OPAQUE_U8), cs); paintLayer->setCompositeOpId(convertBlending(layer->blending)); paintLayer->setVisible(layer->visible); paintLayer->enableAnimation(); layer->layer = paintLayer; layer->channel = qobject_cast (paintLayer->getKeyframeChannel(KisKeyframeChannel::Content.id(), true)); } if (!layer->last.isEmpty()) { //png image QString filename = layer->path.isNull() ? path : layer->path; filename.append(layer->last); result = importDoc->openUrl(QUrl::fromLocalFile(filename), KisDocument::DontAddToRecent); if (result) layer->channel->importFrame(layer->frame, importDoc->image()->projection(), 0); } else { //blank layer->channel->addKeyframe(layer->frame); } - return (result) ? KisImageBuilder_RESULT_OK : KisImageBuilder_RESULT_FAILURE; + return (result) ? ImportExportCodes::OK : ImportExportCodes::Failure; } -KisImageBuilder_Result CSVLoader::createNewImage(int width, int height, float ratio, const QString &name) +KisImportExportErrorCode CSVLoader::createNewImage(int width, int height, float ratio, const QString &name) { //the CSV is RGBA 8bits, sRGB if (!m_image) { const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), 0); if (cs) m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, name); - if (!m_image) return KisImageBuilder_RESULT_FAILURE; + if (!m_image) return ImportExportCodes::Failure; m_image->setResolution(ratio, 1.0); m_image->lock(); } - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } -KisImageBuilder_Result CSVLoader::buildAnimation(QIODevice *io, const QString &filename) +KisImportExportErrorCode CSVLoader::buildAnimation(QIODevice *io, const QString &filename) { return decode(io, filename); } KisImageSP CSVLoader::image() { return m_image; } void CSVLoader::cancel() { m_stop = true; } diff --git a/plugins/impex/csv/csv_loader.h b/plugins/impex/csv/csv_loader.h index 1b419aa031..8a1d1b5ef4 100644 --- a/plugins/impex/csv/csv_loader.h +++ b/plugins/impex/csv/csv_loader.h @@ -1,62 +1,62 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * 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 CSV_LOADER_H_ #define CSV_LOADER_H_ #include #include #include "kis_image.h" #include "kritaui_export.h" -#include +#include class KisDocument; #include "csv_layer_record.h" class CSVLoader : public QObject { Q_OBJECT public: CSVLoader(KisDocument* doc, bool batchMode); ~CSVLoader() override; - KisImageBuilder_Result buildAnimation(QIODevice *io, const QString &filename); + KisImportExportErrorCode buildAnimation(QIODevice *io, const QString &filename); KisImageSP image(); private: - KisImageBuilder_Result decode(QIODevice *io, const QString &filename); - KisImageBuilder_Result setLayer(CSVLayerRecord* , KisDocument* ,const QString &); - KisImageBuilder_Result createNewImage(int, int, float, const QString &); + KisImportExportErrorCode decode(QIODevice *io, const QString &filename); + KisImportExportErrorCode setLayer(CSVLayerRecord* , KisDocument* ,const QString &); + KisImportExportErrorCode createNewImage(int, int, float, const QString &); QString convertBlending(const QString &); QString validPath(const QString &, const QString &); private Q_SLOTS: void cancel(); private: KisImageSP m_image; KisDocument* m_doc; bool m_batchMode; bool m_stop; }; #endif diff --git a/plugins/impex/csv/csv_saver.cpp b/plugins/impex/csv/csv_saver.cpp index e76ee539b8..ea6e7a9ba1 100644 --- a/plugins/impex/csv/csv_saver.cpp +++ b/plugins/impex/csv/csv_saver.cpp @@ -1,478 +1,476 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * 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 "csv_saver.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "csv_layer_record.h" CSVSaver::CSVSaver(KisDocument *doc, bool batchMode) : m_image(doc->savingImage()) , m_doc(doc) , m_batchMode(batchMode) , m_stop(false) { } CSVSaver::~CSVSaver() { } KisImageSP CSVSaver::image() { return m_image; } -KisImageBuilder_Result CSVSaver::encode(QIODevice *io) +KisImportExportErrorCode CSVSaver::encode(QIODevice *io) { int idx; int start, end; KisNodeSP node; QByteArray ba; KisKeyframeSP keyframe; QVector layers; KisImageAnimationInterface *animation = m_image->animationInterface(); QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); // XXX: Stream was unused? // //DataStream instead of TextStream for correct line endings // QDataStream stream(&f); //Using the original local path QString path = m_doc->localFilePath(); if (path.right(4).toUpper() == ".CSV") path = path.left(path.size() - 4); else { // something is wrong: the local file name is not .csv! // trying the given (probably temporary) filename as well KIS_SAFE_ASSERT_RECOVER(0 && "Wrong extension of the saved file!") { path = path.left(path.size() - 4); } } path.append(".frames"); //create directory QDir dir(path); if (!dir.exists()) { dir.mkpath("."); } //according to the QT docs, the slash is a universal directory separator path.append("/"); node = m_image->rootLayer()->firstChild(); //TODO: correct handling of the layer tree. //for now, only top level paint layers are saved idx = 0; while (node) { if (node->inherits("KisLayer")) { KisLayer* paintLayer = qobject_cast(node.data()); CSVLayerRecord* layerRecord = new CSVLayerRecord(); layers.prepend(layerRecord); //reverse order! layerRecord->name = paintLayer->name(); layerRecord->name.replace(QRegExp("[\"\\r\\n]"), "_"); if (layerRecord->name.isEmpty()) layerRecord->name= QString("Unnamed-%1").arg(idx); layerRecord->visible = (paintLayer->visible()) ? 1 : 0; layerRecord->density = (float)(paintLayer->opacity()) / OPACITY_OPAQUE_U8; layerRecord->blending = convertToBlending(paintLayer->compositeOpId()); layerRecord->layer = paintLayer; layerRecord->channel = paintLayer->original()->keyframeChannel(); layerRecord->last = ""; layerRecord->frame = 0; idx++; } node = node->nextSibling(); } KisTimeRange range = animation->fullClipRange(); start = (range.isValid()) ? range.start() : 0; if (!range.isInfinite()) { end = range.end(); if (end < start) end = start; } else { //undefined length, searching for the last keyframe end = start; for (idx = 0; idx < layers.size(); idx++) { KisRasterKeyframeChannel *channel = layers.at(idx)->channel; if (channel) { keyframe = channel->lastKeyframe(); if ( (!keyframe.isNull()) && (keyframe->time() > end) ) end = keyframe->time(); } } } //create temporary doc for exporting QScopedPointer exportDoc(KisPart::instance()->createDocument()); createTempImage(exportDoc.data()); - KisImageBuilder_Result retval= KisImageBuilder_RESULT_OK; + KisImportExportErrorCode retval= ImportExportCodes::OK; if (!m_batchMode) { // TODO: use other systems of progress reporting (KisViewManager::createUnthreadedUpdater() //emit m_doc->statusBarMessage(i18n("Saving CSV file...")); //emit m_doc->sigProgress(0); //connect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); } int frame = start; int step = 0; do { qApp->processEvents(); if (m_stop) { - retval = KisImageBuilder_RESULT_CANCEL; + retval = ImportExportCodes::Cancelled; break; } switch(step) { case 0 : //first row if (io->write("UTF-8, TVPaint, \"CSV 1.0\"\r\n") < 0) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; } break; case 1 : //scene header names if (io->write("Project Name, Width, Height, Frame Count, Layer Count, Frame Rate, Pixel Aspect Ratio, Field Mode\r\n") < 0) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; } break; case 2 : //scene header values ba = QString("\"%1\", ").arg(m_image->objectName()).toUtf8(); if (io->write(ba.data()) < 0) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } ba = QString("%1, %2, ").arg(m_image->width()).arg(m_image->height()).toUtf8(); if (io->write(ba.data()) < 0) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } ba = QString("%1, %2, ").arg(end - start + 1).arg(layers.size()).toUtf8(); if (io->write(ba.data()) < 0) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } //the framerate is an integer here ba = QString("%1, ").arg((double)(animation->framerate()),0,'f',6).toUtf8(); if (io->write(ba.data()) < 0) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } ba = QString("%1, Progressive\r\n").arg((double)(m_image->xRes() / m_image->yRes()),0,'f',6).toUtf8(); if (io->write(ba.data()) < 0) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } break; case 3 : //layer header values if (io->write("#Layers") < 0) { //Layers - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", \"%1\"").arg(layers.at(idx)->name).toUtf8(); if (io->write(ba.data()) < 0) break; } break; case 4 : if (io->write("\r\n#Density") < 0) { //Density - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", %1").arg((double)(layers.at(idx)->density), 0, 'f', 6).toUtf8(); if (io->write(ba.data()) < 0) break; } break; case 5 : if (io->write("\r\n#Blending") < 0) { //Blending - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", \"%1\"").arg(layers.at(idx)->blending).toUtf8(); if (io->write(ba.data()) < 0) break; } break; case 6 : if (io->write("\r\n#Visible") < 0) { //Visible - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } for (idx = 0; idx < layers.size(); idx++) { ba = QString(", %1").arg(layers.at(idx)->visible).toUtf8(); if (io->write(ba.data()) < 0) break; } if (idx < layers.size()) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; } break; default : //frames if (frame > end) { if (io->write("\r\n") < 0) - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; step = 8; break; } ba = QString("\r\n#%1").arg(frame, 5, 10, QChar('0')).toUtf8(); if (io->write(ba.data()) < 0) { - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; break; } for (idx = 0; idx < layers.size(); idx++) { CSVLayerRecord *layer = layers.at(idx); KisRasterKeyframeChannel *channel = layer->channel; if (channel) { if (frame == start) { keyframe = channel->activeKeyframeAt(frame); } else { keyframe = channel->keyframeAt(frame); } } else { keyframe.clear(); // without animation } if ( !keyframe.isNull() || (frame == start) ) { if (!m_batchMode) { //emit m_doc->sigProgress(((frame - start) * layers.size() + idx) * 100 / // ((end - start) * layers.size())); } retval = getLayer(layer, exportDoc.data(), keyframe, path, frame, idx); - if (retval != KisImageBuilder_RESULT_OK) + if (!retval.isOk()) break; } ba = QString(", \"%1\"").arg(layer->last).toUtf8(); if (io->write(ba.data()) < 0) break; } if (idx < layers.size()) - retval = KisImageBuilder_RESULT_FAILURE; + retval = ImportExportCodes::Failure; frame++; step = 6; //keep step here break; } step++; - } while((retval == KisImageBuilder_RESULT_OK) && (step < 8)); + } while((retval.isOk()) && (step < 8)); qDeleteAll(layers); // io->close(); it seems this is not required anymore if (!m_batchMode) { //disconnect(m_doc, SIGNAL(sigProgressCanceled()), this, SLOT(cancel())); //emit m_doc->sigProgress(100); //emit m_doc->clearStatusBarMessage(); } QApplication::restoreOverrideCursor(); return retval; } QString CSVSaver::convertToBlending(const QString &opid) { if (opid == COMPOSITE_OVER) return "Color"; if (opid == COMPOSITE_BEHIND) return "Behind"; if (opid == COMPOSITE_ERASE) return "Erase"; // "Shade" if (opid == COMPOSITE_LINEAR_LIGHT) return "Light"; if (opid == COMPOSITE_COLORIZE) return "Colorize"; if (opid == COMPOSITE_HUE) return "Hue"; if ((opid == COMPOSITE_ADD) || (opid == COMPOSITE_LINEAR_DODGE)) return "Add"; if (opid == COMPOSITE_INVERSE_SUBTRACT) return "Sub"; if (opid == COMPOSITE_MULT) return "Multiply"; if (opid == COMPOSITE_SCREEN) return "Screen"; // "Replace" // "Substitute" if (opid == COMPOSITE_DIFF) return "Difference"; if (opid == COMPOSITE_DIVIDE) return "Divide"; if (opid == COMPOSITE_OVERLAY) return "Overlay"; if (opid == COMPOSITE_DODGE) return "Light2"; if (opid == COMPOSITE_BURN) return "Shade2"; if (opid == COMPOSITE_HARD_LIGHT) return "HardLight"; if ((opid == COMPOSITE_SOFT_LIGHT_PHOTOSHOP) || (opid == COMPOSITE_SOFT_LIGHT_SVG)) return "SoftLight"; if (opid == COMPOSITE_GRAIN_EXTRACT) return "GrainExtract"; if (opid == COMPOSITE_GRAIN_MERGE) return "GrainMerge"; if (opid == COMPOSITE_SUBTRACT) return "Sub2"; if (opid == COMPOSITE_DARKEN) return "Darken"; if (opid == COMPOSITE_LIGHTEN) return "Lighten"; if (opid == COMPOSITE_SATURATION) return "Saturation"; return "Color"; } -KisImageBuilder_Result CSVSaver::getLayer(CSVLayerRecord* layer, KisDocument* exportDoc, KisKeyframeSP keyframe, const QString &path, int frame, int idx) +KisImportExportErrorCode CSVSaver::getLayer(CSVLayerRecord* layer, KisDocument* exportDoc, KisKeyframeSP keyframe, const QString &path, int frame, int idx) { //render to the temp layer KisImageSP image = exportDoc->savingImage(); if (!image) image= exportDoc->image(); KisPaintDeviceSP device = image->rootLayer()->firstChild()->projection(); if (!keyframe.isNull()) { layer->channel->fetchFrame(keyframe, device); } else { device->makeCloneFrom(layer->layer->projection(),image->bounds()); // without animation } QRect bounds = device->exactBounds(); if (bounds.isEmpty()) { layer->last = ""; //empty frame - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } layer->last = QString("frame%1-%2.png").arg(idx + 1,5,10,QChar('0')).arg(frame,5,10,QChar('0')); QString filename = path; filename.append(layer->last); //save to PNG KisSequentialConstIterator it(device, image->bounds()); const KoColorSpace* cs = device->colorSpace(); bool isThereAlpha = false; while (it.nextPixel()) { if (cs->opacityU8(it.oldRawData()) != OPACITY_OPAQUE_U8) { isThereAlpha = true; break; } } if (!KisPNGConverter::isColorSpaceSupported(cs)) { device = new KisPaintDevice(*device.data()); device->convertTo(KoColorSpaceRegistry::instance()->rgb8()); } KisPNGOptions options; options.alpha = isThereAlpha; options.interlace = false; options.compression = 8; options.tryToSaveAsIndexed = false; options.transparencyFillColor = QColor(0,0,0); options.saveSRGBProfile = true; //TVPaint can use only sRGB options.forceSRGB = false; KisPNGConverter kpc(exportDoc); - KisImageBuilder_Result result = kpc.buildFile(filename, image->bounds(), + KisImportExportErrorCode result = kpc.buildFile(filename, image->bounds(), image->xRes(), image->yRes(), device, image->beginAnnotations(), image->endAnnotations(), options, (KisMetaData::Store* )0 ); return result; } void CSVSaver::createTempImage(KisDocument* exportDoc) { exportDoc->setInfiniteAutoSaveInterval(); exportDoc->setFileBatchMode(true); KisImageSP exportImage = new KisImage(exportDoc->createUndoStore(), m_image->width(), m_image->height(), m_image->colorSpace(), QString()); exportImage->setResolution(m_image->xRes(), m_image->yRes()); exportDoc->setCurrentImage(exportImage); KisPaintLayer* paintLayer = new KisPaintLayer(exportImage, "paint device", OPACITY_OPAQUE_U8); exportImage->addNode(paintLayer, exportImage->rootLayer(), KisLayerSP(0)); } -KisImageBuilder_Result CSVSaver::buildAnimation(QIODevice *io) +KisImportExportErrorCode CSVSaver::buildAnimation(QIODevice *io) { - if (!m_image) { - return KisImageBuilder_RESULT_EMPTY; - } + KIS_ASSERT_RECOVER_RETURN_VALUE(m_image, ImportExportCodes::InternalError); return encode(io); } void CSVSaver::cancel() { m_stop = true; } diff --git a/plugins/impex/csv/csv_saver.h b/plugins/impex/csv/csv_saver.h index 395f84ef23..d382d8a818 100644 --- a/plugins/impex/csv/csv_saver.h +++ b/plugins/impex/csv/csv_saver.h @@ -1,61 +1,61 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * 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 CSV_SAVER_H_ #define CSV_SAVER_H_ #include #include #include "kis_types.h" #include "kis_raster_keyframe_channel.h" #include "kis_png_converter.h" -/* The KisImageBuilder_Result definitions come from kis_png_converter.h here */ +/* The KisImportExportErrorCode definitions come from kis_png_converter.h here */ #include "csv_layer_record.h" class KisDocument; class CSVSaver : public QObject { Q_OBJECT public: CSVSaver(KisDocument* doc, bool batchMode); ~CSVSaver() override; - KisImageBuilder_Result buildAnimation(QIODevice *io); + KisImportExportErrorCode buildAnimation(QIODevice *io); KisImageSP image(); private: - KisImageBuilder_Result encode(QIODevice *io); - KisImageBuilder_Result getLayer(CSVLayerRecord* , KisDocument* , KisKeyframeSP, const QString &, int, int); + KisImportExportErrorCode encode(QIODevice *io); + KisImportExportErrorCode getLayer(CSVLayerRecord* , KisDocument* , KisKeyframeSP, const QString &, int, int); void createTempImage(KisDocument* ); QString convertToBlending(const QString &); private Q_SLOTS: void cancel(); private: KisImageSP m_image; KisDocument* m_doc; bool m_batchMode; bool m_stop; }; #endif diff --git a/plugins/impex/csv/kis_csv_export.cpp b/plugins/impex/csv/kis_csv_export.cpp index 402dc70614..f16924aeab 100644 --- a/plugins/impex/csv/kis_csv_export.cpp +++ b/plugins/impex/csv/kis_csv_export.cpp @@ -1,80 +1,70 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * 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_csv_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "csv_saver.h" K_PLUGIN_FACTORY_WITH_JSON(KisCSVExportFactory, "krita_csv_export.json", registerPlugin();) KisCSVExport::KisCSVExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisCSVExport::~KisCSVExport() { } -KisImportExportFilter::ConversionStatus KisCSVExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisCSVExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { CSVSaver kpc(document, batchMode()); - KisImageBuilder_Result res; - - if ((res = kpc.buildAnimation(io)) == KisImageBuilder_RESULT_OK) { - dbgFile <<"success!"; - return KisImportExportFilter::OK; - } - dbgFile <<" Result =" << res; - - if (res == KisImageBuilder_RESULT_CANCEL) - return KisImportExportFilter::ProgressCancelled; - - return KisImportExportFilter::InternalError; + KisImportExportErrorCode res = kpc.buildAnimation(io); + return res; } void KisCSVExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("AnimationCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "CSV"); addCapability(KisExportCheckRegistry::instance()->get("ColorModelPerLayerCheck/" + RGBAColorModelID.id() + "/" + Integer8BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED)); } #include "kis_csv_export.moc" diff --git a/plugins/impex/csv/kis_csv_export.h b/plugins/impex/csv/kis_csv_export.h index f2063884fb..2ceaa38c4c 100644 --- a/plugins/impex/csv/kis_csv_export.h +++ b/plugins/impex/csv/kis_csv_export.h @@ -1,38 +1,38 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * 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_CSV_EXPORT_H_ #define _KIS_CSV_EXPORT_H_ #include #include class KisCSVExport : public KisImportExportFilter { Q_OBJECT public: KisCSVExport(QObject *parent, const QVariantList &); ~KisCSVExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/csv/kis_csv_import.cpp b/plugins/impex/csv/kis_csv_import.cpp index 4f39d5eb0d..fb9a555d7c 100644 --- a/plugins/impex/csv/kis_csv_import.cpp +++ b/plugins/impex/csv/kis_csv_import.cpp @@ -1,77 +1,55 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * 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_csv_import.h" #include #include #include #include #include #include #include "csv_loader.h" K_PLUGIN_FACTORY_WITH_JSON(CSVImportFactory, "krita_csv_import.json", registerPlugin();) KisCSVImport::KisCSVImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisCSVImport::~KisCSVImport() { } -KisImportExportFilter::ConversionStatus KisCSVImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisCSVImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { CSVLoader ib(document, batchMode()); - - KisImageBuilder_Result result = ib.buildAnimation(io, filename()); - - switch (result) { - case KisImageBuilder_RESULT_UNSUPPORTED: - return KisImportExportFilter::NotImplemented; - case KisImageBuilder_RESULT_INVALID_ARG: - return KisImportExportFilter::BadMimeType; - case KisImageBuilder_RESULT_NO_URI: - case KisImageBuilder_RESULT_NOT_EXIST: - case KisImageBuilder_RESULT_NOT_LOCAL: - qDebug() << "ib returned KisImageBuilder_RESULT_NOT_LOCAL"; - return KisImportExportFilter::FileNotFound; - case KisImageBuilder_RESULT_BAD_FETCH: - case KisImageBuilder_RESULT_EMPTY: - return KisImportExportFilter::ParsingError; - case KisImageBuilder_RESULT_FAILURE: - return KisImportExportFilter::InternalError; - case KisImageBuilder_RESULT_CANCEL: - return KisImportExportFilter::ProgressCancelled; - case KisImageBuilder_RESULT_OK: - document -> setCurrentImage( ib.image()); - return KisImportExportFilter::OK; - default: - return KisImportExportFilter::StorageCreationError; + KisImportExportErrorCode result = ib.buildAnimation(io, filename()); + if (result.isOk()) { + document->setCurrentImage(ib.image()); } - return KisImportExportFilter::InternalError; + return result; } #include diff --git a/plugins/impex/csv/kis_csv_import.h b/plugins/impex/csv/kis_csv_import.h index dd342f05e4..edf85ef992 100644 --- a/plugins/impex/csv/kis_csv_import.h +++ b/plugins/impex/csv/kis_csv_import.h @@ -1,36 +1,36 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * 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_CSV_IMPORT_H_ #define _KIS_CSV_IMPORT_H_ #include #include class KisCSVImport : public KisImportExportFilter { Q_OBJECT public: KisCSVImport(QObject *parent, const QVariantList &); ~KisCSVImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/csv/tests/data/incorrectFormatFile.txt b/plugins/impex/csv/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/csv/tests/data/readonlyFile.txt b/plugins/impex/csv/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/csv/tests/data/writeonlyFile.txt b/plugins/impex/csv/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/csv/tests/kis_csv_test.cpp b/plugins/impex/csv/tests/kis_csv_test.cpp index f68aeff2dd..bd804f3aed 100644 --- a/plugins/impex/csv/tests/kis_csv_test.cpp +++ b/plugins/impex/csv/tests/kis_csv_test.cpp @@ -1,37 +1,58 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * 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_csv_test.h" #include #include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif +const QString CsvMimetype = "text/csv"; + void KisCsvTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList()); } + + +void KisCsvTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), CsvMimetype); +} + + +void KisCsvTest::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), CsvMimetype); +} + + +void KisCsvTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), CsvMimetype); +} + QTEST_MAIN(KisCsvTest) diff --git a/plugins/impex/csv/tests/kis_csv_test.h b/plugins/impex/csv/tests/kis_csv_test.h index c1df27049b..ae4fb3c76f 100644 --- a/plugins/impex/csv/tests/kis_csv_test.h +++ b/plugins/impex/csv/tests/kis_csv_test.h @@ -1,31 +1,34 @@ /* * Copyright (c) 2016 Laszlo Fazekas * * 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_CSV_TEST_H_ #define _KIS_CSV_TEST_H_ #include class KisCsvTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; #endif diff --git a/plugins/impex/exr/exr_converter.cc b/plugins/impex/exr/exr_converter.cc index 7bcdd2cca7..dbd24add84 100644 --- a/plugins/impex/exr/exr_converter.cc +++ b/plugins/impex/exr/exr_converter.cc @@ -1,1373 +1,1399 @@ /* * Copyright (c) 2005 Adrian Page * Copyright (c) 2010 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 "exr_converter.h" #include #include #include #include #include #include #include "exr_extra_tags.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include #include #include #include #include #include #include "kis_kra_savexml_visitor.h" +#include + // Do not translate! #define HDR_LAYER "HDR Layer" template struct Rgba { _T_ r; _T_ g; _T_ b; _T_ a; }; struct ExrGroupLayerInfo; struct ExrLayerInfoBase { ExrLayerInfoBase() : colorSpace(0), parent(0) { } const KoColorSpace* colorSpace; QString name; const ExrGroupLayerInfo* parent; }; struct ExrGroupLayerInfo : public ExrLayerInfoBase { ExrGroupLayerInfo() : groupLayer(0) {} KisGroupLayerSP groupLayer; }; enum ImageType { IT_UNKNOWN, IT_FLOAT16, IT_FLOAT32, IT_UNSUPPORTED }; struct ExrPaintLayerInfo : public ExrLayerInfoBase { ExrPaintLayerInfo() : imageType(IT_UNKNOWN) { } ImageType imageType; QMap< QString, QString> channelMap; ///< first is either R, G, B or A second is the EXR channel name struct Remap { Remap(const QString& _original, const QString& _current) : original(_original), current(_current) { } QString original; QString current; }; QList< Remap > remappedChannels; ///< this is used to store in the metadata the mapping between exr channel name, and channels used in Krita void updateImageType(ImageType channelType); }; void ExrPaintLayerInfo::updateImageType(ImageType channelType) { if (imageType == IT_UNKNOWN) { imageType = channelType; } else if (imageType != channelType) { imageType = IT_UNSUPPORTED; } } struct ExrPaintLayerSaveInfo { QString name; ///< name of the layer with a "." at the end (ie "group1.group2.layer1.") KisPaintDeviceSP layerDevice; KisPaintLayerSP layer; QList channels; Imf::PixelType pixelType; }; struct EXRConverter::Private { Private() : doc(0) , alphaWasModified(false) , showNotifications(false) {} KisImageSP image; KisDocument *doc; bool alphaWasModified; bool showNotifications; QString errorMessage; template void unmultiplyAlpha(typename WrapperType::pixel_type *pixel); template void decodeData4(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype); template void decodeData1(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype); QDomDocument loadExtraLayersInfo(const Imf::Header &header); bool checkExtraLayersInfoConsistent(const QDomDocument &doc, std::set exrLayerNames); void makeLayerNamesUnique(QList& informationObjects); void recBuildPaintLayerSaveInfo(QList& informationObjects, const QString& name, KisGroupLayerSP parent); void reportLayersNotSaved(const QSet &layersNotSaved); QString fetchExtraLayersInfo(QList& informationObjects); }; EXRConverter::EXRConverter(KisDocument *doc, bool showNotifications) : d(new Private) { d->doc = doc; d->showNotifications = showNotifications; // Set thread count for IlmImf library Imf::setGlobalThreadCount(QThread::idealThreadCount()); dbgFile << "EXR Threadcount was set to: " << QThread::idealThreadCount(); } EXRConverter::~EXRConverter() { } ImageType imfTypeToKisType(Imf::PixelType type) { switch (type) { case Imf::UINT: case Imf::NUM_PIXELTYPES: return IT_UNSUPPORTED; case Imf::HALF: return IT_FLOAT16; case Imf::FLOAT: return IT_FLOAT32; default: qFatal("Out of bound enum"); return IT_UNKNOWN; } } const KoColorSpace* kisTypeToColorSpace(QString model, ImageType imageType) { const QString profileName = KisConfig(false).readEntry("ExrDefaultColorProfile", KoColorSpaceRegistry::instance()->defaultProfileForColorSpace(model)); switch (imageType) { case IT_FLOAT16: return KoColorSpaceRegistry::instance()->colorSpace(model, Float16BitsColorDepthID.id(), profileName); case IT_FLOAT32: return KoColorSpaceRegistry::instance()->colorSpace(model, Float32BitsColorDepthID.id(), profileName); case IT_UNKNOWN: case IT_UNSUPPORTED: return 0; default: qFatal("Out of bound enum"); return 0; } } template static inline T alphaEpsilon() { return static_cast(HALF_EPSILON); } template static inline T alphaNoiseThreshold() { return static_cast(0.01); // 1% } static inline bool qFuzzyCompare(half p1, half p2) { return std::abs(p1 - p2) < float(HALF_EPSILON); } static inline bool qFuzzyIsNull(half h) { return std::abs(h) < float(HALF_EPSILON); } template struct RgbPixelWrapper { typedef T channel_type; typedef Rgba pixel_type; RgbPixelWrapper(Rgba &_pixel) : pixel(_pixel) {} inline T alpha() const { return pixel.a; } inline bool checkMultipliedColorsConsistent() const { return !(std::abs(pixel.a) < alphaEpsilon() && (!qFuzzyIsNull(pixel.r) || !qFuzzyIsNull(pixel.g) || !qFuzzyIsNull(pixel.b))); } inline bool checkUnmultipliedColorsConsistent(const Rgba &mult) const { const T alpha = std::abs(pixel.a); return alpha >= alphaNoiseThreshold() || (qFuzzyCompare(T(pixel.r * alpha), mult.r) && qFuzzyCompare(T(pixel.g * alpha), mult.g) && qFuzzyCompare(T(pixel.b * alpha), mult.b)); } inline void setUnmultiplied(const Rgba &mult, T newAlpha) { const T absoluteAlpha = std::abs(newAlpha); pixel.r = mult.r / absoluteAlpha; pixel.g = mult.g / absoluteAlpha; pixel.b = mult.b / absoluteAlpha; pixel.a = newAlpha; } Rgba &pixel; }; template struct GrayPixelWrapper { typedef T channel_type; typedef typename KoGrayTraits::Pixel pixel_type; GrayPixelWrapper(pixel_type &_pixel) : pixel(_pixel) {} inline T alpha() const { return pixel.alpha; } inline bool checkMultipliedColorsConsistent() const { return !(std::abs(pixel.alpha) < alphaEpsilon() && !qFuzzyIsNull(pixel.gray)); } inline bool checkUnmultipliedColorsConsistent(const pixel_type &mult) const { const T alpha = std::abs(pixel.alpha); return alpha >= alphaNoiseThreshold() || qFuzzyCompare(T(pixel.gray * alpha), mult.gray); } inline void setUnmultiplied(const pixel_type &mult, T newAlpha) { const T absoluteAlpha = std::abs(newAlpha); pixel.gray = mult.gray / absoluteAlpha; pixel.alpha = newAlpha; } pixel_type &pixel; }; template void EXRConverter::Private::unmultiplyAlpha(typename WrapperType::pixel_type *pixel) { typedef typename WrapperType::pixel_type pixel_type; typedef typename WrapperType::channel_type channel_type; WrapperType srcPixel(*pixel); if (!srcPixel.checkMultipliedColorsConsistent()) { channel_type newAlpha = srcPixel.alpha(); pixel_type __dstPixelData; WrapperType dstPixel(__dstPixelData); /** * Division by a tiny alpha may result in an overflow of half * value. That is why we use safe iterational approach. */ while (1) { dstPixel.setUnmultiplied(srcPixel.pixel, newAlpha); if (dstPixel.checkUnmultipliedColorsConsistent(srcPixel.pixel)) { break; } newAlpha += alphaEpsilon(); alphaWasModified = true; } *pixel = dstPixel.pixel; } else if (srcPixel.alpha() > 0.0) { srcPixel.setUnmultiplied(srcPixel.pixel, srcPixel.alpha()); } } template void multiplyAlpha(Pixel *pixel) { if (alphaPos >= 0) { T alpha = pixel->data[alphaPos]; if (alpha > 0.0) { for (int i = 0; i < size; ++i) { if (i != alphaPos) { pixel->data[i] *= alpha; } } pixel->data[alphaPos] = alpha; } } } template void EXRConverter::Private::decodeData4(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype) { typedef Rgba<_T_> Rgba; QVector pixels(width * height); bool hasAlpha = info.channelMap.contains("A"); Imf::FrameBuffer frameBuffer; Rgba* frameBufferData = (pixels.data()) - xstart - ystart * width; frameBuffer.insert(info.channelMap["R"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->r, sizeof(Rgba) * 1, sizeof(Rgba) * width)); frameBuffer.insert(info.channelMap["G"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->g, sizeof(Rgba) * 1, sizeof(Rgba) * width)); frameBuffer.insert(info.channelMap["B"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->b, sizeof(Rgba) * 1, sizeof(Rgba) * width)); if (hasAlpha) { frameBuffer.insert(info.channelMap["A"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->a, sizeof(Rgba) * 1, sizeof(Rgba) * width)); } file.setFrameBuffer(frameBuffer); file.readPixels(ystart, height + ystart - 1); Rgba *rgba = pixels.data(); QRect paintRegion(xstart, ystart, width, height); KisSequentialIterator it(layer->paintDevice(), paintRegion); while (it.nextPixel()) { if (hasAlpha) { unmultiplyAlpha >(rgba); } typename KoRgbTraits<_T_>::Pixel* dst = reinterpret_cast::Pixel*>(it.rawData()); dst->red = rgba->r; dst->green = rgba->g; dst->blue = rgba->b; if (hasAlpha) { dst->alpha = rgba->a; } else { dst->alpha = 1.0; } ++rgba; } } template void EXRConverter::Private::decodeData1(Imf::InputFile& file, ExrPaintLayerInfo& info, KisPaintLayerSP layer, int width, int xstart, int ystart, int height, Imf::PixelType ptype) { typedef typename GrayPixelWrapper<_T_>::channel_type channel_type; typedef typename GrayPixelWrapper<_T_>::pixel_type pixel_type; KIS_ASSERT_RECOVER_RETURN( layer->paintDevice()->colorSpace()->colorModelId() == GrayAColorModelID); QVector pixels(width * height); Q_ASSERT(info.channelMap.contains("G")); dbgFile << "G -> " << info.channelMap["G"]; bool hasAlpha = info.channelMap.contains("A"); dbgFile << "Has Alpha:" << hasAlpha; Imf::FrameBuffer frameBuffer; pixel_type* frameBufferData = (pixels.data()) - xstart - ystart * width; frameBuffer.insert(info.channelMap["G"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->gray, sizeof(pixel_type) * 1, sizeof(pixel_type) * width)); if (hasAlpha) { frameBuffer.insert(info.channelMap["A"].toLatin1().constData(), Imf::Slice(ptype, (char *) &frameBufferData->alpha, sizeof(pixel_type) * 1, sizeof(pixel_type) * width)); } file.setFrameBuffer(frameBuffer); file.readPixels(ystart, height + ystart - 1); pixel_type *srcPtr = pixels.data(); QRect paintRegion(xstart, ystart, width, height); KisSequentialIterator it(layer->paintDevice(), paintRegion); do { if (hasAlpha) { unmultiplyAlpha >(srcPtr); } pixel_type* dstPtr = reinterpret_cast(it.rawData()); dstPtr->gray = srcPtr->gray; dstPtr->alpha = hasAlpha ? srcPtr->alpha : channel_type(1.0); ++srcPtr; } while (it.nextPixel()); } bool recCheckGroup(const ExrGroupLayerInfo& group, QStringList list, int idx1, int idx2) { if (idx1 > idx2) return true; if (group.name == list[idx2]) { return recCheckGroup(*group.parent, list, idx1, idx2 - 1); } return false; } ExrGroupLayerInfo* searchGroup(QList* groups, QStringList list, int idx1, int idx2) { if (idx1 > idx2) { return 0; } // Look for the group for (int i = 0; i < groups->size(); ++i) { if (recCheckGroup(groups->at(i), list, idx1, idx2)) { return &(*groups)[i]; } } // Create the group ExrGroupLayerInfo info; info.name = list.at(idx2); info.parent = searchGroup(groups, list, idx1, idx2 - 1); groups->append(info); return &groups->last(); } QDomDocument EXRConverter::Private::loadExtraLayersInfo(const Imf::Header &header) { const Imf::StringAttribute *layersInfoAttribute = header.findTypedAttribute(EXR_KRITA_LAYERS); if (!layersInfoAttribute) return QDomDocument(); QString layersInfoString = QString::fromUtf8(layersInfoAttribute->value().c_str()); QDomDocument doc; doc.setContent(layersInfoString); return doc; } bool EXRConverter::Private::checkExtraLayersInfoConsistent(const QDomDocument &doc, std::set exrLayerNames) { std::set extraInfoLayers; QDomElement root = doc.documentElement(); KIS_ASSERT_RECOVER(!root.isNull() && root.hasChildNodes()) { return false; }; QDomElement el = root.firstChildElement(); while(!el.isNull()) { KIS_ASSERT_RECOVER(el.hasAttribute(EXR_NAME)) { return false; }; QString layerName = el.attribute(EXR_NAME).toUtf8(); if (layerName != QString(HDR_LAYER)) { extraInfoLayers.insert(el.attribute(EXR_NAME).toUtf8().constData()); } el = el.nextSiblingElement(); } bool result = (extraInfoLayers == exrLayerNames); if (!result) { dbgKrita << "WARINING: Krita EXR extra layers info is inconsistent!"; dbgKrita << ppVar(extraInfoLayers.size()) << ppVar(exrLayerNames.size()); std::set::const_iterator it1 = extraInfoLayers.begin(); std::set::const_iterator it2 = exrLayerNames.begin(); std::set::const_iterator end1 = extraInfoLayers.end(); for (; it1 != end1; ++it1, ++it2) { dbgKrita << it1->c_str() << it2->c_str(); } } return result; } -KisImageBuilder_Result EXRConverter::decode(const QString &filename) +KisImportExportErrorCode EXRConverter::decode(const QString &filename) { - Imf::InputFile file(QFile::encodeName(filename)); + try { + Imf::InputFile file(QFile::encodeName(filename)); - Imath::Box2i dw = file.header().dataWindow(); - Imath::Box2i displayWindow = file.header().displayWindow(); + Imath::Box2i dw = file.header().dataWindow(); + Imath::Box2i displayWindow = file.header().displayWindow(); - int width = dw.max.x - dw.min.x + 1; - int height = dw.max.y - dw.min.y + 1; - int dx = dw.min.x; - int dy = dw.min.y; + int width = dw.max.x - dw.min.x + 1; + int height = dw.max.y - dw.min.y + 1; + int dx = dw.min.x; + int dy = dw.min.y; - // Display the attributes of a file - for (Imf::Header::ConstIterator it = file.header().begin(); - it != file.header().end(); ++it) { - dbgFile << "Attribute: " << it.name() << " type: " << it.attribute().typeName(); - } + // Display the attributes of a file + for (Imf::Header::ConstIterator it = file.header().begin(); + it != file.header().end(); ++it) { + dbgFile << "Attribute: " << it.name() << " type: " << it.attribute().typeName(); + } - // fetch Krita's extra layer info, which might have been stored previously - QDomDocument extraLayersInfo = d->loadExtraLayersInfo(file.header()); + // fetch Krita's extra layer info, which might have been stored previously + QDomDocument extraLayersInfo = d->loadExtraLayersInfo(file.header()); - // Construct the list of LayerInfo + // Construct the list of LayerInfo - QList informationObjects; - QList groups; + QList informationObjects; + QList groups; - ImageType imageType = IT_UNKNOWN; + ImageType imageType = IT_UNKNOWN; - const Imf::ChannelList &channels = file.header().channels(); - std::set layerNames; - channels.layers(layerNames); + const Imf::ChannelList &channels = file.header().channels(); + std::set layerNames; + channels.layers(layerNames); - if (!extraLayersInfo.isNull() && - !d->checkExtraLayersInfoConsistent(extraLayersInfo, layerNames)) { + if (!extraLayersInfo.isNull() && + !d->checkExtraLayersInfoConsistent(extraLayersInfo, layerNames)) { - // it is inconsistent anyway - extraLayersInfo = QDomDocument(); - } + // it is inconsistent anyway + extraLayersInfo = QDomDocument(); + } - // Check if there are A, R, G, B channels - - dbgFile << "Checking for ARGB channels, they can occur in single-layer _or_ multi-layer images:"; - ExrPaintLayerInfo info; - bool topLevelRGBFound = false; - info.name = HDR_LAYER; - - QStringList topLevelChannelNames = QStringList() << "A" << "R" << "G" << "B" - << ".A" << ".R" << ".G" << ".B" - << "A." << "R." << "G." << "B." - << "A." << "R." << "G." << "B." - << ".alpha" << ".red" << ".green" << ".blue"; - - for (Imf::ChannelList::ConstIterator i = channels.begin(); i != channels.end(); ++i) { - const Imf::Channel &channel = i.channel(); - dbgFile << "Channel name = " << i.name() << " type = " << channel.type; - - QString qname = i.name(); - if (topLevelChannelNames.contains(qname)) { - topLevelRGBFound = true; - dbgFile << "Found top-level channel" << qname; - info.channelMap[qname] = qname; - info.updateImageType(imfTypeToKisType(channel.type)); + // Check if there are A, R, G, B channels + + dbgFile << "Checking for ARGB channels, they can occur in single-layer _or_ multi-layer images:"; + ExrPaintLayerInfo info; + bool topLevelRGBFound = false; + info.name = HDR_LAYER; + + QStringList topLevelChannelNames = QStringList() << "A" << "R" << "G" << "B" + << ".A" << ".R" << ".G" << ".B" + << "A." << "R." << "G." << "B." + << "A." << "R." << "G." << "B." + << ".alpha" << ".red" << ".green" << ".blue"; + + for (Imf::ChannelList::ConstIterator i = channels.begin(); i != channels.end(); ++i) { + const Imf::Channel &channel = i.channel(); + dbgFile << "Channel name = " << i.name() << " type = " << channel.type; + + QString qname = i.name(); + if (topLevelChannelNames.contains(qname)) { + topLevelRGBFound = true; + dbgFile << "Found top-level channel" << qname; + info.channelMap[qname] = qname; + info.updateImageType(imfTypeToKisType(channel.type)); + } + // Channel names that don't contain a "." or that contain a + // "." only at the beginning or at the end are not considered + // to be part of any layer. + else if (!qname.contains('.') + || !qname.mid(1).contains('.') + || !qname.left(qname.size() - 1).contains('.')) { + warnFile << "Found a top-level channel that is not part of the rendered image" << qname << ". Krita will not load this channel."; + } } - // Channel names that don't contain a "." or that contain a - // "." only at the beginning or at the end are not considered - // to be part of any layer. - else if (!qname.contains('.') - || !qname.mid(1).contains('.') - || !qname.left(qname.size() - 1).contains('.')) { - warnFile << "Found a top-level channel that is not part of the rendered image" << qname << ". Krita will not load this channel."; + if (topLevelRGBFound) { + dbgFile << "Toplevel layer" << info.name << ":Image type:" << imageType << "Layer type" << info.imageType; + informationObjects.push_back(info); + imageType = info.imageType; } - } - if (topLevelRGBFound) { - dbgFile << "Toplevel layer" << info.name << ":Image type:" << imageType << "Layer type" << info.imageType; - informationObjects.push_back(info); - imageType = info.imageType; - } - dbgFile << "Extra layers:" << layerNames.size(); + dbgFile << "Extra layers:" << layerNames.size(); - for (std::set::const_iterator i = layerNames.begin();i != layerNames.end(); ++i) { + for (std::set::const_iterator i = layerNames.begin();i != layerNames.end(); ++i) { - info = ExrPaintLayerInfo(); + info = ExrPaintLayerInfo(); - dbgFile << "layer name = " << i->c_str(); - info.name = i->c_str(); - Imf::ChannelList::ConstIterator layerBegin, layerEnd; - channels.channelsInLayer(*i, layerBegin, layerEnd); - for (Imf::ChannelList::ConstIterator j = layerBegin; - j != layerEnd; ++j) { - const Imf::Channel &channel = j.channel(); + dbgFile << "layer name = " << i->c_str(); + info.name = i->c_str(); + Imf::ChannelList::ConstIterator layerBegin, layerEnd; + channels.channelsInLayer(*i, layerBegin, layerEnd); + for (Imf::ChannelList::ConstIterator j = layerBegin; + j != layerEnd; ++j) { + const Imf::Channel &channel = j.channel(); - info.updateImageType(imfTypeToKisType(channel.type)); + info.updateImageType(imfTypeToKisType(channel.type)); - QString qname = j.name(); - QStringList list = qname.split('.'); - QString layersuffix = list.last(); + QString qname = j.name(); + QStringList list = qname.split('.'); + QString layersuffix = list.last(); - dbgFile << "\tchannel " << j.name() << "suffix" << layersuffix << " type = " << channel.type; + dbgFile << "\tchannel " << j.name() << "suffix" << layersuffix << " type = " << channel.type; - // Nuke writes the channels for sublayers as .red instead of .R, so convert those. - // See https://bugs.kde.org/show_bug.cgi?id=393771 - if (topLevelChannelNames.contains("." + layersuffix)) { - layersuffix = layersuffix.at(0).toUpper(); - } - dbgFile << "\t\tsuffix" << layersuffix; + // Nuke writes the channels for sublayers as .red instead of .R, so convert those. + // See https://bugs.kde.org/show_bug.cgi?id=393771 + if (topLevelChannelNames.contains("." + layersuffix)) { + layersuffix = layersuffix.at(0).toUpper(); + } + dbgFile << "\t\tsuffix" << layersuffix; - if (list.size() > 1) { - info.name = list[list.size()-2]; - info.parent = searchGroup(&groups, list, 0, list.size() - 3); - } + if (list.size() > 1) { + info.name = list[list.size()-2]; + info.parent = searchGroup(&groups, list, 0, list.size() - 3); + } - info.channelMap[layersuffix] = qname; - } + info.channelMap[layersuffix] = qname; + } - if (info.imageType != IT_UNKNOWN && info.imageType != IT_UNSUPPORTED) { - informationObjects.push_back(info); - if (imageType < info.imageType) { - imageType = info.imageType; + if (info.imageType != IT_UNKNOWN && info.imageType != IT_UNSUPPORTED) { + informationObjects.push_back(info); + if (imageType < info.imageType) { + imageType = info.imageType; + } } } - } - dbgFile << "File has" << informationObjects.size() << "layer(s)"; - - // Set the colorspaces - for (int i = 0; i < informationObjects.size(); ++i) { - ExrPaintLayerInfo& info = informationObjects[i]; - QString modelId; - - if (info.channelMap.size() == 1) { - modelId = GrayAColorModelID.id(); - QString key = info.channelMap.begin().key(); - if (key != "G") { - info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(key, "G")); - QString channel = info.channelMap.begin().value(); - info.channelMap.clear(); - info.channelMap["G"] = channel; + dbgFile << "File has" << informationObjects.size() << "layer(s)"; + + // Set the colorspaces + for (int i = 0; i < informationObjects.size(); ++i) { + ExrPaintLayerInfo& info = informationObjects[i]; + QString modelId; + + if (info.channelMap.size() == 1) { + modelId = GrayAColorModelID.id(); + QString key = info.channelMap.begin().key(); + if (key != "G") { + info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(key, "G")); + QString channel = info.channelMap.begin().value(); + info.channelMap.clear(); + info.channelMap["G"] = channel; + } } - } - else if (info.channelMap.size() == 2) { - modelId = GrayAColorModelID.id(); + else if (info.channelMap.size() == 2) { + modelId = GrayAColorModelID.id(); - QMap::const_iterator it = info.channelMap.constBegin(); - QMap::const_iterator end = info.channelMap.constEnd(); + QMap::const_iterator it = info.channelMap.constBegin(); + QMap::const_iterator end = info.channelMap.constEnd(); - QString failingChannelKey; + QString failingChannelKey; - for (; it != end; ++it) { - if (it.key() != "G" && it.key() != "A") { - failingChannelKey = it.key(); - break; + for (; it != end; ++it) { + if (it.key() != "G" && it.key() != "A") { + failingChannelKey = it.key(); + break; + } } - } - info.remappedChannels.push_back( - ExrPaintLayerInfo::Remap(failingChannelKey, "G")); + info.remappedChannels.push_back( + ExrPaintLayerInfo::Remap(failingChannelKey, "G")); - QString failingChannelValue = info.channelMap[failingChannelKey]; - info.channelMap.remove(failingChannelKey); - info.channelMap["G"] = failingChannelValue; + QString failingChannelValue = info.channelMap[failingChannelKey]; + info.channelMap.remove(failingChannelKey); + info.channelMap["G"] = failingChannelValue; - } - else if (info.channelMap.size() == 3 || info.channelMap.size() == 4) { - - if (info.channelMap.contains("R") && info.channelMap.contains("G") && info.channelMap.contains("B")) { - modelId = RGBAColorModelID.id(); } - else if (info.channelMap.contains("X") && info.channelMap.contains("Y") && info.channelMap.contains("Z")) { - modelId = XYZAColorModelID.id(); - QMap newChannelMap; - if (info.channelMap.contains("W")) { - newChannelMap["A"] = info.channelMap["W"]; - info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("W", "A")); - info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("X", "X")); - info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Y", "Y")); - info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Z", "Z")); - } else if (info.channelMap.contains("A")) { - newChannelMap["A"] = info.channelMap["A"]; + else if (info.channelMap.size() == 3 || info.channelMap.size() == 4) { + + if (info.channelMap.contains("R") && info.channelMap.contains("G") && info.channelMap.contains("B")) { + modelId = RGBAColorModelID.id(); } - // The decode function expect R, G, B in the channel map - newChannelMap["B"] = info.channelMap["X"]; - newChannelMap["G"] = info.channelMap["Y"]; - newChannelMap["R"] = info.channelMap["Z"]; - info.channelMap = newChannelMap; - } - else { - modelId = RGBAColorModelID.id(); - QMap newChannelMap; - QMap::iterator it = info.channelMap.begin(); - newChannelMap["R"] = it.value(); - info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "R")); - ++it; - newChannelMap["G"] = it.value(); - info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "G")); - ++it; - newChannelMap["B"] = it.value(); - info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "B")); - if (info.channelMap.size() == 4) { - ++it; - newChannelMap["A"] = it.value(); - info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "A")); + else if (info.channelMap.contains("X") && info.channelMap.contains("Y") && info.channelMap.contains("Z")) { + modelId = XYZAColorModelID.id(); + QMap newChannelMap; + if (info.channelMap.contains("W")) { + newChannelMap["A"] = info.channelMap["W"]; + info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("W", "A")); + info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("X", "X")); + info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Y", "Y")); + info.remappedChannels.push_back(ExrPaintLayerInfo::Remap("Z", "Z")); + } else if (info.channelMap.contains("A")) { + newChannelMap["A"] = info.channelMap["A"]; + } + // The decode function expect R, G, B in the channel map + newChannelMap["B"] = info.channelMap["X"]; + newChannelMap["G"] = info.channelMap["Y"]; + newChannelMap["R"] = info.channelMap["Z"]; + info.channelMap = newChannelMap; } + else { + modelId = RGBAColorModelID.id(); + QMap newChannelMap; + QMap::iterator it = info.channelMap.begin(); + newChannelMap["R"] = it.value(); + info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "R")); + ++it; + newChannelMap["G"] = it.value(); + info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "G")); + ++it; + newChannelMap["B"] = it.value(); + info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "B")); + if (info.channelMap.size() == 4) { + ++it; + newChannelMap["A"] = it.value(); + info.remappedChannels.push_back(ExrPaintLayerInfo::Remap(it.key(), "A")); + } - info.channelMap = newChannelMap; + info.channelMap = newChannelMap; + } + } + else { + dbgFile << info.name << "has" << info.channelMap.size() << "channels, and we don't know what to do."; + } + if (!modelId.isEmpty()) { + info.colorSpace = kisTypeToColorSpace(modelId, info.imageType); } } - else { - dbgFile << info.name << "has" << info.channelMap.size() << "channels, and we don't know what to do."; - } - if (!modelId.isEmpty()) { - info.colorSpace = kisTypeToColorSpace(modelId, info.imageType); - } - } - // Get colorspace - dbgFile << "Image type = " << imageType; - const KoColorSpace* colorSpace = kisTypeToColorSpace(RGBAColorModelID.id(), imageType); + // Get colorspace + dbgFile << "Image type = " << imageType; + const KoColorSpace* colorSpace = kisTypeToColorSpace(RGBAColorModelID.id(), imageType); - if (!colorSpace) return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; - dbgFile << "Colorspace: " << colorSpace->name(); + if (!colorSpace) return ImportExportCodes::FormatColorSpaceUnsupported; + dbgFile << "Colorspace: " << colorSpace->name(); - // Set the colorspace on all groups - for (int i = 0; i < groups.size(); ++i) { - ExrGroupLayerInfo& info = groups[i]; - info.colorSpace = colorSpace; - } + // Set the colorspace on all groups + for (int i = 0; i < groups.size(); ++i) { + ExrGroupLayerInfo& info = groups[i]; + info.colorSpace = colorSpace; + } - // Create the image - // Make sure the created image is the same size as the displayWindow since - // the dataWindow can be cropped in some cases. - int displayWidth = displayWindow.max.x - displayWindow.min.x + 1; - int displayHeight = displayWindow.max.y - displayWindow.min.y + 1; - d->image = new KisImage(d->doc->createUndoStore(), displayWidth, displayHeight, colorSpace, ""); + // Create the image + // Make sure the created image is the same size as the displayWindow since + // the dataWindow can be cropped in some cases. + int displayWidth = displayWindow.max.x - displayWindow.min.x + 1; + int displayHeight = displayWindow.max.y - displayWindow.min.y + 1; + d->image = new KisImage(d->doc->createUndoStore(), displayWidth, displayHeight, colorSpace, ""); - if (!d->image) { - return KisImageBuilder_RESULT_FAILURE; - } + if (!d->image) { + return ImportExportCodes::Failure; + } - /** - * EXR semi-transparent images are expected to be rendered on - * black to ensure correctness of the light model - */ - d->image->setDefaultProjectionColor(KoColor(Qt::black, colorSpace)); - - // Create group layers - for (int i = 0; i < groups.size(); ++i) { - ExrGroupLayerInfo& info = groups[i]; - Q_ASSERT(info.parent == 0 || info.parent->groupLayer); - KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer(); - info.groupLayer = new KisGroupLayer(d->image, info.name, OPACITY_OPAQUE_U8); - d->image->addNode(info.groupLayer, groupLayerParent); - } + /** + * EXR semi-transparent images are expected to be rendered on + * black to ensure correctness of the light model + */ + d->image->setDefaultProjectionColor(KoColor(Qt::black, colorSpace)); - // Load the layers - for (int i = informationObjects.size() - 1; i >= 0; --i) { - ExrPaintLayerInfo& info = informationObjects[i]; - if (info.colorSpace) { - dbgFile << "Decoding " << info.name << " with " << info.channelMap.size() << " channels, and color space " << info.colorSpace->id(); - KisPaintLayerSP layer = new KisPaintLayer(d->image, info.name, OPACITY_OPAQUE_U8, info.colorSpace); + // Create group layers + for (int i = 0; i < groups.size(); ++i) { + ExrGroupLayerInfo& info = groups[i]; + Q_ASSERT(info.parent == 0 || info.parent->groupLayer); + KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer(); + info.groupLayer = new KisGroupLayer(d->image, info.name, OPACITY_OPAQUE_U8); + d->image->addNode(info.groupLayer, groupLayerParent); + } - layer->setCompositeOpId(COMPOSITE_OVER); + // Load the layers + for (int i = informationObjects.size() - 1; i >= 0; --i) { + ExrPaintLayerInfo& info = informationObjects[i]; + if (info.colorSpace) { + dbgFile << "Decoding " << info.name << " with " << info.channelMap.size() << " channels, and color space " << info.colorSpace->id(); + KisPaintLayerSP layer = new KisPaintLayer(d->image, info.name, OPACITY_OPAQUE_U8, info.colorSpace); - if (!layer) { - return KisImageBuilder_RESULT_FAILURE; - } + if (!layer) { + return ImportExportCodes::Failure; + } - switch (info.channelMap.size()) { - case 1: - case 2: - // Decode the data - switch (info.imageType) { - case IT_FLOAT16: - d->decodeData1(file, info, layer, width, dx, dy, height, Imf::HALF); + layer->setCompositeOpId(COMPOSITE_OVER); + + switch (info.channelMap.size()) { + case 1: + case 2: + // Decode the data + switch (info.imageType) { + case IT_FLOAT16: + d->decodeData1(file, info, layer, width, dx, dy, height, Imf::HALF); + break; + case IT_FLOAT32: + d->decodeData1(file, info, layer, width, dx, dy, height, Imf::FLOAT); + break; + case IT_UNKNOWN: + case IT_UNSUPPORTED: + qFatal("Impossible error"); + } break; - case IT_FLOAT32: - d->decodeData1(file, info, layer, width, dx, dy, height, Imf::FLOAT); + case 3: + case 4: + // Decode the data + switch (info.imageType) { + case IT_FLOAT16: + d->decodeData4(file, info, layer, width, dx, dy, height, Imf::HALF); + break; + case IT_FLOAT32: + d->decodeData4(file, info, layer, width, dx, dy, height, Imf::FLOAT); + break; + case IT_UNKNOWN: + case IT_UNSUPPORTED: + qFatal("Impossible error"); + } break; - case IT_UNKNOWN: - case IT_UNSUPPORTED: - qFatal("Impossible error"); + default: + qFatal("Invalid number of channels: %i", info.channelMap.size()); } - break; - case 3: - case 4: - // Decode the data - switch (info.imageType) { - case IT_FLOAT16: - d->decodeData4(file, info, layer, width, dx, dy, height, Imf::HALF); - break; - case IT_FLOAT32: - d->decodeData4(file, info, layer, width, dx, dy, height, Imf::FLOAT); - break; - case IT_UNKNOWN: - case IT_UNSUPPORTED: - qFatal("Impossible error"); + // Check if should set the channels + if (!info.remappedChannels.isEmpty()) { + QList values; + Q_FOREACH (const ExrPaintLayerInfo::Remap& remap, info.remappedChannels) { + QMap map; + map["original"] = KisMetaData::Value(remap.original); + map["current"] = KisMetaData::Value(remap.current); + values.append(map); + } + layer->metaData()->addEntry(KisMetaData::Entry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap", values)); } - break; - default: - qFatal("Invalid number of channels: %i", info.channelMap.size()); + // Add the layer + KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer(); + d->image->addNode(layer, groupLayerParent); + } else { + dbgFile << "No decoding " << info.name << " with " << info.channelMap.size() << " channels, and lack of a color space"; } - // Check if should set the channels - if (!info.remappedChannels.isEmpty()) { - QList values; - Q_FOREACH (const ExrPaintLayerInfo::Remap& remap, info.remappedChannels) { - QMap map; - map["original"] = KisMetaData::Value(remap.original); - map["current"] = KisMetaData::Value(remap.current); - values.append(map); - } - layer->metaData()->addEntry(KisMetaData::Entry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap", values)); + } + // Set projectionColor to opaque + d->image->setDefaultProjectionColor(KoColor(Qt::transparent, colorSpace)); + + // After reading the image, notify the user about changed alpha. + if (d->alphaWasModified) { + QString msg = + i18nc("@info", + "The image contains pixels with zero alpha channel and non-zero " + "color channels. Krita has modified those pixels to have " + "at least some alpha. The initial values will not " + "be reverted on saving the image back." + "

" + "This will hardly make any visual difference just keep it in mind."); + if (d->showNotifications) { + QMessageBox::information(0, i18nc("@title:window", "EXR image has been modified"), msg); + } else { + warnKrita << "WARNING:" << msg; } - // Add the layer - KisGroupLayerSP groupLayerParent = (info.parent) ? info.parent->groupLayer : d->image->rootLayer(); - d->image->addNode(layer, groupLayerParent); - } else { - dbgFile << "No decoding " << info.name << " with " << info.channelMap.size() << " channels, and lack of a color space"; } - } - // Set projectionColor to opaque - d->image->setDefaultProjectionColor(KoColor(Qt::transparent, colorSpace)); - - // After reading the image, notify the user about changed alpha. - if (d->alphaWasModified) { - QString msg = - i18nc("@info", - "The image contains pixels with zero alpha channel and non-zero " - "color channels. Krita has modified those pixels to have " - "at least some alpha. The initial values will not " - "be reverted on saving the image back." - "

" - "This will hardly make any visual difference just keep it in mind."); - if (d->showNotifications) { - QMessageBox::information(0, i18nc("@title:window", "EXR image has been modified"), msg); - } else { - warnKrita << "WARNING:" << msg; + + if (!extraLayersInfo.isNull()) { + KisExrLayersSorter sorter(extraLayersInfo, d->image); } - } - if (!extraLayersInfo.isNull()) { - KisExrLayersSorter sorter(extraLayersInfo, d->image); + return ImportExportCodes::OK; + + } catch (std::exception &e) { + dbgFile << "Error while reading from the exr file: " << e.what(); + + if (!KisImportExportAdditionalChecks::doesFileExist(filename)) { + return ImportExportCodes::FileNotExist; + } else if(!KisImportExportAdditionalChecks::isFileReadable(filename)) { + return ImportExportCodes::NoAccessToRead; + } else { + return ImportExportCodes::ErrorWhileReading; + } } - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } -KisImageBuilder_Result EXRConverter::buildImage(const QString &filename) +KisImportExportErrorCode EXRConverter::buildImage(const QString &filename) { return decode(filename); } KisImageSP EXRConverter::image() { return d->image; } QString EXRConverter::errorMessage() const { return d->errorMessage; } template struct ExrPixel_ { _T_ data[size]; }; class Encoder { public: virtual ~Encoder() {} virtual void prepareFrameBuffer(Imf::FrameBuffer*, int line) = 0; virtual void encodeData(int line) = 0; }; template class EncoderImpl : public Encoder { public: EncoderImpl(Imf::OutputFile* _file, const ExrPaintLayerSaveInfo* _info, int width) : file(_file), info(_info), pixels(width), m_width(width) {} ~EncoderImpl() override {} void prepareFrameBuffer(Imf::FrameBuffer*, int line) override; void encodeData(int line) override; private: typedef ExrPixel_<_T_, size> ExrPixel; Imf::OutputFile* file; const ExrPaintLayerSaveInfo* info; QVector pixels; int m_width; }; template void EncoderImpl<_T_, size, alphaPos>::prepareFrameBuffer(Imf::FrameBuffer* frameBuffer, int line) { int xstart = 0; int ystart = 0; ExrPixel* frameBufferData = (pixels.data()) - xstart - (ystart + line) * m_width; for (int k = 0; k < size; ++k) { frameBuffer->insert(info->channels[k].toUtf8(), Imf::Slice(info->pixelType, (char *) &frameBufferData->data[k], sizeof(ExrPixel) * 1, sizeof(ExrPixel) * m_width)); } } template void EncoderImpl<_T_, size, alphaPos>::encodeData(int line) { ExrPixel *rgba = pixels.data(); KisHLineConstIteratorSP it = info->layerDevice->createHLineConstIteratorNG(0, line, m_width); do { const _T_* dst = reinterpret_cast < const _T_* >(it->oldRawData()); for (int i = 0; i < size; ++i) { rgba->data[i] = dst[i]; } if (alphaPos != -1) { multiplyAlpha<_T_, ExrPixel, size, alphaPos>(rgba); } ++rgba; } while (it->nextPixel()); } Encoder* encoder(Imf::OutputFile& file, const ExrPaintLayerSaveInfo& info, int width) { dbgFile << "Create encoder for" << info.name << info.channels << info.layerDevice->colorSpace()->channelCount(); switch (info.layerDevice->colorSpace()->channelCount()) { case 1: { if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::HALF); return new EncoderImpl < half, 1, -1 > (&file, &info, width); } else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::FLOAT); return new EncoderImpl < float, 1, -1 > (&file, &info, width); } break; } case 2: { if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::HALF); return new EncoderImpl(&file, &info, width); } else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::FLOAT); return new EncoderImpl(&file, &info, width); } break; } case 4: { if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::HALF); return new EncoderImpl(&file, &info, width); } else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { Q_ASSERT(info.pixelType == Imf::FLOAT); return new EncoderImpl(&file, &info, width); } break; } default: qFatal("Impossible error"); } return 0; } void encodeData(Imf::OutputFile& file, const QList& informationObjects, int width, int height) { QList encoders; Q_FOREACH (const ExrPaintLayerSaveInfo& info, informationObjects) { encoders.push_back(encoder(file, info, width)); } for (int y = 0; y < height; ++y) { Imf::FrameBuffer frameBuffer; Q_FOREACH (Encoder* encoder, encoders) { encoder->prepareFrameBuffer(&frameBuffer, y); } file.setFrameBuffer(frameBuffer); Q_FOREACH (Encoder* encoder, encoders) { encoder->encodeData(y); } file.writePixels(1); } qDeleteAll(encoders); } KisPaintDeviceSP wrapLayerDevice(KisPaintDeviceSP device) { const KoColorSpace *cs = device->colorSpace(); if (cs->colorDepthId() != Float16BitsColorDepthID && cs->colorDepthId() != Float32BitsColorDepthID) { cs = KoColorSpaceRegistry::instance()->colorSpace( cs->colorModelId() == GrayAColorModelID ? GrayAColorModelID.id() : RGBAColorModelID.id(), Float16BitsColorDepthID.id()); } else if (cs->colorModelId() != GrayColorModelID && cs->colorModelId() != RGBAColorModelID) { cs = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), cs->colorDepthId().id()); } if (*cs != *device->colorSpace()) { device = new KisPaintDevice(*device); device->convertTo(cs); } return device; } -KisImageBuilder_Result EXRConverter::buildFile(const QString &filename, KisPaintLayerSP layer) +KisImportExportErrorCode EXRConverter::buildFile(const QString &filename, KisPaintLayerSP layer) { - if (!layer) - return KisImageBuilder_RESULT_INVALID_ARG; + KIS_ASSERT_RECOVER_RETURN_VALUE(layer, ImportExportCodes::InternalError); KisImageSP image = layer->image(); - if (!image) - return KisImageBuilder_RESULT_EMPTY; + KIS_ASSERT_RECOVER_RETURN_VALUE(image, ImportExportCodes::InternalError); + // Make the header qint32 height = image->height(); qint32 width = image->width(); Imf::Header header(width, height); ExrPaintLayerSaveInfo info; info.layer = layer; info.layerDevice = wrapLayerDevice(layer->paintDevice()); - Imf::PixelType pixelType = Imf::NUM_PIXELTYPES; if (info.layerDevice->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { pixelType = Imf::HALF; } else if (info.layerDevice->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { pixelType = Imf::FLOAT; } - + ENTER_FUNCTION() << "(4)"; header.channels().insert("R", Imf::Channel(pixelType)); header.channels().insert("G", Imf::Channel(pixelType)); header.channels().insert("B", Imf::Channel(pixelType)); header.channels().insert("A", Imf::Channel(pixelType)); info.channels.push_back("R"); info.channels.push_back("G"); info.channels.push_back("B"); info.channels.push_back("A"); info.pixelType = pixelType; + ENTER_FUNCTION() << "(5)"; // Open file for writing - Imf::OutputFile file(QFile::encodeName(filename), header); + try { + Imf::OutputFile file(QFile::encodeName(filename), header); - QList informationObjects; - informationObjects.push_back(info); + QList informationObjects; + informationObjects.push_back(info); + encodeData(file, informationObjects, width, height); + return ImportExportCodes::OK; - encodeData(file, informationObjects, width, height); + } catch(std::exception &e) { + dbgFile << "Exception while writing to exr file: " << e.what(); + if (!KisImportExportAdditionalChecks::isFileWritable(QFile::encodeName(filename))) { + return ImportExportCodes::NoAccessToWrite; + } + return ImportExportCodes::Failure; + } - return KisImageBuilder_RESULT_OK; } QString remap(const QMap& current2original, const QString& current) { if (current2original.contains(current)) { return current2original[current]; } return current; } void EXRConverter::Private::makeLayerNamesUnique(QList& informationObjects) { typedef QMultiMap::iterator> NamesMap; NamesMap namesMap; { QList::iterator it = informationObjects.begin(); QList::iterator end = informationObjects.end(); for (; it != end; ++it) { namesMap.insert(it->name, it); } } Q_FOREACH (const QString &key, namesMap.keys()) { if (namesMap.count(key) > 1) { KIS_ASSERT_RECOVER(key.endsWith(".")) { continue; } QString strippedName = key.left(key.size() - 1); // trim the ending dot int nameCounter = 0; NamesMap::iterator it = namesMap.find(key); NamesMap::iterator end = namesMap.end(); for (; it != end; ++it) { QString newName = QString("%1_%2.") .arg(strippedName) .arg(nameCounter++); it.value()->name = newName; QList::iterator channelsIt = it.value()->channels.begin(); QList::iterator channelsEnd = it.value()->channels.end(); for (; channelsIt != channelsEnd; ++channelsIt) { channelsIt->replace(key, newName); } } } } } void EXRConverter::Private::recBuildPaintLayerSaveInfo(QList& informationObjects, const QString& name, KisGroupLayerSP parent) { QSet layersNotSaved; for (uint i = 0; i < parent->childCount(); ++i) { KisNodeSP node = parent->at(i); if (KisPaintLayerSP paintLayer = dynamic_cast(node.data())) { QMap current2original; if (paintLayer->metaData()->containsEntry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap")) { const KisMetaData::Entry& entry = paintLayer->metaData()->getEntry(KisMetaData::SchemaRegistry::instance()->create("http://krita.org/exrchannels/1.0/" , "exrchannels"), "channelsmap"); QList< KisMetaData::Value> values = entry.value().asArray(); Q_FOREACH (const KisMetaData::Value& value, values) { QMap map = value.asStructure(); if (map.contains("original") && map.contains("current")) { current2original[map["current"].toString()] = map["original"].toString(); } } } ExrPaintLayerSaveInfo info; info.name = name + paintLayer->name() + '.'; info.layer = paintLayer; info.layerDevice = wrapLayerDevice(paintLayer->paintDevice()); if (info.name == QString(HDR_LAYER) + ".") { info.channels.push_back("R"); info.channels.push_back("G"); info.channels.push_back("B"); info.channels.push_back("A"); } else { if (paintLayer->colorSpace()->colorModelId() == RGBAColorModelID) { info.channels.push_back(info.name + remap(current2original, "R")); info.channels.push_back(info.name + remap(current2original, "G")); info.channels.push_back(info.name + remap(current2original, "B")); info.channels.push_back(info.name + remap(current2original, "A")); } else if (paintLayer->colorSpace()->colorModelId() == GrayAColorModelID) { info.channels.push_back(info.name + remap(current2original, "G")); info.channels.push_back(info.name + remap(current2original, "A")); } else if (paintLayer->colorSpace()->colorModelId() == GrayColorModelID) { info.channels.push_back(info.name + remap(current2original, "G")); } else if (paintLayer->colorSpace()->colorModelId() == XYZAColorModelID) { info.channels.push_back(info.name + remap(current2original, "X")); info.channels.push_back(info.name + remap(current2original, "Y")); info.channels.push_back(info.name + remap(current2original, "Z")); info.channels.push_back(info.name + remap(current2original, "A")); } } if (paintLayer->colorSpace()->colorDepthId() == Float16BitsColorDepthID) { info.pixelType = Imf::HALF; } else if (paintLayer->colorSpace()->colorDepthId() == Float32BitsColorDepthID) { info.pixelType = Imf::FLOAT; } else { info.pixelType = Imf::NUM_PIXELTYPES; } if (info.pixelType < Imf::NUM_PIXELTYPES) { dbgFile << "Going to save layer" << info.name; informationObjects.push_back(info); } else { warnFile << "Will not save layer" << info.name; layersNotSaved << node; } } else if (KisGroupLayerSP groupLayer = dynamic_cast(node.data())) { recBuildPaintLayerSaveInfo(informationObjects, name + groupLayer->name() + '.', groupLayer); } else { /** * The EXR can store paint and group layers only. The rest will * go to /dev/null :( */ layersNotSaved.insert(node); } } if (!layersNotSaved.isEmpty()) { reportLayersNotSaved(layersNotSaved); } } void EXRConverter::Private::reportLayersNotSaved(const QSet &layersNotSaved) { QString layersList; QTextStream textStream(&layersList); textStream.setCodec("UTF-8"); Q_FOREACH (KisNodeSP node, layersNotSaved) { textStream << "
  • " << i18nc("@item:unsupported-node-message", "%1 (type: \"%2\")", node->name(), node->metaObject()->className()) << "
  • "; } QString msg = i18nc("@info", "

    The following layers have a type that is not supported by EXR format:

    " "
      %1

    " "

    these layers have not been saved to the final EXR file

    ", layersList); errorMessage = msg; } QString EXRConverter::Private::fetchExtraLayersInfo(QList& informationObjects) { KIS_ASSERT_RECOVER_NOOP(!informationObjects.isEmpty()); if (informationObjects.size() == 1 && informationObjects[0].name == QString(HDR_LAYER) + ".") { return QString(); } QDomDocument doc("krita-extra-layers-info"); doc.appendChild(doc.createElement("root")); QDomElement rootElement = doc.documentElement(); for (int i = 0; i < informationObjects.size(); i++) { ExrPaintLayerSaveInfo &info = informationObjects[i]; quint32 unused; KisSaveXmlVisitor visitor(doc, rootElement, unused, QString(), false); QDomElement el = visitor.savePaintLayerAttributes(info.layer.data(), doc); // cut the ending '.' QString strippedName = info.name.left(info.name.size() - 1); el.setAttribute(EXR_NAME, strippedName); rootElement.appendChild(el); } return doc.toString(); } -KisImageBuilder_Result EXRConverter::buildFile(const QString &filename, KisGroupLayerSP layer, bool flatten) +KisImportExportErrorCode EXRConverter::buildFile(const QString &filename, KisGroupLayerSP layer, bool flatten) { - if (!layer) - return KisImageBuilder_RESULT_INVALID_ARG; + KIS_ASSERT_RECOVER_RETURN_VALUE(layer, ImportExportCodes::InternalError); KisImageSP image = layer->image(); - if (!image) - return KisImageBuilder_RESULT_EMPTY; - + KIS_ASSERT_RECOVER_RETURN_VALUE(image, ImportExportCodes::InternalError); qint32 height = image->height(); qint32 width = image->width(); Imf::Header header(width, height); if (flatten) { KisPaintDeviceSP pd = new KisPaintDevice(*image->projection()); KisPaintLayerSP l = new KisPaintLayer(image, "projection", OPACITY_OPAQUE_U8, pd); return buildFile(filename, l); } else { - QList informationObjects; d->recBuildPaintLayerSaveInfo(informationObjects, "", layer); if(informationObjects.isEmpty()) { - return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; + return ImportExportCodes::FormatColorSpaceUnsupported; } - d->makeLayerNamesUnique(informationObjects); QByteArray extraLayersInfo = d->fetchExtraLayersInfo(informationObjects).toUtf8(); if (!extraLayersInfo.isNull()) { header.insert(EXR_KRITA_LAYERS, Imf::StringAttribute(extraLayersInfo.constData())); } dbgFile << informationObjects.size() << " layers to save"; - Q_FOREACH (const ExrPaintLayerSaveInfo& info, informationObjects) { if (info.pixelType < Imf::NUM_PIXELTYPES) { Q_FOREACH (const QString& channel, info.channels) { dbgFile << channel << " " << info.pixelType; header.channels().insert(channel.toUtf8().data(), Imf::Channel(info.pixelType)); } } } // Open file for writing - Imf::OutputFile file(QFile::encodeName(filename), header); + try { + Imf::OutputFile file(QFile::encodeName(filename), header); + encodeData(file, informationObjects, width, height); + return ImportExportCodes::OK; + } catch(std::exception &e) { + dbgFile << "Exception while writing to exr file: " << e.what(); + if (!KisImportExportAdditionalChecks::isFileWritable(QFile::encodeName(filename))) { + return ImportExportCodes::NoAccessToWrite; + } + return ImportExportCodes::ErrorWhileWriting; + } - encodeData(file, informationObjects, width, height); - return KisImageBuilder_RESULT_OK; } } void EXRConverter::cancel() { warnKrita << "WARNING: Cancelling of an EXR loading is not supported!"; } diff --git a/plugins/impex/exr/exr_converter.h b/plugins/impex/exr/exr_converter.h index 6db6795339..6a11120e02 100644 --- a/plugins/impex/exr/exr_converter.h +++ b/plugins/impex/exr/exr_converter.h @@ -1,56 +1,57 @@ /* * Copyright (c) 2010 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. */ #ifndef _EXR_CONVERTER_H_ #define _EXR_CONVERTER_H_ #include #include #include "kis_types.h" -#include +#include + class KisDocument; class EXRConverter : public QObject { Q_OBJECT public: EXRConverter(KisDocument *doc, bool showNotifications); ~EXRConverter() override; public: - KisImageBuilder_Result buildImage(const QString &filename); - KisImageBuilder_Result buildFile(const QString &filename, KisPaintLayerSP layer); - KisImageBuilder_Result buildFile(const QString &filename, KisGroupLayerSP layer, bool flatten=false); + KisImportExportErrorCode buildImage(const QString &filename); + KisImportExportErrorCode buildFile(const QString &filename, KisPaintLayerSP layer); + KisImportExportErrorCode buildFile(const QString &filename, KisGroupLayerSP layer, bool flatten=false); /** * Retrieve the constructed image */ KisImageSP image(); QString errorMessage() const; private: - KisImageBuilder_Result decode(const QString &filename); + KisImportExportErrorCode decode(const QString &filename); public Q_SLOTS: virtual void cancel(); private: struct Private; const QScopedPointer d; }; #endif diff --git a/plugins/impex/exr/exr_export.cc b/plugins/impex/exr/exr_export.cc index fd23fe0ad4..6c60865aec 100644 --- a/plugins/impex/exr/exr_export.cc +++ b/plugins/impex/exr/exr_export.cc @@ -1,159 +1,127 @@ /* * Copyright (c) 2010 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 "exr_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "exr_converter.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_exr_export.json", registerPlugin();) EXRExport::EXRExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } EXRExport::~EXRExport() { } KisPropertiesConfigurationSP EXRExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("flatten", false); return cfg; } KisConfigWidget *EXRExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const { return new KisWdgOptionsExr(parent); } -KisImportExportFilter::ConversionStatus EXRExport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode EXRExport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP configuration) { Q_ASSERT(document); Q_ASSERT(configuration); KisImageSP image = document->savingImage(); Q_ASSERT(image); EXRConverter exrConverter(document, !batchMode()); - KisImageBuilder_Result res; + KisImportExportErrorCode res; if (configuration && configuration->getBool("flatten")) { res = exrConverter.buildFile(filename(), image->rootLayer(), true); } else { res = exrConverter.buildFile(filename(), image->rootLayer()); } dbgFile << " Result =" << res; - switch (res) { - case KisImageBuilder_RESULT_INVALID_ARG: - document->setErrorMessage(i18n("This layer cannot be saved to EXR.")); - return KisImportExportFilter::WrongFormat; - - case KisImageBuilder_RESULT_EMPTY: - document->setErrorMessage(i18n("The layer does not have an image associated with it.")); - return KisImportExportFilter::WrongFormat; - - case KisImageBuilder_RESULT_NO_URI: - document->setErrorMessage(i18n("The filename is empty.")); - return KisImportExportFilter::CreationError; - - case KisImageBuilder_RESULT_NOT_LOCAL: - document->setErrorMessage(i18n("EXR images cannot be saved remotely.")); - return KisImportExportFilter::InternalError; - - case KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE: - document->setErrorMessage(i18n("Colorspace not supported: EXR images must be 16 or 32 bits floating point RGB.")); - return KisImportExportFilter::WrongFormat; - - case KisImageBuilder_RESULT_OK: - if (!exrConverter.errorMessage().isNull()) { - document->setErrorMessage(exrConverter.errorMessage()); - } - return KisImportExportFilter::OK; - default: - break; - } - - document->setErrorMessage(i18n("Internal Error")); - return KisImportExportFilter::InternalError; - + return res; } void EXRExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisGroupLayer")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Float16BitsColorDepthID) << QPair(RGBAColorModelID, Float32BitsColorDepthID) << QPair(GrayAColorModelID, Float16BitsColorDepthID) << QPair(GrayAColorModelID, Float32BitsColorDepthID) << QPair(GrayColorModelID, Float16BitsColorDepthID) << QPair(GrayColorModelID, Float32BitsColorDepthID) << QPair(XYZAColorModelID, Float16BitsColorDepthID) << QPair(XYZAColorModelID, Float32BitsColorDepthID); addSupportedColorModels(supportedColorModels, "EXR"); } void KisWdgOptionsExr::setConfiguration(const KisPropertiesConfigurationSP cfg) { chkFlatten->setChecked(cfg->getBool("flatten", false)); } KisPropertiesConfigurationSP KisWdgOptionsExr::configuration() const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("flatten", chkFlatten->isChecked()); return cfg; } #include diff --git a/plugins/impex/exr/exr_export.h b/plugins/impex/exr/exr_export.h index 11a501ea19..a95034e9fb 100644 --- a/plugins/impex/exr/exr_export.h +++ b/plugins/impex/exr/exr_export.h @@ -1,59 +1,59 @@ /* * Copyright (c) 2010 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. */ #ifndef _EXR_EXPORT_H_ #define _EXR_EXPORT_H_ #include #include #include #include "ui_exr_export_widget.h" class KisWdgOptionsExr : public KisConfigWidget, public Ui::ExrExportWidget { Q_OBJECT public: KisWdgOptionsExr(QWidget *parent) : KisConfigWidget(parent) { setupUi(this); } void setConfiguration(const KisPropertiesConfigurationSP cfg) override; KisPropertiesConfigurationSP configuration() const override; }; class EXRExport : public KisImportExportFilter { Q_OBJECT public: EXRExport(QObject *parent, const QVariantList &); ~EXRExport() override; bool supportsIO() const override { return false; } - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/exr/exr_import.cc b/plugins/impex/exr/exr_import.cc index baae10dbd5..a6926dd6bc 100644 --- a/plugins/impex/exr/exr_import.cc +++ b/plugins/impex/exr/exr_import.cc @@ -1,83 +1,53 @@ /* * Copyright (c) 2010 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 "exr_import.h" #include #include #include #include #include #include "exr_converter.h" K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_exr_import.json", registerPlugin();) exrImport::exrImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } exrImport::~exrImport() { } -KisImportExportFilter::ConversionStatus exrImport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode exrImport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/) { EXRConverter ib(document, !batchMode()); - - switch (ib.buildImage(filename())) { - case KisImageBuilder_RESULT_UNSUPPORTED: - case KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE: - document->setErrorMessage(i18n("Krita does not support this type of EXR file.")); - return KisImportExportFilter::NotImplemented; - - case KisImageBuilder_RESULT_INVALID_ARG: - document->setErrorMessage(i18n("This is not an EXR file.")); - return KisImportExportFilter::BadMimeType; - - case KisImageBuilder_RESULT_NO_URI: - case KisImageBuilder_RESULT_NOT_LOCAL: - document->setErrorMessage(i18n("The EXR file does not exist.")); - return KisImportExportFilter::FileNotFound; - - case KisImageBuilder_RESULT_BAD_FETCH: - case KisImageBuilder_RESULT_EMPTY: - document->setErrorMessage(i18n("The EXR is corrupted.")); - return KisImportExportFilter::ParsingError; - - case KisImageBuilder_RESULT_FAILURE: - document->setErrorMessage(i18n("Krita could not create a new image.")); - return KisImportExportFilter::InternalError; - - case KisImageBuilder_RESULT_OK: - Q_ASSERT(ib.image()); - document -> setCurrentImage(ib.image()); - return KisImportExportFilter::OK; - - default: - break; + KisImportExportErrorCode result = ib.buildImage(filename()); + if (result.isOk()) { + document->setCurrentImage(ib.image()); } - - return KisImportExportFilter::StorageCreationError; + return result; } #include diff --git a/plugins/impex/exr/exr_import.h b/plugins/impex/exr/exr_import.h index f009c2b341..5841f3ff32 100644 --- a/plugins/impex/exr/exr_import.h +++ b/plugins/impex/exr/exr_import.h @@ -1,37 +1,37 @@ /* * Copyright (c) 2010 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. */ #ifndef EXR_IMPORT_H_ #define EXR_IMPORT_H_ #include #include class exrImport : public KisImportExportFilter { Q_OBJECT public: exrImport(QObject *parent, const QVariantList &); ~exrImport() override; bool supportsIO() const override { return false; } - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/exr/tests/data/incorrectFormatFile.txt b/plugins/impex/exr/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/exr/tests/data/readonlyFile.txt b/plugins/impex/exr/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/exr/tests/data/writeonlyFile.txt b/plugins/impex/exr/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/exr/tests/kis_exr_test.cpp b/plugins/impex/exr/tests/kis_exr_test.cpp index f3e2f0fb55..0a649b6db7 100644 --- a/plugins/impex/exr/tests/kis_exr_test.cpp +++ b/plugins/impex/exr/tests/kis_exr_test.cpp @@ -1,94 +1,110 @@ /* * Copyright (C) 2007 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_exr_test.h" #include #include #include #include #include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif +const QString ExrMimetype = "application/x-extension-exr"; void KisExrTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 5); } +void KisExrTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), ExrMimetype); +} + +void KisExrTest::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), ExrMimetype, true); +} + +void KisExrTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), ExrMimetype); +} + void KisExrTest::testRoundTrip() { QString inputFileName(TestUtil::fetchDataFileLazy("CandleGlass.exr")); KisDocument *doc1 = KisPart::instance()->createDocument(); doc1->setFileBatchMode(true); bool r = doc1->importDocument(QUrl::fromLocalFile(inputFileName)); QVERIFY(r); QVERIFY(doc1->errorMessage().isEmpty()); QVERIFY(doc1->image()); QTemporaryFile savedFile(QDir::tempPath() + QLatin1String("/krita_XXXXXX") + QLatin1String(".exr")); savedFile.setAutoRemove(true); savedFile.open(); QString savedFileName(savedFile.fileName()); QString typeName = KisMimeDatabase::mimeTypeForFile(savedFileName, false); QByteArray mimeType(typeName.toLatin1()); r = doc1->exportDocumentSync(QUrl::fromLocalFile(savedFileName), mimeType); QVERIFY(r); QVERIFY(QFileInfo(savedFileName).exists()); { KisDocument *doc2 = KisPart::instance()->createDocument(); doc2->setFileBatchMode(true); r = doc2->importDocument(QUrl::fromLocalFile(savedFileName)); QVERIFY(r); QVERIFY(doc2->errorMessage().isEmpty()); QVERIFY(doc2->image()); doc1->image()->root()->firstChild()->paintDevice()->convertToQImage(0).save("1.png"); doc2->image()->root()->firstChild()->paintDevice()->convertToQImage(0).save("2.png"); QVERIFY(TestUtil::comparePaintDevicesClever( doc1->image()->root()->firstChild()->paintDevice(), doc2->image()->root()->firstChild()->paintDevice(), 0.01 /* meaningless alpha */)); delete doc2; } savedFile.close(); delete doc1; } KISTEST_MAIN(KisExrTest) diff --git a/plugins/impex/exr/tests/kis_exr_test.h b/plugins/impex/exr/tests/kis_exr_test.h index 4dad62dfca..7119e96947 100644 --- a/plugins/impex/exr/tests/kis_exr_test.h +++ b/plugins/impex/exr/tests/kis_exr_test.h @@ -1,32 +1,35 @@ /* * Copyright (C) 2007 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. */ #ifndef _KIS_EXR_TEST_H_ #define _KIS_EXR_TEST_H_ #include class KisExrTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); void testRoundTrip(); }; #endif diff --git a/plugins/impex/gif/CMakeLists.txt b/plugins/impex/gif/CMakeLists.txt index 31ad0a488d..bb7f582b6c 100644 --- a/plugins/impex/gif/CMakeLists.txt +++ b/plugins/impex/gif/CMakeLists.txt @@ -1,26 +1,28 @@ +add_subdirectory(tests) + set(kritagifexport_SOURCES kis_gif_export.cpp qgiflibhandler.cpp ) ki18n_wrap_ui(kritagifexport_SOURCES ) add_library(kritagifexport MODULE ${kritagifexport_SOURCES}) target_link_libraries(kritagifexport kritaui kritaimpex ${GIF_LIBRARY}) install(TARGETS kritagifexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) set(kritagifimport_SOURCES kis_gif_import.cpp qgiflibhandler.cpp ) ki18n_wrap_ui(kritagifimport_SOURCES ) add_library(kritagifimport MODULE ${kritagifimport_SOURCES}) target_link_libraries(kritagifimport kritaui ${GIF_LIBRARY}) install(TARGETS kritagifimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_gif.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/gif/kis_gif_export.cpp b/plugins/impex/gif/kis_gif_export.cpp index f0b2471a64..c9332d3670 100644 --- a/plugins/impex/gif/kis_gif_export.cpp +++ b/plugins/impex/gif/kis_gif_export.cpp @@ -1,72 +1,76 @@ /* * Copyright (c) 2018 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_gif_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "qgiflibhandler.h" K_PLUGIN_FACTORY_WITH_JSON(KisGIFExportFactory, "krita_gif_export.json", registerPlugin();) KisGIFExport::KisGIFExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisGIFExport::~KisGIFExport() { } -KisImportExportFilter::ConversionStatus KisGIFExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisGIFExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { Q_UNUSED(configuration); QRect rc = document->savingImage()->bounds(); QImage image = document->savingImage()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); QGIFLibHandler handler; handler.setDevice(io); bool result = handler.write(image); - return (result ? KisImportExportFilter::OK : KisImportExportFilter::InternalError); + if (!result) { + KIS_ASSERT_RECOVER_RETURN_VALUE(true, ImportExportCodes::InternalError); + return ImportExportCodes::InternalError; + } + return ImportExportCodes::OK; } void KisGIFExport::initializeCapabilities() { QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "GIF"); } #include "kis_gif_export.moc" diff --git a/plugins/impex/gif/kis_gif_export.h b/plugins/impex/gif/kis_gif_export.h index a0106d9ae8..b2d238ca44 100644 --- a/plugins/impex/gif/kis_gif_export.h +++ b/plugins/impex/gif/kis_gif_export.h @@ -1,38 +1,38 @@ /* * Copyright (c) 2018 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. */ #ifndef _KIS_GIF_EXPORT_H_ #define _KIS_GIF_EXPORT_H_ #include #include class KisGIFExport : public KisImportExportFilter { Q_OBJECT public: KisGIFExport(QObject *parent, const QVariantList &); ~KisGIFExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/gif/kis_gif_import.cpp b/plugins/impex/gif/kis_gif_import.cpp index 86d9a7bb21..11b8957a92 100644 --- a/plugins/impex/gif/kis_gif_import.cpp +++ b/plugins/impex/gif/kis_gif_import.cpp @@ -1,84 +1,94 @@ /* * Copyright (c) 2018 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_gif_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include +#include #include "qgiflibhandler.h" K_PLUGIN_FACTORY_WITH_JSON(KisGIFImportFactory, "krita_gif_import.json", registerPlugin();) KisGIFImport::KisGIFImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisGIFImport::~KisGIFImport() { } -KisImportExportFilter::ConversionStatus KisGIFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisGIFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { Q_UNUSED(configuration); QImage img; bool result = false; QGIFLibHandler handler; + + handler.setDevice(io); + if (!io->isReadable()) { + return ImportExportCodes::NoAccessToRead; + } + if (handler.canRead()) { result = handler.read(&img); + } else { + // handler.canRead() checks for the flag in the file; if it can't read it, maybe the format is incorrect + return ImportExportCodes::FileFormatIncorrect; } if (result == false) { - return KisImportExportFilter::CreationError; + return ImportExportCodes::FileFormatIncorrect; } const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(document->createUndoStore(), img.width(), img.height(), colorSpace, "imported from gif"); KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255); layer->paintDevice()->convertFromQImage(img, 0, 0, 0); image->addNode(layer.data(), image->rootLayer().data()); document->setCurrentImage(image); - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include "kis_gif_import.moc" diff --git a/plugins/impex/gif/kis_gif_import.h b/plugins/impex/gif/kis_gif_import.h index 7b9d9f90bf..8afa60ca9d 100644 --- a/plugins/impex/gif/kis_gif_import.h +++ b/plugins/impex/gif/kis_gif_import.h @@ -1,37 +1,37 @@ /* * Copyright (c) 2018 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. */ #ifndef _KIS_GIF_IMPORT_H_ #define _KIS_GIF_IMPORT_H_ #include #include class KisGIFImport : public KisImportExportFilter { Q_OBJECT public: KisGIFImport(QObject *parent, const QVariantList &); ~KisGIFImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/gif/tests/CMakeLists.txt b/plugins/impex/gif/tests/CMakeLists.txt new file mode 100644 index 0000000000..a58fb5b17f --- /dev/null +++ b/plugins/impex/gif/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisGifTest.cpp + TEST_NAME KisGifTest + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/gif/tests/KisGifTest.cpp similarity index 64% copy from plugins/impex/svg/tests/kis_svg_test.cpp copy to plugins/impex/gif/tests/KisGifTest.cpp index 6ca66688f8..5c87288247 100644 --- a/plugins/impex/svg/tests/kis_svg_test.cpp +++ b/plugins/impex/gif/tests/KisGifTest.cpp @@ -1,40 +1,57 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * 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_svg_test.h" +#include "KisGifTest.h" #include #include -#include - #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif -void KisSvgTest::testFiles() +const QString GifMimetype = "image/gif"; + + + +void KisGifTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), GifMimetype); +} + + +void KisGifTest::testExportToReadonly() { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50); + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), GifMimetype); } -KISTEST_MAIN(KisSvgTest) + +void KisGifTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), GifMimetype); +} + + + +KISTEST_MAIN(KisGifTest) + diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/gif/tests/KisGifTest.h similarity index 74% copy from plugins/impex/jpeg/tests/kis_jpeg_test.h copy to plugins/impex/gif/tests/KisGifTest.h index 5c19b0cda6..a65c46bead 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/gif/tests/KisGifTest.h @@ -1,31 +1,35 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * 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_JPEG_TEST_H_ -#define _KIS_JPEG_TEST_H_ +#ifndef _KIS_GIF_TEST_H_ +#define _KIS_GIF_TEST_H_ #include -class KisJpegTest : public QObject +class KisGifTest : public QObject { Q_OBJECT private Q_SLOTS: - void testFiles(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; -#endif +#endif // _KIS_BRUSH_TEST_H_ + diff --git a/plugins/impex/gif/tests/data/incorrectFormatFile.txt b/plugins/impex/gif/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/gif/tests/data/readonlyFile.txt b/plugins/impex/gif/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/gif/tests/data/writeonlyFile.txt b/plugins/impex/gif/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/heif/CMakeLists.txt b/plugins/impex/heif/CMakeLists.txt index 725e7afed3..d25f3e1704 100644 --- a/plugins/impex/heif/CMakeLists.txt +++ b/plugins/impex/heif/CMakeLists.txt @@ -1,31 +1,33 @@ +add_subdirectory(tests) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${HEIF_CFLAGS}") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${HEIF_CFLAGS}") add_definitions(${HEIF_DEFINITIONS}) set(kritaheifimport_SOURCES HeifImport.cpp HeifError.cpp ) add_library(kritaheifimport MODULE ${kritaheifimport_SOURCES}) target_link_libraries(kritaheifimport kritaui kritalibkra ${HEIF_LDFLAGS} ${HEIF_LIBRARIES} ) install(TARGETS kritaheifimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) set(kritaheifexport_SOURCES HeifExport.cpp HeifError.cpp ) ki18n_wrap_ui(kritaheifexport_SOURCES WdgHeifExport.ui ) add_library(kritaheifexport MODULE ${kritaheifexport_SOURCES}) target_link_libraries(kritaheifexport kritaui kritalibkra kritaimpex ${HEIF_LDFLAGS} ${HEIF_LIBRARIES} ) install(TARGETS kritaheifexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_heif.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/heif/HeifError.cpp b/plugins/impex/heif/HeifError.cpp index 41a324a1ce..e37c655936 100644 --- a/plugins/impex/heif/HeifError.cpp +++ b/plugins/impex/heif/HeifError.cpp @@ -1,64 +1,62 @@ /* * Copyright (c) 2018 Dirk Farin * * 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 "HeifError.h" -KisImportExportFilter::ConversionStatus setHeifError(KisDocument* document, +KisImportExportErrorCode setHeifError(KisDocument* document, heif::Error error) { switch (error.get_code()) { case heif_error_Ok: - return KisImportExportFilter::OK; + return ImportExportCodes::OK; case heif_error_Input_does_not_exist: // this should never happen because we do not read from file names - document->setErrorMessage(i18n("Internal error.")); - return KisImportExportFilter::InternalError; + KIS_ASSERT_RECOVER_RETURN_VALUE(true, ImportExportCodes::InternalError); + return ImportExportCodes::InternalError; case heif_error_Invalid_input: case heif_error_Decoder_plugin_error: - document->setErrorMessage(i18n("The HEIF file is corrupted.")); - return KisImportExportFilter::ParsingError; + return ImportExportCodes::FileFormatIncorrect; case heif_error_Unsupported_filetype: case heif_error_Unsupported_feature: - document->setErrorMessage(i18n("Krita does not support this type of HEIF file.")); - return KisImportExportFilter::NotImplemented; + return ImportExportCodes::FormatFeaturesUnsupported; case heif_error_Usage_error: case heif_error_Encoder_plugin_error: // this should never happen if we use libheif in the correct way - document->setErrorMessage(i18n("Internal libheif API error.")); - return KisImportExportFilter::InternalError; + KIS_ASSERT_RECOVER_RETURN_VALUE(true, ImportExportCodes::InternalError); + return ImportExportCodes::InternalError; case heif_error_Memory_allocation_error: document->setErrorMessage(i18n("Could not allocate memory.")); - return KisImportExportFilter::StorageCreationError; + return ImportExportCodes::InsufficientMemory; case heif_error_Encoding_error: document->setErrorMessage(i18n("Could not encode or write image.")); - return KisImportExportFilter::CreationError; + return ImportExportCodes::NoAccessToWrite; default: // we only get here when we forgot to handle an error ID document->setErrorMessage(i18n("Unknown error.")); - return KisImportExportFilter::InternalError; + return ImportExportCodes::Failure; } } diff --git a/plugins/impex/heif/HeifError.h b/plugins/impex/heif/HeifError.h index 3fd38b018a..39ee3b2b7d 100644 --- a/plugins/impex/heif/HeifError.h +++ b/plugins/impex/heif/HeifError.h @@ -1,31 +1,32 @@ /* * Copyright (c) 2018 Dirk Farin * * 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 HEIF_ERROR_H_ #define HEIF_ERROR_H_ #include #include "libheif/heif_cxx.h" +#include -KisImportExportFilter::ConversionStatus setHeifError(KisDocument* document, +KisImportExportErrorCode setHeifError(KisDocument* document, heif::Error error); #endif diff --git a/plugins/impex/heif/HeifExport.cpp b/plugins/impex/heif/HeifExport.cpp index 4ff1d388a5..ddf761e939 100644 --- a/plugins/impex/heif/HeifExport.cpp +++ b/plugins/impex/heif/HeifExport.cpp @@ -1,282 +1,282 @@ /* * Copyright (c) 2018 Dirk Farin * * 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 "HeifExport.h" #include "HeifError.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include "libheif/heif_cxx.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_heif_export.json", registerPlugin();) HeifExport::HeifExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } HeifExport::~HeifExport() { } KisPropertiesConfigurationSP HeifExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("quality", 50); cfg->setProperty("lossless", true); return cfg; } KisConfigWidget *HeifExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const { return new KisWdgOptionsHeif(parent); } class Writer_QIODevice : public heif::Context::Writer { public: Writer_QIODevice(QIODevice* io) : m_io(io) { } heif_error write(const void* data, size_t size) override { qint64 n = m_io->write((const char*)data,size); if (n != (qint64)size) { QString error = m_io->errorString(); heif_error err = { heif_error_Encoding_error, heif_suberror_Cannot_write_output_data, "Could not write output data" }; return err; } struct heif_error heif_error_ok = { heif_error_Ok, heif_suberror_Unspecified, "Success" }; return heif_error_ok; } private: QIODevice* m_io; }; -KisImportExportFilter::ConversionStatus HeifExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode HeifExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { KisImageSP image = document->savingImage(); const KoColorSpace *cs = image->colorSpace(); // Convert to 8 bits rgba on saving if (cs->colorModelId() != RGBAColorModelID || cs->colorDepthId() != Integer8BitsColorDepthID) { cs = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Integer8BitsColorDepthID.id()); image->convertImageColorSpace(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } int quality = configuration->getInt("quality", 50); bool lossless = configuration->getBool("lossless", false); bool has_alpha = configuration->getBool(KisImportExportFilter::ImageContainsTransparencyTag, false); // If we want to add information from the document to the metadata, // we should do that here. try { // --- use standard HEVC encoder heif::Encoder encoder(heif_compression_HEVC); encoder.set_lossy_quality(quality); encoder.set_lossless(lossless); // --- convert KisImage to HEIF image --- int width = image->width(); int height = image->height(); heif::Context ctx; heif::Image img; img.create(width,height, heif_colorspace_RGB, heif_chroma_444); img.add_plane(heif_channel_R, width,height, 8); img.add_plane(heif_channel_G, width,height, 8); img.add_plane(heif_channel_B, width,height, 8); uint8_t* ptrR {0}; uint8_t* ptrG {0}; uint8_t* ptrB {0}; uint8_t* ptrA {0}; int strideR,strideG,strideB,strideA; ptrR = img.get_plane(heif_channel_R, &strideR); ptrG = img.get_plane(heif_channel_G, &strideG); ptrB = img.get_plane(heif_channel_B, &strideB); if (has_alpha) { img.add_plane(heif_channel_Alpha, width,height, 8); ptrA = img.get_plane(heif_channel_Alpha, &strideA); } KisPaintDeviceSP pd = image->projection(); for (int y=0; ycreateHLineIteratorNG(0, y, width); for (int x=0; x::red(it->rawData()); ptrG[y*strideG+x] = KoBgrTraits::green(it->rawData()); ptrB[y*strideB+x] = KoBgrTraits::blue(it->rawData()); if (has_alpha) { ptrA[y*strideA+x] = cs->opacityU8(it->rawData()); } it->nextPixel(); } } // --- encode and write image heif::ImageHandle handle = ctx.encode_image(img, encoder); // --- add Exif / XMP metadata KisExifInfoVisitor exivInfoVisitor; exivInfoVisitor.visit(image->rootLayer().data()); QScopedPointer metaDataStore; if (exivInfoVisitor.metaDataCount() == 1) { metaDataStore.reset(new KisMetaData::Store(*exivInfoVisitor.exifInfo())); } else { metaDataStore.reset(new KisMetaData::Store()); } if (!metaDataStore->empty()) { { KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); QBuffer buffer; exifIO->saveTo(metaDataStore.data(), &buffer, KisMetaData::IOBackend::NoHeader); // Or JpegHeader? Or something else? QByteArray data = buffer.data(); // Write the data to the file ctx.add_exif_metadata(handle, data.constData(), data.size()); } { KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); QBuffer buffer; xmpIO->saveTo(metaDataStore.data(), &buffer, KisMetaData::IOBackend::NoHeader); // Or JpegHeader? Or something else? QByteArray data = buffer.data(); // Write the data to the file ctx.add_XMP_metadata(handle, data.constData(), data.size()); } } // --- write HEIF file Writer_QIODevice writer(io); ctx.write(writer); } catch (heif::Error err) { return setHeifError(document, err); } - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } void HeifExport::initializeCapabilities() { // This checks before saving for what the file format supports: anything that is supported needs to be mentioned here QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) /*<< QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID)*/ ; addSupportedColorModels(supportedColorModels, "HEIF"); } void KisWdgOptionsHeif::setConfiguration(const KisPropertiesConfigurationSP cfg) { chkLossless->setChecked(cfg->getBool("lossless", true)); sliderQuality->setValue(qreal(cfg->getInt("quality", 50))); m_hasAlpha = cfg->getBool(KisImportExportFilter::ImageContainsTransparencyTag, false); } KisPropertiesConfigurationSP KisWdgOptionsHeif::configuration() const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("lossless", chkLossless->isChecked()); cfg->setProperty("quality", int(sliderQuality->value())); cfg->setProperty(KisImportExportFilter::ImageContainsTransparencyTag, m_hasAlpha); return cfg; } void KisWdgOptionsHeif::toggleQualitySlider(bool toggle) { // Disable the quality slider if lossless is true lossySettings->setEnabled(!toggle); } #include diff --git a/plugins/impex/heif/HeifExport.h b/plugins/impex/heif/HeifExport.h index 3eb7b3601e..3f15e4126d 100644 --- a/plugins/impex/heif/HeifExport.h +++ b/plugins/impex/heif/HeifExport.h @@ -1,72 +1,72 @@ /* * Copyright (c) 2018 Dirk Farin * * 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 HEIF_EXPORT_H_ #define HEIF_EXPORT_H_ #include #include #include #include "ui_WdgHeifExport.h" class KisWdgOptionsHeif : public KisConfigWidget, public Ui::WdgHeifExport { Q_OBJECT public: KisWdgOptionsHeif(QWidget *parent) : KisConfigWidget(parent) { setupUi(this); connect(chkLossless, SIGNAL(toggled(bool)), SLOT(toggleQualitySlider(bool))); sliderQuality->setRange(0, 100, 0); } void setConfiguration(const KisPropertiesConfigurationSP cfg) override; KisPropertiesConfigurationSP configuration() const override; private Q_SLOTS: void toggleQualitySlider(bool toggle); private: bool m_hasAlpha {false}; }; class HeifExport : public KisImportExportFilter { Q_OBJECT public: HeifExport(QObject *parent, const QVariantList &); ~HeifExport() override; // This should return true if the library can work with a QIODevice, and doesn't want to open the file by itself bool supportsIO() const override { return true; } - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/heif/HeifImport.cpp b/plugins/impex/heif/HeifImport.cpp index ec9675feba..e903f23483 100644 --- a/plugins/impex/heif/HeifImport.cpp +++ b/plugins/impex/heif/HeifImport.cpp @@ -1,189 +1,189 @@ /* * Copyright (c) 2018 Dirk Farin * * 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 "HeifImport.h" #include "HeifError.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include "libheif/heif_cxx.h" K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_heif_import.json", registerPlugin();) HeifImport::HeifImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } HeifImport::~HeifImport() { } class Reader_QIODevice : public heif::Context::Reader { public: Reader_QIODevice(QIODevice* device) : m_device(device) { m_total_length=m_device->bytesAvailable(); } int64_t get_position() const { return m_device->pos(); } int read(void* data, size_t size) { return m_device->read((char*)data,size) != size; } int seek(int64_t position) { return !m_device->seek(position); } heif_reader_grow_status wait_for_file_size(int64_t target_size) { return (target_size > m_total_length) ? heif_reader_grow_status_size_beyond_eof : heif_reader_grow_status_size_reached; } private: QIODevice* m_device; int64_t m_total_length; }; -KisImportExportFilter::ConversionStatus HeifImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode HeifImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { // Wrap input stream into heif Reader object Reader_QIODevice reader(io); try { heif::Context ctx; ctx.read_from_reader(reader); // decode primary image heif::ImageHandle handle = ctx.get_primary_image_handle(); heif::Image heifimage = handle.decode_image(heif_colorspace_RGB, heif_chroma_444); int width =handle.get_width(); int height=handle.get_height(); bool hasAlpha = handle.has_alpha_channel(); // convert HEIF image to Krita KisDocument int strideR, strideG, strideB, strideA; const uint8_t* imgR = heifimage.get_plane(heif_channel_R, &strideR); const uint8_t* imgG = heifimage.get_plane(heif_channel_G, &strideG); const uint8_t* imgB = heifimage.get_plane(heif_channel_B, &strideB); const uint8_t* imgA = heifimage.get_plane(heif_channel_Alpha, &strideA); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(document->createUndoStore(), width, height, colorSpace, "HEIF image"); KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255); for (int y=0;ypaintDevice()->createHLineIteratorNG(0, y, width); for (int x=0;x::setRed(it->rawData(), imgR[y*strideR+x]); KoBgrTraits::setGreen(it->rawData(), imgG[y*strideG+x]); KoBgrTraits::setBlue(it->rawData(), imgB[y*strideB+x]); if (hasAlpha) { colorSpace->setOpacity(it->rawData(), quint8(imgA[y*strideA+x]), 1); } else { colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); } it->nextPixel(); } } image->addNode(layer.data(), image->rootLayer().data()); // --- Iterate through all metadata blocks and extract Exif and XMP metadata --- std::vector metadata_IDs = handle.get_list_of_metadata_block_IDs(); for (heif_item_id id : metadata_IDs) { if (handle.get_metadata_type(id) == "Exif") { // Read exif information std::vector exif_data = handle.get_metadata(id); if (exif_data.size()>4) { uint32_t skip = ((exif_data[0]<<24) | (exif_data[1]<<16) | (exif_data[2]<<8) | exif_data[3]) + 4; if (exif_data.size()>skip) { KisMetaData::IOBackend* exifIO = KisMetaData::IOBackendRegistry::instance()->value("exif"); // Copy the exif data into the byte array QByteArray ba; ba.append((char*)(exif_data.data()+skip), exif_data.size()-skip); QBuffer buf(&ba); exifIO->loadFrom(layer->metaData(), &buf); } } } if (handle.get_metadata_type(id) == "mime" && handle.get_metadata_content_type(id) == "application/rdf+xml") { // Read XMP information std::vector xmp_data = handle.get_metadata(id); KisMetaData::IOBackend* xmpIO = KisMetaData::IOBackendRegistry::instance()->value("xmp"); // Copy the xmp data into the byte array QByteArray ba; ba.append((char*)(xmp_data.data()), xmp_data.size()); QBuffer buf(&ba); xmpIO->loadFrom(layer->metaData(), &buf); } } document->setCurrentImage(image); - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } catch (heif::Error err) { return setHeifError(document, err); } } #include diff --git a/plugins/impex/heif/HeifImport.h b/plugins/impex/heif/HeifImport.h index 6f162eeba6..9c99161af5 100644 --- a/plugins/impex/heif/HeifImport.h +++ b/plugins/impex/heif/HeifImport.h @@ -1,37 +1,37 @@ /* * Copyright (c) 2018 Dirk Farin * * 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 HEIF_IMPORT_H_ #define HEIF_IMPORT_H_ #include #include class HeifImport : public KisImportExportFilter { Q_OBJECT public: HeifImport(QObject *parent, const QVariantList &); ~HeifImport() override; bool supportsIO() const override { return true; } - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/heif/tests/CMakeLists.txt b/plugins/impex/heif/tests/CMakeLists.txt new file mode 100644 index 0000000000..0f9cfe6c94 --- /dev/null +++ b/plugins/impex/heif/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisHeifTest.cpp + TEST_NAME KisHeifTest + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/heif/tests/KisHeifTest.cpp similarity index 64% copy from plugins/impex/svg/tests/kis_svg_test.cpp copy to plugins/impex/heif/tests/KisHeifTest.cpp index 6ca66688f8..7b95b42ec7 100644 --- a/plugins/impex/svg/tests/kis_svg_test.cpp +++ b/plugins/impex/heif/tests/KisHeifTest.cpp @@ -1,40 +1,57 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * 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_svg_test.h" +#include "KisHeifTest.h" #include #include -#include - #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif -void KisSvgTest::testFiles() +const QString HeifMimetype = "image/heic"; + + + +void KisHeifTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), HeifMimetype); +} + + +void KisHeifTest::testExportToReadonly() { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50); + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), HeifMimetype); } -KISTEST_MAIN(KisSvgTest) + +void KisHeifTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), HeifMimetype); +} + + + +KISTEST_MAIN(KisHeifTest) + diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/heif/tests/KisHeifTest.h similarity index 73% copy from plugins/impex/jpeg/tests/kis_jpeg_test.h copy to plugins/impex/heif/tests/KisHeifTest.h index 5c19b0cda6..ea09c9a836 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/heif/tests/KisHeifTest.h @@ -1,31 +1,35 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * 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_JPEG_TEST_H_ -#define _KIS_JPEG_TEST_H_ +#ifndef _KIS_HEIF_TEST_H_ +#define _KIS_HEIF_TEST_H_ #include -class KisJpegTest : public QObject +class KisHeifTest : public QObject { Q_OBJECT private Q_SLOTS: - void testFiles(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; -#endif +#endif // _KIS_HEIF_TEST_H_ + diff --git a/plugins/impex/heif/tests/data/incorrectFormatFile.txt b/plugins/impex/heif/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/heif/tests/data/readonlyFile.txt b/plugins/impex/heif/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/heif/tests/data/writeonlyFile.txt b/plugins/impex/heif/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/heightmap/kis_heightmap_export.cpp b/plugins/impex/heightmap/kis_heightmap_export.cpp index 085dc7e4f3..165b10b2a4 100644 --- a/plugins/impex/heightmap/kis_heightmap_export.cpp +++ b/plugins/impex/heightmap/kis_heightmap_export.cpp @@ -1,148 +1,148 @@ /* * Copyright (c) 2014 Boudewijn Rempt * Copyright (c) 2017 Victor WÃ¥hlström * * 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_heightmap_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_wdg_options_heightmap.h" #include "kis_heightmap_utils.h" K_PLUGIN_FACTORY_WITH_JSON(KisHeightMapExportFactory, "krita_heightmap_export.json", registerPlugin();) template static void writeData(KisPaintDeviceSP pd, const QRect &bounds, QDataStream &out_stream) { KIS_ASSERT_RECOVER_RETURN(pd); KisSequentialConstIterator it(pd, bounds); while (it.nextPixel()) { out_stream << KoGrayTraits::gray(const_cast(it.rawDataConst())); } } KisHeightMapExport::KisHeightMapExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisHeightMapExport::~KisHeightMapExport() { } KisPropertiesConfigurationSP KisHeightMapExport::defaultConfiguration(const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("endianness", 0); return cfg; } KisConfigWidget *KisHeightMapExport::createConfigurationWidget(QWidget *parent, const QByteArray &from, const QByteArray &to) const { Q_UNUSED(from); Q_UNUSED(to); bool export_mode = true; KisWdgOptionsHeightmap* wdg = new KisWdgOptionsHeightmap(parent, export_mode); return wdg; } void KisHeightMapExport::initializeCapabilities() { if (mimeType() == "image/x-r8") { QList > supportedColorModels; supportedColorModels << QPair() << QPair(GrayAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "R8 Heightmap"); } else if (mimeType() == "image/x-r16") { QList > supportedColorModels; supportedColorModels << QPair() << QPair(GrayAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "R16 Heightmap"); } else if (mimeType() == "image/x-r32") { QList > supportedColorModels; supportedColorModels << QPair() << QPair(GrayAColorModelID, Float32BitsColorDepthID); addSupportedColorModels(supportedColorModels, "R32 Heightmap"); } } -KisImportExportFilter::ConversionStatus KisHeightMapExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisHeightMapExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { - KIS_ASSERT_RECOVER_RETURN_VALUE(mimeType() == "image/x-r16" || mimeType() == "image/x-r8" || mimeType() == "image/x-r32", KisImportExportFilter::WrongFormat); + KIS_ASSERT_RECOVER_RETURN_VALUE(mimeType() == "image/x-r16" || mimeType() == "image/x-r8" || mimeType() == "image/x-r32", ImportExportCodes::FileFormatIncorrect); KisImageSP image = document->savingImage(); QDataStream::ByteOrder bo = configuration->getInt("endianness", 1) == 0 ? QDataStream::BigEndian : QDataStream::LittleEndian; KisPaintDeviceSP pd = new KisPaintDevice(*image->projection()); QDataStream s(io); s.setByteOrder(bo); // needed for 32bit float data s.setFloatingPointPrecision(QDataStream::SinglePrecision); KoID target_co_model = GrayAColorModelID; KoID target_co_depth = KisHeightmapUtils::mimeTypeToKoID(mimeType()); KIS_ASSERT(!target_co_depth.id().isNull()); if (pd->colorSpace()->colorModelId() != target_co_model || pd->colorSpace()->colorDepthId() != target_co_depth) { pd = new KisPaintDevice(*pd.data()); pd->convertTo(KoColorSpaceRegistry::instance()->colorSpace(target_co_model.id(), target_co_depth.id())); } if (target_co_depth == Float32BitsColorDepthID) { writeData(pd, image->bounds(), s); } else if (target_co_depth == Integer16BitsColorDepthID) { writeData(pd, image->bounds(), s); } else if (target_co_depth == Integer8BitsColorDepthID) { writeData(pd, image->bounds(), s); } else { - return KisImportExportFilter::InternalError; + KIS_ASSERT_RECOVER_RETURN_VALUE(true, ImportExportCodes::InternalError); } - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include "kis_heightmap_export.moc" diff --git a/plugins/impex/heightmap/kis_heightmap_export.h b/plugins/impex/heightmap/kis_heightmap_export.h index f686961e27..cf966192c9 100644 --- a/plugins/impex/heightmap/kis_heightmap_export.h +++ b/plugins/impex/heightmap/kis_heightmap_export.h @@ -1,41 +1,41 @@ /* * Copyright (c) 2014 Boudewijn Rempt * Copyright (c) 2017 Victor WÃ¥hlström * * 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_HeightMap_EXPORT_H_ #define _KIS_HeightMap_EXPORT_H_ #include #include #include class KisHeightMapExport : public KisImportExportFilter { Q_OBJECT public: KisHeightMapExport(QObject *parent, const QVariantList &); ~KisHeightMapExport() override; KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; void initializeCapabilities() override; - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/heightmap/kis_heightmap_import.cpp b/plugins/impex/heightmap/kis_heightmap_import.cpp index 9f899267b5..6e53044f7f 100644 --- a/plugins/impex/heightmap/kis_heightmap_import.cpp +++ b/plugins/impex/heightmap/kis_heightmap_import.cpp @@ -1,198 +1,204 @@ /* * Copyright (c) 2014 Boudewijn Rempt * Copyright (c) 2017 Victor WÃ¥hlström * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_heightmap_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_wdg_options_heightmap.h" #include "kis_heightmap_utils.h" K_PLUGIN_FACTORY_WITH_JSON(HeightMapImportFactory, "krita_heightmap_import.json", registerPlugin();) template void fillData(KisPaintDeviceSP pd, int w, int h, QDataStream &stream) { KIS_ASSERT_RECOVER_RETURN(pd); T pixel; for (int i = 0; i < h; ++i) { KisHLineIteratorSP it = pd->createHLineIteratorNG(0, i, w); do { stream >> pixel; KoGrayTraits::setGray(it->rawData(), pixel); KoGrayTraits::setOpacity(it->rawData(), OPACITY_OPAQUE_F, 1); } while(it->nextPixel()); } } KisHeightMapImport::KisHeightMapImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisHeightMapImport::~KisHeightMapImport() { } -KisImportExportFilter::ConversionStatus KisHeightMapImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisHeightMapImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { Q_UNUSED(configuration); KoID depthId = KisHeightmapUtils::mimeTypeToKoID(mimeType()); if (depthId.id().isNull()) { document->setErrorMessage(i18n("Unknown file type")); - return KisImportExportFilter::WrongFormat; + return ImportExportCodes::FileFormatIncorrect; } int w = 0; int h = 0; KIS_ASSERT(io->isOpen()); const quint64 size = io->size(); + if (size == 0) { + return ImportExportCodes::FileFormatIncorrect; + } + QDataStream::ByteOrder bo = QDataStream::LittleEndian; if (!batchMode()) { QApplication::restoreOverrideCursor(); KoDialog* kdb = new KoDialog(0); kdb->setWindowTitle(i18n("Heightmap Import Options")); kdb->setButtons(KoDialog::Ok | KoDialog::Cancel); KisWdgOptionsHeightmap* wdg = new KisWdgOptionsHeightmap(kdb); kdb->setMainWidget(wdg); connect(wdg, SIGNAL(statusUpdated(bool)), kdb, SLOT(enableButtonOk(bool))); KisConfig config(true); QString filterConfig = config.importConfiguration(mimeType()); KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration); cfg->fromXML(filterConfig); int endianness = cfg->getInt("endianness", 1); if (endianness == 0) { wdg->radioBig->setChecked(true); } else { wdg->radioLittle->setChecked(true); } wdg->fileSizeLabel->setText(QString::number(size)); if(depthId == Integer8BitsColorDepthID) { wdg->bppLabel->setText(QString::number(8)); wdg->typeLabel->setText("Integer"); } else if(depthId == Integer16BitsColorDepthID) { wdg->bppLabel->setText(QString::number(16)); wdg->typeLabel->setText("Integer"); } else if(depthId == Float32BitsColorDepthID) { wdg->bppLabel->setText(QString::number(32)); wdg->typeLabel->setText("Float"); } else { - return KisImportExportFilter::InternalError; + KIS_ASSERT_RECOVER_RETURN_VALUE(true, ImportExportCodes::InternalError); + return ImportExportCodes::InternalError; } if (kdb->exec() == QDialog::Rejected) { - return KisImportExportFilter::UserCancelled; + return ImportExportCodes::Cancelled; } cfg->setProperty("endianness", wdg->radioBig->isChecked() ? 0 : 1); config.setImportConfiguration(mimeType(), cfg); w = wdg->widthInput->value(); h = wdg->heightInput->value(); bo = QDataStream::LittleEndian; cfg->setProperty("endianness", 1); if (wdg->radioBig->isChecked()) { bo = QDataStream::BigEndian; cfg->setProperty("endianness", 0); } KisConfig(true).setExportConfiguration(mimeType(), cfg); } else { const int pixelSize = depthId == Float32BitsColorDepthID ? 4 : depthId == Integer16BitsColorDepthID ? 2 : 1; const int numPixels = size / pixelSize; w = std::sqrt(numPixels); h = numPixels / w; bo = QDataStream::LittleEndian; } QDataStream s(io); s.setByteOrder(bo); // needed for 32bit float data s.setFloatingPointPrecision(QDataStream::SinglePrecision); const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), depthId.id(), "Gray-D50-elle-V2-srgbtrc.icc"); KisImageSP image = new KisImage(document->createUndoStore(), w, h, colorSpace, "imported heightmap"); KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255); if (depthId == Float32BitsColorDepthID) { fillData(layer->paintDevice(), w, h, s); } else if (depthId == Integer16BitsColorDepthID) { fillData(layer->paintDevice(), w, h, s); } else if (depthId == Integer8BitsColorDepthID) { fillData(layer->paintDevice(), w, h, s); } else { - return KisImportExportFilter::InternalError; + KIS_ASSERT_RECOVER_RETURN_VALUE(true, ImportExportCodes::InternalError); + return ImportExportCodes::InternalError; } image->addNode(layer.data(), image->rootLayer().data()); document->setCurrentImage(image); - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include "kis_heightmap_import.moc" diff --git a/plugins/impex/heightmap/kis_heightmap_import.h b/plugins/impex/heightmap/kis_heightmap_import.h index 94f1df9449..1a89a8171d 100644 --- a/plugins/impex/heightmap/kis_heightmap_import.h +++ b/plugins/impex/heightmap/kis_heightmap_import.h @@ -1,38 +1,38 @@ /* * Copyright (c) 2014 Boudewijn Rempt * Copyright (c) 2017 Victor WÃ¥hlström * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_HeightMap_IMPORT_H_ #define _KIS_HeightMap_IMPORT_H_ #include #include class KisHeightMapImport : public KisImportExportFilter { Q_OBJECT public: KisHeightMapImport(QObject *parent, const QVariantList &); ~KisHeightMapImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/heightmap/tests/data/incorrectFormatFile.txt b/plugins/impex/heightmap/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/heightmap/tests/data/readonlyFile.txt b/plugins/impex/heightmap/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/heightmap/tests/data/writeonlyFile.txt b/plugins/impex/heightmap/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/heightmap/tests/kis_heightmap_test.cpp b/plugins/impex/heightmap/tests/kis_heightmap_test.cpp index 13fc7f3026..82c5d7d3ad 100644 --- a/plugins/impex/heightmap/tests/kis_heightmap_test.cpp +++ b/plugins/impex/heightmap/tests/kis_heightmap_test.cpp @@ -1,38 +1,62 @@ /* * Copyright (c) 2017 Victor WÃ¥hlström * * 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_heightmap_test.h" #include #include #include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif +const QString HeightmapMimetype = "image/x-r8"; + + void KisHeightmapTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1); } + + + +void KisHeightmapTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), HeightmapMimetype); +} + + +void KisHeightmapTest::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), HeightmapMimetype); +} + + +void KisHeightmapTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), HeightmapMimetype); +} + + KISTEST_MAIN(KisHeightmapTest) diff --git a/plugins/impex/heightmap/tests/kis_heightmap_test.h b/plugins/impex/heightmap/tests/kis_heightmap_test.h index b22fa1d2ba..05d960740e 100644 --- a/plugins/impex/heightmap/tests/kis_heightmap_test.h +++ b/plugins/impex/heightmap/tests/kis_heightmap_test.h @@ -1,31 +1,35 @@ /* * Copyright (c) 2017 Victor WÃ¥hlström * * 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_HEIGHTMAP_TEST_H_ #define _KIS_HEIGHTMAP_TEST_H_ #include class KisHeightmapTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; #endif // _KIS_HEIGHTMAP_TEST_H_ diff --git a/plugins/impex/jpeg/kis_jpeg_converter.cc b/plugins/impex/jpeg/kis_jpeg_converter.cc index eca195fde1..276b8af756 100644 --- a/plugins/impex/jpeg/kis_jpeg_converter.cc +++ b/plugins/impex/jpeg/kis_jpeg_converter.cc @@ -1,730 +1,728 @@ /* * 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]; /* Create the message */ ( *( cinfo->err->format_message ) ) ( cinfo, jpegLastErrorMsg ); /* Jump to the setjmp point */ throw std::runtime_error( jpegLastErrorMsg ); // or your preferred exception ... } 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() { } -KisImageBuilder_Result KisJPEGConverter::decode(QIODevice *io) +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 KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; + return ImportExportCodes::FormatColorSpaceUnsupported; } uchar* profile_data; uint profile_len; 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 } } } 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 KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; + 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 KisImageBuilder_RESULT_UNSUPPORTED; + 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 KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } catch( std::runtime_error &e) { jpeg_destroy_decompress(&cinfo); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::FileFormatIncorrect; } } -KisImageBuilder_Result KisJPEGConverter::buildImage(QIODevice *io) +KisImportExportErrorCode KisJPEGConverter::buildImage(QIODevice *io) { return decode(io); } KisImageSP KisJPEGConverter::image() { return m_d->image; } -KisImageBuilder_Result KisJPEGConverter::buildFile(QIODevice *io, KisPaintLayerSP layer, KisJPEGOptions options, KisMetaData::Store* metaData) +KisImportExportErrorCode KisJPEGConverter::buildFile(QIODevice *io, KisPaintLayerSP layer, KisJPEGOptions options, KisMetaData::Store* metaData) { - if (!layer) - return KisImageBuilder_RESULT_INVALID_ARG; + KIS_ASSERT_RECOVER_RETURN_VALUE(layer, ImportExportCodes::InternalError); KisImageSP image = KisImageSP(layer->image()); - if (!image) - return KisImageBuilder_RESULT_EMPTY; + KIS_ASSERT_RECOVER_RETURN_VALUE(layer, 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); 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 KisImageBuilder_RESULT_UNSUPPORTED; + 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 KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } void KisJPEGConverter::cancel() { m_d->stop = true; } diff --git a/plugins/impex/jpeg/kis_jpeg_converter.h b/plugins/impex/jpeg/kis_jpeg_converter.h index 45a2c9c0ac..070c013ce3 100644 --- a/plugins/impex/jpeg/kis_jpeg_converter.h +++ b/plugins/impex/jpeg/kis_jpeg_converter.h @@ -1,86 +1,86 @@ /* * 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. */ #ifndef _KIS_JPEG_CONVERTER_H_ #define _KIS_JPEG_CONVERTER_H_ #include extern "C" { #include } #include #include #include "kis_types.h" #include "kis_annotation.h" -#include +#include class KisDocument; namespace KisMetaData { class Filter; } struct KisJPEGOptions { int quality; bool progressive; bool optimize; int smooth; bool baseLineJPEG; int subsampling; bool exif; bool iptc; bool xmp; QList filters; QColor transparencyFillColor; bool forceSRGB; bool saveProfile; bool storeDocumentMetaData; //this is for getting the metadata from the document info. bool storeAuthor; //this is for storing author data from the document info. }; namespace KisMetaData { class Store; } class KisJPEGConverter : public QObject { Q_OBJECT public: KisJPEGConverter(KisDocument *doc, bool batchMode = false); ~KisJPEGConverter() override; public: - KisImageBuilder_Result buildImage(QIODevice *io); - KisImageBuilder_Result buildFile(QIODevice *io, KisPaintLayerSP layer, KisJPEGOptions options, KisMetaData::Store* metaData); + KisImportExportErrorCode buildImage(QIODevice *io); + KisImportExportErrorCode buildFile(QIODevice *io, KisPaintLayerSP layer, KisJPEGOptions options, KisMetaData::Store* metaData); /** Retrieve the constructed image */ KisImageSP image(); public Q_SLOTS: virtual void cancel(); private: - KisImageBuilder_Result decode(QIODevice *io); + KisImportExportErrorCode decode(QIODevice *io); private: struct Private; QScopedPointer m_d; }; #endif diff --git a/plugins/impex/jpeg/kis_jpeg_export.cc b/plugins/impex/jpeg/kis_jpeg_export.cc index eb2a7526cd..6bd8b31c72 100644 --- a/plugins/impex/jpeg/kis_jpeg_export.cc +++ b/plugins/impex/jpeg/kis_jpeg_export.cc @@ -1,296 +1,291 @@ /* * 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_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_jpeg_converter.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(KisJPEGExportFactory, "krita_jpeg_export.json", registerPlugin();) KisJPEGExport::KisJPEGExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisJPEGExport::~KisJPEGExport() { } -KisImportExportFilter::ConversionStatus KisJPEGExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisJPEGExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { KisImageSP image = document->savingImage(); Q_CHECK_PTR(image); // An extra option to pass to the config widget to set the state correctly, this isn't saved const KoColorSpace* cs = image->projection()->colorSpace(); bool sRGB = cs->profile()->name().contains(QLatin1String("srgb"), Qt::CaseInsensitive); configuration->setProperty("is_sRGB", sRGB); KisJPEGOptions options; options.progressive = configuration->getBool("progressive", false); options.quality = configuration->getInt("quality", 80); options.forceSRGB = configuration->getBool("forceSRGB", false); options.saveProfile = configuration->getBool("saveProfile", true); options.optimize = configuration->getBool("optimize", true); options.smooth = configuration->getInt("smoothing", 0); options.baseLineJPEG = configuration->getBool("baseline", true); options.subsampling = configuration->getInt("subsampling", 0); options.exif = configuration->getBool("exif", true); options.iptc = configuration->getBool("iptc", true); options.xmp = configuration->getBool("xmp", true); KoColor c(KoColorSpaceRegistry::instance()->rgb8()); c.fromQColor(Qt::white); options.transparencyFillColor = configuration->getColor("transparencyFillcolor", c).toQColor(); KisMetaData::FilterRegistryModel m; m.setEnabledFilters(configuration->getString("filters").split(",")); options.filters = m.enabledFilters(); options.storeAuthor = configuration->getBool("storeAuthor", false); options.storeDocumentMetaData = configuration->getBool("storeMetaData", false); KisPaintDeviceSP pd = new KisPaintDevice(*image->projection()); KisJPEGConverter kpc(document, batchMode()); KisPaintLayerSP l = new KisPaintLayer(image, "projection", OPACITY_OPAQUE_U8, pd); KisExifInfoVisitor exivInfoVisitor; exivInfoVisitor.visit(image->rootLayer().data()); QScopedPointer metaDataStore; if (exivInfoVisitor.metaDataCount() == 1) { metaDataStore.reset(new KisMetaData::Store(*exivInfoVisitor.exifInfo())); } else { metaDataStore.reset(new KisMetaData::Store()); } //add extra meta-data here const KisMetaData::Schema* dcSchema = KisMetaData::SchemaRegistry::instance()->schemaFromUri(KisMetaData::Schema::DublinCoreSchemaUri); Q_ASSERT(dcSchema); if (options.storeDocumentMetaData) { QString title = document->documentInfo()->aboutInfo("title"); if (!title.isEmpty()) { if (metaDataStore->containsEntry("title")) { metaDataStore->removeEntry("title"); } metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "title", KisMetaData::Value(QVariant(title)))); } QString description = document->documentInfo()->aboutInfo("subject"); if (description.isEmpty()) { description = document->documentInfo()->aboutInfo("abstract"); } if (!description.isEmpty()) { QString keywords = document->documentInfo()->aboutInfo("keyword"); if (!keywords.isEmpty()) { description = description + " keywords: " + keywords; } if (metaDataStore->containsEntry("description")) { metaDataStore->removeEntry("description"); } metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "description", KisMetaData::Value(QVariant(description)))); } QString license = document->documentInfo()->aboutInfo("license"); if (!license.isEmpty()) { if (metaDataStore->containsEntry("rights")) { metaDataStore->removeEntry("rights"); } metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "rights", KisMetaData::Value(QVariant(license)))); } QString date = document->documentInfo()->aboutInfo("date"); if (!date.isEmpty() && !metaDataStore->containsEntry("rights")) { metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "date", KisMetaData::Value(QVariant(date)))); } } if (options.storeAuthor) { QString author = document->documentInfo()->authorInfo("creator"); if (!author.isEmpty()) { if (!document->documentInfo()->authorContactInfo().isEmpty()) { QString contact = document->documentInfo()->authorContactInfo().at(0); if (!contact.isEmpty()) { author = author+"("+contact+")"; } } if (metaDataStore->containsEntry("creator")) { metaDataStore->removeEntry("creator"); } metaDataStore->addEntry(KisMetaData::Entry(dcSchema, "creator", KisMetaData::Value(QVariant(author)))); } } - KisImageBuilder_Result res = kpc.buildFile(io, l, options, metaDataStore.data()); - - if (res == KisImageBuilder_RESULT_OK) { - return KisImportExportFilter::OK; - } - dbgFile << " Result =" << res; - return KisImportExportFilter::InternalError; + KisImportExportErrorCode res = kpc.buildFile(io, l, options, metaDataStore.data()); + return res; } KisPropertiesConfigurationSP KisJPEGExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("progressive", false); cfg->setProperty("quality", 80); cfg->setProperty("forceSRGB", false); cfg->setProperty("saveProfile", true); cfg->setProperty("optimize", true); cfg->setProperty("smoothing", 0); cfg->setProperty("baseline", true); cfg->setProperty("subsampling", 0); cfg->setProperty("exif", true); cfg->setProperty("iptc", true); cfg->setProperty("xmp", true); cfg->setProperty("storeAuthor", false); cfg->setProperty("storeMetaData", false); KoColor fill_color(KoColorSpaceRegistry::instance()->rgb8()); fill_color = KoColor(); fill_color.fromQColor(Qt::white); QVariant v; v.setValue(fill_color); cfg->setProperty("transparencyFillcolor", v); cfg->setProperty("filters", ""); return cfg; } KisConfigWidget *KisJPEGExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const { return new KisWdgOptionsJPEG(parent); } void KisJPEGExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("ExifCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(CMYKAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "JPEG"); } KisWdgOptionsJPEG::KisWdgOptionsJPEG(QWidget *parent) : KisConfigWidget(parent) { setupUi(this); metaDataFilters->setModel(&m_filterRegistryModel); qualityLevel->setRange(0, 100, 0); qualityLevel->setSuffix("%"); smoothLevel->setRange(0, 100, 0); smoothLevel->setSuffix("%"); } void KisWdgOptionsJPEG::setConfiguration(const KisPropertiesConfigurationSP cfg) { progressive->setChecked(cfg->getBool("progressive", false)); qualityLevel->setValue(cfg->getInt("quality", 80)); optimize->setChecked(cfg->getBool("optimize", true)); smoothLevel->setValue(cfg->getInt("smoothing", 0)); baseLineJPEG->setChecked(cfg->getBool("baseline", true)); subsampling->setCurrentIndex(cfg->getInt("subsampling", 0)); exif->setChecked(cfg->getBool("exif", true)); iptc->setChecked(cfg->getBool("iptc", true)); xmp->setChecked(cfg->getBool("xmp", true)); chkForceSRGB->setVisible(cfg->getBool("is_sRGB")); chkForceSRGB->setChecked(cfg->getBool("forceSRGB", false)); chkSaveProfile->setChecked(cfg->getBool("saveProfile", true)); KoColor background(KoColorSpaceRegistry::instance()->rgb8()); background.fromQColor(Qt::white); bnTransparencyFillColor->setDefaultColor(background); bnTransparencyFillColor->setColor(cfg->getColor("transparencyFillcolor", background)); chkAuthor->setChecked(cfg->getBool("storeAuthor", false)); chkMetaData->setChecked(cfg->getBool("storeMetaData", false)); m_filterRegistryModel.setEnabledFilters(cfg->getString("filters").split(',')); } KisPropertiesConfigurationSP KisWdgOptionsJPEG::configuration() const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); QVariant transparencyFillcolor; transparencyFillcolor.setValue(bnTransparencyFillColor->color()); cfg->setProperty("progressive", progressive->isChecked()); cfg->setProperty("quality", (int)qualityLevel->value()); cfg->setProperty("forceSRGB", chkForceSRGB->isChecked()); cfg->setProperty("saveProfile", chkSaveProfile->isChecked()); cfg->setProperty("optimize", optimize->isChecked()); cfg->setProperty("smoothing", (int)smoothLevel->value()); cfg->setProperty("baseline", baseLineJPEG->isChecked()); cfg->setProperty("subsampling", subsampling->currentIndex()); cfg->setProperty("exif", exif->isChecked()); cfg->setProperty("iptc", iptc->isChecked()); cfg->setProperty("xmp", xmp->isChecked()); cfg->setProperty("transparencyFillcolor", transparencyFillcolor); cfg->setProperty("storeAuthor", chkAuthor->isChecked()); cfg->setProperty("storeMetaData", chkMetaData->isChecked()); QString enabledFilters; Q_FOREACH (const KisMetaData::Filter* filter, m_filterRegistryModel.enabledFilters()) { enabledFilters = enabledFilters + filter->id() + ','; } cfg->setProperty("filters", enabledFilters); return cfg; } #include diff --git a/plugins/impex/jpeg/kis_jpeg_export.h b/plugins/impex/jpeg/kis_jpeg_export.h index 624f9cea68..65ca4a44e9 100644 --- a/plugins/impex/jpeg/kis_jpeg_export.h +++ b/plugins/impex/jpeg/kis_jpeg_export.h @@ -1,58 +1,58 @@ /* * 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. */ #ifndef _KIS_JPEG_EXPORT_H_ #define _KIS_JPEG_EXPORT_H_ #include #include #include #include "ui_kis_wdg_options_jpeg.h" #include #include class KisWdgOptionsJPEG : public KisConfigWidget, public Ui::WdgOptionsJPEG { Q_OBJECT public: KisWdgOptionsJPEG(QWidget *parent); void setConfiguration(const KisPropertiesConfigurationSP cfg) override; KisPropertiesConfigurationSP configuration() const override; private: KisMetaData::FilterRegistryModel m_filterRegistryModel; }; class KisJPEGExport : public KisImportExportFilter { Q_OBJECT public: KisJPEGExport(QObject *parent, const QVariantList &); ~KisJPEGExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/jpeg/kis_jpeg_import.cc b/plugins/impex/jpeg/kis_jpeg_import.cc index 4a80e3dc53..911c3c453d 100644 --- a/plugins/impex/jpeg/kis_jpeg_import.cc +++ b/plugins/impex/jpeg/kis_jpeg_import.cc @@ -1,80 +1,54 @@ /* * 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_import.h" #include #include #include #include #include #include #include "kis_jpeg_converter.h" K_PLUGIN_FACTORY_WITH_JSON(JPEGImportFactory, "krita_jpeg_import.json", registerPlugin();) KisJPEGImport::KisJPEGImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisJPEGImport::~KisJPEGImport() { } -KisImportExportFilter::ConversionStatus KisJPEGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisJPEGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { - KisJPEGConverter ib(document, batchMode()); - - switch (ib.buildImage(io)) { - case KisImageBuilder_RESULT_UNSUPPORTED: - case KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE: - return KisImportExportFilter::NotImplemented; - break; - case KisImageBuilder_RESULT_INVALID_ARG: - return KisImportExportFilter::BadMimeType; - break; - case KisImageBuilder_RESULT_NO_URI: - case KisImageBuilder_RESULT_NOT_LOCAL: - return KisImportExportFilter::FileNotFound; - break; - case KisImageBuilder_RESULT_BAD_FETCH: - case KisImageBuilder_RESULT_EMPTY: - return KisImportExportFilter::ParsingError; - break; - case KisImageBuilder_RESULT_FAILURE: - return KisImportExportFilter::InternalError; - break; - case KisImageBuilder_RESULT_OK: + KisImportExportErrorCode result = ib.buildImage(io); + if (result.isOk()) { document->setCurrentImage(ib.image()); - return KisImportExportFilter::OK; - default: - break; - } - - return KisImportExportFilter::InternalError; - + return result; } #include diff --git a/plugins/impex/jpeg/kis_jpeg_import.h b/plugins/impex/jpeg/kis_jpeg_import.h index 598237b859..63555c740b 100644 --- a/plugins/impex/jpeg/kis_jpeg_import.h +++ b/plugins/impex/jpeg/kis_jpeg_import.h @@ -1,36 +1,36 @@ /* * 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. */ #ifndef _KIS_JPEG_IMPORT_H_ #define _KIS_JPEG_IMPORT_H_ #include #include class KisJPEGImport : public KisImportExportFilter { Q_OBJECT public: KisJPEGImport(QObject *parent, const QVariantList &); ~KisJPEGImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/jpeg/tests/data/incorrectFormatFile.txt b/plugins/impex/jpeg/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/jpeg/tests/data/readonlyFile.txt b/plugins/impex/jpeg/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/jpeg/tests/data/writeonlyFile.txt b/plugins/impex/jpeg/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.cpp b/plugins/impex/jpeg/tests/kis_jpeg_test.cpp index 59da037645..7d84ef5cdc 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.cpp +++ b/plugins/impex/jpeg/tests/kis_jpeg_test.cpp @@ -1,55 +1,74 @@ /* * Copyright (C) 2007 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_test.h" #include #include #include "kisexiv2/kis_exiv2.h" #include "filestest.h" #include "jpeglib.h" #include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif #ifndef JPEG_LIB_VERSION #error "JPEG_LIB_VERSION not set. libjpeg should set it." #endif +const QString JpegMimetype = "image/jpeg"; + void KisJpegTest::testFiles() { KisExiv2::initialize(); /** * Different versions of JPEG library may produce a bit different * result, so just compare in a weak way, i.e, only the size for real */ const int fuzziness = 1; const int maxNumFailingPixels = 2592 * 1952; // All pixels can be different... + const bool showDebug = false; // No need to write down all pixels that are different since all of them can be. if (JPEG_LIB_VERSION == 80){ - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), "_80", fuzziness, maxNumFailingPixels); + TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), "_80", fuzziness, maxNumFailingPixels, showDebug); }else { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), fuzziness, maxNumFailingPixels); + TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), fuzziness, maxNumFailingPixels, showDebug); } +} + +void KisJpegTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), JpegMimetype); +} + +void KisJpegTest::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), JpegMimetype); +} + + +void KisJpegTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), JpegMimetype); } KISTEST_MAIN(KisJpegTest) diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/jpeg/tests/kis_jpeg_test.h index 5c19b0cda6..dfc815be3c 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/jpeg/tests/kis_jpeg_test.h @@ -1,31 +1,34 @@ /* * Copyright (C) 2007 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. */ #ifndef _KIS_JPEG_TEST_H_ #define _KIS_JPEG_TEST_H_ #include class KisJpegTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; #endif diff --git a/plugins/impex/kra/kra_converter.cpp b/plugins/impex/kra/kra_converter.cpp index 5bce748908..807bfbe6de 100644 --- a/plugins/impex/kra/kra_converter.cpp +++ b/plugins/impex/kra/kra_converter.cpp @@ -1,372 +1,377 @@ /* * 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() { delete m_store; delete m_kraSaver; delete m_kraLoader; } -KisImageBuilder_Result KraConverter::buildImage(QIODevice *io) +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 KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::FileFormatIncorrect; } bool success; { if (m_store->hasFile("root") || m_store->hasFile("maindoc.xml")) { // Fallback to "old" file format (maindoc.xml) KoXmlDocument doc; - bool ok = oldLoadAndParse(m_store, "root", doc); - if (ok) - ok = loadXML(doc, m_store); - if (!ok) { - return KisImageBuilder_RESULT_FAILURE; + 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 KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::FileFormatIncorrect; } if (m_store->hasFile("documentinfo.xml")) { KoXmlDocument doc; - if (oldLoadAndParse(m_store, "documentinfo.xml", doc)) { + if (oldLoadAndParse(m_store, "documentinfo.xml", doc).isOk()) { m_doc->documentInfo()->load(doc); } } success = completeLoading(m_store); } - return success ? KisImageBuilder_RESULT_OK : KisImageBuilder_RESULT_FAILURE; + return success ? ImportExportCodes::OK : ImportExportCodes::Failure; } KisImageSP KraConverter::image() { return m_image; } vKisNodeSP KraConverter::activeNodes() { return m_activeNodes; } QList KraConverter::assistants() { return m_assistants; } -KisImageBuilder_Result KraConverter::buildFile(QIODevice *io, const QString &filename) +KisImportExportErrorCode KraConverter::buildFile(QIODevice *io, const QString &filename) { 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 KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::CannotCreateFile; } - bool result = false; m_kraSaver = new KisKraSaver(m_doc, filename); - result = saveRootDocuments(m_store); + KisImportExportErrorCode resultCode = saveRootDocuments(m_store); - if (!result) { - return KisImageBuilder_RESULT_FAILURE; + if (!resultCode.isOk()) { + return resultCode; } + bool result; + result = m_kraSaver->saveKeyframes(m_store, m_doc->url().toLocalFile(), true); if (!result) { qWarning() << "saving key frames failed"; } result = m_kraSaver->saveBinaryData(m_store, m_image, m_doc->url().toLocalFile(), true, m_doc->isAutosaving()); if (!result) { qWarning() << "saving binary data failed"; } result = m_kraSaver->savePalettes(m_store, m_image, m_doc->url().toLocalFile()); if (!result) { qWarning() << "saving palettes data failed"; } if (!m_store->finalize()) { - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::Failure; } if (!m_kraSaver->errorMessages().isEmpty()) { m_doc->setErrorMessage(m_kraSaver->errorMessages().join(".\n")); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::Failure; } - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } -bool KraConverter::saveRootDocuments(KoStore *store) +KisImportExportErrorCode KraConverter::saveRootDocuments(KoStore *store) { dbgFile << "Saving root"; if (store->open("root")) { KoStoreDevice dev(store); if (!saveToStream(&dev) || !store->close()) { dbgUI << "saveToStream failed"; - return false; + return ImportExportCodes::NoAccessToWrite; } } else { m_doc->setErrorMessage(i18n("Not able to write '%1'. Partition full?", QString("maindoc.xml"))); - return false; + return ImportExportCodes::ErrorWhileWriting; } - bool success = false; + 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! - success = dev.write(s.data(), s.size()); + 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!) - savePreview(store); + 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(); - // Success - return success; + 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; } -bool KraConverter::savePreview(KoStore *store) +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 false; + return ImportExportCodes::NoAccessToWrite; } bool ret = preview.save(&io, "PNG"); io.close(); - return ret; + return ret ? ImportExportCodes::OK : ImportExportCodes::ErrorWhileWriting; } -bool KraConverter::oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc) +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 false; + 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 false; + return ImportExportCodes::FileFormatIncorrect; } dbgUI << "File" << filename << " loaded and parsed"; - return true; + return ImportExportCodes::OK; } -bool KraConverter::loadXML(const KoXmlDocument &doc, KoStore *store) +KisImportExportErrorCode KraConverter::loadXML(const KoXmlDocument &doc, KoStore *store) { Q_UNUSED(store); KoXmlElement root; KoXmlNode node; if (doc.doctype().name() != "DOC") { - m_doc->setErrorMessage(i18n("The format is not supported or the file is corrupted")); - return false; + errUI << "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) { - m_doc->setErrorMessage(i18n("The file is too new for this version of Krita (%1).", syntaxVersion)); - return false; + errUI << "The file is too new for this version of Krita: " + syntaxVersion; + return ImportExportCodes::FormatFeaturesUnsupported; } if (!root.hasChildNodes()) { - m_doc->setErrorMessage(i18n("The file has no layers.")); - return false; + errUI << "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()) { - m_doc->setErrorMessage(i18n("Unknown error.")); + errUI << "Unknown error while opening the .kra file."; } else { - m_doc->setErrorMessage(m_kraLoader->errorMessages().join("\n")); + errUI << m_kraLoader->errorMessages().join("\n"); } - return false; + return ImportExportCodes::Failure; } // HACK ALERT! m_doc->hackPreliminarySetImage(m_image); - return true; + return ImportExportCodes::OK; } else { if (m_kraLoader->errorMessages().isEmpty()) { m_doc->setErrorMessage(i18n("The file does not contain an image.")); } - return false; + return ImportExportCodes::FileFormatIncorrect; } } } - return false; + 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; } diff --git a/plugins/impex/kra/kra_converter.h b/plugins/impex/kra/kra_converter.h index ff5b50a331..46a634e9d7 100644 --- a/plugins/impex/kra/kra_converter.h +++ b/plugins/impex/kra/kra_converter.h @@ -1,80 +1,80 @@ /* * 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 class KisDocument; class KraConverter : public QObject { Q_OBJECT public: KraConverter(KisDocument *doc); ~KraConverter() override; - KisImageBuilder_Result buildImage(QIODevice *io); - KisImageBuilder_Result buildFile(QIODevice *io, const QString &filename); + 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: - bool saveRootDocuments(KoStore *store); + KisImportExportErrorCode saveRootDocuments(KoStore *store); bool saveToStream(QIODevice *dev); QDomDocument createDomDocument(); - bool savePreview(KoStore *store); - bool oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc); - bool loadXML(const KoXmlDocument &doc, KoStore *store); + KisImportExportErrorCode savePreview(KoStore *store); + KisImportExportErrorCode oldLoadAndParse(KoStore *store, const QString &filename, KoXmlDocument &xmldoc); + KisImportExportErrorCode loadXML(const KoXmlDocument &doc, KoStore *store); bool completeLoading(KoStore *store); 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}; }; #endif diff --git a/plugins/impex/kra/kra_export.cpp b/plugins/impex/kra/kra_export.cpp index 69a1a6984b..4e9c4d8a7e 100644 --- a/plugins/impex/kra/kra_export.cpp +++ b/plugins/impex/kra/kra_export.cpp @@ -1,83 +1,78 @@ /* * 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() { } -KisImportExportFilter::ConversionStatus KraExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KraExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { KisImageSP image = document->savingImage(); - KIS_ASSERT_RECOVER_RETURN_VALUE(image, CreationError); + KIS_ASSERT_RECOVER_RETURN_VALUE(image, ImportExportCodes::InternalError); KraConverter kraConverter(document); - KisImageBuilder_Result res = kraConverter.buildFile(io, filename()); - - if (res == KisImageBuilder_RESULT_OK) { - dbgFile << "KraExport::convert success !"; - return KisImportExportFilter::OK; - } + KisImportExportErrorCode res = kraConverter.buildFile(io, filename()); dbgFile << "KraExport::convert result =" << res; - return KisImportExportFilter::InternalError; + 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)); } } #include diff --git a/plugins/impex/kra/kra_export.h b/plugins/impex/kra/kra_export.h index 2dc08f2cb7..bffdb4f824 100644 --- a/plugins/impex/kra/kra_export.h +++ b/plugins/impex/kra/kra_export.h @@ -1,39 +1,39 @@ /* * 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_EXPORT_H_ #define _KRA_EXPORT_H_ #include #include class KraExport : public KisImportExportFilter { Q_OBJECT public: KraExport(QObject *parent, const QVariantList &); ~KraExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/kra/kra_import.cpp b/plugins/impex/kra/kra_import.cpp index bbc6bcaff2..3f85a6835b 100644 --- a/plugins/impex/kra/kra_import.cpp +++ b/plugins/impex/kra/kra_import.cpp @@ -1,77 +1,47 @@ /* * 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_import.h" #include #include #include #include #include "kra_converter.h" K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_kra_import.json", registerPlugin();) KraImport::KraImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KraImport::~KraImport() { } -KisImportExportFilter::ConversionStatus KraImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KraImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { KraConverter kraConverter(document); - switch (kraConverter.buildImage(io)) { - case KisImageBuilder_RESULT_UNSUPPORTED: - return KisImportExportFilter::NotImplemented; - break; - case KisImageBuilder_RESULT_INVALID_ARG: - return KisImportExportFilter::BadMimeType; - break; - case KisImageBuilder_RESULT_NO_URI: - case KisImageBuilder_RESULT_NOT_LOCAL: - return KisImportExportFilter::FileNotFound; - break; - case KisImageBuilder_RESULT_BAD_FETCH: - case KisImageBuilder_RESULT_EMPTY: - return KisImportExportFilter::ParsingError; - break; - case KisImageBuilder_RESULT_FAILURE: - return KisImportExportFilter::InvalidFormat; - break; - case KisImageBuilder_RESULT_OK: - document->setCurrentImage(kraConverter.image()); - if (kraConverter.activeNodes().size() > 0) { - document->setPreActivatedNode(kraConverter.activeNodes()[0]); - } - if (kraConverter.assistants().size() > 0) { - document->setAssistants(kraConverter.assistants()); - } - return KisImportExportFilter::OK; - default: - break; - } - return KisImportExportFilter::InternalError; + return kraConverter.buildImage(io); } #include diff --git a/plugins/impex/kra/kra_import.h b/plugins/impex/kra/kra_import.h index f5eb04a2db..e921cdd060 100644 --- a/plugins/impex/kra/kra_import.h +++ b/plugins/impex/kra/kra_import.h @@ -1,37 +1,37 @@ /* * 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_IMPORT_H_ #define KRA_IMPORT_H_ #include #include class KraImport : public KisImportExportFilter { Q_OBJECT public: KraImport(QObject *parent, const QVariantList &); ~KraImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/libkra/tests/data/incorrectFormatFile.txt b/plugins/impex/libkra/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/libkra/tests/data/readonlyFile.txt b/plugins/impex/libkra/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/libkra/tests/data/writeonlyFile.txt b/plugins/impex/libkra/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/libkra/tests/kis_kra_loader_test.cpp b/plugins/impex/libkra/tests/kis_kra_loader_test.cpp index 10350d829c..500295dcd7 100644 --- a/plugins/impex/libkra/tests/kis_kra_loader_test.cpp +++ b/plugins/impex/libkra/tests/kis_kra_loader_test.cpp @@ -1,173 +1,192 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * 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_kra_loader_test.h" #include #include #include #include #include #include #include "kis_image.h" #include "testutil.h" #include "KisPart.h" #include #include #include "kis_image_animation_interface.h" #include "kis_keyframe_channel.h" #include "kis_time_range.h" +#include + #include + +const QString KraMimetype = "application/x-krita"; + void KisKraLoaderTest::initTestCase() { KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); } void KisKraLoaderTest::testLoading() { QScopedPointer doc(KisPart::instance()->createDocument()); doc->loadNativeFormat(QString(FILES_DATA_DIR) + QDir::separator() + "load_test.kra"); KisImageSP image = doc->image(); image->lock(); QCOMPARE(image->nlayers(), 12); QCOMPARE(doc->documentInfo()->aboutInfo("title"), QString("test image for loading")); QCOMPARE(image->height(), 753); QCOMPARE(image->width(), 1000); QCOMPARE(image->colorSpace()->id(), KoColorSpaceRegistry::instance()->rgb8()->id()); KisNodeSP node = image->root()->firstChild(); QVERIFY(node); QCOMPARE(node->name(), QString("Background")); QVERIFY(node->inherits("KisPaintLayer")); node = node->nextSibling(); QVERIFY(node); QCOMPARE(node->name(), QString("Group 1")); QVERIFY(node->inherits("KisGroupLayer")); QCOMPARE((int) node->childCount(), 2); } void testObligeSingleChildImpl(bool transpDefaultPixel) { QString id = !transpDefaultPixel ? "single_layer_no_channel_flags_nontransp_def_pixel.kra" : "single_layer_no_channel_flags_transp_def_pixel.kra"; QString fileName = TestUtil::fetchDataFileLazy(id); QScopedPointer doc(KisPart::instance()->createDocument()); const bool result = doc->loadNativeFormat(fileName); QVERIFY(result); KisImageSP image = doc->image(); QVERIFY(image); QCOMPARE(image->nlayers(), 2); KisNodeSP root = image->root(); KisNodeSP child = root->firstChild(); QVERIFY(child); QCOMPARE(root->original(), root->projection()); if (transpDefaultPixel) { QCOMPARE(root->original(), child->projection()); } else { QVERIFY(root->original() != child->projection()); } } void KisKraLoaderTest::testObligeSingleChild() { testObligeSingleChildImpl(true); } void KisKraLoaderTest::testObligeSingleChildNonTranspPixel() { testObligeSingleChildImpl(false); } void KisKraLoaderTest::testLoadAnimated() { QScopedPointer doc(KisPart::instance()->createDocument()); doc->loadNativeFormat(QString(FILES_DATA_DIR) + QDir::separator() + "load_test_animation.kra"); KisImageSP image = doc->image(); KisNodeSP node1 = image->root()->firstChild(); KisNodeSP node2 = node1->nextSibling(); QVERIFY(node1->inherits("KisPaintLayer")); QVERIFY(node2->inherits("KisPaintLayer")); KisPaintLayerSP layer1 = qobject_cast(node1.data()); KisPaintLayerSP layer2 = qobject_cast(node2.data()); QVERIFY(layer1->isAnimated()); QVERIFY(!layer2->isAnimated()); KisKeyframeChannel *channel1 = layer1->getKeyframeChannel(KisKeyframeChannel::Content.id()); QVERIFY(channel1); QCOMPARE(channel1->keyframeCount(), 3); QCOMPARE(image->animationInterface()->framerate(), 17); QCOMPARE(image->animationInterface()->fullClipRange(), KisTimeRange::fromTime(15, 45)); QCOMPARE(image->animationInterface()->currentTime(), 19); KisPaintDeviceSP dev = layer1->paintDevice(); const KoColorSpace *cs = dev->colorSpace(); KoColor transparent(Qt::transparent, cs); KoColor white(Qt::white, cs); KoColor red(Qt::red, cs); image->animationInterface()->switchCurrentTimeAsync(0); image->waitForDone(); QCOMPARE(dev->exactBounds(), QRect(506, 378, 198, 198)); QCOMPARE(dev->x(), -26); QCOMPARE(dev->y(), -128); QCOMPARE(dev->defaultPixel(), transparent); image->animationInterface()->switchCurrentTimeAsync(20); image->waitForDone(); QCOMPARE(dev->nonDefaultPixelArea(), QRect(615, 416, 129, 129)); QCOMPARE(dev->x(), 502); QCOMPARE(dev->y(), 224); QCOMPARE(dev->defaultPixel(), white); image->animationInterface()->switchCurrentTimeAsync(30); image->waitForDone(); QCOMPARE(dev->nonDefaultPixelArea(), QRect(729, 452, 45, 44)); QCOMPARE(dev->x(), 645); QCOMPARE(dev->y(), -10); QCOMPARE(dev->defaultPixel(), red); } + +void KisKraLoaderTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), KraMimetype); +} + + +void KisKraLoaderTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), KraMimetype); +} + + + KISTEST_MAIN(KisKraLoaderTest) diff --git a/plugins/impex/libkra/tests/kis_kra_loader_test.h b/plugins/impex/libkra/tests/kis_kra_loader_test.h index 88da2d5448..99a1caf0a4 100644 --- a/plugins/impex/libkra/tests/kis_kra_loader_test.h +++ b/plugins/impex/libkra/tests/kis_kra_loader_test.h @@ -1,37 +1,42 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * 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_KRA_LOADER_TEST_H #define KIS_KRA_LOADER_TEST_H #include class KisKraLoaderTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testLoading(); void testObligeSingleChild(); void testObligeSingleChildNonTranspPixel(); void testLoadAnimated(); + + void testImportFromWriteonly(); + void testImportIncorrectFormat(); + + }; #endif diff --git a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp index bd7fbfb1ee..e9afb6f14c 100644 --- a/plugins/impex/libkra/tests/kis_kra_saver_test.cpp +++ b/plugins/impex/libkra/tests/kis_kra_saver_test.cpp @@ -1,543 +1,552 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * 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_kra_saver_test.h" #include #include #include #include #include #include #include "filter/kis_filter_registry.h" #include "filter/kis_filter_configuration.h" #include "filter/kis_filter.h" #include "kis_image.h" #include "kis_pixel_selection.h" #include "kis_group_layer.h" #include "kis_paint_layer.h" #include "kis_clone_layer.h" #include "kis_adjustment_layer.h" #include "kis_shape_layer.h" #include "kis_filter_mask.h" #include "kis_transparency_mask.h" #include "kis_selection_mask.h" #include "kis_selection.h" #include "kis_fill_painter.h" #include "kis_shape_selection.h" #include "util.h" #include "testutil.h" #include "kis_keyframe_channel.h" #include "kis_image_animation_interface.h" #include "kis_layer_properties_icons.h" #include "kis_transform_mask_params_interface.h" #include #include #include +#include + +const QString KraMimetype = "application/x-krita"; void KisKraSaverTest::initTestCase() { KoResourcePaths::addResourceDir("ko_patterns", QString(SYSTEM_RESOURCES_DATA_DIR) + "/patterns"); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); } void KisKraSaverTest::testCrashyShapeLayer() { /** * KisShapeLayer used to call setImage from its destructor and * therefore causing an infinite recursion (when at least one transparency * mask was preset. This testcase just checks that. */ //QScopedPointer doc(createCompleteDocument(true)); //Q_UNUSED(doc); } void KisKraSaverTest::testRoundTrip() { KisDocument* doc = createCompleteDocument(); KoColor bgColor(Qt::red, doc->image()->colorSpace()); doc->image()->setDefaultProjectionColor(bgColor); doc->exportDocumentSync(QUrl::fromLocalFile("roundtriptest.kra"), doc->mimeType()); QStringList list; KisCountVisitor cv1(list, KoProperties()); doc->image()->rootLayer()->accept(cv1); KisDocument *doc2 = KisPart::instance()->createDocument(); bool result = doc2->loadNativeFormat("roundtriptest.kra"); QVERIFY(result); KisCountVisitor cv2(list, KoProperties()); doc2->image()->rootLayer()->accept(cv2); QCOMPARE(cv1.count(), cv2.count()); // check whether the BG color is saved correctly QCOMPARE(doc2->image()->defaultProjectionColor(), bgColor); // test round trip of a transform mask KisNode* tnode = TestUtil::findNode(doc2->image()->rootLayer(), "testTransformMask").data(); QVERIFY(tnode); KisTransformMask *tmask = dynamic_cast(tnode); QVERIFY(tmask); KisDumbTransformMaskParams *params = dynamic_cast(tmask->transformParams().data()); QVERIFY(params); QTransform t = params->testingGetTransform(); QCOMPARE(t, createTestingTransform()); delete doc2; delete doc; } void KisKraSaverTest::testSaveEmpty() { KisDocument* doc = createEmptyDocument(); doc->exportDocumentSync(QUrl::fromLocalFile("emptytest.kra"), doc->mimeType()); QStringList list; KisCountVisitor cv1(list, KoProperties()); doc->image()->rootLayer()->accept(cv1); KisDocument *doc2 = KisPart::instance()->createDocument(); doc2->loadNativeFormat("emptytest.kra"); KisCountVisitor cv2(list, KoProperties()); doc2->image()->rootLayer()->accept(cv2); QCOMPARE(cv1.count(), cv2.count()); delete doc2; delete doc; } #include void testRoundTripFillLayerImpl(const QString &testName, KisFilterConfigurationSP config) { TestUtil::ReferenceImageChecker chk(testName, "fill_layer"); chk.setFuzzy(2); QScopedPointer doc(KisPart::instance()->createDocument()); // mask parent should be destructed before the document! QRect refRect(0,0,512,512); TestUtil::MaskParent p(refRect); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); KisSelectionSP selection; KisGeneratorLayerSP glayer = new KisGeneratorLayer(p.image, "glayer", config, selection); p.image->addNode(glayer, p.image->root(), KisNodeSP()); glayer->setDirty(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_fill_layer_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_fill_layer_test.kra"); doc2->image()->waitForDone(); chk.checkImage(doc2->image(), "01_fill_layer_round_trip"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripFillLayerColor() { const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("color"); Q_ASSERT(generator); // warning: we pass null paint device to the default constructed value KisFilterConfigurationSP config = generator->factoryConfiguration(); Q_ASSERT(config); QVariant v; v.setValue(KoColor(Qt::red, cs)); config->setProperty("color", v); testRoundTripFillLayerImpl("fill_layer_color", config); } void KisKraSaverTest::testRoundTripFillLayerPattern() { KisGeneratorSP generator = KisGeneratorRegistry::instance()->get("pattern"); QVERIFY(generator); // warning: we pass null paint device to the default constructed value KisFilterConfigurationSP config = generator->factoryConfiguration(); QVERIFY(config); QVariant v; v.setValue(QString("11_drawed_furry.png")); config->setProperty("pattern", v); testRoundTripFillLayerImpl("fill_layer_pattern", config); } #include "kis_psd_layer_style.h" void KisKraSaverTest::testRoundTripLayerStyles() { TestUtil::ReferenceImageChecker chk("kra_saver_test", "layer_styles"); QRect imageRect(0,0,512,512); // the document should be created before the image! QScopedPointer doc(KisPart::instance()->createDocument()); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); KisPaintLayerSP layer2 = new KisPaintLayer(image, "paint2", OPACITY_OPAQUE_U8); KisPaintLayerSP layer3 = new KisPaintLayer(image, "paint3", OPACITY_OPAQUE_U8); image->addNode(layer1); image->addNode(layer2); image->addNode(layer3); doc->setCurrentImage(image); doc->documentInfo()->setAboutInfo("title", image->objectName()); layer1->paintDevice()->fill(QRect(100, 100, 100, 100), KoColor(Qt::red, cs)); layer2->paintDevice()->fill(QRect(200, 200, 100, 100), KoColor(Qt::green, cs)); layer3->paintDevice()->fill(QRect(300, 300, 100, 100), KoColor(Qt::blue, cs)); KisPSDLayerStyleSP style(new KisPSDLayerStyle()); style->dropShadow()->setEffectEnabled(true); style->dropShadow()->setAngle(-90); style->dropShadow()->setUseGlobalLight(false); layer1->setLayerStyle(style->clone()); style->dropShadow()->setAngle(180); style->dropShadow()->setUseGlobalLight(true); layer2->setLayerStyle(style->clone()); style->dropShadow()->setAngle(90); style->dropShadow()->setUseGlobalLight(false); layer3->setLayerStyle(style->clone()); image->initialRefreshGraph(); chk.checkImage(image, "00_initial_layers"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_layer_styles.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_layer_styles.kra"); doc2->image()->waitForDone(); chk.checkImage(doc2->image(), "00_initial_layers"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripAnimation() { QScopedPointer doc(KisPart::instance()->createDocument()); QRect imageRect(0,0,512,512); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8); image->addNode(layer1); layer1->paintDevice()->fill(QRect(100, 100, 50, 50), KoColor(Qt::black, cs)); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::red, cs)); KUndo2Command parentCommand; layer1->enableAnimation(); KisKeyframeChannel *rasterChannel = layer1->getKeyframeChannel(KisKeyframeChannel::Content.id(), true); QVERIFY(rasterChannel); rasterChannel->addKeyframe(10, &parentCommand); image->animationInterface()->switchCurrentTimeAsync(10); image->waitForDone(); layer1->paintDevice()->fill(QRect(200, 50, 10, 10), KoColor(Qt::black, cs)); layer1->paintDevice()->moveTo(25, 15); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::green, cs)); rasterChannel->addKeyframe(20, &parentCommand); image->animationInterface()->switchCurrentTimeAsync(20); image->waitForDone(); layer1->paintDevice()->fill(QRect(150, 200, 30, 30), KoColor(Qt::black, cs)); layer1->paintDevice()->moveTo(100, 50); layer1->paintDevice()->setDefaultPixel(KoColor(Qt::blue, cs)); QVERIFY(!layer1->useInTimeline()); layer1->setUseInTimeline(true); doc->setCurrentImage(image); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_animation.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_animation.kra"); KisImageSP image2 = doc2->image(); KisNodeSP node = image2->root()->firstChild(); QVERIFY(node->inherits("KisPaintLayer")); KisPaintLayerSP layer2 = qobject_cast(node.data()); cs = layer2->paintDevice()->colorSpace(); QCOMPARE(image2->animationInterface()->currentTime(), 20); KisKeyframeChannel *channel = layer2->getKeyframeChannel(KisKeyframeChannel::Content.id()); QVERIFY(channel); QCOMPARE(channel->keyframeCount(), 3); image2->animationInterface()->switchCurrentTimeAsync(0); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(64, 64, 128, 128)); QCOMPARE(layer2->paintDevice()->x(), 0); QCOMPARE(layer2->paintDevice()->y(), 0); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::red, cs)); image2->animationInterface()->switchCurrentTimeAsync(10); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(217, 15, 64, 64)); QCOMPARE(layer2->paintDevice()->x(), 25); QCOMPARE(layer2->paintDevice()->y(), 15); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::green, cs)); image2->animationInterface()->switchCurrentTimeAsync(20); image2->waitForDone(); QCOMPARE(layer2->paintDevice()->nonDefaultPixelArea(), QRect(228, 242, 64, 64)); QCOMPARE(layer2->paintDevice()->x(), 100); QCOMPARE(layer2->paintDevice()->y(), 50); QCOMPARE(layer2->paintDevice()->defaultPixel(), KoColor(Qt::blue, cs)); QVERIFY(layer2->useInTimeline()); } #include "lazybrush/kis_lazy_fill_tools.h" void KisKraSaverTest::testRoundTripColorizeMask() { QRect imageRect(0,0,512,512); const KoColorSpace * cs = KoColorSpaceRegistry::instance()->rgb8(); const KoColorSpace * weirdCS = KoColorSpaceRegistry::instance()->rgb16(); QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP image = new KisImage(new KisSurrogateUndoStore(), imageRect.width(), imageRect.height(), cs, "test image"); doc->setCurrentImage(image); KisPaintLayerSP layer1 = new KisPaintLayer(image, "paint1", OPACITY_OPAQUE_U8, weirdCS); image->addNode(layer1); KisColorizeMaskSP mask = new KisColorizeMask(); image->addNode(mask, layer1); mask->initializeCompositeOp(); delete mask->setColorSpace(layer1->colorSpace()); { KisPaintDeviceSP key1 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key1->fill(QRect(50,50,10,20), KoColor(Qt::black, key1->colorSpace())); mask->testingAddKeyStroke(key1, KoColor(Qt::green, layer1->colorSpace())); // KIS_DUMP_DEVICE_2(key1, refRect, "key1", "dd"); } { KisPaintDeviceSP key2 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key2->fill(QRect(150,50,10,20), KoColor(Qt::black, key2->colorSpace())); mask->testingAddKeyStroke(key2, KoColor(Qt::red, layer1->colorSpace())); // KIS_DUMP_DEVICE_2(key2, refRect, "key2", "dd"); } { KisPaintDeviceSP key3 = new KisPaintDevice(KoColorSpaceRegistry::instance()->alpha8()); key3->fill(QRect(0,0,10,10), KoColor(Qt::black, key3->colorSpace())); mask->testingAddKeyStroke(key3, KoColor(Qt::blue, layer1->colorSpace()), true); // KIS_DUMP_DEVICE_2(key3, refRect, "key3", "dd"); } KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, false, image); KisLayerPropertiesIcons::setNodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, false, image); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_colorize.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_colorize.kra"); KisImageSP image2 = doc2->image(); KisNodeSP node = image2->root()->firstChild()->firstChild(); KisColorizeMaskSP mask2 = dynamic_cast(node.data()); QVERIFY(mask2); QCOMPARE(mask2->compositeOpId(), mask->compositeOpId()); QCOMPARE(mask2->colorSpace(), mask->colorSpace()); QCOMPARE(KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeEditKeyStrokes, true).toBool(), false); QCOMPARE(KisLayerPropertiesIcons::nodeProperty(mask, KisLayerPropertiesIcons::colorizeShowColoring, true).toBool(), false); QList strokes = mask->fetchKeyStrokesDirect(); qDebug() << ppVar(strokes.size()); QCOMPARE(strokes[0].dev->exactBounds(), QRect(50,50,10,20)); QCOMPARE(strokes[0].isTransparent, false); QCOMPARE(strokes[0].color.colorSpace(), weirdCS); QCOMPARE(strokes[1].dev->exactBounds(), QRect(150,50,10,20)); QCOMPARE(strokes[1].isTransparent, false); QCOMPARE(strokes[1].color.colorSpace(), weirdCS); QCOMPARE(strokes[2].dev->exactBounds(), QRect(0,0,10,10)); QCOMPARE(strokes[2].isTransparent, true); QCOMPARE(strokes[2].color.colorSpace(), weirdCS); } #include void KisKraSaverTest::testRoundTripShapeLayer() { TestUtil::ReferenceImageChecker chk("kra_saver_test", "shape_layer"); QRect refRect(0,0,512,512); QScopedPointer doc(KisPart::instance()->createDocument()); TestUtil::MaskParent p(refRect); const qreal resolution = 144.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF( 10, 110)); path->lineTo(QPointF(110, 110)); path->lineTo(QPointF(110, 10)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("my_precious_shape"); KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), p.image, "shapeLayer1", 75); shapeLayer->addShape(path); p.image->addNode(shapeLayer); shapeLayer->setDirty(); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_layer_update"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_shapelayer_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_shapelayer_test.kra"); qApp->processEvents(); doc2->image()->waitForDone(); QCOMPARE(doc2->image()->xRes(), resolution); QCOMPARE(doc2->image()->yRes(), resolution); chk.checkImage(doc2->image(), "01_shape_layer_round_trip"); QVERIFY(chk.testPassed()); } void KisKraSaverTest::testRoundTripShapeSelection() { TestUtil::ReferenceImageChecker chk("kra_saver_test", "shape_selection"); QRect refRect(0,0,512,512); QScopedPointer doc(KisPart::instance()->createDocument()); TestUtil::MaskParent p(refRect); doc->setCurrentImage(p.image); const qreal resolution = 144.0 / 72.0; p.image->setResolution(resolution, resolution); doc->setCurrentImage(p.image); doc->documentInfo()->setAboutInfo("title", p.image->objectName()); p.layer->paintDevice()->setDefaultPixel(KoColor(Qt::green, p.layer->colorSpace())); KisSelectionSP selection = new KisSelection(p.layer->paintDevice()->defaultBounds()); KisShapeSelection *shapeSelection = new KisShapeSelection(doc->shapeController(), p.image, selection); selection->setShapeSelection(shapeSelection); KoPathShape* path = new KoPathShape(); path->setShapeId(KoPathShapeId); path->moveTo(QPointF(10, 10)); path->lineTo(QPointF( 10, 110)); path->lineTo(QPointF(110, 110)); path->lineTo(QPointF(110, 10)); path->close(); path->normalize(); path->setBackground(toQShared(new KoColorBackground(Qt::red))); path->setName("my_precious_shape"); shapeSelection->addShape(path); KisTransparencyMaskSP tmask = new KisTransparencyMask(); tmask->setSelection(selection); p.image->addNode(tmask, p.layer); tmask->setDirty(p.image->bounds()); qApp->processEvents(); p.image->waitForDone(); chk.checkImage(p.image, "00_initial_shape_selection"); doc->exportDocumentSync(QUrl::fromLocalFile("roundtrip_shapeselection_test.kra"), doc->mimeType()); QScopedPointer doc2(KisPart::instance()->createDocument()); doc2->loadNativeFormat("roundtrip_shapeselection_test.kra"); qApp->processEvents(); doc2->image()->waitForDone(); QCOMPARE(doc2->image()->xRes(), resolution); QCOMPARE(doc2->image()->yRes(), resolution); chk.checkImage(doc2->image(), "00_initial_shape_selection"); KisNodeSP node = doc2->image()->root()->firstChild()->firstChild(); KisTransparencyMask *newMask = dynamic_cast(node.data()); QVERIFY(newMask); QVERIFY(newMask->selection()->hasShapeSelection()); QVERIFY(chk.testPassed()); } + +void KisKraSaverTest::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), KraMimetype); +} + KISTEST_MAIN(KisKraSaverTest) diff --git a/plugins/impex/libkra/tests/kis_kra_saver_test.h b/plugins/impex/libkra/tests/kis_kra_saver_test.h index 04ff99c8d5..4a6c1310bb 100644 --- a/plugins/impex/libkra/tests/kis_kra_saver_test.h +++ b/plugins/impex/libkra/tests/kis_kra_saver_test.h @@ -1,51 +1,53 @@ /* * Copyright (c) 2007 Boudewijn Rempt boud@valdyas.org * * 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_KRA_SAVER_TEST_H #define KIS_KRA_SAVER_TEST_H #include class KisKraSaverTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase(); void testCrashyShapeLayer(); // XXX: Also test roundtripping of metadata void testRoundTrip(); void testSaveEmpty(); void testRoundTripFillLayerColor(); void testRoundTripFillLayerPattern(); void testRoundTripLayerStyles(); void testRoundTripAnimation(); void testRoundTripColorizeMask(); void testRoundTripShapeLayer(); void testRoundTripShapeSelection(); + void testExportToReadonly(); + }; #endif diff --git a/plugins/impex/ora/CMakeLists.txt b/plugins/impex/ora/CMakeLists.txt index 6cedf854e4..a476f422b4 100644 --- a/plugins/impex/ora/CMakeLists.txt +++ b/plugins/impex/ora/CMakeLists.txt @@ -1,32 +1,34 @@ +add_subdirectory(tests) + set(libkritaconverter_LIB_SRCS ora_converter.cpp kis_open_raster_load_context.cpp kis_open_raster_save_context.cpp kis_open_raster_stack_load_visitor.cpp kis_open_raster_stack_save_visitor.cpp ) set(kritaoraimport_SOURCES ora_import.cc ${libkritaconverter_LIB_SRCS} ) add_library(kritaoraimport MODULE ${kritaoraimport_SOURCES}) target_link_libraries(kritaoraimport kritaui ) install(TARGETS kritaoraimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) set(kritaoraexport_SOURCES ora_export.cc ${libkritaconverter_LIB_SRCS} ) add_library(kritaoraexport MODULE ${kritaoraexport_SOURCES}) target_link_libraries(kritaoraexport kritaui kritaimpex) install(TARGETS kritaoraexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_ora.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/ora/ora_converter.cpp b/plugins/impex/ora/ora_converter.cpp index 044b41fdf1..1df32a0c15 100644 --- a/plugins/impex/ora/ora_converter.cpp +++ b/plugins/impex/ora/ora_converter.cpp @@ -1,116 +1,117 @@ /* * 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ora_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include "kis_png_converter.h" #include "kis_open_raster_load_context.h" #include "kis_open_raster_save_context.h" OraConverter::OraConverter(KisDocument *doc) : m_doc(doc) , m_stop(false) { } OraConverter::~OraConverter() { } -KisImageBuilder_Result OraConverter::buildImage(QIODevice *io) +KisImportExportErrorCode OraConverter::buildImage(QIODevice *io) { KoStore* store = KoStore::createStore(io, KoStore::Read, "image/openraster", KoStore::Zip); if (!store) { delete store; - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::Failure; } KisOpenRasterLoadContext olc(store); KisOpenRasterStackLoadVisitor orslv(m_doc->createUndoStore(), &olc); orslv.loadImage(); m_image = orslv.image(); m_activeNodes = orslv.activeNodes(); delete store; - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } KisImageSP OraConverter::image() { return m_image; } vKisNodeSP OraConverter::activeNodes() { return m_activeNodes; } -KisImageBuilder_Result OraConverter::buildFile(QIODevice *io, KisImageSP image, vKisNodeSP activeNodes) +KisImportExportErrorCode OraConverter::buildFile(QIODevice *io, KisImageSP image, vKisNodeSP activeNodes) { // Open file for writing KoStore* store = KoStore::createStore(io, KoStore::Write, "image/openraster", KoStore::Zip); if (!store) { - return KisImageBuilder_RESULT_FAILURE; + delete store; + return ImportExportCodes::Failure; } KisOpenRasterSaveContext osc(store); KisOpenRasterStackSaveVisitor orssv(&osc, activeNodes); image->rootLayer()->accept(orssv); if (store->open("Thumbnails/thumbnail.png")) { QSize previewSize = image->bounds().size(); previewSize.scale(QSize(256,256), Qt::KeepAspectRatio); QImage preview = image->convertToQImage(previewSize, 0); KoStoreDevice io(store); if (io.open(QIODevice::WriteOnly)) { preview.save(&io, "PNG"); } io.close(); store->close(); } KisPaintDeviceSP dev = image->projection(); KisPNGConverter::saveDeviceToStore("mergedimage.png", image->bounds(), image->xRes(), image->yRes(), dev, store); delete store; - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } void OraConverter::cancel() { m_stop = true; } diff --git a/plugins/impex/ora/ora_converter.h b/plugins/impex/ora/ora_converter.h index 33f8c12d8c..5e1c2f0c27 100644 --- a/plugins/impex/ora/ora_converter.h +++ b/plugins/impex/ora/ora_converter.h @@ -1,53 +1,53 @@ /* * 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ORA_CONVERTER_H_ #define _ORA_CONVERTER_H_ #include #include #include "kis_png_converter.h" #include "kis_types.h" class KisDocument; class OraConverter : public QObject { Q_OBJECT public: OraConverter(KisDocument *doc); ~OraConverter() override; public: - KisImageBuilder_Result buildImage(QIODevice *io); - KisImageBuilder_Result buildFile(QIODevice *io, KisImageSP image, vKisNodeSP activeNodes); + KisImportExportErrorCode buildImage(QIODevice *io); + KisImportExportErrorCode buildFile(QIODevice *io, KisImageSP image, vKisNodeSP activeNodes); /** * Retrieve the constructed image */ KisImageSP image(); vKisNodeSP activeNodes(); public Q_SLOTS: virtual void cancel(); private: KisImageSP m_image; KisDocument *m_doc; vKisNodeSP m_activeNodes; bool m_stop; }; #endif diff --git a/plugins/impex/ora/ora_export.cc b/plugins/impex/ora/ora_export.cc index ed8ec1328c..711dbbbe79 100644 --- a/plugins/impex/ora/ora_export.cc +++ b/plugins/impex/ora/ora_export.cc @@ -1,109 +1,103 @@ /* * 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ora_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ora_converter.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_ora_export.json", registerPlugin();) OraExport::OraExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } OraExport::~OraExport() { } bool hasShapeLayerChild(KisNodeSP node) { if (!node) return false; Q_FOREACH (KisNodeSP child, node->childNodes(QStringList(), KoProperties())) { if (child->inherits("KisShapeLayer") || child->inherits("KisGeneratorLayer") || child->inherits("KisCloneLayer")) { return true; } else { if (hasShapeLayerChild(child)) { return true; } } } return false; } -KisImportExportFilter::ConversionStatus OraExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode OraExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { KisImageSP image = document->savingImage(); Q_CHECK_PTR(image); OraConverter oraConverter(document); - KisImageBuilder_Result res; - - if ((res = oraConverter.buildFile(io, image, {document->preActivatedNode()})) == KisImageBuilder_RESULT_OK) { - dbgFile << "success !"; - return KisImportExportFilter::OK; - } - dbgFile << " Result =" << res; - return KisImportExportFilter::InternalError; + KisImportExportErrorCode res = oraConverter.buildFile(io, image, {document->preActivatedNode()}); + return res; } void OraExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisGroupLayer")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisAdjustmentLayer")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("ColorModelHomogenousCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "OpenRaster"); } #include diff --git a/plugins/impex/ora/ora_export.h b/plugins/impex/ora/ora_export.h index a3a614475c..2e06e207f3 100644 --- a/plugins/impex/ora/ora_export.h +++ b/plugins/impex/ora/ora_export.h @@ -1,36 +1,36 @@ /* * 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ORA_EXPORT_H_ #define _ORA_EXPORT_H_ #include #include class OraExport : public KisImportExportFilter { Q_OBJECT public: OraExport(QObject *parent, const QVariantList &); ~OraExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/ora/ora_import.cc b/plugins/impex/ora/ora_import.cc index 635b88fe2d..2d49dee39c 100644 --- a/plugins/impex/ora/ora_import.cc +++ b/plugins/impex/ora/ora_import.cc @@ -1,76 +1,52 @@ /* * 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ora_import.h" #include #include #include #include #include "ora_converter.h" K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_ora_import.json", registerPlugin();) OraImport::OraImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } OraImport::~OraImport() { } -KisImportExportFilter::ConversionStatus OraImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode OraImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { OraConverter oraConverter(document); - - - switch (oraConverter.buildImage(io)) { - case KisImageBuilder_RESULT_UNSUPPORTED: - return KisImportExportFilter::NotImplemented; - break; - case KisImageBuilder_RESULT_INVALID_ARG: - return KisImportExportFilter::BadMimeType; - break; - case KisImageBuilder_RESULT_NO_URI: - case KisImageBuilder_RESULT_NOT_LOCAL: - return KisImportExportFilter::FileNotFound; - break; - case KisImageBuilder_RESULT_BAD_FETCH: - case KisImageBuilder_RESULT_EMPTY: - return KisImportExportFilter::ParsingError; - break; - case KisImageBuilder_RESULT_FAILURE: - return KisImportExportFilter::InternalError; - break; - case KisImageBuilder_RESULT_OK: + KisImportExportErrorCode result = oraConverter.buildImage(io); + if (result.isOk()) { document->setCurrentImage(oraConverter.image()); if (oraConverter.activeNodes().size() > 0) { document->setPreActivatedNode(oraConverter.activeNodes()[0]); } - return KisImportExportFilter::OK; - default: - break; } - - - return KisImportExportFilter::InternalError; + return result; } #include diff --git a/plugins/impex/ora/ora_import.h b/plugins/impex/ora/ora_import.h index bc40596deb..7e8b434752 100644 --- a/plugins/impex/ora/ora_import.h +++ b/plugins/impex/ora/ora_import.h @@ -1,35 +1,35 @@ /* * 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef ORA_IMPORT_H_ #define ORA_IMPORT_H_ #include #include class OraImport : public KisImportExportFilter { Q_OBJECT public: OraImport(QObject *parent, const QVariantList &); ~OraImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/ora/tests/CMakeLists.txt b/plugins/impex/ora/tests/CMakeLists.txt new file mode 100644 index 0000000000..40a1fd500f --- /dev/null +++ b/plugins/impex/ora/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisOraTest.cpp + TEST_NAME KisOraTest + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/ora/tests/KisOraTest.cpp similarity index 64% copy from plugins/impex/svg/tests/kis_svg_test.cpp copy to plugins/impex/ora/tests/KisOraTest.cpp index 6ca66688f8..39931639a0 100644 --- a/plugins/impex/svg/tests/kis_svg_test.cpp +++ b/plugins/impex/ora/tests/KisOraTest.cpp @@ -1,40 +1,57 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * 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_svg_test.h" +#include "KisOraTest.h" #include #include -#include - #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif -void KisSvgTest::testFiles() +const QString OraMimetype = "image/openraster"; + + + +void KisOraTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), OraMimetype); +} + + +void KisOraTest::testExportToReadonly() { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50); + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), OraMimetype); } -KISTEST_MAIN(KisSvgTest) + +void KisOraTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), OraMimetype); +} + + + +KISTEST_MAIN(KisOraTest) + diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/ora/tests/KisOraTest.h similarity index 74% copy from plugins/impex/jpeg/tests/kis_jpeg_test.h copy to plugins/impex/ora/tests/KisOraTest.h index 5c19b0cda6..a044b20b25 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/ora/tests/KisOraTest.h @@ -1,31 +1,35 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * 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_JPEG_TEST_H_ -#define _KIS_JPEG_TEST_H_ +#ifndef _KIS_ORA_TEST_H_ +#define _KIS_ORA_TEST_H_ #include -class KisJpegTest : public QObject +class KisOraTest : public QObject { Q_OBJECT private Q_SLOTS: - void testFiles(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; -#endif +#endif // _KIS_ORA_TEST_H_ + diff --git a/plugins/impex/ora/tests/data/incorrectFormatFile.txt b/plugins/impex/ora/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/ora/tests/data/readonlyFile.txt b/plugins/impex/ora/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/ora/tests/data/writeonlyFile.txt b/plugins/impex/ora/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/pdf/CMakeLists.txt b/plugins/impex/pdf/CMakeLists.txt index d3ada35792..320da291a4 100644 --- a/plugins/impex/pdf/CMakeLists.txt +++ b/plugins/impex/pdf/CMakeLists.txt @@ -1,10 +1,12 @@ +add_subdirectory(tests) + set(kritapdfimport_SOURCES kis_pdf_import.cpp kis_pdf_import_widget.cpp ) ki18n_wrap_ui(kritapdfimport_SOURCES pdfimportwidgetbase.ui ) add_library(kritapdfimport MODULE ${kritapdfimport_SOURCES}) target_link_libraries(kritapdfimport kritaui Poppler::Qt5) install(TARGETS kritapdfimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install(PROGRAMS krita_pdf.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/pdf/kis_pdf_import.cpp b/plugins/impex/pdf/kis_pdf_import.cpp index 7854cc0627..a30e49cd21 100644 --- a/plugins/impex/pdf/kis_pdf_import.cpp +++ b/plugins/impex/pdf/kis_pdf_import.cpp @@ -1,135 +1,136 @@ /* * Copyright (c) 2006 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_pdf_import.h" // poppler's headers #include // Qt's headers #include #include #include #include #include // KDE's headers #include #include #include #include #include // calligra's headers #include #include #include #include // krita's headers #include #include #include #include #include // plugins's headers #include "kis_pdf_import_widget.h" +#include K_PLUGIN_FACTORY_WITH_JSON(PDFImportFactory, "krita_pdf_import.json", registerPlugin();) KisPDFImport::KisPDFImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisPDFImport::~KisPDFImport() { } -KisPDFImport::ConversionStatus KisPDFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisPDFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { Poppler::Document* pdoc = Poppler::Document::loadFromData(io->readAll()); if (!pdoc) { dbgFile << "Error when reading the PDF"; - return KisImportExportFilter::StorageCreationError; + return ImportExportCodes::ErrorWhileReading; } pdoc->setRenderHint(Poppler::Document::Antialiasing, true); pdoc->setRenderHint(Poppler::Document::TextAntialiasing, true); while (pdoc->isLocked()) { KPasswordDialog dlg(0); dlg.setPrompt(i18n("A password is required to read that pdf")); dlg.setWindowTitle(i18n("A password is required to read that pdf")); if (dlg.exec() != QDialog::Accepted) { dbgFile << "Password canceled"; - return KisImportExportFilter::StorageCreationError; + return ImportExportCodes::Cancelled; } else pdoc->unlock(dlg.password().toLocal8Bit(), dlg.password().toLocal8Bit()); } KoDialog* kdb = new KoDialog(0); kdb->setCaption(i18n("PDF Import Options")); kdb->setModal(false); KisPDFImportWidget* wdg = new KisPDFImportWidget(pdoc, kdb); kdb->setMainWidget(wdg); QApplication::restoreOverrideCursor(); if (kdb->exec() == QDialog::Rejected) { delete pdoc; delete kdb; - return KisImportExportFilter::StorageCreationError; // FIXME Cancel doesn't exist :( + return ImportExportCodes::Cancelled; } // Create the krita image const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); int width = wdg->intWidth->value(); int height = wdg->intHeight->value(); KisImageSP image = new KisImage(document->createUndoStore(), width, height, cs, "built image"); image->setResolution(wdg->intResolution->value() / 72.0, wdg->intResolution->value() / 72.0); // create a layer QList pages = wdg->pages(); for (QList::const_iterator it = pages.constBegin(); it != pages.constEnd(); ++it) { KisPaintLayer* layer = new KisPaintLayer(image.data(), i18n("Page %1", *it + 1), quint8_MAX); Poppler::Page* page = pdoc->page(*it); QImage img = page->renderToImage(wdg->intResolution->value(), wdg->intResolution->value(), 0, 0, width, height); layer->paintDevice()->convertFromQImage(img, 0, 0, 0); delete page; image->addNode(layer, image->rootLayer(), 0); setProgress(qreal(*it + 1) * 100 / pages.count()); } document->setCurrentImage(image); delete pdoc; delete kdb; - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include "kis_pdf_import.moc" diff --git a/plugins/impex/pdf/kis_pdf_import.h b/plugins/impex/pdf/kis_pdf_import.h index df387515ff..76593a58b7 100644 --- a/plugins/impex/pdf/kis_pdf_import.h +++ b/plugins/impex/pdf/kis_pdf_import.h @@ -1,37 +1,37 @@ /* * Copyright (c) 2006 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. */ #ifndef KIS_PDF_IMPORT_H #define KIS_PDF_IMPORT_H #include #include class KisPDFImport : public KisImportExportFilter { Q_OBJECT public: KisPDFImport(QObject *parent, const QVariantList &); virtual ~KisPDFImport(); public: - virtual KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0); + virtual KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0); }; #endif diff --git a/plugins/impex/pdf/tests/CMakeLists.txt b/plugins/impex/pdf/tests/CMakeLists.txt new file mode 100644 index 0000000000..19cb770163 --- /dev/null +++ b/plugins/impex/pdf/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisPdfTest.cpp + TEST_NAME KisPdfTest + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/pdf/tests/KisPdfTest.cpp similarity index 64% copy from plugins/impex/svg/tests/kis_svg_test.cpp copy to plugins/impex/pdf/tests/KisPdfTest.cpp index 6ca66688f8..1f0dcec5ab 100644 --- a/plugins/impex/svg/tests/kis_svg_test.cpp +++ b/plugins/impex/pdf/tests/KisPdfTest.cpp @@ -1,40 +1,57 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * 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_svg_test.h" +#include "KisPdfTest.h" #include #include -#include - #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif -void KisSvgTest::testFiles() +const QString PdfMimetype = "image/x-gimp-brush"; + + + +void KisPdfTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), PdfMimetype); +} + + +void KisPdfTest::testExportToReadonly() { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50); + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), PdfMimetype); } -KISTEST_MAIN(KisSvgTest) + +void KisPdfTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), PdfMimetype); +} + + + +KISTEST_MAIN(KisPdfTest) + diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/pdf/tests/KisPdfTest.h similarity index 74% copy from plugins/impex/jpeg/tests/kis_jpeg_test.h copy to plugins/impex/pdf/tests/KisPdfTest.h index 5c19b0cda6..a6d0203eed 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/pdf/tests/KisPdfTest.h @@ -1,31 +1,35 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * 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_JPEG_TEST_H_ -#define _KIS_JPEG_TEST_H_ +#ifndef _KIS_PDF_TEST_H_ +#define _KIS_PDF_TEST_H_ #include -class KisJpegTest : public QObject +class KisPdfTest : public QObject { Q_OBJECT private Q_SLOTS: - void testFiles(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; -#endif +#endif // _KIS_PDF_TEST_H_ + diff --git a/plugins/impex/pdf/tests/data/incorrectFormatFile.txt b/plugins/impex/pdf/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/pdf/tests/data/readonlyFile.txt b/plugins/impex/pdf/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/pdf/tests/data/writeonlyFile.txt b/plugins/impex/pdf/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/png/kis_png_export.cc b/plugins/impex/png/kis_png_export.cc index bd937658f2..d813603159 100644 --- a/plugins/impex/png/kis_png_export.cc +++ b/plugins/impex/png/kis_png_export.cc @@ -1,252 +1,247 @@ /* * 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_png_export.h" #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_png_converter.h" #include K_PLUGIN_FACTORY_WITH_JSON(KisPNGExportFactory, "krita_png_export.json", registerPlugin();) KisPNGExport::KisPNGExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisPNGExport::~KisPNGExport() { } -KisImportExportFilter::ConversionStatus KisPNGExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisPNGExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { KisImageSP image = document->savingImage(); KisPNGOptions options; options.alpha = configuration->getBool("alpha", true); options.interlace = configuration->getBool("interlaced", false); options.compression = configuration->getInt("compression", 3); options.tryToSaveAsIndexed = configuration->getBool("indexed", false); KoColor c(KoColorSpaceRegistry::instance()->rgb8()); c.fromQColor(Qt::white); options.transparencyFillColor = configuration->getColor("transparencyFillcolor", c).toQColor(); options.saveSRGBProfile = configuration->getBool("saveSRGBProfile", false); options.forceSRGB = configuration->getBool("forceSRGB", true); options.storeAuthor = configuration->getBool("storeAuthor", false); options.storeMetaData = configuration->getBool("storeMetaData", false); options.saveAsHDR = configuration->getBool("saveAsHDR", false); vKisAnnotationSP_it beginIt = image->beginAnnotations(); vKisAnnotationSP_it endIt = image->endAnnotations(); KisExifInfoVisitor eIV; eIV.visit(image->rootLayer().data()); KisMetaData::Store *eI = 0; if (eIV.metaDataCount() == 1) { eI = eIV.exifInfo(); } if (eI) { KisMetaData::Store* copy = new KisMetaData::Store(*eI); eI = copy; } KisPNGConverter pngConverter(document); - KisImageBuilder_Result res = pngConverter.buildFile(io, image->bounds(), image->xRes(), image->yRes(), image->projection(), beginIt, endIt, options, eI); - - if (res == KisImageBuilder_RESULT_OK) { - delete eI; - return KisImportExportFilter::OK; - } - + KisImportExportErrorCode res = pngConverter.buildFile(io, image->bounds(), image->xRes(), image->yRes(), image->projection(), beginIt, endIt, options, eI); delete eI; dbgFile << " Result =" << res; - return KisImportExportFilter::InternalError; + return res; } KisPropertiesConfigurationSP KisPNGExport::defaultConfiguration(const QByteArray &, const QByteArray &) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("alpha", true); cfg->setProperty("indexed", false); cfg->setProperty("compression", 3); cfg->setProperty("interlaced", false); KoColor fill_color(KoColorSpaceRegistry::instance()->rgb8()); fill_color = KoColor(); fill_color.fromQColor(Qt::white); QVariant v; v.setValue(fill_color); cfg->setProperty("transparencyFillcolor", v); cfg->setProperty("saveSRGBProfile", false); cfg->setProperty("forceSRGB", true); cfg->setProperty("saveAsHDR", false); cfg->setProperty("storeMetaData", false); cfg->setProperty("storeAuthor", false); return cfg; } KisConfigWidget *KisPNGExport::createConfigurationWidget(QWidget *parent, const QByteArray &, const QByteArray &) const { return new KisWdgOptionsPNG(parent); } void KisPNGExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "PNG"); } KisWdgOptionsPNG::KisWdgOptionsPNG(QWidget *parent) : KisConfigWidget(parent) { setupUi(this); connect(chkSaveAsHDR, SIGNAL(toggled(bool)), this, SLOT(slotUseHDRChanged(bool))); } void KisWdgOptionsPNG::setConfiguration(const KisPropertiesConfigurationSP cfg) { // the export manager should have prepared some info for us! KIS_SAFE_ASSERT_RECOVER_NOOP(cfg->hasProperty(KisImportExportFilter::ImageContainsTransparencyTag)); KIS_SAFE_ASSERT_RECOVER_NOOP(cfg->hasProperty(KisImportExportFilter::ColorModelIDTag)); KIS_SAFE_ASSERT_RECOVER_NOOP(cfg->hasProperty(KisImportExportFilter::sRGBTag)); const bool isThereAlpha = cfg->getBool(KisImportExportFilter::ImageContainsTransparencyTag); alpha->setChecked(cfg->getBool("alpha", isThereAlpha)); bnTransparencyFillColor->setEnabled(!alpha->isChecked()); if (cfg->getString(KisImportExportFilter::ColorModelIDTag) == RGBAColorModelID.id()) { tryToSaveAsIndexed->setVisible(true); if (alpha->isChecked()) { tryToSaveAsIndexed->setChecked(false); } else { tryToSaveAsIndexed->setChecked(cfg->getBool("indexed", false)); } } else { tryToSaveAsIndexed->setVisible(false); } interlacing->setChecked(cfg->getBool("interlaced", false)); compressionLevel->setValue(cfg->getInt("compression", 3)); compressionLevel->setRange(1, 9, 0); tryToSaveAsIndexed->setVisible(!isThereAlpha); //const bool sRGB = cfg->getBool(KisImportExportFilter::sRGBTag, false); //chkSRGB->setEnabled(sRGB); chkSRGB->setChecked(cfg->getBool("saveSRGBProfile", true)); //chkForceSRGB->setEnabled(!sRGB); chkForceSRGB->setChecked(cfg->getBool("forceSRGB", false)); chkSaveAsHDR->setChecked(cfg->getBool("saveAsHDR", false)); slotUseHDRChanged(chkSaveAsHDR->isChecked()); chkAuthor->setChecked(cfg->getBool("storeAuthor", false)); chkMetaData->setChecked(cfg->getBool("storeMetaData", false)); KoColor background(KoColorSpaceRegistry::instance()->rgb8()); background.fromQColor(Qt::white); bnTransparencyFillColor->setDefaultColor(background); bnTransparencyFillColor->setColor(cfg->getColor("transparencyFillcolor", background)); } KisPropertiesConfigurationSP KisWdgOptionsPNG::configuration() const { KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration()); bool alpha = this->alpha->isChecked(); bool interlace = interlacing->isChecked(); int compression = (int)compressionLevel->value(); bool saveAsHDR = chkSaveAsHDR->isChecked(); bool tryToSaveAsIndexed = !saveAsHDR && this->tryToSaveAsIndexed->isChecked(); bool saveSRGB = !saveAsHDR && chkSRGB->isChecked(); bool forceSRGB = !saveAsHDR && chkForceSRGB->isChecked(); bool storeAuthor = chkAuthor->isChecked(); bool storeMetaData = chkMetaData->isChecked(); QVariant transparencyFillcolor; transparencyFillcolor.setValue(bnTransparencyFillColor->color()); cfg->setProperty("alpha", alpha); cfg->setProperty("indexed", tryToSaveAsIndexed); cfg->setProperty("compression", compression); cfg->setProperty("interlaced", interlace); cfg->setProperty("transparencyFillcolor", transparencyFillcolor); cfg->setProperty("saveAsHDR", saveAsHDR); cfg->setProperty("saveSRGBProfile", saveSRGB); cfg->setProperty("forceSRGB", forceSRGB); cfg->setProperty("storeAuthor", storeAuthor); cfg->setProperty("storeMetaData", storeMetaData); return cfg; } void KisWdgOptionsPNG::on_alpha_toggled(bool checked) { bnTransparencyFillColor->setEnabled(!checked); } void KisWdgOptionsPNG::slotUseHDRChanged(bool value) { tryToSaveAsIndexed->setDisabled(value); chkForceSRGB->setDisabled(value); chkSRGB->setDisabled(value); } #include "kis_png_export.moc" diff --git a/plugins/impex/png/kis_png_export.h b/plugins/impex/png/kis_png_export.h index 18c91e2c78..3be3e51478 100644 --- a/plugins/impex/png/kis_png_export.h +++ b/plugins/impex/png/kis_png_export.h @@ -1,59 +1,59 @@ /* * 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. */ #ifndef _KIS_PNG_EXPORT_H_ #define _KIS_PNG_EXPORT_H_ #include "ui_kis_wdg_options_png.h" #include #include class KisWdgOptionsPNG : public KisConfigWidget, public Ui::KisWdgOptionsPNG { Q_OBJECT public: KisWdgOptionsPNG(QWidget *parent); void setConfiguration(const KisPropertiesConfigurationSP config) override; KisPropertiesConfigurationSP configuration() const override; private Q_SLOTS: void on_alpha_toggled(bool checked); void slotUseHDRChanged(bool value); }; class KisPNGExport : public KisImportExportFilter { Q_OBJECT public: KisPNGExport(QObject *parent, const QVariantList &); ~KisPNGExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/png/kis_png_import.cc b/plugins/impex/png/kis_png_import.cc index b45fcad441..29a82cea2d 100644 --- a/plugins/impex/png/kis_png_import.cc +++ b/plugins/impex/png/kis_png_import.cc @@ -1,79 +1,56 @@ /* * 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_png_import.h" #include #include #include #include #include #include #include "kis_png_converter.h" K_PLUGIN_FACTORY_WITH_JSON(PNGImportFactory, "krita_png_import.json", registerPlugin();) KisPNGImport::KisPNGImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisPNGImport::~KisPNGImport() { } -KisImportExportFilter::ConversionStatus KisPNGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisPNGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { KisPNGConverter ib(document, batchMode()); - - switch (ib.buildImage(io)) { - case KisImageBuilder_RESULT_UNSUPPORTED: - return KisImportExportFilter::NotImplemented; - break; - case KisImageBuilder_RESULT_INVALID_ARG: - return KisImportExportFilter::BadMimeType; - break; - case KisImageBuilder_RESULT_NO_URI: - case KisImageBuilder_RESULT_NOT_EXIST: - case KisImageBuilder_RESULT_NOT_LOCAL: - return KisImportExportFilter::FileNotFound; - break; - case KisImageBuilder_RESULT_BAD_FETCH: - case KisImageBuilder_RESULT_EMPTY: - return KisImportExportFilter::ParsingError; - break; - case KisImageBuilder_RESULT_FAILURE: - return KisImportExportFilter::InternalError; - break; - case KisImageBuilder_RESULT_OK: - document -> setCurrentImage(ib.image()); - return KisImportExportFilter::OK; - break; - default: - break; + KisImportExportErrorCode res = ib.buildImage(io); + if (res.isOk()){ + document->setCurrentImage(ib.image()); } - return KisImportExportFilter::InternalError; + return res; } #include diff --git a/plugins/impex/png/kis_png_import.h b/plugins/impex/png/kis_png_import.h index 51d0ac7134..8b43397eb8 100644 --- a/plugins/impex/png/kis_png_import.h +++ b/plugins/impex/png/kis_png_import.h @@ -1,36 +1,36 @@ /* * 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. */ #ifndef _KIS_PNG_IMPORT_H_ #define _KIS_PNG_IMPORT_H_ #include #include class KisPNGImport : public KisImportExportFilter { Q_OBJECT public: KisPNGImport(QObject *parent, const QVariantList &); ~KisPNGImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/png/tests/data/writeonlyFile.txt b/plugins/impex/png/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/png/tests/kis_png_test.cpp b/plugins/impex/png/tests/kis_png_test.cpp index 583ba20488..2259cb7822 100644 --- a/plugins/impex/png/tests/kis_png_test.cpp +++ b/plugins/impex/png/tests/kis_png_test.cpp @@ -1,155 +1,162 @@ /* * Copyright (C) 2007 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_png_test.h" #include #include #include "filestest.h" #include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif +const QString PngMimetype = "image/png"; + void KisPngTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1); } +void KisPngTest::testWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), PngMimetype); +} + void roudTripHdrImage(const KoColorSpace *savingColorSpace) { qDebug() << "Test saving" << savingColorSpace->id() << savingColorSpace->profile()->name(); const KoColorSpace * scRGBF32 = KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Float32BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->p709G10Profile()); KoColor fillColor(scRGBF32); float *pixelPtr = reinterpret_cast(fillColor.data()); pixelPtr[0] = 2.7; pixelPtr[1] = 1.6; pixelPtr[2] = 0.8; pixelPtr[3] = 0.9; { QScopedPointer doc(KisPart::instance()->createDocument()); KisImageSP image = new KisImage(0, 3, 3, scRGBF32, "png test"); KisPaintLayerSP paintLayer0 = new KisPaintLayer(image, "paint0", OPACITY_OPAQUE_U8); paintLayer0->paintDevice()->fill(image->bounds(), fillColor); image->addNode(paintLayer0, image->root()); // convert image color space before saving image->convertImageColorSpace(savingColorSpace, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); image->waitForDone(); KisImportExportManager manager(doc.data()); doc->setFileBatchMode(true); doc->setCurrentImage(image); KisPropertiesConfigurationSP exportConfiguration = new KisPropertiesConfiguration(); exportConfiguration->setProperty("saveAsHDR", true); exportConfiguration->setProperty("saveSRGBProfile", false); exportConfiguration->setProperty("forceSRGB", false); doc->exportDocumentSync(QUrl::fromLocalFile("test.png"), "image/png", exportConfiguration); } { QScopedPointer doc(KisPart::instance()->createDocument()); KisImportExportManager manager(doc.data()); doc->setFileBatchMode(true); - KisImportExportFilter::ConversionStatus loadingStatus = + KisImportExportErrorCode loadingStatus = manager.importDocument("test.png", QString()); - QCOMPARE(loadingStatus, KisImportExportFilter::OK); + QVERIFY(loadingStatus.isOk()); KisImageSP image = doc->image(); image->initialRefreshGraph(); KoColor resultColor; // qDebug() << ppVar(image->colorSpace()) << image->colorSpace()->profile()->name(); // image->projection()->pixel(1, 1, &resultColor); // qDebug() << ppVar(resultColor); image->convertImageColorSpace(scRGBF32, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); image->waitForDone(); image->projection()->pixel(1, 1, &resultColor); // qDebug() << ppVar(resultColor); const float tolerance = savingColorSpace->colorDepthId() == Integer8BitsColorDepthID ? 0.02 : 0.01; bool resultIsValid = true; float *resultPtr = reinterpret_cast(resultColor.data()); for (int i = 0; i < 4; i++) { resultIsValid &= qAbs(resultPtr[i] - pixelPtr[i]) < tolerance; } if (!resultIsValid) { qDebug() << ppVar(fillColor) << ppVar(resultColor); } QVERIFY(resultIsValid); } } void KisPngTest::testSaveHDR() { QVector colorDepthIds; colorDepthIds << Float16BitsColorDepthID; colorDepthIds << Float32BitsColorDepthID; QVector profiles; profiles << KoColorSpaceRegistry::instance()->p709G10Profile(); profiles << KoColorSpaceRegistry::instance()->p2020G10Profile(); profiles << KoColorSpaceRegistry::instance()->p2020PQProfile(); Q_FOREACH(const KoID &depth, colorDepthIds) { Q_FOREACH(const KoColorProfile *profile, profiles) { roudTripHdrImage( KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), depth.id(), profile)); } } roudTripHdrImage( KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Integer16BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->p2020PQProfile())); roudTripHdrImage( KoColorSpaceRegistry::instance()->colorSpace( RGBAColorModelID.id(), Integer8BitsColorDepthID.id(), KoColorSpaceRegistry::instance()->p2020PQProfile())); } KISTEST_MAIN(KisPngTest) diff --git a/plugins/impex/png/tests/kis_png_test.h b/plugins/impex/png/tests/kis_png_test.h index e312f85756..0c2374b4c6 100644 --- a/plugins/impex/png/tests/kis_png_test.h +++ b/plugins/impex/png/tests/kis_png_test.h @@ -1,32 +1,33 @@ /* * Copyright (C) 2007 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. */ #ifndef _KIS_PNG_TEST_H_ #define _KIS_PNG_TEST_H_ #include class KisPngTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); + void testWriteonly(); void testSaveHDR(); }; #endif diff --git a/plugins/impex/ppm/kis_ppm_export.cpp b/plugins/impex/ppm/kis_ppm_export.cpp index b93eb05a09..9dcb24478a 100644 --- a/plugins/impex/ppm/kis_ppm_export.cpp +++ b/plugins/impex/ppm/kis_ppm_export.cpp @@ -1,283 +1,316 @@ /* * Copyright (c) 2009 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_ppm_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include K_PLUGIN_FACTORY_WITH_JSON(KisPPMExportFactory, "krita_ppm_export.json", registerPlugin();) KisPPMExport::KisPPMExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisPPMExport::~KisPPMExport() { } class KisPPMFlow { public: KisPPMFlow() { } virtual ~KisPPMFlow() { } virtual void writeBool(quint8 v) = 0; virtual void writeBool(quint16 v) = 0; virtual void writeNumber(quint8 v) = 0; virtual void writeNumber(quint16 v) = 0; virtual void flush() = 0; private: }; class KisPPMAsciiFlow : public KisPPMFlow { public: KisPPMAsciiFlow(QIODevice* device) : m_device(device) { } ~KisPPMAsciiFlow() override { } void writeBool(quint8 v) override { if (v > 127) { m_device->write("1 "); } else { m_device->write("0 "); } } void writeBool(quint16 v) override { writeBool(quint8(v >> 8)); } void writeNumber(quint8 v) override { m_device->write(QByteArray::number(v)); m_device->write(" "); } void writeNumber(quint16 v) override { m_device->write(QByteArray::number(v)); m_device->write(" "); } void flush() override { } private: QIODevice* m_device; }; class KisPPMBinaryFlow : public KisPPMFlow { public: KisPPMBinaryFlow(QIODevice* device) : m_device(device), m_pos(0), m_current(0) { } ~KisPPMBinaryFlow() override { } void writeBool(quint8 v) override { m_current = m_current << 1; m_current |= (v > 127); ++m_pos; if (m_pos >= 8) { m_current = 0; m_pos = 0; flush(); } } void writeBool(quint16 v) override { writeBool(quint8(v >> 8)); } void writeNumber(quint8 v) override { m_device->write((char*)&v, 1); } void writeNumber(quint16 v) override { quint16 vo = qToBigEndian(v); m_device->write((char*)&vo, 2); } void flush() override { m_device->write((char*)&m_current, 1); } private: QIODevice* m_device; int m_pos; quint8 m_current; }; -KisImportExportFilter::ConversionStatus KisPPMExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisPPMExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { bool rgb = (mimeType() == "image/x-portable-pixmap"); bool binary = (configuration->getInt("type") == 0); bool bitmap = (mimeType() == "image/x-portable-bitmap"); KisImageSP image = document->savingImage(); KisPaintDeviceSP pd = new KisPaintDevice(*image->projection()); // Test color space if (((rgb && (pd->colorSpace()->id() != "RGBA" && pd->colorSpace()->id() != "RGBA16")) || (!rgb && (pd->colorSpace()->id() != "GRAYA" && pd->colorSpace()->id() != "GRAYA16" && pd->colorSpace()->id() != "GRAYAU16")))) { if (rgb) { pd->convertTo(KoColorSpaceRegistry::instance()->rgb8(0), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } else { pd->convertTo(KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), 0), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } } bool is16bit = pd->colorSpace()->id() == "RGBA16" || pd->colorSpace()->id() == "GRAYAU16"; // Write the magic + QString toWrite = ""; + int written = 0; if (rgb) { - if (binary) io->write("P6"); - else io->write("P3"); + if (binary) { + toWrite = "P6"; + } + else { + toWrite = "P3"; + } } else if (bitmap) { - if (binary) io->write("P4"); - else io->write("P1"); + if (binary) { + toWrite = "P4"; + } + else { + toWrite = "P1"; + } } else { - if (binary) io->write("P5"); - else io->write("P2"); + if (binary) { + toWrite = "P5"; + } + else { + toWrite = "P2"; + } } - io->write("\n"); + written = io->write(toWrite.toUtf8()); + if (written != toWrite.toUtf8().length()) { + return ImportExportCodes::ErrorWhileWriting; + } + + written = io->write("\n"); // Write the header - io->write(QByteArray::number(image->width())); - io->write(" "); - io->write(QByteArray::number(image->height())); + QByteArray width = QByteArray::number(image->width()); + QByteArray height = QByteArray::number(image->height()); + + written += io->write(width); + written += io->write(" "); + written += io->write(height); + if (written != QString(" ").length() + QString("\n").length() + width.length() + height.length()) { + return ImportExportCodes::ErrorWhileWriting; + } if (!bitmap) { - if (is16bit) io->write(" 65535\n"); - else io->write(" 255\n"); + if (is16bit) { + toWrite = " 65535\n"; + } + else { + toWrite = " 255\n"; + } } else { - io->write("\n"); + toWrite = "\n"; + } + written = io->write(toWrite.toUtf8()); + if (written != toWrite.toUtf8().length()) { + return ImportExportCodes::ErrorWhileWriting; } // Write the data KisPPMFlow* flow = 0; if (binary) flow = new KisPPMBinaryFlow(io); else flow = new KisPPMAsciiFlow(io); for (int y = 0; y < image->height(); ++y) { KisHLineIteratorSP it = pd->createHLineIteratorNG(0, y, image->width()); if (is16bit) { if (rgb) { do { flow->writeNumber(KoBgrU16Traits::red(it->rawData())); flow->writeNumber(KoBgrU16Traits::green(it->rawData())); flow->writeNumber(KoBgrU16Traits::blue(it->rawData())); } while (it->nextPixel()); } else if (bitmap) { do { flow->writeBool(*reinterpret_cast(it->rawData())); } while (it->nextPixel()); } else { do { flow->writeNumber(*reinterpret_cast(it->rawData())); } while (it->nextPixel()); } } else { if (rgb) { do { flow->writeNumber(KoBgrTraits::red(it->rawData())); flow->writeNumber(KoBgrTraits::green(it->rawData())); flow->writeNumber(KoBgrTraits::blue(it->rawData())); } while (it->nextPixel()); } else if (bitmap) { do { flow->writeBool(*reinterpret_cast(it->rawData())); } while (it->nextPixel()); } else { do { flow->writeNumber(*reinterpret_cast(it->rawData())); } while (it->nextPixel()); } } } if (bitmap) { flow->flush(); } delete flow; - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } KisPropertiesConfigurationSP KisPPMExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("type", 0); return cfg; } KisConfigWidget *KisPPMExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const { return new KisWdgOptionsPPM(parent); } void KisWdgOptionsPPM::setConfiguration(const KisPropertiesConfigurationSP cfg) { cmbType->setCurrentIndex(cfg->getInt("type", 0)); } KisPropertiesConfigurationSP KisWdgOptionsPPM::configuration() const { KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("type", cmbType->currentIndex()); return cfg; } void KisPPMExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("ColorModelCheck/" + RGBAColorModelID.id() + "/" + Integer8BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("ColorModelCheck/" + RGBAColorModelID.id() + "/" + Integer16BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("ColorModelCheck/" + GrayAColorModelID.id() + "/" + Integer8BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("ColorModelCheck/" + GrayAColorModelID.id() + "/" + Integer16BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "PPM"); } #include "kis_ppm_export.moc" diff --git a/plugins/impex/ppm/kis_ppm_export.h b/plugins/impex/ppm/kis_ppm_export.h index 7689305acb..53f15c5e70 100644 --- a/plugins/impex/ppm/kis_ppm_export.h +++ b/plugins/impex/ppm/kis_ppm_export.h @@ -1,58 +1,58 @@ /* * Copyright (c) 2009 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_PPM_EXPORT_H_ #define _KIS_PPM_EXPORT_H_ #include #include #include #include "ui_kis_wdg_options_ppm.h" class KisWdgOptionsPPM : public KisConfigWidget, public Ui::WdgOptionsPPM { Q_OBJECT public: KisWdgOptionsPPM(QWidget *parent) : KisConfigWidget(parent) { setupUi(this); } void setConfiguration(const KisPropertiesConfigurationSP cfg) override; KisPropertiesConfigurationSP configuration() const override; }; class KisPPMExport : public KisImportExportFilter { Q_OBJECT public: KisPPMExport(QObject *parent, const QVariantList &); ~KisPPMExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/ppm/kis_ppm_import.cpp b/plugins/impex/ppm/kis_ppm_import.cpp index 32eae9206d..ee31b591d3 100644 --- a/plugins/impex/ppm/kis_ppm_import.cpp +++ b/plugins/impex/ppm/kis_ppm_import.cpp @@ -1,314 +1,320 @@ /* * Copyright (c) 2009 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_ppm_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" K_PLUGIN_FACTORY_WITH_JSON(PPMImportFactory, "krita_ppm_import.json", registerPlugin();) KisPPMImport::KisPPMImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisPPMImport::~KisPPMImport() { } int readNumber(QIODevice* device) { char c; int val = 0; while (true) { if (!device->getChar(&c)) break; // End of the file if (isdigit(c)) { val = 10 * val + c - '0'; } else if (c == '#') { device->readLine(); break; } else if (isspace((uchar) c)) { break; } } return val; } class KisPpmFlow { public: KisPpmFlow() { } virtual ~KisPpmFlow() { } virtual void nextRow() = 0; virtual bool valid() = 0; virtual bool nextUint1() = 0; virtual quint8 nextUint8() = 0; virtual quint16 nextUint16() = 0; }; class KisAsciiPpmFlow : public KisPpmFlow { public: KisAsciiPpmFlow(QIODevice* device) : m_device(device) { } ~KisAsciiPpmFlow() override { } void nextRow() override { } bool valid() override { return !m_device->atEnd(); } bool nextUint1() override { return readNumber(m_device) == 1; } quint8 nextUint8() override { return readNumber(m_device); } quint16 nextUint16() override { return readNumber(m_device); } private: QIODevice* m_device; }; class KisBinaryPpmFlow : public KisPpmFlow { public: KisBinaryPpmFlow(QIODevice* device, int lineWidth) : m_pos(0), m_device(device), m_lineWidth(lineWidth) { } ~KisBinaryPpmFlow() override { } void nextRow() override { m_array = m_device->read(m_lineWidth); m_ptr = m_array.data(); } bool valid() override { return m_array.size() == m_lineWidth; } bool nextUint1() override { if (m_pos == 0) { m_current = nextUint8(); m_pos = 8; } bool v = (m_current & 1) == 1; --m_pos; m_current = m_current >> 1; return v; } quint8 nextUint8() override { quint8 v = *reinterpret_cast(m_ptr); m_ptr += 1; return v; } quint16 nextUint16() override { quint16 v = *reinterpret_cast(m_ptr); m_ptr += 2; return qFromBigEndian(v); } private: int m_pos; quint8 m_current; char* m_ptr; QIODevice* m_device; QByteArray m_array; int m_lineWidth; }; -KisImportExportFilter::ConversionStatus KisPPMImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisPPMImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { QByteArray array = io->read(2); - if (array.size() < 2) return KisImportExportFilter::CreationError; + if (array.size() < 2) { + return ImportExportCodes::FileFormatIncorrect; + } // Read the type of the ppm file enum { Puk, P1, P2, P3, P4, P5, P6 } fileType = Puk; // Puk => unknown int channels = -1; bool isAscii = false; if (array == "P1") { fileType = P1; isAscii = true; channels = 0; } else if (array == "P2") { fileType = P2; channels = 1; isAscii = true; } else if (array == "P3") { fileType = P3; channels = 3; isAscii = true; } else if (array == "P4") { fileType = P4; channels = 0; } else if (array == "P5") { // PGM fileType = P5; channels = 1; } else if (array == "P6") { // PPM fileType = P6; channels = 3; } Q_ASSERT(channels != -1); char c; io->getChar(&c); - if (!isspace(c)) return KisImportExportFilter::CreationError; // Invalid file, it should have a separator now + if (!isspace(c)) { + return ImportExportCodes::FileFormatIncorrect;; // Invalid file, it should have a separator now + } while (io->peek(1) == "#") { io->readLine(); } // Read width int width = readNumber(io); int height = readNumber(io); int maxval = 1; if (fileType != P1 && fileType != P4) { maxval = readNumber(io); } dbgFile << "Width = " << width << " height = " << height << "maxval = " << maxval; // Select the colorspace depending on the maximum value int pixelsize = -1; const KoColorSpace* colorSpace = 0; const KoColorProfile *profile = 0; QString colorSpaceId; QString bitDepthId; if (maxval <= 255) { bitDepthId = Integer8BitsColorDepthID.id(); if (channels == 1 || channels == 0) { pixelsize = 1; colorSpaceId = GrayAColorModelID.id(); } else { pixelsize = 3; colorSpaceId = RGBAColorModelID.id(); } } else if (maxval <= 65535) { bitDepthId = Integer16BitsColorDepthID.id(); if (channels == 1 || channels == 0) { pixelsize = 2; colorSpaceId = GrayAColorModelID.id(); } else { pixelsize = 6; colorSpaceId = RGBAColorModelID.id(); } } else { dbgFile << "Unknown colorspace"; - return KisImportExportFilter::CreationError; + return ImportExportCodes::FormatColorSpaceUnsupported; } if (colorSpaceId == RGBAColorModelID.id()) { profile = KoColorSpaceRegistry::instance()->profileByName("sRGB-elle-V2-srgbtrc.icc"); } else if (colorSpaceId == GrayAColorModelID.id()) { profile = KoColorSpaceRegistry::instance()->profileByName("Gray-D50-elle-V2-srgbtrc.icc"); } colorSpace = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId, bitDepthId, profile); KisImageSP image = new KisImage(document->createUndoStore(), width, height, colorSpace, "built image"); KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255); QScopedPointer ppmFlow; if (isAscii) { ppmFlow.reset(new KisAsciiPpmFlow(io)); } else { ppmFlow.reset(new KisBinaryPpmFlow(io, pixelsize * width)); } for (int v = 0; v < height; ++v) { KisHLineIteratorSP it = layer->paintDevice()->createHLineIteratorNG(0, v, width); ppmFlow->nextRow(); - if (!ppmFlow->valid()) return KisImportExportFilter::CreationError; + if (!ppmFlow->valid()) { + return ImportExportCodes::FileFormatIncorrect; + } if (maxval <= 255) { if (channels == 3) { do { KoBgrTraits::setRed(it->rawData(), ppmFlow->nextUint8()); KoBgrTraits::setGreen(it->rawData(), ppmFlow->nextUint8()); KoBgrTraits::setBlue(it->rawData(), ppmFlow->nextUint8()); colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); } while (it->nextPixel()); } else if (channels == 1) { do { *reinterpret_cast(it->rawData()) = ppmFlow->nextUint8(); colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); } while (it->nextPixel()); } else if (channels == 0) { do { if (ppmFlow->nextUint1()) { *reinterpret_cast(it->rawData()) = 255; } else { *reinterpret_cast(it->rawData()) = 0; } colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); } while (it->nextPixel()); } } else { if (channels == 3) { do { KoBgrU16Traits::setRed(it->rawData(), ppmFlow->nextUint16()); KoBgrU16Traits::setGreen(it->rawData(), ppmFlow->nextUint16()); KoBgrU16Traits::setBlue(it->rawData(), ppmFlow->nextUint16()); colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); } while (it->nextPixel()); } else if (channels == 1) { do { *reinterpret_cast(it->rawData()) = ppmFlow->nextUint16(); colorSpace->setOpacity(it->rawData(), OPACITY_OPAQUE_U8, 1); } while (it->nextPixel()); } } } image->addNode(layer.data(), image->rootLayer().data()); document->setCurrentImage(image); - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include "kis_ppm_import.moc" diff --git a/plugins/impex/ppm/kis_ppm_import.h b/plugins/impex/ppm/kis_ppm_import.h index f3921fece6..07268bff36 100644 --- a/plugins/impex/ppm/kis_ppm_import.h +++ b/plugins/impex/ppm/kis_ppm_import.h @@ -1,40 +1,40 @@ /* * Copyright (c) 2009 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_PPM_IMPORT_H_ #define _KIS_PPM_IMPORT_H_ #include #include #include class KisDocument; class KisPPMImport : public KisImportExportFilter { Q_OBJECT public: KisPPMImport(QObject *parent, const QVariantList &); ~KisPPMImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/ppm/tests/data/incorrectFormatFile.txt b/plugins/impex/ppm/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/ppm/tests/data/readonlyFile.txt b/plugins/impex/ppm/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/ppm/tests/data/writeonlyFile.txt b/plugins/impex/ppm/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/ppm/tests/kis_ppm_test.cpp b/plugins/impex/ppm/tests/kis_ppm_test.cpp index 6aa3859d0a..fce6ee9d5c 100644 --- a/plugins/impex/ppm/tests/kis_ppm_test.cpp +++ b/plugins/impex/ppm/tests/kis_ppm_test.cpp @@ -1,39 +1,61 @@ /* * Copyright (C) 2007 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_ppm_test.h" #include #include #include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif + +const QString PPMMimetype = "image/x-portable-pixmap"; + void KisPPMTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1); } + +void KisPPMTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), PPMMimetype); +} + + +void KisPPMTest::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), PPMMimetype); +} + + +void KisPPMTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), PPMMimetype); +} + + KISTEST_MAIN(KisPPMTest) diff --git a/plugins/impex/ppm/tests/kis_ppm_test.h b/plugins/impex/ppm/tests/kis_ppm_test.h index 36e9d19953..827856e793 100644 --- a/plugins/impex/ppm/tests/kis_ppm_test.h +++ b/plugins/impex/ppm/tests/kis_ppm_test.h @@ -1,31 +1,35 @@ /* * Copyright (C) 2007 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. */ #ifndef _KIS_PPM_TEST_H_ #define _KIS_PPM_TEST_H_ #include class KisPPMTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; #endif diff --git a/plugins/impex/psd/psd_export.cc b/plugins/impex/psd/psd_export.cc index 05df481795..ca04ebeb8c 100644 --- a/plugins/impex/psd/psd_export.cc +++ b/plugins/impex/psd/psd_export.cc @@ -1,102 +1,91 @@ /* * Copyright (c) 2009 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 "psd_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "psd_saver.h" class KisExternalLayer; K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_psd_export.json", registerPlugin();) psdExport::psdExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } psdExport::~psdExport() { } -KisImportExportFilter::ConversionStatus psdExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode psdExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { PSDSaver psdSaver(document); - KisImageBuilder_Result res; - - if ((res = psdSaver.buildFile(io)) == KisImageBuilder_RESULT_OK) { - dbgFile <<"success !"; - return KisImportExportFilter::OK; - } - else if (res == KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE) { - document->setErrorMessage(i18n("Could not convert this colorspace to something Krita can save.")); - return KisImportExportFilter::WrongFormat; - } - dbgFile <<" Result =" << res; - return KisImportExportFilter::InternalError; + return psdSaver.buildFile(io); } void psdExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("PSDLayerStyleCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisGroupLayer")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("NodeTypeCheck/KisTransparencyMask")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("ColorModelHomogenousCheck")->create(KisExportCheckBase::UNSUPPORTED, i18nc("image conversion warning", "Your image contains one or more layers with a color model that is different from the image."))); ImageSizeCheckFactory *factory = dynamic_cast(KisExportCheckRegistry::instance()->get("ImageSizeCheck")); if (factory) { addCapability(factory->create(30000, 30000, KisExportCheckBase::SUPPORTED)); } QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) // << QPair(RGBAColorModelID, Float16BitsColorDepthID) // << QPair(RGBAColorModelID, Float32BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID) << QPair(CMYKAColorModelID, Integer8BitsColorDepthID) << QPair(CMYKAColorModelID, Integer16BitsColorDepthID) << QPair(LABAColorModelID, Integer8BitsColorDepthID) << QPair(LABAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "PSD"); } #include diff --git a/plugins/impex/psd/psd_export.h b/plugins/impex/psd/psd_export.h index 0d66e19571..55d79b4d70 100644 --- a/plugins/impex/psd/psd_export.h +++ b/plugins/impex/psd/psd_export.h @@ -1,35 +1,35 @@ /* * Copyright (c) 2009 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. */ #ifndef _PSD_EXPORT_H_ #define _PSD_EXPORT_H_ #include #include class psdExport : public KisImportExportFilter { Q_OBJECT public: psdExport(QObject *parent, const QVariantList &); ~psdExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/psd/psd_import.cc b/plugins/impex/psd/psd_import.cc index 0d8722f207..a73917e1e6 100644 --- a/plugins/impex/psd/psd_import.cc +++ b/plugins/impex/psd/psd_import.cc @@ -1,72 +1,49 @@ /* * Copyright (c) 2009 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 "psd_import.h" #include #include #include #include #include "psd_loader.h" K_PLUGIN_FACTORY_WITH_JSON(ImportFactory, "krita_psd_import.json", registerPlugin();) psdImport::psdImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } psdImport::~psdImport() { } -KisImportExportFilter::ConversionStatus psdImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode psdImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { - - PSDLoader ib(document); - - KisImageBuilder_Result result = ib.buildImage(io); - - switch (result) { - case KisImageBuilder_RESULT_UNSUPPORTED: - return KisImportExportFilter::NotImplemented; - case KisImageBuilder_RESULT_INVALID_ARG: - return KisImportExportFilter::BadMimeType; - case KisImageBuilder_RESULT_NO_URI: - case KisImageBuilder_RESULT_NOT_EXIST: - case KisImageBuilder_RESULT_NOT_LOCAL: - qDebug() << "ib returned KisImageBuilder_RESULT_NOT_LOCAL"; - return KisImportExportFilter::FileNotFound; - case KisImageBuilder_RESULT_BAD_FETCH: - case KisImageBuilder_RESULT_EMPTY: - return KisImportExportFilter::ParsingError; - case KisImageBuilder_RESULT_FAILURE: - return KisImportExportFilter::InternalError; - case KisImageBuilder_RESULT_OK: - document -> setCurrentImage( ib.image()); - return KisImportExportFilter::OK; - default: - return KisImportExportFilter::StorageCreationError; - //dbgFile << "Result was: " << result; + KisImportExportErrorCode result = ib.buildImage(io); + if (result.isOk()) { + document->setCurrentImage(ib.image()); } - return KisImportExportFilter::InternalError; + return result; } #include diff --git a/plugins/impex/psd/psd_import.h b/plugins/impex/psd/psd_import.h index a522c7d5a0..606d7563ab 100644 --- a/plugins/impex/psd/psd_import.h +++ b/plugins/impex/psd/psd_import.h @@ -1,34 +1,34 @@ /* * Copyright (c) 2009 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. */ #ifndef PSD_IMPORT_H_ #define PSD_IMPORT_H_ #include #include class psdImport : public KisImportExportFilter { Q_OBJECT public: psdImport(QObject *parent, const QVariantList &); ~psdImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/psd/psd_loader.cpp b/plugins/impex/psd/psd_loader.cpp index 0b99d9d1e6..f68e47a503 100644 --- a/plugins/impex/psd/psd_loader.cpp +++ b/plugins/impex/psd/psd_loader.cpp @@ -1,380 +1,380 @@ /* * Copyright (c) 2009 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 "psd_loader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "KisResourceServerProvider.h" #include "psd.h" #include "psd_header.h" #include "psd_colormode_block.h" #include "psd_utils.h" #include "psd_resource_section.h" #include "psd_layer_section.h" #include "psd_resource_block.h" #include "psd_image_data.h" PSDLoader::PSDLoader(KisDocument *doc) : m_image(0) , m_doc(doc) , m_stop(false) { } PSDLoader::~PSDLoader() { } -KisImageBuilder_Result PSDLoader::decode(QIODevice *io) +KisImportExportErrorCode PSDLoader::decode(QIODevice *io) { // open the file dbgFile << "pos:" << io->pos(); PSDHeader header; - if (!header.read( io)) { + if (!header.read(io)) { dbgFile << "failed reading header: " << header.error; - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::FileFormatIncorrect; } dbgFile << header; dbgFile << "Read header. pos:" << io->pos(); PSDColorModeBlock colorModeBlock(header.colormode); if (!colorModeBlock.read(io)) { dbgFile << "failed reading colormode block: " << colorModeBlock.error; - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::FileFormatIncorrect; } dbgFile << "Read color mode block. pos:" << io->pos(); PSDImageResourceSection resourceSection; if (!resourceSection.read(io)) { dbgFile << "failed image reading resource section: " << resourceSection.error; - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::FileFormatIncorrect; } dbgFile << "Read image resource section. pos:" << io->pos(); PSDLayerMaskSection layerSection(header); if (!layerSection.read(io)) { dbgFile << "failed reading layer/mask section: " << layerSection.error; - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::FileFormatIncorrect; } dbgFile << "Read layer/mask section. " << layerSection.nLayers << "layers. pos:" << io->pos(); // Done reading, except possibly for the image data block, which is only relevant if there // are no layers. // Get the right colorspace QPair colorSpaceId = psd_colormode_to_colormodelid(header.colormode, header.channelDepth); if (colorSpaceId.first.isNull()) { dbgFile << "Unsupported colorspace" << header.colormode << header.channelDepth; - return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; + return ImportExportCodes::FormatColorSpaceUnsupported; } // Get the icc profile from the image resource section const KoColorProfile* profile = 0; if (resourceSection.resources.contains(PSDImageResourceSection::ICC_PROFILE)) { ICC_PROFILE_1039 *iccProfileData = dynamic_cast(resourceSection.resources[PSDImageResourceSection::ICC_PROFILE]->resource); if (iccProfileData ) { profile = KoColorSpaceRegistry::instance()->createColorProfile(colorSpaceId.first, colorSpaceId.second, iccProfileData->icc); dbgFile << "Loaded ICC profile" << profile->name(); delete resourceSection.resources.take(PSDImageResourceSection::ICC_PROFILE); } } // Create the colorspace const KoColorSpace* cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceId.first, colorSpaceId.second, profile); if (!cs) { - return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; + return ImportExportCodes::FormatColorSpaceUnsupported; } // Creating the KisImage QFile *file = dynamic_cast(io); QString name = file ? file->fileName() : "Imported"; m_image = new KisImage(m_doc->createUndoStore(), header.width, header.height, cs, name); Q_CHECK_PTR(m_image); m_image->lock(); // set the correct resolution if (resourceSection.resources.contains(PSDImageResourceSection::RESN_INFO)) { RESN_INFO_1005 *resInfo = dynamic_cast(resourceSection.resources[PSDImageResourceSection::RESN_INFO]->resource); if (resInfo) { // check resolution size is not zero if (resInfo->hRes * resInfo->vRes > 0) m_image->setResolution(POINT_TO_INCH(resInfo->hRes), POINT_TO_INCH(resInfo->vRes)); // let's skip the unit for now; we can only set that on the KisDocument, and krita doesn't use it. delete resourceSection.resources.take(PSDImageResourceSection::RESN_INFO); } } // Preserve all the annotations Q_FOREACH (PSDResourceBlock *resourceBlock, resourceSection.resources.values()) { m_image->addAnnotation(resourceBlock); } // Preserve the duotone colormode block for saving back to psd if (header.colormode == DuoTone) { KisAnnotationSP annotation = new KisAnnotation("DuotoneColormodeBlock", i18n("Duotone Colormode Block"), colorModeBlock.data); m_image->addAnnotation(annotation); } // Read the projection into our single layer. Since we only read the projection when // we have just one layer, we don't need to later on apply the alpha channel of the // first layer to the projection if the number of layers is negative/ // See http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/#50577409_16000. if (layerSection.nLayers == 0) { dbgFile << "Position" << io->pos() << "Going to read the projection into the first layer, which Photoshop calls 'Background'"; KisPaintLayerSP layer = new KisPaintLayer(m_image, i18n("Background"), OPACITY_OPAQUE_U8); PSDImageData imageData(&header); imageData.read(io, layer->paintDevice()); m_image->addNode(layer, m_image->rootLayer()); // Only one layer, the background layer, so we're done. m_image->unlock(); - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } // More than one layer, so now construct the Krita image from the info we read. QStack groupStack; groupStack.push(m_image->rootLayer()); /** * PSD has a weird "optimization": if a group layer has only one * child layer, it omits it's 'psd_bounding_divider' section. So * fi you ever see an unbalanced layers group in PSD, most * probably, it is just a single layered group. */ KisNodeSP lastAddedLayer; typedef QPair LayerStyleMapping; QVector allStylesXml; // read the channels for the various layers for(int i = 0; i < layerSection.nLayers; ++i) { PSDLayerRecord* layerRecord = layerSection.layers.at(i); dbgFile << "Going to read channels for layer" << i << layerRecord->layerName; KisLayerSP newLayer; if (layerRecord->infoBlocks.keys.contains("lsct") && layerRecord->infoBlocks.sectionDividerType != psd_other) { if (layerRecord->infoBlocks.sectionDividerType == psd_bounding_divider && !groupStack.isEmpty()) { KisGroupLayerSP groupLayer = new KisGroupLayer(m_image, "temp", OPACITY_OPAQUE_U8); m_image->addNode(groupLayer, groupStack.top()); groupStack.push(groupLayer); newLayer = groupLayer; } else if ((layerRecord->infoBlocks.sectionDividerType == psd_open_folder || layerRecord->infoBlocks.sectionDividerType == psd_closed_folder) && (groupStack.size() > 1 || (lastAddedLayer && !groupStack.isEmpty()))) { KisGroupLayerSP groupLayer; if (groupStack.size() <= 1) { groupLayer = new KisGroupLayer(m_image, "temp", OPACITY_OPAQUE_U8); m_image->addNode(groupLayer, groupStack.top()); m_image->moveNode(lastAddedLayer, groupLayer, KisNodeSP()); } else { groupLayer = groupStack.pop(); } const QDomDocument &styleXml = layerRecord->infoBlocks.layerStyleXml; if (!styleXml.isNull()) { allStylesXml << LayerStyleMapping(styleXml, groupLayer); } groupLayer->setName(layerRecord->layerName); groupLayer->setVisible(layerRecord->visible); QString compositeOp = psd_blendmode_to_composite_op(layerRecord->infoBlocks.sectionDividerBlendMode); // Krita doesn't support pass-through blend // mode. Instead it is just a property of a group // layer, so flip it if (compositeOp == COMPOSITE_PASS_THROUGH) { compositeOp = COMPOSITE_OVER; groupLayer->setPassThroughMode(true); } groupLayer->setCompositeOpId(compositeOp); newLayer = groupLayer; } else { /** * In some files saved by PS CS6 the group layer sections seem * to be unbalanced. I don't know why it happens because the * reporter didn't provide us an example file. So here we just * check if the new layer was created, and if not, skip the * initialization of masks. * * See bug: 357559 */ warnKrita << "WARNING: Provided PSD has unbalanced group " << "layer markers. Some masks and/or layers can " << "be lost while loading this file. Please " << "report a bug to Krita developers and attach " << "this file to the bugreport\n" << " " << ppVar(layerRecord->layerName) << "\n" << " " << ppVar(layerRecord->infoBlocks.sectionDividerType) << "\n" << " " << ppVar(groupStack.size()); continue; } } else { KisPaintLayerSP layer = new KisPaintLayer(m_image, layerRecord->layerName, layerRecord->opacity); layer->setCompositeOpId(psd_blendmode_to_composite_op(layerRecord->blendModeKey)); const QDomDocument &styleXml = layerRecord->infoBlocks.layerStyleXml; if (!styleXml.isNull()) { allStylesXml << LayerStyleMapping(styleXml, layer); } if (!layerRecord->readPixelData(io, layer->paintDevice())) { dbgFile << "failed reading channels for layer: " << layerRecord->layerName << layerRecord->error; - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::FileFormatIncorrect; } if (!groupStack.isEmpty()) { m_image->addNode(layer, groupStack.top()); } else { m_image->addNode(layer, m_image->root()); } layer->setVisible(layerRecord->visible); newLayer = layer; } Q_FOREACH (ChannelInfo *channelInfo, layerRecord->channelInfoRecords) { if (channelInfo->channelId < -1) { KisTransparencyMaskSP mask = new KisTransparencyMask(); mask->setName(i18n("Transparency Mask")); mask->initSelection(newLayer); if (!layerRecord->readMask(io, mask->paintDevice(), channelInfo)) { dbgFile << "failed reading masks for layer: " << layerRecord->layerName << layerRecord->error; } m_image->addNode(mask, newLayer); } } lastAddedLayer = newLayer; } const QVector &embeddedPatterns = layerSection.globalInfoSection.embeddedPatterns; KisAslLayerStyleSerializer serializer; if (!embeddedPatterns.isEmpty()) { Q_FOREACH (const QDomDocument &doc, embeddedPatterns) { serializer.registerPSDPattern(doc); } } QVector allStylesForServer; if (!allStylesXml.isEmpty()) { Q_FOREACH (const LayerStyleMapping &mapping, allStylesXml) { serializer.readFromPSDXML(mapping.first); if (serializer.styles().size() == 1) { KisPSDLayerStyleSP layerStyle = serializer.styles().first(); KisLayerSP layer = mapping.second; layerStyle->setName(layer->name()); allStylesForServer << layerStyle; layer->setLayerStyle(layerStyle->clone()); } else { warnKrita << "WARNING: Couldn't read layer style!" << ppVar(serializer.styles()); } } } if (!allStylesForServer.isEmpty()) { KisPSDLayerStyleCollectionResource *collection = new KisPSDLayerStyleCollectionResource("Embedded PSD Styles.asl"); collection->setName(i18nc("Auto-generated layer style collection name for embedded styles (collection)", "<%1> (embedded)", m_image->objectName())); KIS_SAFE_ASSERT_RECOVER_NOOP(!collection->valid()); collection->setLayerStyles(allStylesForServer); KIS_SAFE_ASSERT_RECOVER_NOOP(collection->valid()); KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); server->addResource(collection, false); } m_image->unlock(); - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } -KisImageBuilder_Result PSDLoader::buildImage(QIODevice *io) +KisImportExportErrorCode PSDLoader::buildImage(QIODevice *io) { return decode(io); } KisImageSP PSDLoader::image() { return m_image; } void PSDLoader::cancel() { m_stop = true; } diff --git a/plugins/impex/psd/psd_loader.h b/plugins/impex/psd/psd_loader.h index 0618f803f5..0f096b20e2 100644 --- a/plugins/impex/psd/psd_loader.h +++ b/plugins/impex/psd/psd_loader.h @@ -1,59 +1,59 @@ /* * Copyright (c) 2009 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. */ #ifndef _PSD_LOADER_H_ #define _PSD_LOADER_H_ #include #include #include #include "kis_types.h" -#include +#include class KisDocument; class PSDLoader : public QObject { Q_OBJECT public: PSDLoader(KisDocument *doc); ~PSDLoader() override; - KisImageBuilder_Result buildImage(QIODevice *io); + KisImportExportErrorCode buildImage(QIODevice *io); KisImageSP image(); public Q_SLOTS: virtual void cancel(); private: - KisImageBuilder_Result decode(QIODevice *io); + KisImportExportErrorCode decode(QIODevice *io); private: KisImageSP m_image; KisDocument *m_doc; bool m_stop; }; #endif diff --git a/plugins/impex/psd/psd_saver.cpp b/plugins/impex/psd/psd_saver.cpp index 3ed8d70b26..8ea25a8e84 100644 --- a/plugins/impex/psd/psd_saver.cpp +++ b/plugins/impex/psd/psd_saver.cpp @@ -1,250 +1,252 @@ /* * Copyright (c) 2009 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 "psd_saver.h" #include #include #include #include #include #include #include #include #include #include "kis_painter.h" #include #include #include #include #include #include #include "psd.h" #include "psd_header.h" #include "psd_colormode_block.h" #include "psd_utils.h" #include "psd_resource_section.h" #include "psd_layer_section.h" #include "psd_resource_block.h" #include "psd_image_data.h" +const int MAX_PSD_SIZE = 30000; + + QPair colormodelid_to_psd_colormode(const QString &colorSpaceId, const QString &colorDepthId) { psd_color_mode colorMode = COLORMODE_UNKNOWN; if (colorSpaceId == RGBAColorModelID.id()) { colorMode = RGB; } else if (colorSpaceId == CMYKAColorModelID.id()) { colorMode = CMYK; } else if (colorSpaceId == GrayAColorModelID.id()) { colorMode = Grayscale; } else if (colorSpaceId == LABAColorModelID.id()) { colorMode = Lab; } quint16 depth = 0; if (colorDepthId == Integer8BitsColorDepthID.id()) { depth = 8; } else if (colorDepthId == Integer16BitsColorDepthID.id()) { depth = 16; } else if (colorDepthId == Float16BitsColorDepthID.id()) { depth = 32; } else if (colorDepthId == Float32BitsColorDepthID.id()) { depth = 32; } return QPair(colorMode, depth); } PSDSaver::PSDSaver(KisDocument *doc) : m_image(doc->savingImage()) , m_doc(doc) , m_stop(false) { } PSDSaver::~PSDSaver() { } KisImageSP PSDSaver::image() { return m_image; } -KisImageBuilder_Result PSDSaver::buildFile(QIODevice *io) +KisImportExportErrorCode PSDSaver::buildFile(QIODevice *io) { - if (!m_image) { - return KisImageBuilder_RESULT_EMPTY; - } - if (m_image->width() > 30000 || m_image->height() > 30000) { - return KisImageBuilder_RESULT_FAILURE; + KIS_ASSERT_RECOVER_RETURN_VALUE(m_image, ImportExportCodes::InternalError); + + if (m_image->width() > MAX_PSD_SIZE || m_image->height() > MAX_PSD_SIZE) { + return ImportExportCodes::Failure; } const bool haveLayers = m_image->rootLayer()->childCount() > 1 || KisPainter::checkDeviceHasTransparency( m_image->rootLayer()->firstChild()->projection()); // HEADER PSDHeader header; header.signature = "8BPS"; header.version = 1; header.nChannels = haveLayers ? m_image->colorSpace()->channelCount() : m_image->colorSpace()->colorChannelCount(); header.width = m_image->width(); header.height = m_image->height(); QPair colordef = colormodelid_to_psd_colormode(m_image->colorSpace()->colorModelId().id(), m_image->colorSpace()->colorDepthId().id()); if (colordef.first == COLORMODE_UNKNOWN || colordef.second == 0 || colordef.second == 32) { m_image->convertImageColorSpace(KoColorSpaceRegistry::instance()->rgb16(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); colordef = colormodelid_to_psd_colormode(m_image->colorSpace()->colorModelId().id(), m_image->colorSpace()->colorDepthId().id()); } header.colormode = colordef.first; header.channelDepth = colordef.second; dbgFile << "header" << header << io->pos(); if (!header.write(io)) { dbgFile << "Failed to write header. Error:" << header.error << io->pos(); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::ErrorWhileWriting; } // COLORMODE BlOCK PSDColorModeBlock colorModeBlock(header.colormode); // XXX: check for annotations that contain the duotone spec KisAnnotationSP annotation = m_image->annotation("DuotoneColormodeBlock"); if (annotation) { colorModeBlock.duotoneSpecification = annotation->annotation(); } dbgFile << "colormode block" << io->pos(); if (!colorModeBlock.write(io)) { dbgFile << "Failed to write colormode block. Error:" << colorModeBlock.error << io->pos(); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::ErrorWhileWriting; } // IMAGE RESOURCES SECTION PSDImageResourceSection resourceSection; vKisAnnotationSP_it it = m_image->beginAnnotations(); vKisAnnotationSP_it endIt = m_image->endAnnotations(); while (it != endIt) { KisAnnotationSP annotation = (*it); if (!annotation || annotation->type().isEmpty()) { dbgFile << "Warning: empty annotation"; it++; continue; } dbgFile << "Annotation:" << annotation->type() << annotation->description(); if (annotation->type().startsWith(QString("PSD Resource Block:"))) { // PSDResourceBlock *resourceBlock = dynamic_cast(annotation.data()); if (resourceBlock) { dbgFile << "Adding PSD Resource Block" << resourceBlock->identifier; resourceSection.resources[(PSDImageResourceSection::PSDResourceID)resourceBlock->identifier] = resourceBlock; } } it++; } // Add resolution block { RESN_INFO_1005 *resInfo = new RESN_INFO_1005; resInfo->hRes = INCH_TO_POINT(m_image->xRes()); resInfo->vRes = INCH_TO_POINT(m_image->yRes()); PSDResourceBlock *block = new PSDResourceBlock; block->identifier = PSDImageResourceSection::RESN_INFO; block->resource = resInfo; resourceSection.resources[PSDImageResourceSection::RESN_INFO] = block; } // Add icc block { ICC_PROFILE_1039 *profileInfo = new ICC_PROFILE_1039; profileInfo->icc = m_image->profile()->rawData(); PSDResourceBlock *block = new PSDResourceBlock; block->identifier = PSDImageResourceSection::ICC_PROFILE; block->resource = profileInfo; resourceSection.resources[PSDImageResourceSection::ICC_PROFILE] = block; } dbgFile << "resource section" << io->pos(); if (!resourceSection.write(io)) { dbgFile << "Failed to write resource section. Error:" << resourceSection.error << io->pos(); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::ErrorWhileWriting; } // LAYER AND MASK DATA // Only save layers and masks if there is more than one layer dbgFile << "m_image->rootLayer->childCount" << m_image->rootLayer()->childCount() << io->pos(); if (haveLayers) { PSDLayerMaskSection layerSection(header); layerSection.hasTransparency = true; if (!layerSection.write(io, m_image->rootLayer())) { dbgFile << "failed to write layer section. Error:" << layerSection.error << io->pos(); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::ErrorWhileWriting; } } else { // else write a zero length block dbgFile << "No layers, saving empty layers/mask block" << io->pos(); psdwrite(io, (quint32)0); } // IMAGE DATA dbgFile << "Saving composited image" << io->pos(); PSDImageData imagedata(&header); if (!imagedata.write(io, m_image->projection(), haveLayers)) { dbgFile << "Failed to write image data. Error:" << imagedata.error; - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::ErrorWhileWriting; } - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } void PSDSaver::cancel() { m_stop = true; } diff --git a/plugins/impex/psd/psd_saver.h b/plugins/impex/psd/psd_saver.h index bf142d5128..f0aaa45c17 100644 --- a/plugins/impex/psd/psd_saver.h +++ b/plugins/impex/psd/psd_saver.h @@ -1,57 +1,62 @@ /* * Copyright (c) 2009 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. */ #ifndef _PSD_CONVERTER_H_ #define _PSD_CONVERTER_H_ #include #include #include #include "kis_types.h" -#include +#include + + +// max number of pixels in one dimension of psd file +extern const int MAX_PSD_SIZE; + + class KisDocument; class PSDSaver : public QObject { Q_OBJECT public: PSDSaver(KisDocument *doc); ~PSDSaver() override; public: - KisImageBuilder_Result buildFile(QIODevice *io); + KisImportExportErrorCode buildFile(QIODevice *io); KisImageSP image(); public Q_SLOTS: virtual void cancel(); private: - KisImageSP m_image; KisDocument *m_doc; bool m_stop; }; #endif diff --git a/plugins/impex/psd/tests/data/incorrectFormatFile.txt b/plugins/impex/psd/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/psd/tests/data/readonlyFile.txt b/plugins/impex/psd/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/psd/tests/data/writeonlyFile.txt b/plugins/impex/psd/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/psd/tests/kis_psd_test.cpp b/plugins/impex/psd/tests/kis_psd_test.cpp index f309ef2125..89b1e7100b 100644 --- a/plugins/impex/psd/tests/kis_psd_test.cpp +++ b/plugins/impex/psd/tests/kis_psd_test.cpp @@ -1,351 +1,377 @@ /* * Copyright (C) 2009 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_psd_test.h" #include #include #include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif #include #include "kis_group_layer.h" #include "kis_psd_layer_style.h" #include "kis_paint_device_debug_utils.h" +#include + + + +const QString PSDMimetype = "image/vnd.adobe.photoshop"; void KisPSDTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList()); } void KisPSDTest::testOpening() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "testing_psd_ls.psd"); QScopedPointer doc(qobject_cast(KisPart::instance()->createDocument())); KisImportExportManager manager(doc.data()); doc->setFileBatchMode(true); - KisImportExportFilter::ConversionStatus status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString()); - QCOMPARE(status, KisImportExportFilter::OK); + KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString()); + QVERIFY(status.isOk()); Q_ASSERT(doc->image()); } QSharedPointer openPsdDocument(const QFileInfo &fileInfo) { QSharedPointer doc(qobject_cast(KisPart::instance()->createDocument())); KisImportExportManager manager(doc.data()); doc->setFileBatchMode(true); - KisImportExportFilter::ConversionStatus status = manager.importDocument(fileInfo.absoluteFilePath(), QString()); + KisImportExportErrorCode status = manager.importDocument(fileInfo.absoluteFilePath(), QString()); Q_UNUSED(status); return doc; } void KisPSDTest::testTransparencyMask() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "sources/masks.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); QImage result = doc->image()->projection()->convertToQImage(0, doc->image()->bounds()); QVERIFY(TestUtil::checkQImageExternal(result, "psd_test", "transparency_masks", "kiki_single", 1, 1)); doc->setFileBatchMode(true); doc->setMimeType("image/vnd.adobe.photoshop"); QFileInfo dstFileInfo(QDir::currentPath() + QDir::separator() + "test_tmask.psd"); bool retval = doc->exportDocumentSync(QUrl::fromLocalFile(dstFileInfo.absoluteFilePath()), "image/vnd.adobe.photoshop"); QVERIFY(retval); { QSharedPointer doc = openPsdDocument(dstFileInfo); QVERIFY(doc->image()); QImage result = doc->image()->projection()->convertToQImage(0, doc->image()->bounds()); QVERIFY(TestUtil::checkQImageExternal(result, "psd_test", "transparency_masks", "kiki_single", 1, 1)); QVERIFY(doc->image()->root()->lastChild()); QVERIFY(doc->image()->root()->lastChild()->firstChild()); QVERIFY(doc->image()->root()->lastChild()->firstChild()->inherits("KisTransparencyMask")); } } void KisPSDTest::testOpenGrayscaleMultilayered() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "sources/gray.psd"); //QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "sources/100x100gray8.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); } void KisPSDTest::testOpenGroupLayers() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "group_layers.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisNodeSP node = TestUtil::findNode(doc->image()->root(), "Group 1 PT"); KisGroupLayer *group = dynamic_cast(node.data()); QVERIFY(group); QVERIFY(group->passThroughMode()); } void KisPSDTest::testOpenLayerStyles() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "testing_psd_ls.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->dropShadow()); QVERIFY(layer->layerStyle()->dropShadow()->effectEnabled()); } void KisPSDTest::testOpenLayerStylesWithPattern() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_ls_pattern.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->patternOverlay()); QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid()); } void KisPSDTest::testOpenLayerStylesWithPatternMulti() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_ls_pattern_multi.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->patternOverlay()); QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid()); QVERIFY(layer->layerStyle()->stroke()); QVERIFY(layer->layerStyle()->stroke()->effectEnabled()); QVERIFY(layer->layerStyle()->stroke()->pattern()); QVERIFY(layer->layerStyle()->stroke()->pattern()->valid()); } void KisPSDTest::testSaveLayerStylesWithPatternMulti() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_ls_pattern_multi.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->patternOverlay()); QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid()); QVERIFY(layer->layerStyle()->stroke()); QVERIFY(layer->layerStyle()->stroke()->effectEnabled()); QVERIFY(layer->layerStyle()->stroke()->pattern()); QVERIFY(layer->layerStyle()->stroke()->pattern()->valid()); doc->setFileBatchMode(true); const QByteArray mimeType("image/vnd.adobe.photoshop"); QFileInfo dstFileInfo(QDir::currentPath() + QDir::separator() + "test_save_styles.psd"); bool retval = doc->exportDocumentSync(QUrl::fromLocalFile(dstFileInfo.absoluteFilePath()), mimeType); QVERIFY(retval); { QSharedPointer doc = openPsdDocument(dstFileInfo); QVERIFY(doc->image()); QImage result = doc->image()->projection()->convertToQImage(0, doc->image()->bounds()); //QVERIFY(TestUtil::checkQImageExternal(result, "psd_test", "transparency_masks", "kiki_single")); KisLayerSP layer = qobject_cast(doc->image()->root()->lastChild().data()); QVERIFY(layer->layerStyle()); QVERIFY(layer->layerStyle()->patternOverlay()); QVERIFY(layer->layerStyle()->patternOverlay()->effectEnabled()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()); QVERIFY(layer->layerStyle()->patternOverlay()->pattern()->valid()); QVERIFY(layer->layerStyle()->stroke()); QVERIFY(layer->layerStyle()->stroke()->effectEnabled()); QVERIFY(layer->layerStyle()->stroke()->pattern()); QVERIFY(layer->layerStyle()->stroke()->pattern()->valid()); } } void KisPSDTest::testOpeningFromOpenCanvas() { QFileInfo sourceFileInfo(QString(FILES_DATA_DIR) + QDir::separator() + "test_krita_psd_from_opencanvas.psd"); Q_ASSERT(sourceFileInfo.exists()); QSharedPointer doc = openPsdDocument(sourceFileInfo); QVERIFY(doc->image()); QVERIFY(doc->image()->root()->firstChild()); } void KisPSDTest::testOpeningAllFormats() { QString path = TestUtil::fetchExternalDataFileName("psd_format_test_files"); QDir dirSources(path); bool shouldFailTheTest = false; Q_FOREACH (QFileInfo sourceFileInfo, dirSources.entryInfoList()) { Q_ASSERT(sourceFileInfo.exists()); if (sourceFileInfo.isHidden() || sourceFileInfo.isDir()) { continue; } if (sourceFileInfo.fileName() != "ml_cmyk_16b.psd") { //continue; } //dbgKrita << "Opening" << ppVar(sourceFileInfo.fileName()); QSharedPointer doc = openPsdDocument(sourceFileInfo); if (!doc->image()) { /** * 32bit images are expected to fail atm, their loading is not implemented */ if (!sourceFileInfo.fileName().contains("_32b")) { shouldFailTheTest = true; } errKrita << "FAILED to open" << sourceFileInfo.fileName(); continue; } // just check visually if the file loads fine KIS_DUMP_DEVICE_2(doc->image()->projection(), QRect(0,0,100,100), sourceFileInfo.fileName(), "dd"); } QVERIFY(!shouldFailTheTest); } void KisPSDTest::testSavingAllFormats() { QString path = TestUtil::fetchExternalDataFileName("psd_format_test_files"); QDir dirSources(path); Q_FOREACH (QFileInfo sourceFileInfo, dirSources.entryInfoList()) { Q_ASSERT(sourceFileInfo.exists()); if (sourceFileInfo.isHidden() || sourceFileInfo.isDir()) { continue; } if (sourceFileInfo.fileName() != "sl_rgb_8b.psd") { //continue; } dbgKrita << "Opening" << ppVar(sourceFileInfo.fileName()); QSharedPointer doc = openPsdDocument(sourceFileInfo); if (!doc->image()) { errKrita << "FAILED to open" << sourceFileInfo.fileName(); continue; } QString baseName = sourceFileInfo.fileName(); //QString originalName = QString("%1_0orig").arg(baseName); //QString resultName = QString("%1_1result").arg(baseName); QString tempPsdName = QString("%1_3interm.psd").arg(baseName); QImage refImage = doc->image()->projection()->convertToQImage(0, QRect(0,0,100,100)); // uncomment to do a visual check // KIS_DUMP_DEVICE_2(doc->image()->projection(), QRect(0,0,100,100), originalName, "dd"); doc->setFileBatchMode(true); doc->setMimeType("image/vnd.adobe.photoshop"); QFileInfo dstFileInfo(QDir::currentPath() + QDir::separator() + tempPsdName); dbgKrita << "Saving" << ppVar(dstFileInfo.fileName()); bool retval = doc->exportDocumentSync(QUrl::fromLocalFile(dstFileInfo.absoluteFilePath()), "image/vnd.adobe.photoshop"); QVERIFY(retval); { QSharedPointer doc = openPsdDocument(dstFileInfo); QVERIFY(doc->image()); // uncomment to do a visual check //KIS_DUMP_DEVICE_2(doc->image()->projection(), QRect(0,0,100,100), resultName, "dd"); QImage resultImage = doc->image()->projection()->convertToQImage(0, QRect(0,0,100,100)); QCOMPARE(resultImage, refImage); } } } + +void KisPSDTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), PSDMimetype); +} + + +void KisPSDTest::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), PSDMimetype); +} + + +void KisPSDTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), PSDMimetype); +} + + + + KISTEST_MAIN(KisPSDTest) diff --git a/plugins/impex/psd/tests/kis_psd_test.h b/plugins/impex/psd/tests/kis_psd_test.h index 6ed0b7c537..326368d64e 100644 --- a/plugins/impex/psd/tests/kis_psd_test.h +++ b/plugins/impex/psd/tests/kis_psd_test.h @@ -1,45 +1,50 @@ /* * Copyright (C) 2009 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. */ #ifndef _KIS_PSD_TEST_H_ #define _KIS_PSD_TEST_H_ #include class KisPSDTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); void testOpening(); void testTransparencyMask(); void testOpenGrayscaleMultilayered(); void testOpenGroupLayers(); void testOpenLayerStyles(); void testOpenLayerStylesWithPattern(); void testOpenLayerStylesWithPatternMulti(); void testSaveLayerStylesWithPatternMulti(); void testOpeningFromOpenCanvas(); void testOpeningAllFormats(); void testSavingAllFormats(); + + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; #endif diff --git a/plugins/impex/qimageio/CMakeLists.txt b/plugins/impex/qimageio/CMakeLists.txt index 1c4c9d8616..07221802c9 100644 --- a/plugins/impex/qimageio/CMakeLists.txt +++ b/plugins/impex/qimageio/CMakeLists.txt @@ -1,25 +1,27 @@ +add_subdirectory(tests) + set(kritaqimageioexport_SOURCES kis_qimageio_export.cpp ) ki18n_wrap_ui(kritaqimageioexport_SOURCES ) add_library(kritaqimageioexport MODULE ${kritaqimageioexport_SOURCES}) target_link_libraries(kritaqimageioexport kritaui kritaimpex) install(TARGETS kritaqimageioexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) set(kritaqimageioimport_SOURCES kis_qimageio_import.cpp ) ki18n_wrap_ui(kritaqimageioimport_SOURCES ) add_library(kritaqimageioimport MODULE ${kritaqimageioimport_SOURCES}) target_link_libraries(kritaqimageioimport kritaui) install(TARGETS kritaqimageioimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_qimageio.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/qimageio/kis_qimageio_export.cpp b/plugins/impex/qimageio/kis_qimageio_export.cpp index ee1c2a9aac..55db76bcc3 100644 --- a/plugins/impex/qimageio/kis_qimageio_export.cpp +++ b/plugins/impex/qimageio/kis_qimageio_export.cpp @@ -1,69 +1,69 @@ /* * Copyright (c) 2007 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_qimageio_export.h" #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KisQImageIOExportFactory, "krita_qimageio_export.json", registerPlugin();) KisQImageIOExport::KisQImageIOExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisQImageIOExport::~KisQImageIOExport() { } -KisImportExportFilter::ConversionStatus KisQImageIOExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisQImageIOExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { QRect rc = document->savingImage()->bounds(); QImage image = document->savingImage()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); - bool r = image.save(io, QFileInfo(filename()).suffix().toLatin1()); - if (r) { - return KisImportExportFilter::OK; + bool result = image.save(io, QFileInfo(filename()).suffix().toLatin1()); + if (result) { + return ImportExportCodes::OK; } else { - return KisImportExportFilter::InvalidFormat; + return ImportExportCodes::FileFormatIncorrect; } } void KisQImageIOExport::initializeCapabilities() { QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, KisMimeDatabase::descriptionForMimeType(mimeType())); addCapability(KisExportCheckRegistry::instance()->get("ColorModelPerLayerCheck/" + RGBAColorModelID.id() + "/" + Integer8BitsColorDepthID.id())->create(KisExportCheckBase::SUPPORTED)); } #include "kis_qimageio_export.moc" diff --git a/plugins/impex/qimageio/kis_qimageio_export.h b/plugins/impex/qimageio/kis_qimageio_export.h index 6922fa357b..423f9876b5 100644 --- a/plugins/impex/qimageio/kis_qimageio_export.h +++ b/plugins/impex/qimageio/kis_qimageio_export.h @@ -1,38 +1,38 @@ /* * Copyright (c) 2007 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. */ #ifndef _KIS_BMP_EXPORT_H_ #define _KIS_BMP_EXPORT_H_ #include #include class KisQImageIOExport : public KisImportExportFilter { Q_OBJECT public: KisQImageIOExport(QObject *parent, const QVariantList &); ~KisQImageIOExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/qimageio/kis_qimageio_import.cpp b/plugins/impex/qimageio/kis_qimageio_import.cpp index 73c2b78f0d..411162e8a4 100644 --- a/plugins/impex/qimageio/kis_qimageio_import.cpp +++ b/plugins/impex/qimageio/kis_qimageio_import.cpp @@ -1,73 +1,73 @@ /* * Copyright (c) 2007 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_qimageio_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KisQImageIOImportFactory, "krita_qimageio_import.json", registerPlugin();) KisQImageIOImport::KisQImageIOImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisQImageIOImport::~KisQImageIOImport() { } -KisImportExportFilter::ConversionStatus KisQImageIOImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisQImageIOImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { QFileInfo fi(filename()); QImage img; if (!img.loadFromData(io->readAll(), fi.suffix().toLower().toLatin1())) { - return KisImportExportFilter::InvalidFormat; + return ImportExportCodes::FileFormatIncorrect; } const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(document->createUndoStore(), img.width(), img.height(), colorSpace, i18n("Imported Image")); KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255); layer->paintDevice()->convertFromQImage(img, 0, 0, 0); image->addNode(layer.data(), image->rootLayer().data()); document->setCurrentImage(image); - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include "kis_qimageio_import.moc" diff --git a/plugins/impex/qimageio/kis_qimageio_import.h b/plugins/impex/qimageio/kis_qimageio_import.h index 4e93f74beb..a97eade902 100644 --- a/plugins/impex/qimageio/kis_qimageio_import.h +++ b/plugins/impex/qimageio/kis_qimageio_import.h @@ -1,37 +1,37 @@ /* * Copyright (c) 2007 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. */ #ifndef _KIS_BMP_IMPORT_H_ #define _KIS_BMP_IMPORT_H_ #include #include class KisQImageIOImport : public KisImportExportFilter { Q_OBJECT public: KisQImageIOImport(QObject *parent, const QVariantList &); ~KisQImageIOImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/qimageio/tests/CMakeLists.txt b/plugins/impex/qimageio/tests/CMakeLists.txt new file mode 100644 index 0000000000..bed630f553 --- /dev/null +++ b/plugins/impex/qimageio/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisQImageIOTest.cpp + TEST_NAME KisQImageIOTest + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/qimageio/tests/KisQImageIOTest.cpp similarity index 62% copy from plugins/impex/svg/tests/kis_svg_test.cpp copy to plugins/impex/qimageio/tests/KisQImageIOTest.cpp index 6ca66688f8..65d4cb9ce2 100644 --- a/plugins/impex/svg/tests/kis_svg_test.cpp +++ b/plugins/impex/qimageio/tests/KisQImageIOTest.cpp @@ -1,40 +1,57 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * 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_svg_test.h" +#include "KisQImageIOTest.h" #include #include -#include - #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif -void KisSvgTest::testFiles() +const QString QImageIOMimetype = "image/x-gimp-brush"; + + + +void KisQImageIOTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), QImageIOMimetype); +} + + +void KisQImageIOTest::testExportToReadonly() { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50); + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), QImageIOMimetype); } -KISTEST_MAIN(KisSvgTest) + +void KisQImageIOTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), QImageIOMimetype); +} + + + +KISTEST_MAIN(KisQImageIOTest) + diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/qimageio/tests/KisQImageIOTest.h similarity index 72% copy from plugins/impex/jpeg/tests/kis_jpeg_test.h copy to plugins/impex/qimageio/tests/KisQImageIOTest.h index 5c19b0cda6..ea154f3dbc 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/qimageio/tests/KisQImageIOTest.h @@ -1,31 +1,35 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * 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_JPEG_TEST_H_ -#define _KIS_JPEG_TEST_H_ +#ifndef _KIS_QIMAGEIO_TEST_H_ +#define _KIS_QIMAGEIO_TEST_H_ #include -class KisJpegTest : public QObject +class KisQImageIOTest : public QObject { Q_OBJECT private Q_SLOTS: - void testFiles(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; -#endif +#endif // _KIS_QIMAGEIO_TEST_H_ + diff --git a/plugins/impex/qimageio/tests/data/incorrectFormatFile.txt b/plugins/impex/qimageio/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/qimageio/tests/data/readonlyFile.txt b/plugins/impex/qimageio/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/qimageio/tests/data/writeonlyFile.txt b/plugins/impex/qimageio/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/qml/CMakeLists.txt b/plugins/impex/qml/CMakeLists.txt index 2f3826d5d1..e53156905a 100644 --- a/plugins/impex/qml/CMakeLists.txt +++ b/plugins/impex/qml/CMakeLists.txt @@ -1,10 +1,12 @@ +add_subdirectory(tests) + set(kritaqmlexport_SOURCES qml_converter.cc qml_export.cc ) add_library(kritaqmlexport MODULE ${kritaqmlexport_SOURCES}) target_link_libraries(kritaqmlexport kritaui kritaimpex) install(TARGETS kritaqmlexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) diff --git a/plugins/impex/qml/qml_converter.cc b/plugins/impex/qml/qml_converter.cc index 8422672254..4d98234f0a 100644 --- a/plugins/impex/qml/qml_converter.cc +++ b/plugins/impex/qml/qml_converter.cc @@ -1,91 +1,96 @@ /* * Copyright (c) 2013 Sven Langkamp * * 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "qml_converter.h" #include #include #include #include #define SPACE " " QMLConverter::QMLConverter() { } QMLConverter::~QMLConverter() { } -KisImageBuilder_Result QMLConverter::buildFile(const QString &filename, const QString &realFilename, QIODevice *io, KisImageSP image) +KisImportExportErrorCode QMLConverter::buildFile(const QString &filename, const QString &realFilename, QIODevice *io, KisImageSP image) { QTextStream out(io); out.setCodec("UTF-8"); out << "import QtQuick 1.1" << "\n\n"; out << "Rectangle {\n"; writeInt(out, 1, "width", image->width()); writeInt(out, 1, "height", image->height()); out << "\n"; QFileInfo info(filename); QFileInfo infoRealFile(realFilename); KisNodeSP node = image->rootLayer()->firstChild(); QString imageDir = infoRealFile.baseName() + "_images"; QString imagePath = infoRealFile.absolutePath() + '/' + imageDir; if (node) { QDir dir; - dir.mkpath(imagePath); + bool success = dir.mkpath(imagePath); + if (!success) + { + return ImportExportCodes::CannotCreateFile; + } } + dbgFile << "Saving images to " << imagePath; while(node) { KisPaintDeviceSP projection = node->projection(); QRect rect = projection->exactBounds(); QImage qmlImage = projection->convertToQImage(0, rect.x(), rect.y(), rect.width(), rect.height()); QString name = node->name().replace(' ', '_').toLower(); QString fileName = name + ".png"; qmlImage.save(imagePath +'/'+ fileName); out << SPACE << "Image {\n"; writeString(out, 2, "id", name); writeInt(out, 2, "x", rect.x()); writeInt(out, 2, "y", rect.y()); writeInt(out, 2, "width", rect.width()); writeInt(out, 2, "height", rect.height()); writeString(out, 2, "source", "\"" + imageDir + '/' + fileName + "\"" ); writeString(out, 2, "opacity", QString().setNum(node->opacity()/255.0)); out << SPACE << "}\n"; node = node->nextSibling(); } out << "}\n"; - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } void QMLConverter::writeString(QTextStream& out, int spacing, const QString& setting, const QString& value) { for (int space = 0; space < spacing; space++) { out << SPACE; } out << setting << ": " << value << "\n"; } void QMLConverter::writeInt(QTextStream& out, int spacing, const QString& setting, int value) { writeString(out, spacing, setting, QString::number(value)); } diff --git a/plugins/impex/qml/qml_converter.h b/plugins/impex/qml/qml_converter.h index e70aa3e176..ab03696947 100644 --- a/plugins/impex/qml/qml_converter.h +++ b/plugins/impex/qml/qml_converter.h @@ -1,44 +1,44 @@ /* * Copyright (c) 2013 Sven Langkamp * * 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _QML_CONVERTER_H_ #define _QML_CONVERTER_H_ #include #include #include #include "kis_types.h" -#include +#include class QMLConverter : public QObject { Q_OBJECT public: QMLConverter(); ~QMLConverter() override; public: - KisImageBuilder_Result buildFile(const QString &filename, const QString &realFilename, QIODevice *io, KisImageSP image); + KisImportExportErrorCode buildFile(const QString &filename, const QString &realFilename, QIODevice *io, KisImageSP image); private: void writeString(QTextStream& out, int spacing, const QString& setting, const QString& value); void writeInt(QTextStream& out, int spacing, const QString& setting, int value); }; #endif diff --git a/plugins/impex/qml/qml_export.cc b/plugins/impex/qml/qml_export.cc index d70a3278bb..8de5713de9 100644 --- a/plugins/impex/qml/qml_export.cc +++ b/plugins/impex/qml/qml_export.cc @@ -1,73 +1,67 @@ /* * Copyright (c) 2013 Sven Langkamp * * 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "qml_export.h" #include #include #include #include #include #include #include #include #include "qml_converter.h" #include K_PLUGIN_FACTORY_WITH_JSON(ExportFactory, "krita_qml_export.json", registerPlugin();) QMLExport::QMLExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } QMLExport::~QMLExport() { } -KisImportExportFilter::ConversionStatus QMLExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode QMLExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { KisImageSP image = document->savingImage(); Q_CHECK_PTR(image); QMLConverter converter; - KisImageBuilder_Result result = converter.buildFile(filename(), realFilename(), io, image); - if (result == KisImageBuilder_RESULT_OK) { - dbgFile << "success !"; - return KisImportExportFilter::OK; - } - dbgFile << " Result =" << result; - return KisImportExportFilter::InternalError; + return converter.buildFile(filename(), realFilename(), io, image); } void QMLExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "QML"); } #include diff --git a/plugins/impex/qml/qml_export.h b/plugins/impex/qml/qml_export.h index 1cf9b0038d..7ca4de2a36 100644 --- a/plugins/impex/qml/qml_export.h +++ b/plugins/impex/qml/qml_export.h @@ -1,36 +1,36 @@ /* * Copyright (c) 2013 Sven Langkamp * * 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; version 2.1 of the License. * * 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _QML_EXPORT_H_ #define _QML_EXPORT_H_ #include #include class QMLExport : public KisImportExportFilter { Q_OBJECT public: QMLExport(QObject *parent, const QVariantList &); ~QMLExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/qml/tests/CMakeLists.txt b/plugins/impex/qml/tests/CMakeLists.txt new file mode 100644 index 0000000000..d197d45935 --- /dev/null +++ b/plugins/impex/qml/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisQmlTest.cpp + TEST_NAME KisQmlTest + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/ppm/tests/kis_ppm_test.cpp b/plugins/impex/qml/tests/KisQmlTest.cpp similarity index 78% copy from plugins/impex/ppm/tests/kis_ppm_test.cpp copy to plugins/impex/qml/tests/KisQmlTest.cpp index 6aa3859d0a..c92533ae76 100644 --- a/plugins/impex/ppm/tests/kis_ppm_test.cpp +++ b/plugins/impex/qml/tests/KisQmlTest.cpp @@ -1,39 +1,43 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * 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_ppm_test.h" +#include "KisQmlTest.h" #include #include -#include - #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif -void KisPPMTest::testFiles() + +const QString QmlMimetype = "text/x-qml"; + + +void KisQmlTest::testExportToReadonly() { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1); + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), QmlMimetype); } -KISTEST_MAIN(KisPPMTest) + +KISTEST_MAIN(KisQmlTest) + diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/qml/tests/KisQmlTest.h similarity index 79% copy from plugins/impex/jpeg/tests/kis_jpeg_test.h copy to plugins/impex/qml/tests/KisQmlTest.h index 5c19b0cda6..9944e9ca65 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/qml/tests/KisQmlTest.h @@ -1,31 +1,33 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * 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_JPEG_TEST_H_ -#define _KIS_JPEG_TEST_H_ +#ifndef _KIS_QML_TEST_H_ +#define _KIS_QML_TEST_H_ #include -class KisJpegTest : public QObject +class KisQmlTest : public QObject { Q_OBJECT private Q_SLOTS: - void testFiles(); + + void testExportToReadonly(); }; -#endif +#endif // _KIS_QML_TEST_H_ + diff --git a/plugins/impex/qml/tests/data/readonlyFile.txt b/plugins/impex/qml/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/raw/CMakeLists.txt b/plugins/impex/raw/CMakeLists.txt index f03ae6dd5c..f7b63c845c 100644 --- a/plugins/impex/raw/CMakeLists.txt +++ b/plugins/impex/raw/CMakeLists.txt @@ -1,33 +1,35 @@ +add_subdirectory(tests) + if(OPENEXR_FOUND) include_directories(${OPENEXR_INCLUDE_DIR}) endif() include_directories(${LibRaw_INCLUDE_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/3rdparty/libkdcraw/src) set(krita_raw_import_SOURCES kis_raw_import.cpp 3rdparty/libkdcraw/src/dcrawinfocontainer.cpp 3rdparty/libkdcraw/src/dcrawsettingswidget.cpp 3rdparty/libkdcraw/src/kdcraw.cpp 3rdparty/libkdcraw/src/kdcraw_p.cpp 3rdparty/libkdcraw/src/libkdcraw_debug.cpp 3rdparty/libkdcraw/src/ractionjob.cpp 3rdparty/libkdcraw/src/ractionthreadbase.cpp 3rdparty/libkdcraw/src/rawdecodingsettings.cpp 3rdparty/libkdcraw/src/rcombobox.cpp 3rdparty/libkdcraw/src/rexpanderbox.cpp 3rdparty/libkdcraw/src/rnuminput.cpp 3rdparty/libkdcraw/src/rsliderspinbox.cpp 3rdparty/libkdcraw/src/rwidgetutils.cpp 3rdparty/libkdcraw/src/squeezedcombobox.cpp ) ki18n_wrap_ui(krita_raw_import_SOURCES wdgrawimport.ui ) add_library(krita_raw_import MODULE ${krita_raw_import_SOURCES}) target_link_libraries(krita_raw_import kritaui ${LibRaw_LIBRARIES}) install(TARGETS krita_raw_import DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_raw.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/raw/kis_raw_import.cpp b/plugins/impex/raw/kis_raw_import.cpp index 41cdd12c8c..634e4b1e43 100644 --- a/plugins/impex/raw/kis_raw_import.cpp +++ b/plugins/impex/raw/kis_raw_import.cpp @@ -1,192 +1,194 @@ /* * Copyright (c) 2008 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_raw_import.h" #include #include #include #include +#include #include #include "kis_debug.h" #include "KisDocument.h" #include "kis_image.h" #include "kis_paint_device.h" #include "kis_transaction.h" #include "kis_group_layer.h" #include "kis_paint_layer.h" #include "kis_iterator_ng.h" #include #include using namespace KDcrawIface; K_PLUGIN_FACTORY_WITH_JSON(KisRawImportFactory, "krita_raw_import.json", registerPlugin();) KisRawImport::KisRawImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { m_dialog = new KoDialog(); m_dialog->enableButtonApply(false); QWidget* widget = new QWidget; m_rawWidget.setupUi(widget); m_dialog->setMainWidget(widget); connect(m_rawWidget.pushButtonUpdate, SIGNAL(clicked()), this, SLOT(slotUpdatePreview())); } KisRawImport::~KisRawImport() { delete m_dialog; } inline quint16 correctIndian(quint16 v) { #if KDCRAW_VERSION < 0x000400 return ((v & 0x00FF) << 8) | ((v & 0xFF00 >> 8)); #else return v; #endif } -KisImportExportFilter::ConversionStatus KisRawImport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisRawImport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/) { // Show dialog m_dialog->setCursor(Qt::ArrowCursor); QApplication::setOverrideCursor(Qt::ArrowCursor); #if KDCRAW_VERSION < 0x010200 m_rawWidget.rawSettings->setDefaultSettings(); #else m_rawWidget.rawSettings->resetToDefault(); #endif slotUpdatePreview(); if (m_dialog->exec() == QDialog::Accepted) { QApplication::setOverrideCursor(Qt::WaitCursor); // Do the decoding // TODO: it would probably be better done in a thread, while an other thread simulate that the application is still living (or even better if libkdcraw was giving us some progress report QByteArray imageData; RawDecodingSettings settings = rawDecodingSettings(); settings.sixteenBitsImage = true; int width, height, rgbmax; KDcraw dcraw; - if (!dcraw.decodeRAWImage(filename(), settings, imageData, width, height, rgbmax)) return KisImportExportFilter::CreationError; + if (!dcraw.decodeRAWImage(filename(), settings, imageData, width, height, rgbmax)) + return ImportExportCodes::FileFormatIncorrect; QApplication::restoreOverrideCursor(); // Init the image const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb16(); KisImageSP image = new KisImage(document->createUndoStore(), width, height, cs, filename()); - if (image.isNull()) return KisImportExportFilter::CreationError; + KIS_ASSERT_RECOVER_RETURN_VALUE(!image.isNull(), ImportExportCodes::InternalError); KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), quint8_MAX); image->addNode(layer, image->rootLayer()); - if (layer.isNull()) return KisImportExportFilter::CreationError; + KIS_ASSERT_RECOVER_RETURN_VALUE(!layer.isNull(), ImportExportCodes::InternalError); KisPaintDeviceSP device = layer->paintDevice(); - if (device.isNull()) return KisImportExportFilter::CreationError; + KIS_ASSERT_RECOVER_RETURN_VALUE(!device.isNull(), ImportExportCodes::InternalError); // Copy the data KisHLineIteratorSP it = device->createHLineIteratorNG(0, 0, width); for (int y = 0; y < height; ++y) { do { KoBgrU16Traits::Pixel* pixel = reinterpret_cast(it->rawData()); quint16* ptr = ((quint16*)imageData.data()) + (y * width + it->x()) * 3; #if KDCRAW_VERSION < 0x000400 pixel->red = correctIndian(ptr[2]); pixel->green = correctIndian(ptr[1]); pixel->blue = correctIndian(ptr[0]); #else pixel->red = correctIndian(ptr[0]); pixel->green = correctIndian(ptr[1]); pixel->blue = correctIndian(ptr[2]); #endif pixel->alpha = 0xFFFF; } while (it->nextPixel()); it->nextRow(); } QApplication::restoreOverrideCursor(); document->setCurrentImage(image); - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } QApplication::restoreOverrideCursor(); - return KisImportExportFilter::UserCancelled; + return ImportExportCodes::Cancelled; } void KisRawImport::slotUpdatePreview() { QByteArray imageData; RawDecodingSettings settings = rawDecodingSettings(); settings.sixteenBitsImage = false; int width, height, rgbmax; KDcraw dcraw; if (dcraw.decodeHalfRAWImage(filename(), settings, imageData, width, height, rgbmax)) { QImage image(width, height, QImage::Format_RGB32); for (int y = 0; y < height; ++y) { QRgb *pixel= reinterpret_cast(image.scanLine(y)); for (int x = 0; x < width; ++x) { quint8* ptr = ((quint8*)imageData.data()) + (y * width + x) * 3; pixel[x] = qRgb(ptr[0], ptr[1], ptr[2]); } } m_rawWidget.preview->setPixmap(QPixmap::fromImage(image)); } } RawDecodingSettings KisRawImport::rawDecodingSettings() { #if KDCRAW_VERSION < 0x010200 RawDecodingSettings settings; settings.sixteenBitsImage = true; settings.brightness = m_rawWidget.rawSettings->brightness(); settings.RAWQuality = m_rawWidget.rawSettings->quality(); settings.outputColorSpace = m_rawWidget.rawSettings->outputColorSpace(); settings.RGBInterpolate4Colors = m_rawWidget.rawSettings->useFourColor(); settings.DontStretchPixels = m_rawWidget.rawSettings->useDontStretchPixels(); settings.unclipColors = m_rawWidget.rawSettings->unclipColor(); settings.whiteBalance = m_rawWidget.rawSettings->whiteBalance(); settings.customWhiteBalance = m_rawWidget.rawSettings->customWhiteBalance(); settings.customWhiteBalanceGreen = m_rawWidget.rawSettings->customWhiteBalanceGreen(); settings.enableBlackPoint = m_rawWidget.rawSettings->useBlackPoint(); settings.blackPoint = m_rawWidget.rawSettings->blackPoint(); settings.enableNoiseReduction = m_rawWidget.rawSettings->useNoiseReduction(); settings.NRThreshold = m_rawWidget.rawSettings->NRThreshold(); settings.enableCACorrection = m_rawWidget.rawSettings->useCACorrection(); settings.caMultiplier[0] = m_rawWidget.rawSettings->caRedMultiplier(); settings.caMultiplier[1] = m_rawWidget.rawSettings->caBlueMultiplier(); return settings; #else return m_rawWidget.rawSettings->settings(); #endif } #include "kis_raw_import.moc" diff --git a/plugins/impex/raw/kis_raw_import.h b/plugins/impex/raw/kis_raw_import.h index 0e5843580e..8ae4e42af2 100644 --- a/plugins/impex/raw/kis_raw_import.h +++ b/plugins/impex/raw/kis_raw_import.h @@ -1,58 +1,58 @@ /* * Copyright (c) 2005 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. */ #ifndef KIS_RAW_IMPORT_H_ #define KIS_RAW_IMPORT_H_ #include #include "ui_wdgrawimport.h" class KoDialog; class WdgRawImport; namespace KDcrawIface { class RawDecodingSettings; } class KisRawImport : public KisImportExportFilter { Q_OBJECT public: KisRawImport(QObject *parent, const QVariantList &); ~KisRawImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; private Q_SLOTS: void slotUpdatePreview(); private: KDcrawIface::RawDecodingSettings rawDecodingSettings(); private: Ui::WdgRawImport m_rawWidget; KoDialog* m_dialog; }; #endif // KIS_RAW_IMPORT_H_ diff --git a/plugins/impex/raw/tests/CMakeLists.txt b/plugins/impex/raw/tests/CMakeLists.txt new file mode 100644 index 0000000000..9057477638 --- /dev/null +++ b/plugins/impex/raw/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisRawTest.cpp + TEST_NAME KisRawTest + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/raw/tests/KisRawTest.cpp similarity index 70% copy from plugins/impex/svg/tests/kis_svg_test.cpp copy to plugins/impex/raw/tests/KisRawTest.cpp index 6ca66688f8..ca954e6d6a 100644 --- a/plugins/impex/svg/tests/kis_svg_test.cpp +++ b/plugins/impex/raw/tests/KisRawTest.cpp @@ -1,40 +1,51 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * 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_svg_test.h" +#include "KisRawTest.h" #include #include -#include - #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif -void KisSvgTest::testFiles() +const QString RawMimetype = "image/x-krita-raw"; + + + +void KisRawTest::testImportFromWriteonly() { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50); + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), RawMimetype); } -KISTEST_MAIN(KisSvgTest) + +void KisRawTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), RawMimetype); +} + + + +KISTEST_MAIN(KisRawTest) + diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/raw/tests/KisRawTest.h similarity index 76% copy from plugins/impex/jpeg/tests/kis_jpeg_test.h copy to plugins/impex/raw/tests/KisRawTest.h index 5c19b0cda6..ec170f963a 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/raw/tests/KisRawTest.h @@ -1,31 +1,34 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * 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_JPEG_TEST_H_ -#define _KIS_JPEG_TEST_H_ +#ifndef _KIS_RAW_TEST_H_ +#define _KIS_RAW_TEST_H_ #include -class KisJpegTest : public QObject +class KisRawTest : public QObject { Q_OBJECT private Q_SLOTS: - void testFiles(); + + void testImportFromWriteonly(); + void testImportIncorrectFormat(); }; -#endif +#endif // _KIS_RAW_TEST_H_ + diff --git a/plugins/impex/raw/tests/data/incorrectFormatFile.txt b/plugins/impex/raw/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/raw/tests/data/readonlyFile.txt b/plugins/impex/raw/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/raw/tests/data/writeonlyFile.txt b/plugins/impex/raw/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/spriter/kis_spriter_export.cpp b/plugins/impex/spriter/kis_spriter_export.cpp index a55c47948a..c5cc2308ad 100644 --- a/plugins/impex/spriter/kis_spriter_export.cpp +++ b/plugins/impex/spriter/kis_spriter_export.cpp @@ -1,617 +1,651 @@ /* * Copyright (c) 2016 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_spriter_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for KisDegreesToRadians #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KisSpriterExportFactory, "krita_spriter_export.json", registerPlugin();) KisSpriterExport::KisSpriterExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisSpriterExport::~KisSpriterExport() { } -bool KisSpriterExport::savePaintDevice(KisPaintDeviceSP dev, const QString &fileName) +KisImportExportErrorCode KisSpriterExport::savePaintDevice(KisPaintDeviceSP dev, const QString &fileName) { QFileInfo fi(fileName); QDir d = fi.absoluteDir(); d.mkpath(d.path()); QRect rc = m_image->bounds().intersected(dev->exactBounds()); if (!KisPNGConverter::isColorSpaceSupported(dev->colorSpace())) { dev = new KisPaintDevice(*dev.data()); dev->convertTo(KoColorSpaceRegistry::instance()->rgb8()); } KisPNGOptions options; options.forceSRGB = true; vKisAnnotationSP_it beginIt = m_image->beginAnnotations(); vKisAnnotationSP_it endIt = m_image->endAnnotations(); KisPNGConverter converter(0); - KisImageBuilder_Result res = converter.buildFile(fileName, rc, m_image->xRes(), m_image->yRes(), dev, beginIt, endIt, options, 0); + KisImportExportErrorCode res = converter.buildFile(fileName, rc, m_image->xRes(), m_image->yRes(), dev, beginIt, endIt, options, 0); - return (res == KisImageBuilder_RESULT_OK); + return res; } -void KisSpriterExport::parseFolder(KisGroupLayerSP parentGroup, const QString &folderName, const QString &basePath, int *folderId) +KisImportExportErrorCode KisSpriterExport::parseFolder(KisGroupLayerSP parentGroup, const QString &folderName, const QString &basePath, int *folderId) { // qDebug() << "parseFolder: parent" << parentGroup->name() // << "folderName" << folderName // << "basepath" << basePath; int currentFolder=0; if(folderId == 0) { folderId = ¤tFolder; } QString pathName; if (!folderName.isEmpty()) { pathName = folderName + "/"; } + KisNodeSP child = parentGroup->lastChild(); while (child) { if (child->visible() && child->inherits("KisGroupLayer")) { - parseFolder(qobject_cast(child.data()), child->name().split(" ").first(), basePath + "/" + pathName, folderId); + KisImportExportErrorCode res = parseFolder(qobject_cast(child.data()), child->name().split(" ").first(), basePath + "/" + pathName, folderId); + if (!res.isOk()) { + return res; + } } child = child->prevSibling(); } Folder folder; folder.id = *folderId; folder.name = folderName; folder.groupName = parentGroup->name(); int fileId = 0; child = parentGroup->lastChild(); + while (child) { if (child->visible() && !child->inherits("KisGroupLayer") && !child->inherits("KisMask")) { QRectF rc = m_image->bounds().intersected(child->exactBounds()); QString layerBaseName = child->name().split(" ").first(); SpriterFile file; file.id = fileId++; file.pathName = pathName; file.baseName = layerBaseName; file.layerName = child->name(); file.name = folderName + "/" + layerBaseName + ".png"; qreal xmin = rc.left(); qreal ymin = rc.top(); qreal xmax = rc.right(); qreal ymax = rc.bottom(); file.width = xmax - xmin; file.height = ymax - ymin; file.x = xmin; file.y = ymin; //qDebug() << "Created file" << file.id << file.name << file.pathName << file.baseName << file.width << file.height << file.layerName; - - savePaintDevice(child->projection(), basePath + file.name); - folder.files.append(file); + KisImportExportErrorCode result = savePaintDevice(child->projection(), basePath + file.name); + if (result.isOk()) { + folder.files.append(file); + } else { + return result; + } } child = child->prevSibling(); } if (folder.files.size() > 0) { //qDebug() << "Adding folder" << folder.id << folder.name << folder.groupName << folder.files.length(); m_folders.append(folder); (*folderId)++; } + + return ImportExportCodes::OK; } Bone *KisSpriterExport::parseBone(const Bone *parent, KisGroupLayerSP groupLayer) { static int boneId = 0; QString groupBaseName = groupLayer->name().split(" ").first(); Bone *bone = new Bone; bone->id = boneId++; bone->parentBone = parent; bone->name = groupBaseName; if (m_boneLayer) { QRectF rc = m_image->bounds().intersected(m_boneLayer->exactBounds()); qreal xmin = rc.left(); qreal ymin = rc.top(); qreal xmax = rc.right(); qreal ymax = rc.bottom(); bone->x = (xmin + xmax) / 2; bone->y = -(ymin + ymax) / 2; bone->width = xmax - xmin; bone->height = ymax - ymin; } else { bone->x = 0.0; bone->y = 0.0; bone->width = 0.0; bone->height = 0.0; } if (parent) { bone->localX = bone->x - parent->x; bone->localY = bone->y - parent->y; } else { bone->localX = bone->x; bone->localY = bone->y; } bone->localAngle = 0.0; bone->localScaleX = 1.0; bone->localScaleY = 1.0; KisNodeSP child = groupLayer->lastChild(); while (child) { if (child->visible() && child->inherits("KisGroupLayer")) { bone->bones.append(parseBone(bone, qobject_cast(child.data()))); } child = child->prevSibling(); } //qDebug() << "Created bone" << bone->id << "with" << bone->bones.size() << "bones"; return bone; } void copyBone(Bone *startBone) { startBone->fixLocalX = startBone->localX; startBone->fixLocalY = startBone->localY; startBone->fixLocalAngle = startBone->localAngle; startBone->fixLocalScaleX= startBone->localScaleX; startBone->fixLocalScaleY= startBone->localScaleY; Q_FOREACH(Bone *child, startBone->bones) { copyBone(child); } } void KisSpriterExport::fixBone(Bone *bone) { qreal boneLocalAngle = 0; qreal boneLocalScaleX = 1; if (bone->bones.length() >= 1) { // if a bone has one or more children, point at first child Bone *childBone = bone->bones[0]; qreal dx = childBone->x - bone->x; qreal dy = childBone->y - bone->y; if (qAbs(dx) > 0 || qAbs(dy) > 0) { boneLocalAngle = KisFastMath::atan2(dy, dx); boneLocalScaleX = sqrt(dx * dx + dy * dy) / 200; } } else if (bone->parentBone) { // else, if bone has parent, point away from parent qreal dx = bone->x - bone->parentBone->x; qreal dy = bone->y - bone->parentBone->y; if (qAbs(dx) > 0 || qAbs(dy) > 0) { boneLocalAngle = KisFastMath::atan2(dy, dx); boneLocalScaleX = sqrt(dx * dx + dy * dy) / 200; } } // adjust bone angle bone->fixLocalAngle += boneLocalAngle; bone->fixLocalScaleX *= boneLocalScaleX; // rotate all the child bones back to world position for (int i = 0; i < bone->bones.length(); ++i) { Bone *childBone = bone->bones[i]; qreal tx = childBone->fixLocalX; qreal ty = childBone->fixLocalY; childBone->fixLocalX = tx * cos(-boneLocalAngle) - ty * sin(-boneLocalAngle); childBone->fixLocalY = tx * sin(-boneLocalAngle) + ty * cos(-boneLocalAngle); childBone->fixLocalX /= boneLocalScaleX; childBone->fixLocalAngle -= boneLocalAngle; childBone->fixLocalScaleX /= boneLocalScaleX; } // rotate all the child objects back to world position for (int i = 0; i < m_objects.length(); ++i) { if (m_objects[i].bone == bone) { m_objects[i].fixLocalAngle -= boneLocalAngle; m_objects[i].fixLocalScaleX /= boneLocalScaleX; } } // process all child bones for (int i = 0; i < bone->bones.length(); ++i) { fixBone(bone->bones[i]); } } void KisSpriterExport::writeBoneRef(const Bone *bone, QDomElement &key, QDomDocument &scml) { if (!bone) return; QDomElement boneRef = scml.createElement("bone_ref"); key.appendChild(boneRef); boneRef.setAttribute("id", bone->id); if (bone->parentBone) { boneRef.setAttribute("parent", bone->parentBone->id); } boneRef.setAttribute("timeline", m_timelineid++); boneRef.setAttribute("key", "0"); Q_FOREACH(const Bone *childBone, bone->bones) { writeBoneRef(childBone, key, scml); } } void KisSpriterExport::writeBone(const Bone *bone, QDomElement &animation, QDomDocument &scml) { if (!bone) return; QDomElement timeline = scml.createElement("timeline"); animation.appendChild(timeline); timeline.setAttribute("id", m_timelineid); timeline.setAttribute("name", bone->name); timeline.setAttribute("object_type", "bone"); QDomElement key = scml.createElement("key"); timeline.appendChild(key); key.setAttribute("id", "0"); key.setAttribute("spin", 0); QDomElement boneEl = scml.createElement("bone"); key.appendChild(boneEl); boneEl.setAttribute("x", QString::number(bone->fixLocalX, 'f', 2)); boneEl.setAttribute("y", QString::number(bone->fixLocalY, 'f', 2)); boneEl.setAttribute("angle", QString::number(bone->fixLocalAngle, 'f', 2)); boneEl.setAttribute("scale_x", QString::number(bone->fixLocalScaleX, 'f', 2)); boneEl.setAttribute("scale_y", QString::number(bone->fixLocalScaleY, 'f', 2)); m_timelineid++; Q_FOREACH(const Bone *childBone, bone->bones) { writeBone(childBone, animation, scml); } } void KisSpriterExport::fillScml(QDomDocument &scml, const QString &entityName) { //qDebug() << "Creating scml" << entityName; QDomElement root = scml.createElement("spriter_data"); scml.appendChild(root); root.setAttribute("scml_version", 1); root.setAttribute("generator", "krita"); root.setAttribute("generator_version", qApp->applicationVersion()); Q_FOREACH(const Folder &folder, m_folders) { QDomElement fe = scml.createElement("folder"); root.appendChild(fe); fe.setAttribute("id", folder.id); fe.setAttribute("name", folder.name); Q_FOREACH(const SpriterFile &file, folder.files) { QDomElement fileElement = scml.createElement("file"); fe.appendChild(fileElement); fileElement.setAttribute("id", file.id); fileElement.setAttribute("name", file.name); fileElement.setAttribute("width", QString::number(file.width, 'f', 2)); fileElement.setAttribute("height", QString::number(file.height, 'f', 2)); // qreal pivotX=0; // qreal pivotY=1; // Q_FOREACH(const SpriterObject &object, m_objects) { // if(file.id == object.fileId) // { // pivotX = (0.0 -(object.fixLocalX / file.width)); // pivotY = (1.0 -(object.fixLocalY / file.height)); // break; // } // } // fileElement.setAttribute("pivot_x", QString::number(pivotX, 'f', 2)); // fileElement.setAttribute("pivot_y", QString::number(pivotY, 'f', 2)); } } // entity QDomElement entity = scml.createElement("entity"); root.appendChild(entity); entity.setAttribute("id", "0"); entity.setAttribute("name", entityName); // entity/animation QDomElement animation = scml.createElement("animation"); entity.appendChild(animation); animation.setAttribute("id", "0"); animation.setAttribute("name", "default"); animation.setAttribute("length", "1000"); animation.setAttribute("looping", "false"); // entity/animation/mainline QDomElement mainline = scml.createElement("mainline"); animation.appendChild(mainline); QDomElement key = scml.createElement("key"); mainline.appendChild(key); key.setAttribute("id", "0"); m_timelineid = 0; writeBoneRef(m_rootBone, key, scml); Q_FOREACH(const SpriterObject &object, m_objects) { QDomElement oe = scml.createElement("object_ref"); key.appendChild(oe); oe.setAttribute("id", object.id); if (object.bone) { oe.setAttribute("parent", object.bone->id); } oe.setAttribute("timeline", m_timelineid++); oe.setAttribute("key", "0"); oe.setAttribute("z_index", object.id); } // entity/animation/timeline m_timelineid = 0; if (m_rootBone) { writeBone(m_rootBone, animation, scml); } Q_FOREACH(const SpriterObject &object, m_objects) { Folder folder; Q_FOREACH(const Folder & f, m_folders) { if (f.id == object.folderId) { folder = f; break; } } SpriterFile file; file.id = -1; Q_FOREACH(const SpriterFile &f, folder.files) { if (f.id == object.fileId) { file = f; break; } } Q_ASSERT(file.id >= 0); QString objectName = "object-" + file.baseName; QDomElement timeline = scml.createElement("timeline"); animation.appendChild(timeline); timeline.setAttribute("id", m_timelineid++); timeline.setAttribute("name", objectName); QDomElement key = scml.createElement("key"); timeline.appendChild(key); key.setAttribute("id", "0"); key.setAttribute("spin", "0"); QDomElement objectEl = scml.createElement("object"); key.appendChild(objectEl); objectEl.setAttribute("folder", object.folderId); objectEl.setAttribute("file", object.fileId); objectEl.setAttribute("x", object.fixLocalX); objectEl.setAttribute("y", object.fixLocalY); objectEl.setAttribute("angle", QString::number(kisRadiansToDegrees(object.fixLocalAngle), 'f', 2)); objectEl.setAttribute("scale_x", QString::number(object.fixLocalScaleX, 'f', 2)); objectEl.setAttribute("scale_y", QString::number(object.fixLocalScaleY, 'f', 2)); } } Bone *findBoneByName(Bone *startBone, const QString &name) { if (!startBone) return 0; //qDebug() << "findBoneByName" << name << "starting with" << startBone->name; if (startBone->name == name) { return startBone; } Q_FOREACH(Bone *child, startBone->bones) { //qDebug() << "looking for" << name << "found" << child->name; if (child->name == name) { return child; } Bone *grandChild = findBoneByName(child, name); if (grandChild){ return grandChild; } } return 0; } -KisImportExportFilter::ConversionStatus KisSpriterExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisSpriterExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { QFileInfo fi(filename()); m_image = document->savingImage(); if (m_image->rootLayer()->childCount() == 0) { - return KisImportExportFilter::UsageError; + return ImportExportCodes::Failure; } KisGroupLayerSP root = m_image->rootLayer(); m_boneLayer = qobject_cast(root->findChildByName("bone").data()); //qDebug() << "Found boneLayer" << m_boneLayer; m_rootLayer= qobject_cast(root->findChildByName("root").data()); //qDebug() << "Fond rootLayer" << m_rootLayer; - parseFolder(m_image->rootLayer(), "", fi.absolutePath()); + KisImportExportErrorCode result = parseFolder(m_image->rootLayer(), "", fi.absolutePath()); + if (!result.isOk()) { + dbgFile << "There were errors encountered while using the spriter exporter."; + return result; + } m_rootBone = 0; if (m_rootLayer) { m_rootBone = parseBone(0, m_rootLayer); } // Generate objects int objectId = 0; for (int folderIndex = 0, folderCount = m_folders.size(); folderIndex < folderCount; ++folderIndex) { Folder folder = m_folders[folderCount - 1 - folderIndex]; for (int fileIndex = 0, fileCount = folder.files.size(); fileIndex < fileCount; ++ fileIndex) { SpriterFile file = folder.files[fileCount - 1 - fileIndex]; SpriterObject spriterObject; spriterObject.id = objectId++; spriterObject.folderId = folder.id; spriterObject.fileId = file.id; spriterObject.x = file.x; spriterObject.y = -file.y; Bone *bone = 0; //qDebug() << "file layername" << file.layerName; // layer.name format: "base_name bone(bone_name) slot(slot_name)" if (file.layerName.contains("bone(")) { int start = file.layerName.indexOf("bone(") + 5; int end = file.layerName.indexOf(')', start); QString boneName = file.layerName.mid(start, end - start); bone = findBoneByName(m_rootBone, boneName); } // layer.name format: "base_name" if (!bone && m_rootBone) { bone = findBoneByName(m_rootBone, file.layerName); } // group.name format: "base_name bone(bone_name)" if (!bone && m_rootBone) { if (folder.groupName.contains("bone(")) { int start = folder.groupName.indexOf("bone(") + 5; int end = folder.groupName.indexOf(')', start); QString boneName = folder.groupName.mid(start, end - start); bone = findBoneByName(m_rootBone, boneName); } // group.name format: "base_name" if (!bone) { bone = findBoneByName(m_rootBone, folder.groupName); } } if (!bone) { bone = m_rootBone; } if (bone) { spriterObject.bone = bone; spriterObject.localX = spriterObject.x - bone->x; spriterObject.localY = spriterObject.y - bone->y; } else { spriterObject.bone = 0; spriterObject.localX = spriterObject.x; spriterObject.localY = spriterObject.y; } spriterObject.localAngle = 0; spriterObject.localScaleX = 1.0; spriterObject.localScaleY = 1.0; SpriterSlot *slot = 0; // layer.name format: "base_name bone(bone_name) slot(slot_name)" if (file.layerName.contains("slot(")) { int start = file.layerName.indexOf("slot(") + 5; int end = file.layerName.indexOf(')', start); slot = new SpriterSlot(); slot->name = file.layerName.mid(start, end - start); slot->defaultAttachmentFlag = file.layerName.contains("*"); } spriterObject.slot = slot; // qDebug() << "Created object" << spriterObject.id << spriterObject.folderId // << spriterObject.fileId << spriterObject.x << spriterObject.y // << spriterObject.localX << spriterObject.localY; m_objects.append(spriterObject); } } // Copy object transforms for (int i = 0; i < m_objects.size(); ++i) { m_objects[i].fixLocalX = m_objects[i].localX; m_objects[i].fixLocalY = m_objects[i].localY; m_objects[i].fixLocalAngle = m_objects[i].localAngle; m_objects[i].fixLocalScaleX = m_objects[i].localScaleX; m_objects[i].fixLocalScaleY = m_objects[i].localScaleY; } // Calculate bone angles if (m_rootBone) { copyBone(m_rootBone); fixBone(m_rootBone); } // Generate scml QDomDocument scml; fillScml(scml, fi.baseName()); - io->write("\n"); - io->write(scml.toString(4).toUtf8()); + bool openedHere = false; + if (!io->isOpen()) { + openedHere = io->open(QIODevice::WriteOnly); + if (!openedHere) { + // unsuccessful open + return ImportExportCodes::NoAccessToWrite; + } + } + + QString towrite = "\n"; + if (io->write(towrite.toUtf8()) != towrite.length()) { + return ImportExportCodes::ErrorWhileWriting; + } + towrite = scml.toString(4).toUtf8(); + if (io->write(towrite.toUtf8()) != towrite.length()) { + return ImportExportCodes::ErrorWhileWriting; + } delete m_rootBone; - return KisImportExportFilter::OK; + if (openedHere) { + // FIXME: casues crash... + //io->close(); + } + + return ImportExportCodes::OK; } void KisSpriterExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "Spriter"); } #include "kis_spriter_export.moc" diff --git a/plugins/impex/spriter/kis_spriter_export.h b/plugins/impex/spriter/kis_spriter_export.h index 5d3c32c3b3..30e123ac76 100644 --- a/plugins/impex/spriter/kis_spriter_export.h +++ b/plugins/impex/spriter/kis_spriter_export.h @@ -1,135 +1,135 @@ /* * Copyright (c) 2016 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. */ #ifndef _KIS_SPRITER_EXPORT_H_ #define _KIS_SPRITER_EXPORT_H_ #include #include #include #include #include struct SpriterFile { qreal id; QString name; QString pathName; QString baseName; QString layerName; qreal width; qreal height; qreal x; qreal y; }; struct Folder { qreal id; QString name; QString pathName; QString baseName; QString groupName; QList files; }; struct Bone { qreal id; const Bone *parentBone; QString name; qreal x; qreal y; qreal width; qreal height; qreal localX; qreal localY; qreal localAngle; qreal localScaleX; qreal localScaleY; qreal fixLocalX; qreal fixLocalY; qreal fixLocalAngle; qreal fixLocalScaleX; qreal fixLocalScaleY; QList bones; ~Bone() { qDeleteAll(bones); bones.clear(); } }; struct SpriterSlot { QString name; bool defaultAttachmentFlag = false; }; struct SpriterObject { qreal id; qreal folderId; qreal fileId; Bone *bone; SpriterSlot *slot; qreal x; qreal y; qreal localX; qreal localY; qreal localAngle; qreal localScaleX; qreal localScaleY; qreal fixLocalX; qreal fixLocalY; qreal fixLocalAngle; qreal fixLocalScaleX; qreal fixLocalScaleY; ~SpriterObject() { delete slot; } }; class KisSpriterExport : public KisImportExportFilter { Q_OBJECT public: KisSpriterExport(QObject *parent, const QVariantList &); ~KisSpriterExport() override; bool supportsIO() const override { return false; } - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; private: - bool savePaintDevice(KisPaintDeviceSP dev, const QString &fileName); - void parseFolder(KisGroupLayerSP parentGroup, const QString &folderName, const QString &basePath, int *folderId = 0); + KisImportExportErrorCode savePaintDevice(KisPaintDeviceSP dev, const QString &fileName); + KisImportExportErrorCode parseFolder(KisGroupLayerSP parentGroup, const QString &folderName, const QString &basePath, int *folderId = 0); Bone *parseBone(const Bone *parent, KisGroupLayerSP groupLayer); void fixBone(Bone *bone); void fillScml(QDomDocument &scml, const QString &entityName); void writeBoneRef(const Bone *bone, QDomElement &mainline, QDomDocument &scml); void writeBone(const Bone *bone, QDomElement &timeline, QDomDocument &scml); KisImageSP m_image; qreal m_timelineid; QList m_folders; Bone *m_rootBone; QList m_objects; KisGroupLayerSP m_rootLayer; // Not the image's root later, but the one that is named "root" KisLayerSP m_boneLayer; }; #endif diff --git a/plugins/impex/svg/kis_svg_import.cc b/plugins/impex/svg/kis_svg_import.cc index 86c8ae1cca..bf41af28ca 100644 --- a/plugins/impex/svg/kis_svg_import.cc +++ b/plugins/impex/svg/kis_svg_import.cc @@ -1,103 +1,103 @@ /* * Copyright (c) 2016 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_svg_import.h" #include #include #include "kis_config.h" #include #include #include #include #include #include "kis_shape_layer.h" #include K_PLUGIN_FACTORY_WITH_JSON(SVGImportFactory, "krita_svg_import.json", registerPlugin();) KisSVGImport::KisSVGImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisSVGImport::~KisSVGImport() { } -KisImportExportFilter::ConversionStatus KisSVGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisSVGImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { Q_UNUSED(configuration); KisDocument * doc = document; const QString baseXmlDir = QFileInfo(filename()).canonicalPath(); KisConfig cfg(false); qreal resolutionPPI = cfg.preferredVectorImportResolutionPPI(true); if (!batchMode()) { bool okay = false; const QString name = QFileInfo(filename()).fileName(); resolutionPPI = QInputDialog::getInt(0, i18n("Import SVG"), i18n("Enter preferred resolution (PPI) for \"%1\"", name), cfg.preferredVectorImportResolutionPPI(), 0, 100000, 1, &okay); if (!okay) { - return KisImportExportFilter::UserCancelled; + return ImportExportCodes::Cancelled; } cfg.setPreferredVectorImportResolutionPPI(resolutionPPI); } const qreal resolution = resolutionPPI / 72.0; QSizeF fragmentSize; QList shapes = KisShapeLayer::createShapesFromSvg(io, baseXmlDir, QRectF(0,0,1200,800), resolutionPPI, doc->shapeController()->resourceManager(), &fragmentSize); QRectF rawImageRect(QPointF(), fragmentSize); QRect imageRect(rawImageRect.toAlignedRect()); const KoColorSpace* cs = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(doc->createUndoStore(), imageRect.width(), imageRect.height(), cs, "svg image"); image->setResolution(resolution, resolution); doc->setCurrentImage(image); KisShapeLayerSP shapeLayer = new KisShapeLayer(doc->shapeController(), image, i18n("Vector Layer"), OPACITY_OPAQUE_U8); Q_FOREACH (KoShape *shape, shapes) { shapeLayer->addShape(shape); } image->addNode(shapeLayer); - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include diff --git a/plugins/impex/svg/kis_svg_import.h b/plugins/impex/svg/kis_svg_import.h index a4d185566e..31e725a151 100644 --- a/plugins/impex/svg/kis_svg_import.h +++ b/plugins/impex/svg/kis_svg_import.h @@ -1,36 +1,36 @@ /* * Copyright (c) 2016 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 _KIS_SVG_IMPORT_H_ #define _KIS_SVG_IMPORT_H_ #include #include class KisSVGImport : public KisImportExportFilter { Q_OBJECT public: KisSVGImport(QObject *parent, const QVariantList &); ~KisSVGImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) override; }; #endif diff --git a/plugins/impex/svg/tests/data/incorrectFormatFile.txt b/plugins/impex/svg/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/svg/tests/data/readonlyFile.txt b/plugins/impex/svg/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/svg/tests/data/writeonlyFile.txt b/plugins/impex/svg/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/svg/tests/kis_svg_test.cpp index 6ca66688f8..b407b77a44 100644 --- a/plugins/impex/svg/tests/kis_svg_test.cpp +++ b/plugins/impex/svg/tests/kis_svg_test.cpp @@ -1,40 +1,57 @@ /* * Copyright (C) 2007 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_svg_test.h" #include #include #include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif +const QString SvgMimetype = "image/svg+xml"; + + void KisSvgTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50); } -KISTEST_MAIN(KisSvgTest) + +void KisSvgTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), SvgMimetype); +} + + + +void KisSvgTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), SvgMimetype); +} + + +KISTEST_MAIN(KisSvgTest) diff --git a/plugins/impex/svg/tests/kis_svg_test.h b/plugins/impex/svg/tests/kis_svg_test.h index 66cf670f4d..e8e481ded8 100644 --- a/plugins/impex/svg/tests/kis_svg_test.h +++ b/plugins/impex/svg/tests/kis_svg_test.h @@ -1,31 +1,34 @@ /* * Copyright (C) 2007 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. */ #ifndef _KIS_SVG_TEST_H_ #define _KIS_SVG_TEST_H_ #include class KisSvgTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); + + void testImportFromWriteonly(); + void testImportIncorrectFormat(); }; #endif diff --git a/plugins/impex/tga/CMakeLists.txt b/plugins/impex/tga/CMakeLists.txt index a9546c3326..b7fe804897 100644 --- a/plugins/impex/tga/CMakeLists.txt +++ b/plugins/impex/tga/CMakeLists.txt @@ -1,24 +1,26 @@ +add_subdirectory(tests) + set(kritatgaexport_SOURCES kis_tga_export.cpp ) ki18n_wrap_ui(kritatgaexport_SOURCES ) add_library(kritatgaexport MODULE ${kritatgaexport_SOURCES}) target_link_libraries(kritatgaexport kritaui kritaimpex) install(TARGETS kritatgaexport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) set(kritatgaimport_SOURCES kis_tga_import.cpp ) ki18n_wrap_ui(kritatgaimport_SOURCES ) add_library(kritatgaimport MODULE ${kritatgaimport_SOURCES}) target_link_libraries(kritatgaimport kritaui) install(TARGETS kritatgaimport DESTINATION ${KRITA_PLUGIN_INSTALL_DIR}) install( PROGRAMS krita_tga.desktop DESTINATION ${XDG_APPS_INSTALL_DIR}) diff --git a/plugins/impex/tga/kis_tga_export.cpp b/plugins/impex/tga/kis_tga_export.cpp index ac79779c1f..3d8728e4f3 100644 --- a/plugins/impex/tga/kis_tga_export.cpp +++ b/plugins/impex/tga/kis_tga_export.cpp @@ -1,94 +1,94 @@ /* * Copyright (c) 2007 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_tga_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "tga.h" K_PLUGIN_FACTORY_WITH_JSON(KisTGAExportFactory, "krita_tga_export.json", registerPlugin();) KisTGAExport::KisTGAExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisTGAExport::~KisTGAExport() { } -KisImportExportFilter::ConversionStatus KisTGAExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisTGAExport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { Q_UNUSED(configuration); QRect rc = document->savingImage()->bounds(); QImage image = document->savingImage()->projection()->convertToQImage(0, 0, 0, rc.width(), rc.height(), KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); QDataStream s(io); s.setByteOrder(QDataStream::LittleEndian); const QImage& img = image; const bool hasAlpha = (img.format() == QImage::Format_ARGB32); for (int i = 0; i < 12; i++) s << targaMagic[i]; // write header s << quint16(img.width()); // width s << quint16(img.height()); // height s << quint8(hasAlpha ? 32 : 24); // depth (24 bit RGB + 8 bit alpha) s << quint8(hasAlpha ? 0x24 : 0x20); // top left image (0x20) + 8 bit alpha (0x4) for (int y = 0; y < img.height(); y++) { for (int x = 0; x < img.width(); x++) { const QRgb color = img.pixel(x, y); s << quint8(qBlue(color)); s << quint8(qGreen(color)); s << quint8(qRed(color)); if (hasAlpha) s << quint8(qAlpha(color)); } } - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } void KisTGAExport::initializeCapabilities() { QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID); addSupportedColorModels(supportedColorModels, "TGA"); } #include "kis_tga_export.moc" diff --git a/plugins/impex/tga/kis_tga_export.h b/plugins/impex/tga/kis_tga_export.h index a10414659e..8faa01d65e 100644 --- a/plugins/impex/tga/kis_tga_export.h +++ b/plugins/impex/tga/kis_tga_export.h @@ -1,38 +1,38 @@ /* * Copyright (c) 2007 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. */ #ifndef _KIS_TGA_EXPORT_H_ #define _KIS_TGA_EXPORT_H_ #include #include class KisTGAExport : public KisImportExportFilter { Q_OBJECT public: KisTGAExport(QObject *parent, const QVariantList &); ~KisTGAExport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/tga/kis_tga_import.cpp b/plugins/impex/tga/kis_tga_import.cpp index e78c4590b4..860f75237d 100644 --- a/plugins/impex/tga/kis_tga_import.cpp +++ b/plugins/impex/tga/kis_tga_import.cpp @@ -1,291 +1,291 @@ /* * Copyright (c) 2007 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_tga_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KisTGAImportFactory, "krita_tga_import.json", registerPlugin();) KisTGAImport::KisTGAImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisTGAImport::~KisTGAImport() { } static QDataStream & operator>> (QDataStream & s, TgaHeader & head) { s >> head.id_length; s >> head.colormap_type; s >> head.image_type; s >> head.colormap_index; s >> head.colormap_length; s >> head.colormap_size; s >> head.x_origin; s >> head.y_origin; s >> head.width; s >> head.height; s >> head.pixel_size; s >> head.flags; /*dbgKrita << "id_length: " << head.id_length << " - colormap_type: " << head.colormap_type << " - image_type: " << head.image_type; dbgKrita << "colormap_index: " << head.colormap_index << " - colormap_length: " << head.colormap_length << " - colormap_size: " << head.colormap_size; dbgKrita << "x_origin: " << head.x_origin << " - y_origin: " << head.y_origin << " - width:" << head.width << " - height:" << head.height << " - pixelsize: " << head.pixel_size << " - flags: " << head.flags;*/ return s; } static bool isSupported(const TgaHeader & head) { if (head.image_type != TGA_TYPE_INDEXED && head.image_type != TGA_TYPE_RGB && head.image_type != TGA_TYPE_GREY && head.image_type != TGA_TYPE_RLE_INDEXED && head.image_type != TGA_TYPE_RLE_RGB && head.image_type != TGA_TYPE_RLE_GREY) { return false; } if (head.image_type == TGA_TYPE_INDEXED || head.image_type == TGA_TYPE_RLE_INDEXED) { if (head.colormap_length > 256 || head.colormap_size != 24 || head.colormap_type != 1) { return false; } } if (head.image_type == TGA_TYPE_RGB || head.image_type == TGA_TYPE_GREY || head.image_type == TGA_TYPE_RLE_RGB || head.image_type == TGA_TYPE_RLE_GREY) { if (head.colormap_type != 0) { return false; } } if (head.width == 0 || head.height == 0) { return false; } if (head.pixel_size != 8 && head.pixel_size != 16 && head.pixel_size != 24 && head.pixel_size != 32) { return false; } return true; } static bool loadTGA(QDataStream & s, const TgaHeader & tga, QImage &img) { // Create image. img = QImage(tga.width, tga.height, QImage::Format_RGB32); TgaHeaderInfo info(tga); /** * Theoretically, we should check alpha presence via the bits * in flags, but there are a lot of files in the wild that * have this flag unset. It contradicts TGA specification, * but we cannot do anything about it. */ const bool hasAlpha = tga.flags & 0xf; if (tga.pixel_size == 32 && !hasAlpha) { qWarning() << "WARNING: TGA image with 32-bit pixel size reports absence of alpha channel. It is not possible, fixing..."; } if (tga.pixel_size == 32 || tga.pixel_size == 16) { img = QImage(tga.width, tga.height, QImage::Format_ARGB32); } uint pixel_size = (tga.pixel_size / 8); uint size = tga.width * tga.height * pixel_size; if (size < 1) { dbgFile << "This TGA file is broken with size " << size; return false; } // Read palette. char palette[768]; if (info.pal) { // @todo Support palettes in other formats! s.readRawData(palette, 3 * tga.colormap_length); } // Allocate image. uchar * const image = new uchar[size]; if (info.rle) { // Decode image. char * dst = (char *)image; int num = size; while (num > 0) { // Get packet header. uchar c; s >> c; uint count = (c & 0x7f) + 1; num -= count * pixel_size; if (c & 0x80) { // RLE pixels. Q_ASSERT(pixel_size <= 8); char pixel[8]; s.readRawData(pixel, pixel_size); do { memcpy(dst, pixel, pixel_size); dst += pixel_size; } while (--count); } else { // Raw pixels. count *= pixel_size; s.readRawData(dst, count); dst += count; } } } else { // Read raw image. s.readRawData((char *)image, size); } // Convert image to internal format. int y_start, y_step, y_end; if (tga.flags & TGA_ORIGIN_UPPER) { y_start = 0; y_step = 1; y_end = tga.height; } else { y_start = tga.height - 1; y_step = -1; y_end = -1; } uchar* src = image; for (int y = y_start; y != y_end; y += y_step) { QRgb * scanline = (QRgb *) img.scanLine(y); if (info.pal) { // Paletted. for (int x = 0; x < tga.width; x++) { uchar idx = *src++; scanline[x] = qRgb(palette[3 * idx + 2], palette[3 * idx + 1], palette[3 * idx + 0]); } } else if (info.grey) { // Greyscale. for (int x = 0; x < tga.width; x++) { scanline[x] = qRgb(*src, *src, *src); src++; } } else { // True Color. if (tga.pixel_size == 16) { for (int x = 0; x < tga.width; x++) { Color555 c = *reinterpret_cast(src); scanline[x] = qRgb((c.r << 3) | (c.r >> 2), (c.g << 3) | (c.g >> 2), (c.b << 3) | (c.b >> 2)); src += 2; } } else if (tga.pixel_size == 24) { for (int x = 0; x < tga.width; x++) { scanline[x] = qRgb(src[2], src[1], src[0]); src += 3; } } else if (tga.pixel_size == 32) { for (int x = 0; x < tga.width; x++) { const uchar alpha = src[3]; scanline[x] = qRgba(src[2], src[1], src[0], alpha); src += 4; } } } } // Free image. delete []image; return true; } -KisImportExportFilter::ConversionStatus KisTGAImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisTGAImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration) { Q_UNUSED(configuration); QDataStream s(io); s.setByteOrder(QDataStream::LittleEndian); TgaHeader tga; s >> tga; s.device()->seek(TgaHeader::SIZE + tga.id_length); // Check image file format. if (s.atEnd()) { - return KisImportExportFilter::InvalidFormat; + return ImportExportCodes::FileFormatIncorrect; } // Check supported file types. if (!isSupported(tga)) { - return KisImportExportFilter::InvalidFormat; + return ImportExportCodes::FileFormatIncorrect; } QImage img; bool result = loadTGA(s, tga, img); if (result == false) { - return KisImportExportFilter::CreationError; + return ImportExportCodes::FileFormatIncorrect; } const KoColorSpace *colorSpace = KoColorSpaceRegistry::instance()->rgb8(); KisImageSP image = new KisImage(document->createUndoStore(), img.width(), img.height(), colorSpace, "imported from tga"); KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), 255); layer->paintDevice()->convertFromQImage(img, 0, 0, 0); image->addNode(layer.data(), image->rootLayer().data()); document->setCurrentImage(image); - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include "kis_tga_import.moc" diff --git a/plugins/impex/tga/kis_tga_import.h b/plugins/impex/tga/kis_tga_import.h index d8ea64383d..a3869bd16d 100644 --- a/plugins/impex/tga/kis_tga_import.h +++ b/plugins/impex/tga/kis_tga_import.h @@ -1,37 +1,37 @@ /* * Copyright (c) 2007 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. */ #ifndef _KIS_TGA_IMPORT_H_ #define _KIS_TGA_IMPORT_H_ #include #include class KisTGAImport : public KisImportExportFilter { Q_OBJECT public: KisTGAImport(QObject *parent, const QVariantList &); ~KisTGAImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/tga/tests/CMakeLists.txt b/plugins/impex/tga/tests/CMakeLists.txt new file mode 100644 index 0000000000..7180181703 --- /dev/null +++ b/plugins/impex/tga/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} ) +include_directories( ${CMAKE_SOURCE_DIR}/sdk/tests ) + +include(KritaAddBrokenUnitTest) + +macro_add_unittest_definitions() + +ecm_add_test(KisTgaTest.cpp + TEST_NAME KisTgaTest + LINK_LIBRARIES kritaui Qt5::Test + NAME_PREFIX "plugins-impex-") diff --git a/plugins/impex/svg/tests/kis_svg_test.cpp b/plugins/impex/tga/tests/KisTgaTest.cpp similarity index 64% copy from plugins/impex/svg/tests/kis_svg_test.cpp copy to plugins/impex/tga/tests/KisTgaTest.cpp index 6ca66688f8..b3b83d1c05 100644 --- a/plugins/impex/svg/tests/kis_svg_test.cpp +++ b/plugins/impex/tga/tests/KisTgaTest.cpp @@ -1,40 +1,57 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * 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_svg_test.h" +#include "KisTgaTest.h" #include #include -#include - #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif -void KisSvgTest::testFiles() +const QString TgaMimetype = "image/x-gimp-brush"; + + + +void KisTgaTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), TgaMimetype); +} + + +void KisTgaTest::testExportToReadonly() { - TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 30, 50); + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), TgaMimetype); } -KISTEST_MAIN(KisSvgTest) + +void KisTgaTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), TgaMimetype); +} + + + +KISTEST_MAIN(KisTgaTest) + diff --git a/plugins/impex/jpeg/tests/kis_jpeg_test.h b/plugins/impex/tga/tests/KisTgaTest.h similarity index 74% copy from plugins/impex/jpeg/tests/kis_jpeg_test.h copy to plugins/impex/tga/tests/KisTgaTest.h index 5c19b0cda6..172bf40374 100644 --- a/plugins/impex/jpeg/tests/kis_jpeg_test.h +++ b/plugins/impex/tga/tests/KisTgaTest.h @@ -1,31 +1,35 @@ /* - * Copyright (C) 2007 Cyrille Berger + * Copyright (C) 2019 Agata Cacko * * 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_JPEG_TEST_H_ -#define _KIS_JPEG_TEST_H_ +#ifndef _KIS_TGA_TEST_H_ +#define _KIS_TGA_TEST_H_ #include -class KisJpegTest : public QObject +class KisTgaTest : public QObject { Q_OBJECT private Q_SLOTS: - void testFiles(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; -#endif +#endif // _KIS_TGA_TEST_H_ + diff --git a/plugins/impex/tga/tests/data/incorrectFormatFile.txt b/plugins/impex/tga/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/tga/tests/data/readonlyFile.txt b/plugins/impex/tga/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/tga/tests/data/writeonlyFile.txt b/plugins/impex/tga/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/tiff/kis_tiff_converter.cc b/plugins/impex/tiff/kis_tiff_converter.cc index dc873404ab..12806cb7f6 100644 --- a/plugins/impex/tiff/kis_tiff_converter.cc +++ b/plugins/impex/tiff/kis_tiff_converter.cc @@ -1,747 +1,770 @@ /* * Copyright (c) 2005-2006 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_tiff_converter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_tiff_reader.h" #include "kis_tiff_ycbcr_reader.h" #include "kis_buffer_stream.h" #include "kis_tiff_writer_visitor.h" +#include + #if TIFFLIB_VERSION < 20111221 typedef size_t tmsize_t; #endif namespace { QPair getColorSpaceForColorType(uint16 sampletype, uint16 color_type, uint16 color_nb_bits, TIFF *image, uint16 &nbchannels, uint16 &extrasamplescount, uint8 &destDepth) { if (color_type == PHOTOMETRIC_MINISWHITE || color_type == PHOTOMETRIC_MINISBLACK) { if (nbchannels == 0) nbchannels = 1; extrasamplescount = nbchannels - 1; // FIX the extrasamples count in case of if (sampletype == SAMPLEFORMAT_IEEEFP) { if (color_nb_bits == 16) { destDepth = 16; return QPair(GrayAColorModelID.id(), Float16BitsColorDepthID.id()); } else if (color_nb_bits == 32) { destDepth = 32; return QPair(GrayAColorModelID.id(), Float32BitsColorDepthID.id()); } } if (color_nb_bits <= 8) { destDepth = 8; return QPair(GrayAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(GrayAColorModelID.id(), Integer16BitsColorDepthID.id()); } } else if (color_type == PHOTOMETRIC_RGB /*|| color_type == */) { if (nbchannels == 0) nbchannels = 3; extrasamplescount = nbchannels - 3; // FIX the extrasamples count in case of if (sampletype == SAMPLEFORMAT_IEEEFP) { if (color_nb_bits == 16) { destDepth = 16; return QPair(RGBAColorModelID.id(), Float16BitsColorDepthID.id()); } else if (color_nb_bits == 32) { destDepth = 32; return QPair(RGBAColorModelID.id(), Float32BitsColorDepthID.id()); } return QPair(); } else { if (color_nb_bits <= 8) { destDepth = 8; return QPair(RGBAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(RGBAColorModelID.id(), Integer16BitsColorDepthID.id()); } } } else if (color_type == PHOTOMETRIC_YCBCR) { if (nbchannels == 0) nbchannels = 3; extrasamplescount = nbchannels - 3; // FIX the extrasamples count in case of if (color_nb_bits <= 8) { destDepth = 8; return QPair(YCbCrAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(YCbCrAColorModelID.id(), Integer16BitsColorDepthID.id()); } } else if (color_type == PHOTOMETRIC_SEPARATED) { if (nbchannels == 0) nbchannels = 4; // SEPARATED is in general CMYK but not always, so we check uint16 inkset; if ((TIFFGetField(image, TIFFTAG_INKSET, &inkset) == 0)) { dbgFile << "Image does not define the inkset."; inkset = 2; } if (inkset != INKSET_CMYK) { dbgFile << "Unsupported inkset (right now, only CMYK is supported)"; char** ink_names; uint16 numberofinks; if (TIFFGetField(image, TIFFTAG_INKNAMES, &ink_names) == 1 && TIFFGetField(image, TIFFTAG_NUMBEROFINKS, &numberofinks) == 1) { dbgFile << "Inks are :"; for (uint i = 0; i < numberofinks; i++) { dbgFile << ink_names[i]; } } else { dbgFile << "inknames are not defined !"; // To be able to read stupid adobe files, if there are no information about inks and four channels, then it's a CMYK file : if (nbchannels - extrasamplescount != 4) { return QPair(); } } } if (color_nb_bits <= 8) { destDepth = 8; return QPair(CMYKAColorModelID.id(), Integer8BitsColorDepthID.id()); } else { destDepth = 16; return QPair(CMYKAColorModelID.id(), Integer16BitsColorDepthID.id()); } } else if (color_type == PHOTOMETRIC_CIELAB || color_type == PHOTOMETRIC_ICCLAB) { destDepth = 16; if (nbchannels == 0) nbchannels = 3; extrasamplescount = nbchannels - 3; // FIX the extrasamples count return QPair(LABAColorModelID.id(), Integer16BitsColorDepthID.id()); } else if (color_type == PHOTOMETRIC_PALETTE) { destDepth = 16; if (nbchannels == 0) nbchannels = 2; extrasamplescount = nbchannels - 2; // FIX the extrasamples count // <-- we will convert the index image to RGBA16 as the palette is always on 16bits colors return QPair(RGBAColorModelID.id(), Integer16BitsColorDepthID.id()); } return QPair(); } } KisPropertiesConfigurationSP KisTIFFOptions::toProperties() const { QHash compToIndex; compToIndex[COMPRESSION_NONE] = 0; compToIndex[COMPRESSION_JPEG] = 1; compToIndex[COMPRESSION_DEFLATE] = 2; compToIndex[COMPRESSION_LZW] = 3; compToIndex[COMPRESSION_PIXARLOG] = 8; KisPropertiesConfigurationSP cfg = new KisPropertiesConfiguration(); cfg->setProperty("compressiontype", compToIndex.value(compressionType, 0)); cfg->setProperty("predictor", predictor - 1); cfg->setProperty("alpha", alpha); cfg->setProperty("flatten", flatten); cfg->setProperty("quality", jpegQuality); cfg->setProperty("deflate", deflateCompress); cfg->setProperty("pixarlog", pixarLogCompress); cfg->setProperty("saveProfile", saveProfile); return cfg; } void KisTIFFOptions::fromProperties(KisPropertiesConfigurationSP cfg) { QHash indexToComp; indexToComp[0] = COMPRESSION_NONE; indexToComp[1] = COMPRESSION_JPEG; indexToComp[2] = COMPRESSION_DEFLATE; indexToComp[3] = COMPRESSION_LZW; indexToComp[4] = COMPRESSION_PIXARLOG; // old value that might be still stored in a config (remove after Krita 5.0 :) ) indexToComp[8] = COMPRESSION_PIXARLOG; compressionType = indexToComp.value( cfg->getInt("compressiontype", 0), COMPRESSION_NONE); predictor = cfg->getInt("predictor", 0) + 1; alpha = cfg->getBool("alpha", true); flatten = cfg->getBool("flatten", true); jpegQuality = cfg->getInt("quality", 80); deflateCompress = cfg->getInt("deflate", 6); pixarLogCompress = cfg->getInt("pixarlog", 6); saveProfile = cfg->getBool("saveProfile", true); } KisTIFFConverter::KisTIFFConverter(KisDocument *doc) { m_doc = doc; m_stop = false; TIFFSetWarningHandler(0); TIFFSetErrorHandler(0); } KisTIFFConverter::~KisTIFFConverter() { } -KisImageBuilder_Result KisTIFFConverter::decode(const QString &filename) +KisImportExportErrorCode KisTIFFConverter::decode(const QString &filename) { dbgFile << "Start decoding TIFF File"; // Opent the TIFF file TIFF *image = 0; + + if (!KisImportExportAdditionalChecks::doesFileExist(filename)) { + return ImportExportCodes::FileNotExist; + } + if (!KisImportExportAdditionalChecks::isFileReadable(filename)) { + return ImportExportCodes::NoAccessToRead; + } + if ((image = TIFFOpen(QFile::encodeName(filename), "r")) == 0) { dbgFile << "Could not open the file, either it does not exist, either it is not a TIFF :" << filename; - return (KisImageBuilder_RESULT_BAD_FETCH); + return (ImportExportCodes::FileFormatIncorrect); } do { dbgFile << "Read new sub-image"; - KisImageBuilder_Result result = readTIFFDirectory(image); - if (result != KisImageBuilder_RESULT_OK) { + KisImportExportErrorCode result = readTIFFDirectory(image); + if (!result.isOk()) { return result; } } while (TIFFReadDirectory(image)); // Freeing memory TIFFClose(image); - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } -KisImageBuilder_Result KisTIFFConverter::readTIFFDirectory(TIFF* image) +KisImportExportErrorCode KisTIFFConverter::readTIFFDirectory(TIFF* image) { // Read information about the tiff uint32 width, height; if (TIFFGetField(image, TIFFTAG_IMAGEWIDTH, &width) == 0) { dbgFile << "Image does not define its width"; TIFFClose(image); - return KisImageBuilder_RESULT_INVALID_ARG; + return ImportExportCodes::FileFormatIncorrect; } if (TIFFGetField(image, TIFFTAG_IMAGELENGTH, &height) == 0) { dbgFile << "Image does not define its height"; TIFFClose(image); - return KisImageBuilder_RESULT_INVALID_ARG; + return ImportExportCodes::FileFormatIncorrect; } float xres; if (TIFFGetField(image, TIFFTAG_XRESOLUTION, &xres) == 0) { dbgFile << "Image does not define x resolution"; // but we don't stop xres = 100; } float yres; if (TIFFGetField(image, TIFFTAG_YRESOLUTION, &yres) == 0) { dbgFile << "Image does not define y resolution"; // but we don't stop yres = 100; } uint16 depth; if ((TIFFGetField(image, TIFFTAG_BITSPERSAMPLE, &depth) == 0)) { dbgFile << "Image does not define its depth"; depth = 1; } uint16 sampletype; if ((TIFFGetField(image, TIFFTAG_SAMPLEFORMAT, &sampletype) == 0)) { dbgFile << "Image does not define its sample type"; sampletype = SAMPLEFORMAT_UINT; } // Determine the number of channels (useful to know if a file has an alpha or not uint16 nbchannels; if (TIFFGetField(image, TIFFTAG_SAMPLESPERPIXEL, &nbchannels) == 0) { dbgFile << "Image has an undefined number of samples per pixel"; nbchannels = 0; } // Get the number of extrasamples and information about them uint16 *sampleinfo = 0, extrasamplescount; if (TIFFGetField(image, TIFFTAG_EXTRASAMPLES, &extrasamplescount, &sampleinfo) == 0) { extrasamplescount = 0; } // Determine the colorspace uint16 color_type; if (TIFFGetField(image, TIFFTAG_PHOTOMETRIC, &color_type) == 0) { dbgFile << "Image has an undefined photometric interpretation"; color_type = PHOTOMETRIC_MINISWHITE; } uint8 dstDepth = 0; QPair colorSpaceIdTag = getColorSpaceForColorType(sampletype, color_type, depth, image, nbchannels, extrasamplescount, dstDepth); if (colorSpaceIdTag.first.isEmpty()) { dbgFile << "Image has an unsupported colorspace :" << color_type << " for this depth :" << depth; TIFFClose(image); - return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; + return ImportExportCodes::FormatColorSpaceUnsupported; } dbgFile << "Colorspace is :" << colorSpaceIdTag.first << colorSpaceIdTag.second << " with a depth of" << depth << " and with a nb of channels of" << nbchannels; // Read image profile dbgFile << "Reading profile"; const KoColorProfile* profile = 0; quint32 EmbedLen; quint8* EmbedBuffer; if (TIFFGetField(image, TIFFTAG_ICCPROFILE, &EmbedLen, &EmbedBuffer) == 1) { dbgFile << "Profile found"; QByteArray rawdata; rawdata.resize(EmbedLen); memcpy(rawdata.data(), EmbedBuffer, EmbedLen); profile = KoColorSpaceRegistry::instance()->createColorProfile(colorSpaceIdTag.first, colorSpaceIdTag.second, rawdata); } const QString colorSpaceId = KoColorSpaceRegistry::instance()->colorSpaceId(colorSpaceIdTag.first, colorSpaceIdTag.second); // Check that the profile is used by the color space if (profile && !KoColorSpaceRegistry::instance()->profileIsCompatible(profile, colorSpaceId)) { dbgFile << "The profile " << profile->name() << " is not compatible with the color space model " << colorSpaceIdTag.first << " " << colorSpaceIdTag.second; profile = 0; } // Do not use the linear gamma profile for 16 bits/channel by default, tiff files are usually created with // gamma correction. XXX: Should we ask the user? if (!profile) { if (colorSpaceIdTag.first == RGBAColorModelID.id()) { profile = KoColorSpaceRegistry::instance()->profileByName("sRGB-elle-V2-srgbtrc.icc"); } else if (colorSpaceIdTag.first == GrayAColorModelID.id()) { profile = KoColorSpaceRegistry::instance()->profileByName("Gray-D50-elle-V2-srgbtrc.icc"); } } // Retrieve a pointer to the colorspace const KoColorSpace* cs = 0; if (profile && profile->isSuitableForOutput()) { dbgFile << "image has embedded profile:" << profile -> name() << ""; cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceIdTag.first, colorSpaceIdTag.second, profile); } else { cs = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceIdTag.first, colorSpaceIdTag.second, 0); } if (cs == 0) { dbgFile << "Colorspace" << colorSpaceIdTag.first << colorSpaceIdTag.second << " is not available, please check your installation."; TIFFClose(image); - return KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE; + return ImportExportCodes::FormatColorSpaceUnsupported; } // Create the cmsTransform if needed KoColorTransformation* transform = 0; if (profile && !profile->isSuitableForOutput()) { dbgFile << "The profile can't be used in krita, need conversion"; transform = KoColorSpaceRegistry::instance()->colorSpace(colorSpaceIdTag.first, colorSpaceIdTag.second, profile)->createColorConverter(cs, KoColorConversionTransformation::internalRenderingIntent(), KoColorConversionTransformation::internalConversionFlags()); } // Check if there is an alpha channel int8 alphapos = -1; // <- no alpha // Check which extra is alpha if any dbgFile << "There are" << nbchannels << " channels and" << extrasamplescount << " extra channels"; if (sampleinfo) { // index images don't have any sampleinfo, and therefore sampleinfo == 0 for (int i = 0; i < extrasamplescount; i ++) { dbgFile << "sample" << i << "extra sample count" << extrasamplescount << "color channel count" << (cs->colorChannelCount()) << "Number of channels" << nbchannels << "sample info" << sampleinfo[i]; if (sampleinfo[i] == EXTRASAMPLE_UNSPECIFIED) { qWarning() << "Extra sample type not defined for this file, assuming unassociated alpha."; alphapos = i; } if (sampleinfo[i] == EXTRASAMPLE_ASSOCALPHA) { // XXX: dangelo: the color values are already multiplied with // the alpha value. This needs to be reversed later (postprocessor?) qWarning() << "Associated alpha in this file: krita does not handle plremultiplied alpha."; alphapos = i; } if (sampleinfo[i] == EXTRASAMPLE_UNASSALPHA) { // color values are not premultiplied with alpha, and can be used as they are. alphapos = i; } } } dbgFile << "Alpha pos:" << alphapos; // Read META Information KoDocumentInfo * info = m_doc->documentInfo(); char* text; if (TIFFGetField(image, TIFFTAG_ARTIST, &text) == 1) { info->setAuthorInfo("creator", text); } if (TIFFGetField(image, TIFFTAG_DOCUMENTNAME, &text) == 1) { info->setAboutInfo("title", text); } if (TIFFGetField(image, TIFFTAG_IMAGEDESCRIPTION, &text) == 1) { info->setAboutInfo("description", text); } // Get the planar configuration uint16 planarconfig; if (TIFFGetField(image, TIFFTAG_PLANARCONFIG, &planarconfig) == 0) { dbgFile << "Plannar configuration is not define"; TIFFClose(image); - return KisImageBuilder_RESULT_INVALID_ARG; + return ImportExportCodes::FileFormatIncorrect; } // Creating the KisImageSP if (! m_image) { m_image = new KisImage(m_doc->createUndoStore(), width, height, cs, "built image"); m_image->setResolution( POINT_TO_INCH(xres), POINT_TO_INCH(yres )); // It is the "invert" macro because we convert from pointer-per-inchs to points Q_CHECK_PTR(m_image); } else { if (m_image->width() < (qint32)width || m_image->height() < (qint32)height) { quint32 newwidth = (m_image->width() < (qint32)width) ? width : m_image->width(); quint32 newheight = (m_image->height() < (qint32)height) ? height : m_image->height(); m_image->resizeImage(QRect(0,0,newwidth, newheight)); } } KisPaintLayer* layer = new KisPaintLayer(m_image.data(), m_image -> nextLayerName(), quint8_MAX); tdata_t buf = 0; tdata_t* ps_buf = 0; // used only for planar configuration separated KisBufferStreamBase* tiffstream; KisTIFFReaderBase* tiffReader = 0; quint8 poses[5]; KisTIFFPostProcessor* postprocessor = 0; // Configure poses uint8 nbcolorsamples = nbchannels - extrasamplescount; switch (color_type) { case PHOTOMETRIC_MINISWHITE: { poses[0] = 0; poses[1] = 1; postprocessor = new KisTIFFPostProcessorInvert(nbcolorsamples); } break; case PHOTOMETRIC_MINISBLACK: { poses[0] = 0; poses[1] = 1; postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; case PHOTOMETRIC_CIELAB: { poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3; postprocessor = new KisTIFFPostProcessorCIELABtoICCLAB(nbcolorsamples); } break; case PHOTOMETRIC_ICCLAB: { poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3; postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; case PHOTOMETRIC_RGB: { if (sampletype == SAMPLEFORMAT_IEEEFP) { poses[2] = 2; poses[1] = 1; poses[0] = 0; poses[3] = 3; } else { poses[0] = 2; poses[1] = 1; poses[2] = 0; poses[3] = 3; } postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; case PHOTOMETRIC_SEPARATED: { poses[0] = 0; poses[1] = 1; poses[2] = 2; poses[3] = 3; poses[4] = 4; postprocessor = new KisTIFFPostProcessor(nbcolorsamples); } break; default: break; } // Initisalize tiffReader uint16 * lineSizeCoeffs = new uint16[nbchannels]; uint16 vsubsampling = 1; uint16 hsubsampling = 1; for (uint i = 0; i < nbchannels; i++) { lineSizeCoeffs[i] = 1; } if (color_type == PHOTOMETRIC_PALETTE) { uint16 *red; // No need to free them they are free by libtiff uint16 *green; uint16 *blue; if ((TIFFGetField(image, TIFFTAG_COLORMAP, &red, &green, &blue)) == 0) { dbgFile << "Indexed image does not define a palette"; TIFFClose(image); delete [] lineSizeCoeffs; - return KisImageBuilder_RESULT_INVALID_ARG; + return ImportExportCodes::FileFormatIncorrect; } tiffReader = new KisTIFFReaderFromPalette(layer->paintDevice(), red, green, blue, poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor); } else if (color_type == PHOTOMETRIC_YCBCR) { TIFFGetFieldDefaulted(image, TIFFTAG_YCBCRSUBSAMPLING, &hsubsampling, &vsubsampling); lineSizeCoeffs[1] = hsubsampling; lineSizeCoeffs[2] = hsubsampling; uint16 position; TIFFGetFieldDefaulted(image, TIFFTAG_YCBCRPOSITIONING, &position); if (dstDepth == 8) { tiffReader = new KisTIFFYCbCrReaderTarget8Bit(layer->paintDevice(), layer->image()->width(), layer->image()->height(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, hsubsampling, vsubsampling); } else if (dstDepth == 16) { tiffReader = new KisTIFFYCbCrReaderTarget16Bit(layer->paintDevice(), layer->image()->width(), layer->image()->height(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, hsubsampling, vsubsampling); } } else if (dstDepth == 8) { tiffReader = new KisTIFFReaderTarget8bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor); } else if (dstDepth == 16) { uint16 alphaValue; if (sampletype == SAMPLEFORMAT_IEEEFP) { alphaValue = 15360; // representation of 1.0 in half } else { alphaValue = quint16_MAX; } tiffReader = new KisTIFFReaderTarget16bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, alphaValue); } else if (dstDepth == 32) { union { float f; uint32 i; } alphaValue; if (sampletype == SAMPLEFORMAT_IEEEFP) { alphaValue.f = 1.0f; } else { alphaValue.i = quint32_MAX; } tiffReader = new KisTIFFReaderTarget32bit(layer->paintDevice(), poses, alphapos, depth, sampletype, nbcolorsamples, extrasamplescount, transform, postprocessor, alphaValue.i); } if (!tiffReader) { delete postprocessor; delete[] lineSizeCoeffs; TIFFClose(image); dbgFile << "Image has an invalid/unsupported color type: " << color_type; - return KisImageBuilder_RESULT_INVALID_ARG; + return ImportExportCodes::FileFormatIncorrect; } if (TIFFIsTiled(image)) { dbgFile << "tiled image"; uint32 tileWidth, tileHeight; uint32 x, y; TIFFGetField(image, TIFFTAG_TILEWIDTH, &tileWidth); TIFFGetField(image, TIFFTAG_TILELENGTH, &tileHeight); uint32 linewidth = (tileWidth * depth * nbchannels) / 8; if (planarconfig == PLANARCONFIG_CONTIG) { buf = _TIFFmalloc(TIFFTileSize(image)); if (depth < 16) { tiffstream = new KisBufferStreamContigBelow16((uint8*)buf, depth, linewidth); } else if (depth < 32) { tiffstream = new KisBufferStreamContigBelow32((uint8*)buf, depth, linewidth); } else { tiffstream = new KisBufferStreamContigAbove32((uint8*)buf, depth, linewidth); } } else { ps_buf = new tdata_t[nbchannels]; uint32 * lineSizes = new uint32[nbchannels]; tmsize_t baseSize = TIFFTileSize(image) / nbchannels; for (uint i = 0; i < nbchannels; i++) { ps_buf[i] = _TIFFmalloc(baseSize); lineSizes[i] = tileWidth; // baseSize / lineSizeCoeffs[i]; } tiffstream = new KisBufferStreamSeperate((uint8**) ps_buf, nbchannels, depth, lineSizes); delete [] lineSizes; } dbgFile << linewidth << "" << nbchannels << "" << layer->paintDevice()->colorSpace()->colorChannelCount(); for (y = 0; y < height; y += tileHeight) { for (x = 0; x < width; x += tileWidth) { dbgFile << "Reading tile x =" << x << " y =" << y; if (planarconfig == PLANARCONFIG_CONTIG) { TIFFReadTile(image, buf, x, y, 0, (tsample_t) - 1); } else { for (uint i = 0; i < nbchannels; i++) { TIFFReadTile(image, ps_buf[i], x, y, 0, i); } } uint32 realTileWidth = (x + tileWidth) < width ? tileWidth : width - x; for (uint yintile = 0; y + yintile < height && yintile < tileHeight / vsubsampling;) { tiffReader->copyDataToChannels(x, y + yintile , realTileWidth, tiffstream); yintile += 1; tiffstream->moveToLine(yintile); } tiffstream->restart(); } } } else { dbgFile << "striped image"; tsize_t stripsize = TIFFStripSize(image); uint32 rowsPerStrip; TIFFGetFieldDefaulted(image, TIFFTAG_ROWSPERSTRIP, &rowsPerStrip); dbgFile << rowsPerStrip << "" << height; rowsPerStrip = qMin(rowsPerStrip, height); // when TIFFNumberOfStrips(image) == 1 it might happen that rowsPerStrip is incorrectly set if (planarconfig == PLANARCONFIG_CONTIG) { buf = _TIFFmalloc(stripsize); if (depth < 16) { tiffstream = new KisBufferStreamContigBelow16((uint8*)buf, depth, stripsize / rowsPerStrip); } else if (depth < 32) { tiffstream = new KisBufferStreamContigBelow32((uint8*)buf, depth, stripsize / rowsPerStrip); } else { tiffstream = new KisBufferStreamContigAbove32((uint8*)buf, depth, stripsize / rowsPerStrip); } } else { ps_buf = new tdata_t[nbchannels]; uint32 scanLineSize = stripsize / rowsPerStrip; dbgFile << " scanLineSize for each plan =" << scanLineSize; uint32 * lineSizes = new uint32[nbchannels]; for (uint i = 0; i < nbchannels; i++) { ps_buf[i] = _TIFFmalloc(stripsize); lineSizes[i] = scanLineSize / lineSizeCoeffs[i]; } tiffstream = new KisBufferStreamSeperate((uint8**) ps_buf, nbchannels, depth, lineSizes); delete [] lineSizes; } dbgFile << "Scanline size =" << TIFFRasterScanlineSize(image) << " / strip size =" << TIFFStripSize(image) << " / rowsPerStrip =" << rowsPerStrip << " stripsize/rowsPerStrip =" << stripsize / rowsPerStrip; uint32 y = 0; dbgFile << " NbOfStrips =" << TIFFNumberOfStrips(image) << " rowsPerStrip =" << rowsPerStrip << " stripsize =" << stripsize; for (uint32 strip = 0; y < height; strip++) { if (planarconfig == PLANARCONFIG_CONTIG) { TIFFReadEncodedStrip(image, TIFFComputeStrip(image, y, 0) , buf, (tsize_t) - 1); } else { for (uint i = 0; i < nbchannels; i++) { TIFFReadEncodedStrip(image, TIFFComputeStrip(image, y, i), ps_buf[i], (tsize_t) - 1); } } for (uint32 yinstrip = 0 ; yinstrip < rowsPerStrip && y < height ;) { uint linesread = tiffReader->copyDataToChannels(0, y, width, tiffstream); y += linesread; yinstrip += linesread; tiffstream->moveToLine(yinstrip); } tiffstream->restart(); } } tiffReader->finalize(); delete[] lineSizeCoeffs; delete tiffReader; delete tiffstream; if (planarconfig == PLANARCONFIG_CONTIG) { _TIFFfree(buf); } else { for (uint i = 0; i < nbchannels; i++) { _TIFFfree(ps_buf[i]); } delete[] ps_buf; } m_image->addNode(KisNodeSP(layer), m_image->rootLayer().data()); - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } -KisImageBuilder_Result KisTIFFConverter::buildImage(const QString &filename) +KisImportExportErrorCode KisTIFFConverter::buildImage(const QString &filename) { return decode(filename); } KisImageSP KisTIFFConverter::image() { return m_image; } -KisImageBuilder_Result KisTIFFConverter::buildFile(const QString &filename, KisImageSP kisimage, KisTIFFOptions options) +KisImportExportErrorCode KisTIFFConverter::buildFile(const QString &filename, KisImageSP kisimage, KisTIFFOptions options) { dbgFile << "Start writing TIFF File"; - if (!kisimage) - return KisImageBuilder_RESULT_EMPTY; + KIS_ASSERT_RECOVER_RETURN_VALUE(kisimage, ImportExportCodes::InternalError); // Open file for writing TIFF *image; if ((image = TIFFOpen(QFile::encodeName(filename), "w")) == 0) { dbgFile << "Could not open the file for writing" << filename; - TIFFClose(image); - return (KisImageBuilder_RESULT_FAILURE); + return ImportExportCodes::NoAccessToWrite; } // Set the document information KoDocumentInfo * info = m_doc->documentInfo(); QString title = info->aboutInfo("title"); if (!title.isEmpty()) { - TIFFSetField(image, TIFFTAG_DOCUMENTNAME, title.toLatin1().constData()); + if (!TIFFSetField(image, TIFFTAG_DOCUMENTNAME, title.toLatin1().constData())) { + TIFFClose(image); + return ImportExportCodes::ErrorWhileWriting; + } } QString abstract = info->aboutInfo("description"); if (!abstract.isEmpty()) { - TIFFSetField(image, TIFFTAG_IMAGEDESCRIPTION, abstract.toLatin1().constData()); + if (!TIFFSetField(image, TIFFTAG_IMAGEDESCRIPTION, abstract.toLatin1().constData())) { + TIFFClose(image); + return ImportExportCodes::ErrorWhileWriting; + } } QString author = info->authorInfo("creator"); if (!author.isEmpty()) { - TIFFSetField(image, TIFFTAG_ARTIST, author.toLatin1().constData()); + if(!TIFFSetField(image, TIFFTAG_ARTIST, author.toLatin1().constData())) { + TIFFClose(image); + return ImportExportCodes::ErrorWhileWriting; + } } dbgFile << "xres: " << INCH_TO_POINT(kisimage->xRes()) << " yres: " << INCH_TO_POINT(kisimage->yRes()); - TIFFSetField(image, TIFFTAG_XRESOLUTION, INCH_TO_POINT(kisimage->xRes())); // It is the "invert" macro because we convert from pointer-per-inchs to points - TIFFSetField(image, TIFFTAG_YRESOLUTION, INCH_TO_POINT(kisimage->yRes())); + if (!TIFFSetField(image, TIFFTAG_XRESOLUTION, INCH_TO_POINT(kisimage->xRes()))) { // It is the "invert" macro because we convert from pointer-per-inchs to points + TIFFClose(image); + return ImportExportCodes::ErrorWhileWriting; + } + if (!TIFFSetField(image, TIFFTAG_YRESOLUTION, INCH_TO_POINT(kisimage->yRes()))) { + TIFFClose(image); + return ImportExportCodes::ErrorWhileWriting; + } KisGroupLayer* root = dynamic_cast(kisimage->rootLayer().data()); - if (root == 0) { + KIS_ASSERT_RECOVER(root) { TIFFClose(image); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::InternalError; } KisTIFFWriterVisitor* visitor = new KisTIFFWriterVisitor(image, &options); - if (!visitor->visit(root)) { + KIS_ASSERT_RECOVER(visitor->visit(root)) { TIFFClose(image); - return KisImageBuilder_RESULT_FAILURE; + return ImportExportCodes::InternalError; } TIFFClose(image); - return KisImageBuilder_RESULT_OK; + return ImportExportCodes::OK; } void KisTIFFConverter::cancel() { m_stop = true; } diff --git a/plugins/impex/tiff/kis_tiff_converter.h b/plugins/impex/tiff/kis_tiff_converter.h index 47298c85c3..af064a56bd 100644 --- a/plugins/impex/tiff/kis_tiff_converter.h +++ b/plugins/impex/tiff/kis_tiff_converter.h @@ -1,71 +1,71 @@ /* * Copyright (c) 2005-2006 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. */ #ifndef _KIS_TIFF_CONVERTER_H_ #define _KIS_TIFF_CONVERTER_H_ #include #include #include #include "kis_types.h" #include "kis_global.h" #include "kis_annotation.h" -#include +#include class KisDocument; struct KisTIFFOptions { quint16 compressionType = 0; quint16 predictor = 1; bool alpha = true; bool flatten = true; quint16 jpegQuality = 80; quint16 deflateCompress = 6; quint16 pixarLogCompress = 6; bool saveProfile = true; KisPropertiesConfigurationSP toProperties() const; void fromProperties(KisPropertiesConfigurationSP cfg); }; class KisTIFFConverter : public QObject { Q_OBJECT public: KisTIFFConverter(KisDocument *doc); ~KisTIFFConverter() override; public: - KisImageBuilder_Result buildImage(const QString &filename); - KisImageBuilder_Result buildFile(const QString &filename, KisImageSP layer, KisTIFFOptions); + KisImportExportErrorCode buildImage(const QString &filename); + KisImportExportErrorCode buildFile(const QString &filename, KisImageSP layer, KisTIFFOptions); /** Retrieve the constructed image */ KisImageSP image(); public Q_SLOTS: virtual void cancel(); private: - KisImageBuilder_Result decode(const QString &filename); - KisImageBuilder_Result readTIFFDirectory(TIFF* image); + KisImportExportErrorCode decode(const QString &filename); + KisImportExportErrorCode readTIFFDirectory(TIFF* image); private: KisImageSP m_image; KisDocument *m_doc; bool m_stop; }; #endif diff --git a/plugins/impex/tiff/kis_tiff_export.cc b/plugins/impex/tiff/kis_tiff_export.cc index 1bf81d4965..f045bb455f 100644 --- a/plugins/impex/tiff/kis_tiff_export.cc +++ b/plugins/impex/tiff/kis_tiff_export.cc @@ -1,144 +1,138 @@ /* * 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_tiff_export.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_layer_utils.h" #include "kis_tiff_converter.h" #include "kis_dlg_options_tiff.h" #include "ui_kis_wdg_options_tiff.h" K_PLUGIN_FACTORY_WITH_JSON(KisTIFFExportFactory, "krita_tiff_export.json", registerPlugin();) KisTIFFExport::KisTIFFExport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisTIFFExport::~KisTIFFExport() { } -KisImportExportFilter::ConversionStatus KisTIFFExport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP configuration) +KisImportExportErrorCode KisTIFFExport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP configuration) { // If a configuration object was passed to the convert method, we use that, otherwise we load from the settings KisPropertiesConfigurationSP cfg(new KisPropertiesConfiguration()); if (configuration) { cfg->fromXML(configuration->toXML()); } else { cfg = lastSavedConfiguration(KisDocument::nativeFormatMimeType(), "image/tiff"); } const KoColorSpace* cs = document->savingImage()->colorSpace(); cfg->setProperty("type", (int)cs->channels()[0]->channelValueType()); cfg->setProperty("isCMYK", (cs->colorModelId() == CMYKAColorModelID)); KisTIFFOptions options; options.fromProperties(configuration); if (!options.flatten) { const bool hasGroupLayers = KisLayerUtils::recursiveFindNode(document->savingImage()->root(), [] (KisNodeSP node) { return node->parent() && node->inherits("KisGroupLayer"); }); options.flatten = hasGroupLayers; } if ((cs->channels()[0]->channelValueType() == KoChannelInfo::FLOAT16 || cs->channels()[0]->channelValueType() == KoChannelInfo::FLOAT32) && options.predictor == 2) { // FIXME THIS IS AN HACK FIX THAT IN 2.0 !! (62456a7b47636548c6507593df3e2bdf440f7544, BUG:135649) options.predictor = 3; } KisImageSP image; if (options.flatten) { image = new KisImage(0, document->savingImage()->width(), document->savingImage()->height(), document->savingImage()->colorSpace(), ""); image->setResolution(document->savingImage()->xRes(), document->savingImage()->yRes()); KisPaintDeviceSP pd = KisPaintDeviceSP(new KisPaintDevice(*document->savingImage()->projection())); KisPaintLayerSP l = KisPaintLayerSP(new KisPaintLayer(image.data(), "projection", OPACITY_OPAQUE_U8, pd)); image->addNode(KisNodeSP(l.data()), image->rootLayer().data()); } else { image = document->savingImage(); } KisTIFFConverter tiffConverter(document); - KisImageBuilder_Result res; - if ((res = tiffConverter.buildFile(filename(), image, options)) == KisImageBuilder_RESULT_OK) { - dbgFile << "success !"; - return KisImportExportFilter::OK; - } - - dbgFile << " Result =" << res; - return KisImportExportFilter::InternalError; + KisImportExportErrorCode res = tiffConverter.buildFile(filename(), image, options); + return res; } KisPropertiesConfigurationSP KisTIFFExport::defaultConfiguration(const QByteArray &/*from*/, const QByteArray &/*to*/) const { KisTIFFOptions options; return options.toProperties(); } KisConfigWidget *KisTIFFExport::createConfigurationWidget(QWidget *parent, const QByteArray &/*from*/, const QByteArray &/*to*/) const { return new KisTIFFOptionsWidget(parent); } void KisTIFFExport::initializeCapabilities() { addCapability(KisExportCheckRegistry::instance()->get("MultiLayerCheck")->create(KisExportCheckBase::SUPPORTED)); addCapability(KisExportCheckRegistry::instance()->get("sRGBProfileCheck")->create(KisExportCheckBase::SUPPORTED)); QList > supportedColorModels; supportedColorModels << QPair() << QPair(RGBAColorModelID, Integer8BitsColorDepthID) << QPair(RGBAColorModelID, Integer16BitsColorDepthID) << QPair(RGBAColorModelID, Float16BitsColorDepthID) << QPair(RGBAColorModelID, Float32BitsColorDepthID) << QPair(GrayAColorModelID, Integer8BitsColorDepthID) << QPair(GrayAColorModelID, Integer16BitsColorDepthID) << QPair(CMYKAColorModelID, Integer8BitsColorDepthID) << QPair(CMYKAColorModelID, Integer16BitsColorDepthID) << QPair(LABAColorModelID, Integer16BitsColorDepthID); addSupportedColorModels(supportedColorModels, "TIFF"); } #include diff --git a/plugins/impex/tiff/kis_tiff_export.h b/plugins/impex/tiff/kis_tiff_export.h index 7a3d024719..76afc237e2 100644 --- a/plugins/impex/tiff/kis_tiff_export.h +++ b/plugins/impex/tiff/kis_tiff_export.h @@ -1,41 +1,41 @@ /* * 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. */ #ifndef _KIS_TIFF_EXPORT_H_ #define _KIS_TIFF_EXPORT_H_ #include #include #include class KisTIFFExport : public KisImportExportFilter { Q_OBJECT public: KisTIFFExport(QObject *parent, const QVariantList &); ~KisTIFFExport() override; bool supportsIO() const override { return false; } - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; KisPropertiesConfigurationSP defaultConfiguration(const QByteArray& from = "", const QByteArray& to = "") const override; KisConfigWidget *createConfigurationWidget(QWidget *parent, const QByteArray& from = "", const QByteArray& to = "") const override; void initializeCapabilities() override; }; #endif diff --git a/plugins/impex/tiff/kis_tiff_import.cc b/plugins/impex/tiff/kis_tiff_import.cc index c3d795dc07..eb947dc135 100644 --- a/plugins/impex/tiff/kis_tiff_import.cc +++ b/plugins/impex/tiff/kis_tiff_import.cc @@ -1,72 +1,54 @@ /* * 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_tiff_import.h" #include #include #include #include #include #include "kis_tiff_converter.h" K_PLUGIN_FACTORY_WITH_JSON(TIFFImportFactory, "krita_tiff_import.json", registerPlugin();) KisTIFFImport::KisTIFFImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisTIFFImport::~KisTIFFImport() { } -KisImportExportFilter::ConversionStatus KisTIFFImport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisTIFFImport::convert(KisDocument *document, QIODevice */*io*/, KisPropertiesConfigurationSP /*configuration*/) { KisTIFFConverter tiffConverter(document); - - switch (tiffConverter.buildImage(filename())) { - case KisImageBuilder_RESULT_UNSUPPORTED: - return KisImportExportFilter::NotImplemented; - case KisImageBuilder_RESULT_INVALID_ARG: - return KisImportExportFilter::BadMimeType; - case KisImageBuilder_RESULT_NO_URI: - case KisImageBuilder_RESULT_NOT_LOCAL: - return KisImportExportFilter::FileNotFound; - case KisImageBuilder_RESULT_BAD_FETCH: - case KisImageBuilder_RESULT_EMPTY: - return KisImportExportFilter::ParsingError; - case KisImageBuilder_RESULT_FAILURE: - return KisImportExportFilter::InternalError; - case KisImageBuilder_RESULT_UNSUPPORTED_COLORSPACE: - return KisImportExportFilter::WrongFormat; - case KisImageBuilder_RESULT_OK: - document -> setCurrentImage(tiffConverter.image()); - return KisImportExportFilter::OK; - default: - break; + KisImportExportErrorCode result = tiffConverter.buildImage(filename()); + if (result.isOk()) { + document->setCurrentImage(tiffConverter.image()); } - return KisImportExportFilter::InternalError; + return result; } #include diff --git a/plugins/impex/tiff/kis_tiff_import.h b/plugins/impex/tiff/kis_tiff_import.h index 03d54f6be8..ddfaf0bfd7 100644 --- a/plugins/impex/tiff/kis_tiff_import.h +++ b/plugins/impex/tiff/kis_tiff_import.h @@ -1,37 +1,37 @@ /* * 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. */ #ifndef _KIS_TIFF_IMPORT_H_ #define _KIS_TIFF_IMPORT_H_ #include #include class KisTIFFImport : public KisImportExportFilter { Q_OBJECT public: KisTIFFImport(QObject *parent, const QVariantList &); ~KisTIFFImport() override; bool supportsIO() const override { return false; } public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/tiff/tests/data/incorrectFormatFile.txt b/plugins/impex/tiff/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/tiff/tests/data/readonlyFile.txt b/plugins/impex/tiff/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/tiff/tests/data/writeonlyFile.txt b/plugins/impex/tiff/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/tiff/tests/kis_tiff_test.cpp b/plugins/impex/tiff/tests/kis_tiff_test.cpp index 0ce5fcb26c..111eafcfd2 100644 --- a/plugins/impex/tiff/tests/kis_tiff_test.cpp +++ b/plugins/impex/tiff/tests/kis_tiff_test.cpp @@ -1,119 +1,141 @@ /* * Copyright (C) 2007 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_tiff_test.h" #include #include #include "filestest.h" #include #include #include "kisexiv2/kis_exiv2.h" #include #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif +const QString TiffMimetype = "image/tiff"; + void KisTiffTest::testFiles() { // XXX: make the exiv io backends real plugins KisExiv2::initialize(); QStringList excludes; #ifndef CPU_32_BITS excludes << "flower-minisblack-06.tif"; #endif #ifdef HAVE_LCMS2 excludes << "flower-separated-contig-08.tif" << "flower-separated-contig-16.tif" << "flower-separated-planar-08.tif" << "flower-separated-planar-16.tif" << "flower-minisblack-02.tif" << "flower-minisblack-04.tif" << "flower-minisblack-08.tif" << "flower-minisblack-10.tif" << "flower-minisblack-12.tif" << "flower-minisblack-14.tif" << "flower-minisblack-16.tif" << "flower-minisblack-24.tif" << "flower-minisblack-32.tif" << "jim___dg.tif" << "jim___gg.tif" << "strike.tif"; #endif excludes << "text.tif" << "ycbcr-cat.tif"; TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", excludes, QString(), 1); } void KisTiffTest::testRoundTripRGBF16() { // Disabled for now, it's broken because we assumed integers. #if 0 QRect testRect(0,0,1000,1000); QRect fillRect(100,100,100,100); const KoColorSpace *csf16 = KoColorSpaceRegistry::instance()->colorSpace(RGBAColorModelID.id(), Float16BitsColorDepthID.id(), 0); KisDocument *doc0 = qobject_cast(KisPart::instance()->createDocument()); doc0->newImage("test", testRect.width(), testRect.height(), csf16, KoColor(Qt::blue, csf16), QString(), 1.0); QTemporaryFile tmpFile(QDir::tempPath() + QLatin1String("/krita_XXXXXX") + QLatin1String(".tiff")); tmpFile.open(); doc0->setBackupFile(false); doc0->setOutputMimeType("image/tiff"); doc0->setFileBatchMode(true); doc0->saveAs(QUrl::fromLocalFile(tmpFile.fileName())); KisNodeSP layer0 = doc0->image()->root()->firstChild(); Q_ASSERT(layer0); layer0->paintDevice()->fill(fillRect, KoColor(Qt::red, csf16)); KisDocument *doc1 = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc1); doc1->setFileBatchMode(false); - KisImportExportFilter::ConversionStatus status; + KisImportExportErrorCode status; QString s = manager.importDocument(tmpFile.fileName(), QString(), status); dbgKrita << s; Q_ASSERT(doc1->image()); QImage ref0 = doc0->image()->projection()->convertToQImage(0, testRect); QImage ref1 = doc1->image()->projection()->convertToQImage(0, testRect); QCOMPARE(ref0, ref1); #endif } + +void KisTiffTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), TiffMimetype); +} + + +void KisTiffTest::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), TiffMimetype, true); +} + + +void KisTiffTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), TiffMimetype); +} + + + KISTEST_MAIN(KisTiffTest) diff --git a/plugins/impex/tiff/tests/kis_tiff_test.h b/plugins/impex/tiff/tests/kis_tiff_test.h index c86d906d4b..11505376f7 100644 --- a/plugins/impex/tiff/tests/kis_tiff_test.h +++ b/plugins/impex/tiff/tests/kis_tiff_test.h @@ -1,32 +1,36 @@ /* * Copyright (C) 2007 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. */ #ifndef _KIS_TIFF_TEST_H_ #define _KIS_TIFF_TEST_H_ #include class KisTiffTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); void testRoundTripRGBF16(); + + void testImportFromWriteonly(); + void testExportToReadonly(); + void testImportIncorrectFormat(); }; #endif diff --git a/plugins/impex/xcf/3rdparty/xcftools/flatspec.c b/plugins/impex/xcf/3rdparty/xcftools/flatspec.c index ec12a67faf..54c74d90aa 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/flatspec.c +++ b/plugins/impex/xcf/3rdparty/xcftools/flatspec.c @@ -1,380 +1,397 @@ /* Flattening selections function 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 #include void init_flatspec(struct FlattenSpec *spec) { spec->window_mode = USE_CANVAS ; spec->default_pixel = PERHAPS_ALPHA_CHANNEL ; spec->numLayers = 0 ; spec->layers = NULL ; spec->transmap_filename = NULL ; spec->output_filename = "-" ; spec->out_color_mode = COLOR_BY_CONTENTS ; spec->partial_transparency_mode = ALLOW_PARTIAL_TRANSPARENCY ; spec->process_in_memory = 0 ; spec->gimpish_indexed = 1 ; } -void +int add_layer_request(struct FlattenSpec *spec, const char *layer) { spec->layers = realloc(spec->layers, sizeof(struct xcfLayer) * (1+spec->numLayers)); - if( spec->layers == NULL ) + if( spec->layers == NULL ) { FatalUnexpected(_("Out of memory")); + return XCF_ERROR; + } spec->layers[spec->numLayers].name = layer ; spec->layers[spec->numLayers].mode = (GimpLayerModeEffects)-1 ; spec->layers[spec->numLayers].opacity = 9999 ; spec->layers[spec->numLayers].hasMask = -1 ; spec->numLayers++ ; + return XCF_OK; } struct xcfLayer * lastlayerspec(struct FlattenSpec *spec,const char *option) { - if( spec->numLayers == 0 ) + if( spec->numLayers == 0 ) { FatalGeneric(20,_("The %s option must follow a layer name on the " "command line"),option); + return XCF_PTR_EMPTY; + } return spec->layers + (spec->numLayers-1) ; } static int typeHasTransparency(GimpImageType type) { switch( type ) { case GIMP_RGB_IMAGE: case GIMP_GRAY_IMAGE: case GIMP_INDEXED_IMAGE: return 0 ; case GIMP_RGBA_IMAGE: case GIMP_GRAYA_IMAGE: case GIMP_INDEXEDA_IMAGE: return 1 ; } return 1 ; } static enum out_color_mode color_by_layers(struct FlattenSpec *spec) { int colormap_is_colored = 0 ; enum out_color_mode grayish ; int i ; if( spec->default_pixel == CHECKERED_BACKGROUND ) grayish = COLOR_GRAY ; else { int degrayed = degrayPixel(spec->default_pixel); if( degrayed < 0 ) { return COLOR_RGB ; } else if( spec->gimpish_indexed && (degrayed == 0 || degrayed == 255) ) { grayish = COLOR_MONO ; } else { grayish = COLOR_GRAY ; } } for( i=0; inumLayers; i++ ) switch( spec->layers[i].type ) { case GIMP_RGB_IMAGE: case GIMP_RGBA_IMAGE: return COLOR_RGB ; case GIMP_GRAY_IMAGE: case GIMP_GRAYA_IMAGE: grayish = COLOR_GRAY ; break ; case GIMP_INDEXED_IMAGE: case GIMP_INDEXEDA_IMAGE: if( colormap_is_colored ) return COLOR_RGB ; break ; } return grayish ; } -void +int complete_flatspec(struct FlattenSpec *spec, guesser guess_callback) { unsigned i ; int anyPartial ; /* Find the layers to convert. */ if( spec->numLayers == 0 ) { spec->layers = XCF.layers ; spec->numLayers = XCF.numLayers ; } else { for( i=0; inumLayers; i++ ) { GimpLayerModeEffects mode ; int opacity, hasMask ; unsigned j ; for( j=0; ; j++ ) { - if( j == XCF.numLayers ) + if( j == XCF.numLayers ) { FatalGeneric(22,_("The image has no layer called '%s'"), spec->layers[i].name); + return XCF_ERROR; + } if( strcmp(spec->layers[i].name,XCF.layers[j].name) == 0 ) break ; } mode = spec->layers[i].mode == (GimpLayerModeEffects)-1 ? XCF.layers[j].mode : spec->layers[i].mode ; opacity = spec->layers[i].opacity == 9999 ? XCF.layers[j].opacity : spec->layers[i].opacity ; hasMask = spec->layers[i].hasMask == -1 ? XCF.layers[j].hasMask : spec->layers[i].hasMask ; if( hasMask && !XCF.layers[j].hasMask && - XCF.layers[j].mask.hierarchy == 0 ) + XCF.layers[j].mask.hierarchy == 0 ) { FatalGeneric(22,_("Layer '%s' has no layer mask to enable"), spec->layers[i].name); + return XCF_ERROR; + } spec->layers[i] = XCF.layers[j] ; spec->layers[i].mode = mode ; spec->layers[i].opacity = opacity ; spec->layers[i].hasMask = hasMask ; spec->layers[i].isVisible = 1 ; } } /* Force the mode of the lowest visible layer to be Normal or Dissolve. * That may not be logical, but the Gimp does it */ for( i=0; i < spec->numLayers; i++ ) { if( spec->layers[i].isVisible ) { if( spec->layers[i].mode != GIMP_DISSOLVE_MODE ) spec->layers[i].mode = GIMP_NORMAL_MODE ; break ; } } /* Mimic the Gimp's behavior on indexed layers */ if( XCF.type == GIMP_INDEXED && spec->gimpish_indexed ) { for( i=0; inumLayers; i++ ) if( spec->layers[i].mode != GIMP_DISSOLVE_MODE ) spec->layers[i].mode = GIMP_NORMAL_NOPARTIAL_MODE ; } else spec->gimpish_indexed = 0 ; /* compute dimensions of the window */ if( spec->window_mode == AUTOCROP ) { int first = 1 ; for( i=0; inumLayers; i++ ) if( spec->layers[i].isVisible ) { computeDimensions(&spec->layers[i].dim) ; if( first ) { spec->dim = spec->layers[i].dim ; first = 0 ; } else { if( spec->dim.c.l > spec->layers[i].dim.c.l ) spec->dim.c.l = spec->layers[i].dim.c.l ; if( spec->dim.c.r < spec->layers[i].dim.c.r ) spec->dim.c.r = spec->layers[i].dim.c.r ; if( spec->dim.c.t > spec->layers[i].dim.c.t ) spec->dim.c.t = spec->layers[i].dim.c.t ; if( spec->dim.c.b < spec->layers[i].dim.c.b ) spec->dim.c.b = spec->layers[i].dim.c.b ; } } if( first ) { spec->window_mode = USE_CANVAS ; } else { spec->dim.width = spec->dim.c.r - spec->dim.c.l ; spec->dim.height = spec->dim.c.b - spec->dim.c.t ; } } if( spec->window_mode != AUTOCROP ) { if( (spec->window_mode & MANUAL_OFFSET) == 0 ) spec->dim.c.t = spec->dim.c.l = 0 ; if( (spec->window_mode & MANUAL_CROP) == 0 ) { spec->dim.height = XCF.height ; spec->dim.width = XCF.width ; } } computeDimensions(&spec->dim); /* Turn off layers that we don't hit at all */ for( i=0; inumLayers; i++ ) if( spec->layers[i].isVisible && disjointRects(spec->dim.c,spec->layers[i].dim.c) ) spec->layers[i].isVisible = 0 ; /* See if there is a completely covering layer somewhere in the stack */ /* Also check if partial transparency is possible */ anyPartial = 0 ; for( i=spec->numLayers; i-- ; ) { if( !spec->layers[i].isVisible ) continue ; if( typeHasTransparency(spec->layers[i].type) ) { if( spec->layers[i].mode == GIMP_NORMAL_MODE ) anyPartial = 1; } else if( isSubrect(spec->dim.c,spec->layers[i].dim.c) && !spec->layers[i].hasMask && (spec->layers[i].mode == GIMP_NORMAL_MODE || spec->layers[i].mode == GIMP_NORMAL_NOPARTIAL_MODE || spec->layers[i].mode == GIMP_DISSOLVE_MODE) ) { /* This layer fills out the entire image. * Turn off only lower layers, and note that we cannot have * transparency at all. */ while(i) spec->layers[--i].isVisible = 0 ; if( spec->default_pixel != FORCE_ALPHA_CHANNEL ) spec->default_pixel = NEWALPHA(colormap[0],255); anyPartial = 0 ; break ; } } if( spec->partial_transparency_mode == ALLOW_PARTIAL_TRANSPARENCY && (!anyPartial || ALPHA(spec->default_pixel) >= 128) ) spec->partial_transparency_mode = PARTIAL_TRANSPARENCY_IMPOSSIBLE ; /* Initialize layers and print overview if we're verbose */ for( i=spec->numLayers; i--; ) if( spec->layers[i].isVisible ) { - initLayer(&spec->layers[i]) ; + if (initLayer(&spec->layers[i]) != XCF_OK) { + return XCF_ERROR; + } if( verboseFlag ) { fprintf(stderr,"%dx%d%+d%+d %s %s", spec->layers[i].dim.width, spec->layers[i].dim.height, spec->layers[i].dim.c.l - spec->dim.c.l, spec->layers[i].dim.c.t - spec->dim.c.t, _(showGimpImageType(spec->layers[i].type)), _(showGimpLayerModeEffects(spec->layers[i].mode))); if( spec->layers[i].opacity < 255 ) fprintf(stderr,"/%02d%%",spec->layers[i].opacity * 100 / 255); if( XCF.layers[i].hasMask ) fprintf(stderr,_("/mask")); fprintf(stderr," %s\n",spec->layers[i].name); } } /* Resolve color mode unless we wait until we have the entire image */ if( spec->out_color_mode == COLOR_BY_CONTENTS && !spec->process_in_memory ) { if( guess_callback ) spec->out_color_mode = guess_callback(spec,NULL); if( spec->out_color_mode == COLOR_BY_CONTENTS ) spec->out_color_mode = color_by_layers(spec) ; } + return XCF_OK; } -void +int analyse_colormode(struct FlattenSpec *spec,rgba **allPixels, guesser guess_callback) { unsigned x,y ; int status ; /* 8 - looking for any transparency * 4 - looking for partially transparent pixels * 2 - looking for pixels other than black and white * 1 - looking for colored pixels */ int known_absent = 0 ; int assume_present = 0 ; if( spec->out_color_mode == COLOR_BY_CONTENTS && guess_callback ) spec->out_color_mode = guess_callback(spec,allPixels) ; if( spec->out_color_mode == COLOR_RGB ) assume_present |= 3 ; if( spec->out_color_mode == COLOR_INDEXED ) assume_present |= 3 ; if( spec->out_color_mode == COLOR_GRAY ) assume_present |= 2 ; switch( color_by_layers(spec) ) { case COLOR_GRAY: known_absent |= 1 ; break ; case COLOR_MONO: known_absent |= 3 ; break ; default: break ; } if( spec->partial_transparency_mode == DISSOLVE_PARTIAL_TRANSPARENCY || spec->partial_transparency_mode == PARTIAL_TRANSPARENCY_IMPOSSIBLE ) known_absent |= 4 ; if( ALPHA(spec->default_pixel) >= 128 ) known_absent |= 12 ; else if( spec->default_pixel == FORCE_ALPHA_CHANNEL ) assume_present |= 8 ; status = 15 - (known_absent | assume_present) ; for( y=0; status && ydim.height; y++ ) { rgba *row = allPixels[y] ; if( (status & 3) != 0 ) { /* We're still interested in color */ for( x=0; status && xdim.width; x++ ) { if( NULLALPHA(row[x]) ) status &= ~8 ; else { rgba full = row[x] | (255 << ALPHA_SHIFT) ; if( !FULLALPHA(row[x]) ) status &= ~12 ; if( full == NEWALPHA(0,255) || full == NEWALPHA(-1,255) ) /* Black or white */ ; else if( degrayPixel(row[x]) != -1 ) status &= ~2 ; /* gray */ else status &= ~3 ; /* color */ } } } else { /* Not interested in color */ for( x=0; status && xdim.width; x++ ) { if( NULLALPHA(row[x]) ) status &= ~8 ; else if( !FULLALPHA(row[x]) ) status &= ~12 ; } } } status |= known_absent ; switch( spec->out_color_mode ) { case COLOR_INDEXED: /* The caller takes responsibility */ case COLOR_RGB: /* Everything is fine. */ break ; case COLOR_GRAY: - if( (status & 1) == 0 ) + if( (status & 1) == 0 ) { FatalGeneric(103, _("Grayscale output selected, but colored pixel(s) found")); + return XCF_ERROR; + } break ; case COLOR_MONO: - if( (status & 2) == 0 ) + if( (status & 2) == 0 ) { FatalGeneric(103,_("Monochrome output selected, but not all pixels " "are black or white")); + return XCF_ERROR; + } break ; case COLOR_BY_FILENAME: /* Should not happen ... */ case COLOR_BY_CONTENTS: if( (status & 1) == 0 ) spec->out_color_mode = COLOR_RGB ; else if( (status & 2) == 0 ) spec->out_color_mode = COLOR_GRAY ; else spec->out_color_mode = COLOR_MONO ; break ; } if( (status & 12) == 12 ) /* No transparency found */ spec->default_pixel = NEWALPHA(colormap[0],255); else if( (status & 12) == 4 ) spec->partial_transparency_mode = PARTIAL_TRANSPARENCY_IMPOSSIBLE ; + return XCF_OK; } diff --git a/plugins/impex/xcf/3rdparty/xcftools/flatten.c b/plugins/impex/xcf/3rdparty/xcftools/flatten.c index c021bbe9e8..bfce8bf099 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/flatten.c +++ b/plugins/impex/xcf/3rdparty/xcftools/flatten.c @@ -1,690 +1,725 @@ /* 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 void __ATTRIBUTE__((noinline)) +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 ; - if( (top->summary & TILESUMMARY_ALLNULL) != 0 ) return ; + 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!"); + { + 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); + default: { + + FatalUnexpected("Hue hextant is %d", hsvBot.hue); + return XCF_ERROR; + } } break ; } default: - FatalUnsupportedXCF(_("'%s' layer mode"), + { + 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 ; + 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; while( nlayers-- ) { if( tileSummary(top) & TILESUMMARY_ALLFULL ) 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 ) { - assert( below == top ); + 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 ; } - merge_exotic(below,tile,spec->layers[nlayers].mode); + 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 void +static int addBackground(struct FlattenSpec *spec, struct Tile *tile, unsigned ncols) { unsigned i ; if( tileSummary(tile) & TILESUMMARY_ALLFULL ) - return ; + return XCF_OK; switch( spec->partial_transparency_mode ) { case FORBID_PARTIAL_TRANSPARENCY: - if( !(tileSummary(tile) & TILESUMMARY_CRISP) ) + 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 ; + return XCF_OK; } - if( !FULLALPHA(spec->default_pixel) ) return ; + 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; } -void +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 */ - addBackground(spec,tile,ncols); + 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 ; - flattenIncrementally(spec,collector); + 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/impex/xcf/3rdparty/xcftools/flatten.h b/plugins/impex/xcf/3rdparty/xcftools/flatten.h index 2047cbca6d..672afaa39b 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/flatten.h +++ b/plugins/impex/xcf/3rdparty/xcftools/flatten.h @@ -1,77 +1,77 @@ /* 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. */ #ifndef FLATTEN_H #define FLATTEN_H #include "xcftools.h" #include "pixels.h" #define PERHAPS_ALPHA_CHANNEL (NEWALPHA(0,1)) #define FORCE_ALPHA_CHANNEL (NEWALPHA(0,2)) #define CHECKERED_BACKGROUND (NEWALPHA(0,200)) struct FlattenSpec { struct tileDimensions dim ; rgba default_pixel ; int numLayers ; struct xcfLayer *layers ; const char * transmap_filename ; const char * output_filename ; enum out_color_mode { COLOR_BY_FILENAME, COLOR_BY_CONTENTS, COLOR_INDEXED, COLOR_RGB, COLOR_GRAY, COLOR_MONO } out_color_mode ; enum { ALLOW_PARTIAL_TRANSPARENCY, DISSOLVE_PARTIAL_TRANSPARENCY, FORBID_PARTIAL_TRANSPARENCY, PARTIAL_TRANSPARENCY_IMPOSSIBLE } partial_transparency_mode ; enum { USE_CANVAS = 0, MANUAL_OFFSET = 1, MANUAL_CROP = 2, AUTOCROP = 4 } window_mode ; int process_in_memory ; int gimpish_indexed ; }; /* From flatspec.c */ void init_flatspec(struct FlattenSpec *); -void add_layer_request(struct FlattenSpec *,const char *name); +int add_layer_request(struct FlattenSpec *,const char *name); struct xcfLayer *lastlayerspec(struct FlattenSpec *,const char *option); typedef enum out_color_mode (*guesser) (struct FlattenSpec *,rgba **); /* Call this after processing options, and after opening the XCF file */ -void complete_flatspec(struct FlattenSpec *,guesser); -void analyse_colormode(struct FlattenSpec *,rgba **allPixels,guesser); +int complete_flatspec(struct FlattenSpec *,guesser); +int analyse_colormode(struct FlattenSpec *,rgba **allPixels,guesser); /* From flatten.c */ typedef void (*lineCallback)(unsigned num,rgba *pixels); -void flattenIncrementally(struct FlattenSpec *,lineCallback); +int flattenIncrementally(struct FlattenSpec *,lineCallback); rgba **flattenAll(struct FlattenSpec*); void shipoutWithCallback(struct FlattenSpec *,rgba **pixels,lineCallback); #endif /* FLATTEN_H */ diff --git a/plugins/impex/xcf/3rdparty/xcftools/pixels.c b/plugins/impex/xcf/3rdparty/xcftools/pixels.c index d7037937c9..51999520f2 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/pixels.c +++ b/plugins/impex/xcf/3rdparty/xcftools/pixels.c @@ -1,491 +1,583 @@ /* Pixel and tile 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. */ #define DEBUG #include "xcftools.h" #include "pixels.h" #include #include rgba colormap[256] ; unsigned colormapLength=0 ; int degrayPixel(rgba pixel) { if( ((pixel >> RED_SHIFT) & 255) == ((pixel >> GREEN_SHIFT) & 255) && ((pixel >> RED_SHIFT) & 255) == ((pixel >> BLUE_SHIFT) & 255) ) return (pixel >> RED_SHIFT) & 255 ; return -1 ; } /* ****************************************************************** */ typedef const struct _convertParams { int bpp ; int shift[4] ; uint32_t base_pixel ; const rgba *lookup ; } convertParams ; #define RGB_SHIFT RED_SHIFT, GREEN_SHIFT, BLUE_SHIFT #define OPAQUE (255 << ALPHA_SHIFT) static convertParams convertRGB = { 3, {RGB_SHIFT}, OPAQUE, 0 }; static convertParams convertRGBA = { 4, {RGB_SHIFT, ALPHA_SHIFT}, 0,0 }; static convertParams convertGRAY = { 1, {-1}, OPAQUE, graytable }; static convertParams convertGRAYA = { 2, {-1,ALPHA_SHIFT}, 0, graytable }; static convertParams convertINDEXED = { 1, {-1}, OPAQUE, colormap }; static convertParams convertINDEXEDA = { 2, {-1,ALPHA_SHIFT}, 0, colormap }; static convertParams convertColormap = { 3, {RGB_SHIFT}, 0, 0 }; static convertParams convertChannel = { 1, {ALPHA_SHIFT}, 0, 0 }; /* ****************************************************************** */ static int -tileDirectoryOneLevel(struct tileDimensions *dim,uint32_t ptr) +tileDirectoryOneLevel(struct tileDimensions *dim,uint32_t ptr, int* ptrOut) { - if( ptr == 0 ) - return 0 ; + if( ptr == 0 ) { + *ptrOut = 0; + return XCF_OK; /* allowed by xcf, apparently */ + } if( xcfL(ptr ) != dim->c.r - dim->c.l || - xcfL(ptr+4) != dim->c.b - dim->c.t ) + xcfL(ptr+4) != dim->c.b - dim->c.t ) { FatalBadXCF("Drawable size mismatch at %" PRIX32, ptr); - return ptr += 8 ; + *ptrOut = XCF_PTR_EMPTY; + return XCF_ERROR; + } + *ptrOut = (ptr += 8) ; + return XCF_OK; } -static void +static int initTileDirectory(struct tileDimensions *dim,struct xcfTiles *tiles, const char *type) { uint32_t ptr ; uint32_t data ; ptr = tiles->hierarchy ; tiles->hierarchy = 0 ; - if( (ptr = tileDirectoryOneLevel(dim,ptr)) == 0 ) return ; + int ptrOut; + if (tileDirectoryOneLevel(dim,ptr, &ptrOut) != XCF_OK) { + return XCF_ERROR; + } + if (ptrOut == XCF_PTR_EMPTY) { + return XCF_OK; + } + ptr = ptrOut; if( tiles->params == &convertChannel ) { /* A layer mask is a channel. * Skip a name and a property list. */ - xcfString(ptr,&ptr); - while( xcfNextprop(&ptr,&data) != PROP_END ) - ; - ptr = xcfOffset(ptr,4*4); - if( (ptr = tileDirectoryOneLevel(dim,ptr)) == 0 ) return ; + xcfString(ptr,&ptr); + PropType type; + int response; + while( (response = xcfNextprop(&ptr,&data, &type)) != XCF_ERROR && type != PROP_END ) { + + } + if (response != XCF_OK) { + return XCF_ERROR; + } + uint32_t ptrout; + if(xcfOffset(ptr,4*4, &ptrout) != XCF_OK) return XCF_ERROR; + ptr = ptrout; + if (tileDirectoryOneLevel(dim,ptr, &ptrOut) != XCF_OK) { + return XCF_ERROR; + } + if (ptrOut == XCF_PTR_EMPTY) { + return XCF_OK; + } + ptr = ptrOut; } /* The XCF format has a dummy "hierarchy" level which was * once meant to mean something, but never happened. It contains * the bpp value and a list of "level" pointers; but only the * first level actually contains data. */ data = xcfL(ptr) ; - if( xcfL(ptr) != tiles->params->bpp ) + if( xcfL(ptr) != tiles->params->bpp ) { FatalBadXCF("%"PRIu32" bytes per pixel for %s drawable",xcfL(ptr),type); - ptr = xcfOffset(ptr+4,3*4) ; - if( (ptr = tileDirectoryOneLevel(dim,ptr)) == 0 ) return ; + return XCF_ERROR; + } + uint32_t ptrout; + if(xcfOffset(ptr+4,3*4, &ptrout) != XCF_OK) return XCF_ERROR; + ptr = ptrout; + if (tileDirectoryOneLevel(dim,ptr, &ptrOut) != XCF_OK) { + return XCF_ERROR; + } + if (ptrOut == XCF_PTR_EMPTY) { + return XCF_OK; + } + ptr = ptrOut; - xcfCheckspace(ptr,dim->ntiles*4+4,"Tile directory at %" PRIX32,ptr); + if (xcfCheckspace(ptr,dim->ntiles*4+4,"Tile directory at %" PRIX32,ptr) != XCF_OK) { + return XCF_ERROR; + } /* if( xcfL(ptr + dim->ntiles*4) != 0 ) FatalBadXCF("Wrong sized tile directory at %" PRIX32,ptr);*/ #define REUSE_RAW_DATA tiles->tileptrs = (uint32_t*)(xcf_file + ptr) #if defined(WORDS_BIGENDIAN) && defined(CAN_DO_UNALIGNED_WORDS) REUSE_RAW_DATA; #else # if defined(WORDS_BIGENDIAN) if( (ptr&3) == 0 ) REUSE_RAW_DATA; else # endif { unsigned i ; tiles->tileptrs = xcfmalloc(dim->ntiles * sizeof(uint32_t)) ; for( i = 0 ; i < dim->ntiles ; i++ ) tiles->tileptrs[i] = xcfL(ptr+i*4); } #endif + return XCF_OK; } -void +int initLayer(struct xcfLayer *layer) { if( layer->dim.ntiles == 0 || (layer->pixels.hierarchy == 0 && layer->mask.hierarchy == 0) ) - return ; + return XCF_OK; switch(layer->type) { #define DEF(X) case GIMP_##X##_IMAGE: layer->pixels.params = &convert##X; break DEF(RGB); DEF(RGBA); DEF(GRAY); DEF(GRAYA); DEF(INDEXED); DEF(INDEXEDA); default: - FatalUnsupportedXCF(_("Layer type %s"),_(showGimpImageType(layer->type))); + { + FatalUnsupportedXCF(_("Layer type %s"),_(showGimpImageType(layer->type))); + return XCF_ERROR; + } + + } + if (initTileDirectory(&layer->dim,&layer->pixels, + _(showGimpImageType(layer->type))) != XCF_OK) { + return XCF_ERROR; } - initTileDirectory(&layer->dim,&layer->pixels, - _(showGimpImageType(layer->type))); layer->mask.params = &convertChannel ; - initTileDirectory(&layer->dim,&layer->mask,"layer mask"); + if (initTileDirectory(&layer->dim,&layer->mask,"layer mask") != XCF_OK) { + return XCF_ERROR; + } + return XCF_OK; } -static void copyStraightPixels(rgba *dest,unsigned npixels, +static int copyStraightPixels(rgba *dest,unsigned npixels, uint32_t ptr,convertParams *params); -void +int initColormap(void) { uint32_t ncolors ; if( XCF.colormapptr == 0 ) { colormapLength = 0 ; - return ; + return XCF_OK; } ncolors = xcfL(XCF.colormapptr) ; - if( ncolors > 256 ) + if( ncolors > 256 ) { FatalUnsupportedXCF(_("Color map has more than 256 entries")); - copyStraightPixels(colormap,ncolors,XCF.colormapptr+4,&convertColormap); + return XCF_ERROR; + } + if(copyStraightPixels(colormap,ncolors,XCF.colormapptr+4,&convertColormap) != XCF_OK) { + return XCF_ERROR; + } colormapLength = ncolors ; #ifdef xDEBUG { unsigned j ; fprintf(stderr,"Colormap decoding OK\n"); for( j = 0 ; j < ncolors ; j++ ) { if( j % 8 == 0 ) fprintf(stderr,"\n"); fprintf(stderr," %08x",colormap[j]); } fprintf(stderr,"\n"); } #endif + return XCF_OK; } /* ****************************************************************** */ struct Tile * newTile(struct rect r) { unsigned npixels = (unsigned)(r.b-r.t) * (unsigned)(r.r-r.l) ; struct Tile *data = xcfmalloc(sizeof(struct Tile) - sizeof(rgba)*(TILE_HEIGHT*TILE_WIDTH - npixels)) ; data->count = npixels ; data->refcount = 1 ; data->summary = 0 ; return data ; } struct Tile * forkTile(struct Tile* tile) { - if( ++tile->refcount <= 0 ) + if( ++tile->refcount <= 0 ) { FatalUnsupportedXCF(_("Unbelievably many layers?\n" "More likely to be a bug in %s"),progname); + return XCF_PTR_EMPTY; + } return tile ; } void freeTile(struct Tile* tile) { if( --tile->refcount == 0 ) xcffree(tile) ; } summary_t tileSummary(struct Tile *tile) { unsigned i ; summary_t summary ; if( (tile->summary & TILESUMMARY_UPTODATE) != 0 ) return tile->summary ; summary = TILESUMMARY_ALLNULL + TILESUMMARY_ALLFULL + TILESUMMARY_CRISP ; for( i=0; summary && icount; i++ ) { if( FULLALPHA(tile->pixels[i]) ) summary &= ~TILESUMMARY_ALLNULL ; else if( NULLALPHA(tile->pixels[i]) ) summary &= ~TILESUMMARY_ALLFULL ; else summary = 0 ; } summary += TILESUMMARY_UPTODATE ; tile->summary = summary ; return summary ; } void fillTile(struct Tile *tile,rgba data) { unsigned i ; for( i = 0 ; i < tile->count ; i++ ) tile->pixels[i] = data ; if( FULLALPHA(data) ) tile->summary = TILESUMMARY_UPTODATE+TILESUMMARY_ALLFULL+TILESUMMARY_CRISP; else if (NULLALPHA(data) ) tile->summary = TILESUMMARY_UPTODATE+TILESUMMARY_ALLNULL+TILESUMMARY_CRISP; else tile->summary = TILESUMMARY_UPTODATE ; } /* ****************************************************************** */ -static void +static int copyStraightPixels(rgba *dest,unsigned npixels, uint32_t ptr,convertParams *params) { unsigned bpp = params->bpp; const rgba *lookup = params->lookup; rgba base_pixel = params->base_pixel ; uint8_t *bp = xcf_file + ptr ; - xcfCheckspace(ptr,bpp*npixels, - "pixel array (%u x %d bpp) at %"PRIX32,npixels,bpp,ptr); + int response; + if ((response = xcfCheckspace(ptr,bpp*npixels, + "pixel array (%u x %d bpp) at %"PRIX32,npixels,bpp,ptr)) != XCF_OK) { + return XCF_ERROR; + } while( npixels-- ) { rgba pixel = base_pixel ; unsigned i ; for( i = 0 ; i < bpp ; i++ ) { if( params->shift[i] < 0 ) { pixel += lookup[*bp++] ; } else { pixel += *bp++ << params->shift[i] ; } } *dest++ = pixel ; } + return XCF_OK; } -static void +static int copyRLEpixels(rgba *dest,unsigned npixels,uint32_t ptr,convertParams *params) { unsigned i,j ; rgba base_pixel = params->base_pixel ; #ifdef xDEBUG fprintf(stderr,"RLE stream at %x, want %u x %u pixels, base %x\n", ptr,params->bpp,npixels,base_pixel); #endif - /* This algorithm depends on the indexed byte always being the first one */ if( params->shift[0] < -1 ) base_pixel = 0 ; for( j = npixels ; j-- ; ) dest[j] = base_pixel ; for( i = 0 ; i < params->bpp ; i++ ) { int shift = params->shift[i] ; if( shift < 0 ) shift = 0 ; for( j = 0 ; j < npixels ; ) { int countspec ; unsigned count ; - xcfCheckspace(ptr,2,"RLE data stream"); + if (xcfCheckspace(ptr,2,"RLE data stream") != XCF_OK) { + return XCF_ERROR; + } countspec = (int8_t) xcf_file[ptr++] ; count = countspec >= 0 ? countspec+1 : -countspec ; if( count == 128 ) { - xcfCheckspace(ptr,3,"RLE long count"); + if (xcfCheckspace(ptr,3,"RLE long count") != XCF_OK) { + return XCF_ERROR; + } count = xcf_file[ptr++] << 8 ; count += xcf_file[ptr++] ; } - if( j + count > npixels ) + if( j + count > npixels ) { FatalBadXCF("Overlong RLE run at %"PRIX32" (plane %u, %u left)", ptr,i,npixels-j); + return XCF_ERROR; + } if( countspec >= 0 ) { rgba data = (uint32_t) xcf_file[ptr++] << shift ; while( count-- ) dest[j++] += data ; } else { while( count-- ) dest[j++] += (uint32_t) xcf_file[ptr++] << shift ; } } if( i == 0 && params->shift[0] < 0 ) { const rgba *lookup = params->lookup ; base_pixel = params->base_pixel ; for( j = npixels ; j-- ; ) { dest[j] = lookup[dest[j]-base_pixel] + base_pixel ; } } } #ifdef xDEBUG fprintf(stderr,"RLE decoding OK at %"PRIX32"\n",ptr); /* for( j = 0 ; j < npixels ; j++ ) { if( j % 8 == 0 ) fprintf(stderr,"\n"); fprintf(stderr," %8x",dest[j]); } fprintf(stderr,"\n"); */ #endif + return XCF_OK; } -static void +static int copyTilePixels(struct Tile *dest, uint32_t ptr,convertParams *params) { if( FULLALPHA(params->base_pixel) ) dest->summary = TILESUMMARY_UPTODATE+TILESUMMARY_ALLFULL+TILESUMMARY_CRISP; else dest->summary = 0 ; switch( XCF.compression ) { case COMPRESS_NONE: - copyStraightPixels(dest->pixels,dest->count,ptr,params); + if (copyStraightPixels(dest->pixels,dest->count,ptr,params) != XCF_OK) { + return XCF_ERROR; + } break ; case COMPRESS_RLE: - copyRLEpixels(dest->pixels,dest->count,ptr,params); + if (copyRLEpixels(dest->pixels,dest->count,ptr,params) != XCF_OK) { + return XCF_ERROR; + } break ; default: - FatalUnsupportedXCF(_("%s compression"), + { + FatalUnsupportedXCF(_("%s compression"), _(showXcfCompressionType(XCF.compression))); + return XCF_ERROR; + } } + return XCF_OK; } struct Tile * getMaskOrLayerTile(struct tileDimensions *dim, struct xcfTiles *tiles, struct rect want) { struct Tile *tile = newTile(want); - assert( want.l < want.r && want.t < want.b ); + if (want.l >= want.r || want.t >= want.b ) { + freeTile(tile); + return XCF_PTR_EMPTY; + } + + if( tiles->tileptrs == 0 ) { fillTile(tile,0); return tile ; } #ifdef xDEBUG fprintf(stderr,"getMaskOrLayer: (%d-%d),(%d-%d)\n",left,right,top,bottom); #endif if( isSubrect(want,dim->c) && (want.l - dim->c.l) % TILE_WIDTH == 0 && (want.t - dim->c.t) % TILE_HEIGHT == 0 ) { int tx = TILE_NUM(want.l - dim->c.l); int ty = TILE_NUM(want.t - dim->c.t); if( want.r == TILEXn(*dim,tx+1) && want.b == TILEYn(*dim,ty+1) ) { /* The common case? An entire single tile from the layer */ - copyTilePixels(tile,tiles->tileptrs[tx + ty*dim->tilesx],tiles->params); + if (copyTilePixels(tile,tiles->tileptrs[tx + ty*dim->tilesx],tiles->params) != XCF_OK) { + freeTile(tile); + return XCF_PTR_EMPTY; + } return tile ; } } /* OK, we must construct the wanted tile as a jigsaw */ { unsigned width = want.r-want.l ; rgba *pixvert = tile->pixels ; rgba *pixhoriz ; int y, ty, l0, l1 ; int x, tx, c0, c1 ; unsigned lstart, lnum ; unsigned cstart, cnum ; if( !isSubrect(want,dim->c) ) { if( want.l < dim->c.l ) pixvert += (dim->c.l - want.l), want.l = dim->c.l ; if( want.r > dim->c.r ) want.r = dim->c.r ; if( want.t < dim->c.t ) pixvert += (dim->c.t - want.t) * width, want.t = dim->c.t ; if( want.b > dim->c.b ) want.b = dim->c.b ; fillTile(tile,0); } else { tile->summary = -1 ; /* I.e. whatever the jigsaw pieces say */ } #ifdef xDEBUG fprintf(stderr,"jig0 (%d-%d),(%d-%d)\n",left,right,top,bottom); #endif for( y=want.t, ty=TILE_NUM(want.t-dim->c.t), l0=TILEYn(*dim,ty); y want.b ? want.b : l1) - y ; pixhoriz = pixvert ; for( x=want.l, tx=TILE_NUM(want.l-dim->c.l), c0=TILEXn(*dim,tx); x want.r ? want.r : c1) - x ; { static struct Tile tmptile ; unsigned dwidth = c1-c0 ; unsigned i, j ; tmptile.count = (c1-c0)*(l1-l0) ; #ifdef xDEBUG fprintf(stderr,"jig ty=%u(%u-%u-%u)(%u+%u) tx=%u(%u-%u-%u)(%u+%u)\n", ty,l0,y,l1,lstart,lnum, tx,c0,x,c1,cstart,cnum); #endif - copyTilePixels(&tmptile, - tiles->tileptrs[tx+ty*dim->tilesx],tiles->params); + if (copyTilePixels(&tmptile, + tiles->tileptrs[tx+ty*dim->tilesx],tiles->params) != XCF_OK) { + freeTile(tile); + return XCF_PTR_EMPTY; + } + for(i=0; isummary &= tmptile.summary ; } } } } return tile ; } void applyMask(struct Tile *tile, struct Tile *mask) { unsigned i ; assertTileCompatibility(tile,mask); assert( tile->count == mask->count ); INIT_SCALETABLE_IF(1); invalidateSummary(tile,0); for( i=0; i < tile->count ;i++ ) tile->pixels[i] = NEWALPHA(tile->pixels[i], scaletable[mask->pixels[i]>>ALPHA_SHIFT] [ALPHA(tile->pixels[i])]); freeTile(mask); } struct Tile * getLayerTile(struct xcfLayer *layer,const struct rect *where) { struct Tile *data ; #ifdef xDEBUG fprintf(stderr,"getLayerTile(%s): (%d-%d),(%d-%d)\n", layer->name,where->l,where->r,where->t,where->b); #endif if( disjointRects(*where,layer->dim.c) || layer->opacity == 0 ) { data = newTile(*where); fillTile(data,0); return data ; } data = getMaskOrLayerTile(&layer->dim,&layer->pixels,*where); + if (data == XCF_PTR_EMPTY) { + return XCF_PTR_EMPTY; + } if( (data->summary & TILESUMMARY_ALLNULL) != 0 ) return data ; if( layer->hasMask ) { struct Tile *mask = getMaskOrLayerTile(&layer->dim,&layer->mask,*where); + if (mask == XCF_PTR_EMPTY) { /* error */ + return XCF_PTR_EMPTY; + } applyMask(data,mask); } if( layer->opacity < 255 ) { const uint8_t *ourtable ; int i ; invalidateSummary(data,~(TILESUMMARY_CRISP | TILESUMMARY_ALLFULL)); INIT_SCALETABLE_IF(1); ourtable = scaletable[layer->opacity] ; for( i=0; i < data->count; i++ ) data->pixels[i] = NEWALPHA(data->pixels[i],ourtable[ALPHA(data->pixels[i])]) ; } return data ; } diff --git a/plugins/impex/xcf/3rdparty/xcftools/pixels.h b/plugins/impex/xcf/3rdparty/xcftools/pixels.h index 5342351367..6351784b3c 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/pixels.h +++ b/plugins/impex/xcf/3rdparty/xcftools/pixels.h @@ -1,130 +1,130 @@ /* Pixel and tile 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. */ #ifndef PIXELS_H #define PIXELS_H #include "xcftools.h" /* MACROS FOR INTERNAL PIXEL ORDERING HERE */ /*=========================================*/ /* In principle the internal representation of pixels may change. * - this was supposed to allow an optimization where a layer could * be represented as a pointer into the mmapped xcf file, if * alignment, bpp, and endianness agreed (the point was that the * pixel representation had to agree with the endianness). * * However, it turns out that the current Gimp _always_ saves images * with RLE encoding of tiles, so such an effort would be in vain. * * Just for modularity, nevertheless try to isolate knowledge of * the RGBA-to-machine-word packing in this section of the * header file. Define new macros if necessary. * * Given that we don't have to agree with the uncompressed * RLE format, we choose to have the alpha in the _least_ * significant byte on all archs - it is tested and used more * often than the visible channels. */ typedef uint32_t rgba ; #define ALPHA_SHIFT 0 #define RED_SHIFT 8 #define GREEN_SHIFT 16 #define BLUE_SHIFT 24 #define ALPHA(rgba) ((uint8_t)(rgba)) #define FULLALPHA(rgba) ((uint8_t)(rgba) == 255) #define NULLALPHA(rgba) ((uint8_t)(rgba) == 0) #define NEWALPHA(rgb,a) (((rgba)(rgb) & 0xFFFFFF00) + (a)) #ifdef PRECOMPUTED_SCALETABLE extern const uint8_t scaletable[256][256] ; #define INIT_SCALETABLE_IF(foo) ((void)0) #else extern uint8_t scaletable[256][256] ; extern int ok_scaletable ; void mk_scaletable(void); #define INIT_SCALETABLE_IF(foo) \ (ok_scaletable || !(foo) || (mk_scaletable(),0) ) #endif extern const rgba graytable[256] ; extern rgba colormap[256] ; extern unsigned colormapLength ; -void initLayer(struct xcfLayer *); -void initColormap(); +int initLayer(struct xcfLayer *); +int initColormap(); int degrayPixel(rgba); /* returns -1 for non-gray pixels */ /* ******************************************************* */ #define TILEXn(dim,tx) \ ((tx)==(dim).tilesx ? (dim).c.r : (dim).c.l + ((tx)*TILE_WIDTH)) #define TILEYn(dim,ty) \ ((ty)==(dim).tilesy ? (dim).c.b : (dim).c.t + ((ty)*TILE_HEIGHT)) #if defined(__i386__) /* This is probably the only common architecture where small constants * are more efficient for byte operations. */ typedef int8_t summary_t ; typedef short int refcount_t ; #else typedef int summary_t ; typedef int refcount_t ; #endif #define TILESUMMARY_UPTODATE 8 #define TILESUMMARY_ALLNULL 4 #define TILESUMMARY_ALLFULL 2 #define TILESUMMARY_CRISP 1 /* everything either null or full */ struct Tile { refcount_t refcount ; summary_t summary ; /* a combination of TIMESUMMARY_FOO constatns */ unsigned count ; rgba pixels[TILE_WIDTH * TILE_HEIGHT]; }; /* Actually, the Tile structures that get allocated many not have * room for that many pixels. We subtract the space for those we don't * use - which is Not Legal C, but ought to be portable. * OTOH, one can also use a static struct Tile for temporary storage. */ #define assertTileCompatibility(t1,t2) assert((t1)->count==(t2)->count) struct Tile *newTile(struct rect); struct Tile *forkTile(struct Tile*); void freeTile(struct Tile*); #define invalidateSummary(tile,mask) \ do{ assert((tile)->refcount==1); (tile)->summary &= mask; } while(0) summary_t __ATTRIBUTE__((pure)) tileSummary(struct Tile *); void fillTile(struct Tile*,rgba); /* applyMask() destructively changes tile, * applyMask() gets ownership of mask */ void applyMask(struct Tile *tile, struct Tile *mask); struct Tile *getLayerTile(struct xcfLayer *,const struct rect *); struct Tile * getMaskOrLayerTile(struct tileDimensions *dim, struct xcfTiles *tiles, struct rect want); #endif /* FLATTEN_H */ diff --git a/plugins/impex/xcf/3rdparty/xcftools/utils.c b/plugins/impex/xcf/3rdparty/xcftools/utils.c index 37b5aa8236..06958d0be6 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/utils.c +++ b/plugins/impex/xcf/3rdparty/xcftools/utils.c @@ -1,159 +1,168 @@ /* Generic support 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 #include #include #include const char *progname = "$0" ; int verboseFlag = 0 ; -static void __ATTRIBUTE__((noreturn)) +void vFatalGeneric(int status,const char *format,va_list args) { if( format ) { if( *format == '!' ) { vfprintf(stderr,format+1,args); fprintf(stderr,": %s\n",strerror(errno)); } else { vfprintf(stderr,format,args); fputc('\n',stderr); } } - exit(status); + /* don't exit here - Krita can't handle errors otherwise */ + /* exit(status); */ } void FatalGeneric(int status,const char* format,...) { va_list v; va_start(v,format); if( format ) fprintf(stderr,"%s: ",progname); vFatalGeneric(status,format,v); } void FatalUnexpected(const char* format,...) { va_list v; va_start(v,format); fprintf(stderr,"%s: ",progname); vFatalGeneric(127,format,v) ; } void FatalBadXCF(const char* format,...) { va_list v; va_start(v,format); fprintf(stderr,"%s: %s:\n ",progname,_("Corrupted or malformed XCF file")); vFatalGeneric(125,format,v) ; } -void +int xcfCheckspace(uint32_t addr,int spaceafter,const char *format,...) { if( xcf_length < spaceafter || addr > xcf_length - spaceafter ) { va_list v; va_start(v,format); fprintf(stderr,"%s: %s\n ",progname,_("Corrupted or truncated XCF file")); fprintf(stderr,"(0x%" PRIXPTR " bytes): ",(uintptr_t)xcf_length); vFatalGeneric(125,format,v) ; + return XCF_ERROR; } + return XCF_OK; } void FatalUnsupportedXCF(const char* format,...) { va_list v; va_start(v,format); fprintf(stderr,"%s: %s\n ",progname, _("The image contains features not understood by this program:")); vFatalGeneric(123,format,v) ; } void gpl_blurb(void) { fprintf(stderr,PACKAGE_STRING "\n"); fprintf(stderr, _("Type \"%s -h\" to get an option summary.\n"),progname); - exit(1) ; + /* don't exit here - Krita will close otherwise */ + /* exit(1) ; */ } /* ******************************************************* */ void * xcfmalloc(size_t size) { void *ptr = malloc(size); - if( !ptr ) + if( !ptr ) { FatalUnexpected(_("Out of memory")); + return XCF_PTR_EMPTY; + } return ptr ; } void xcffree(void *block) { if( xcf_file && (uint8_t*)block >= xcf_file && (uint8_t*)block < xcf_file + xcf_length ) ; else free(block); } /* ******************************************************* */ FILE * openout(const char *name) { FILE *newfile ; if( strcmp(name,"-") == 0 ) return stdout ; newfile = fopen(name,"wb") ; - if( newfile == NULL ) + if( newfile == NULL ) { FatalUnexpected(_("!Cannot create file %s"),name); + return XCF_PTR_EMPTY; + } return newfile ; } -void +int closeout(FILE *f,const char *name) { if( f == NULL ) - return ; + return XCF_OK; if( fflush(f) == 0 ) { errno = 0 ; if( !ferror(f) ) { if( fclose(f) == 0 ) - return ; + return XCF_OK; } else if( errno == 0 ) { /* Attempt to coax a valid errno out of the standard library, * following an idea by Bruno Haible * http://lists.gnu.org/archive/html/bug-gnulib/2003-09/msg00157.html */ if( fputc('\0', f) != EOF && fflush(f) == 0 ) errno = EIO ; /* Argh, everything succedes. Just call it an I/O error */ } } FatalUnexpected(_("!Error writing file %s"),name); + return XCF_ERROR; } diff --git a/plugins/impex/xcf/3rdparty/xcftools/xcf-general.c b/plugins/impex/xcf/3rdparty/xcftools/xcf-general.c index e5ae5a6049..c6966526ab 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/xcf-general.c +++ b/plugins/impex/xcf/3rdparty/xcftools/xcf-general.c @@ -1,305 +1,388 @@ /* Generic functions for reading XCF files * * 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 #include #ifdef HAVE_ICONV # include #elif !defined(ICONV_CONST) # define ICONV_CONST const #endif uint8_t *xcf_file = 0 ; size_t xcf_length ; int use_utf8 = 0 ; -uint32_t -xcfOffset(uint32_t addr,int spaceafter) +int +xcfOffset(uint32_t addr,int spaceafter, uint32_t* apparent) { - uint32_t apparent ; - xcfCheckspace(addr,4,"(xcfOffset)"); - apparent = xcfL(addr); - xcfCheckspace(apparent,spaceafter, + if (!apparent) { + return XCF_ERROR; + } + if(xcfCheckspace(addr,4,"(xcfOffset)") != XCF_OK) { + return XCF_ERROR; + } + *apparent = xcfL(addr); + if (xcfCheckspace(*apparent,spaceafter, "Too large offset (%" PRIX32 ") at position %" PRIX32, - apparent,addr); - return apparent ; + *apparent,addr) != XCF_OK) { + return XCF_ERROR; + } + return XCF_OK; } int -xcfNextprop(uint32_t *master,uint32_t *body) +xcfNextprop(uint32_t *master,uint32_t *body, PropType *typeOut) { + int response; + + if (typeOut == 0) { + return XCF_ERROR; + } + uint32_t ptr, length, total, minlength ; PropType type ; ptr = *master ; - xcfCheckspace(ptr,8,"(property header)"); + if ((response = xcfCheckspace(ptr,8,"(property header)")) != XCF_OK) { + return XCF_ERROR; + } type = xcfL(ptr); length = xcfL(ptr+4); *body = ptr+8 ; switch(type) { case PROP_COLORMAP: { uint32_t ncolors ; - xcfCheckspace(ptr+8,4,"(colormap length)"); + if ((response = xcfCheckspace(ptr+8,4,"(colormap length)")) != XCF_OK) { + return XCF_ERROR; + } ncolors = xcfL(ptr+8) ; - if( ncolors > 256 ) + if( ncolors > 256 ) { FatalBadXCF("Colormap has %" PRIu32 " entries",ncolors); + return XCF_ERROR; + } + /* Surprise! Some older version of the Gimp computed the wrong length * word, and the _reader_ always just reads three bytes per color * and ignores the length tag! Duplicate this so we too can read * the buggy XCF files. */ length = minlength = 4+3*ncolors; break; } case PROP_COMPRESSION: minlength = 1; break; case PROP_OPACITY: minlength = 4; break; case PROP_APPLY_MASK: minlength = 4; break; case PROP_OFFSETS: minlength = 8; break; case PROP_MODE: minlength = 4; break; default: minlength = 0; break; } - if( length < minlength ) + if( length < minlength ) { FatalBadXCF("Short %s property at %" PRIX32 " (%" PRIu32 "<%" PRIu32 ")", showPropType(type),ptr,length,minlength); + return XCF_ERROR; + } *master = ptr+8+length ; total = 8 + length + (type != PROP_END ? 8 : 0) ; - if( total < length ) /* Check overwrap */ + if( total < length ) { /* Check overwrap */ FatalBadXCF("Overlong property at %" PRIX32, ptr); - xcfCheckspace(ptr,total,"Overlong property at %" PRIX32,ptr) ; - return type ; + return XCF_ERROR; + } + if((response = xcfCheckspace(ptr,total,"Overlong property at %" PRIX32,ptr)) != 0) { + return XCF_ERROR; + } + *typeOut = type; + return XCF_OK; } const char* xcfString(uint32_t ptr,uint32_t *after) { uint32_t length ; unsigned i ; ICONV_CONST char *utf8master ; - xcfCheckspace(ptr,4,"(string length)"); + if (xcfCheckspace(ptr,4,"(string length)") != XCF_OK) { + return XCF_PTR_EMPTY; + } length = xcfL(ptr) ; ptr += 4 ; - xcfCheckspace(ptr,length,"(string)"); + if (xcfCheckspace(ptr,length,"(string)") != XCF_OK) { + return XCF_PTR_EMPTY; + } utf8master = (ICONV_CONST char*)(xcf_file+ptr) ; if( after ) *after = ptr + length ; - if( length == 0 || utf8master[length-1] != 0 ) + if( length == 0 || utf8master[length-1] != 0 ) { FatalBadXCF("String at %" PRIX32 " not zero-terminated",ptr-4); + return XCF_PTR_EMPTY; + } length-- ; if( use_utf8 ) return utf8master ; /* We assume that the local character set includes ASCII... * Check if conversion is needed at all */ for( i=0 ; ; i++ ) { if( i == length ) return utf8master ; /* Only ASCII after all */ - if( utf8master[i] == 0 ) + if( utf8master[i] == 0 ) { FatalBadXCF("String at %" PRIX32 " has embedded zeroes",ptr-4); + return XCF_PTR_EMPTY; + } if( (int8_t) utf8master[i] < 0 ) break ; } #ifdef HAVE_ICONV { size_t targetsize = length+1 ; int sloppy_translation = 0 ; iconv_t cd = iconv_open("//TRANSLIT","UTF-8"); if( cd == (iconv_t) -1 ) { cd = iconv_open("","UTF-8"); sloppy_translation = 1 ; } if( cd == (iconv_t) -1 ) iconv_close(cd) ; /* Give up; perhaps iconv doesn't know UTF-8 */ else while(1) { char *buffer = xcfmalloc(targetsize) ; ICONV_CONST char *inbuf = utf8master ; char *outbuf = buffer ; size_t incount = length ; size_t outcount = targetsize ; while(1) { /* Loop for systems without //ICONV support */ size_t result = iconv(cd,&inbuf,&incount,&outbuf,&outcount) ; if( result == (size_t)-1 && errno == EILSEQ && sloppy_translation && outcount > 0 ) { *outbuf++ = '?' ; outcount-- ; while( (int8_t)*inbuf < 0 ) inbuf++, incount-- ; continue ; } if( result != (size_t)-1 ) { if( outcount == 0 ) errno = E2BIG ; else { *outbuf = 0 ; iconv_close(cd) ; return buffer ; } } break ; } - if( errno == EILSEQ || errno == EINVAL ) + if( errno == EILSEQ || errno == EINVAL ) { FatalBadXCF("Bad UTF-8 encoding '%s' at %" PRIXPTR, inbuf,(uintptr_t)((inbuf-utf8master)+ptr)); + return XCF_PTR_EMPTY; + } if( errno == E2BIG ) { targetsize += 1+incount ; xcffree(buffer) ; continue ; } FatalUnexpected("!iconv on layer name at %"PRIX32,ptr); + return XCF_PTR_EMPTY: } } #endif { static int warned = 0 ; if( !warned ) { fprintf(stderr,_("Warning: one or more layer names could not be\n" " translated to the local character set.\n")); warned = 1 ; } } return utf8master ; } /* ****************************************************************** */ void computeDimensions(struct tileDimensions *d) { d->c.r = d->c.l + d->width ; d->c.b = d->c.t + d->height ; d->tilesx = (d->width+TILE_WIDTH-1)/TILE_WIDTH ; d->tilesy = (d->height+TILE_HEIGHT-1)/TILE_HEIGHT ; d->ntiles = d->tilesx * d->tilesy ; } struct xcfImage XCF ; -void +int getBasicXcfInfo(void) { + uint32_t ptr, data, layerfile ; PropType type ; int i, j ; + + int errorStatus; + uint32_t ptrout; + + if (xcfCheckspace(0,14+7*4,"(very short)") != XCF_OK) { + return XCF_ERROR; + } - xcfCheckspace(0,14+7*4,"(very short)"); if( strcmp((char*)xcf_file,"gimp xcf file") == 0 ) XCF.version = 0 ; else if( xcf_file[13] == 0 && sscanf((char*)xcf_file,"gimp xcf v%d",&XCF.version) == 1 ) ; - else + else { FatalBadXCF(_("Not an XCF file at all (magic not recognized)")); + return XCF_ERROR; + } if (XCF.version < 0 || XCF.version > 3) { - return; + return XCF_ERROR; } XCF.compression = COMPRESS_NONE ; XCF.colormapptr = 0 ; ptr = 14 ; XCF.width = xcfL(ptr); ptr += 4 ; XCF.height = xcfL(ptr); ptr += 4 ; XCF.type = xcfL(ptr); ptr += 4 ; - while( (type = xcfNextprop(&ptr,&data)) != PROP_END ) { + while( (errorStatus = xcfNextprop(&ptr,&data, &type)) != XCF_ERROR && type != PROP_END ) { + if (errorStatus != XCF_OK) { + return XCF_ERROR; + } + switch(type) { case PROP_COLORMAP: XCF.colormapptr = data ; break ; case PROP_COMPRESSION: XCF.compression = xcf_file[data] ; break ; default: /* Ignore unknown properties */ break ; } } layerfile = ptr ; - for( XCF.numLayers = 0 ; xcfOffset(ptr,8*4) ; XCF.numLayers++, ptr+=4 ) - ; + XCF.numLayers = 0; + while (1) { + errorStatus = xcfOffset(ptr,8*4, &ptrout); + if (errorStatus != XCF_OK) { + return XCF_ERROR; + } + if (!ptrout) { + break; + } + XCF.numLayers++; + ptr+=4; + } XCF.layers = xcfmalloc(XCF.numLayers * sizeof(struct xcfLayer)) ; for( i = 0 ; i < XCF.numLayers ; i++ ) { struct xcfLayer *L = XCF.layers + i ; ptr = xcfL(layerfile+4*(XCF.numLayers-1-i)) ; L->mode = GIMP_NORMAL_MODE ; L->opacity = 255 ; L->isVisible = 1 ; L->hasMask = 0 ; L->dim.width = xcfL(ptr); ptr+=4 ; L->dim.height = xcfL(ptr); ptr+=4 ; L->type = xcfL(ptr); ptr+=4 ; L->name = xcfString(ptr,&ptr); + if (L->name == XCF_PTR_EMPTY) { + return XCF_ERROR; + } L->propptr = ptr ; L->isGroup = 0; L->pathLength = 0; L->path = NULL; - while( (type = xcfNextprop(&ptr,&data)) != PROP_END ) { + while( (errorStatus = xcfNextprop(&ptr,&data, &type)) != XCF_ERROR && type != PROP_END ) { + if (errorStatus != XCF_OK) { + xcffree(XCF.layers); + XCF.layers = XCF_PTR_EMPTY; + return XCF_ERROR; + } switch(type) { case PROP_OPACITY: L->opacity = xcfL(data); if( L->opacity > 255 ) L->opacity = 255 ; break ; case PROP_VISIBLE: L->isVisible = xcfL(data) != 0 ; break ; case PROP_APPLY_MASK: L->hasMask = xcfL(data) != 0 ; break ; case PROP_OFFSETS: L->dim.c.l = (int32_t)(xcfL(data )) ; L->dim.c.t = (int32_t)(xcfL(data+4)) ; break ; case PROP_MODE: L->mode = xcfL(data); break ; case PROP_GROUP_ITEM: L->isGroup = 1 ; break; case PROP_ITEM_PATH: L->pathLength = (ptr - data - 2) / 4 ; if ( L->pathLength != 0 ) { L->path = xcfmalloc( L->pathLength * sizeof(unsigned) ) ; for ( j = 0; j!=L->pathLength; j++ ) *(L->path + j) = (unsigned)xcfL(data + 4 * j); } break; default: /* Ignore unknown properties */ break ; } } - xcfCheckspace(ptr,8,"(end of layer %s)",L->name); + if ((errorStatus = xcfCheckspace(ptr,8,"(end of layer %s)",L->name)) != XCF_OK) { + xcffree(XCF.layers); + XCF.layers = XCF_PTR_EMPTY; + return XCF_ERROR; + } L->pixels.tileptrs = 0 ; - L->pixels.hierarchy = xcfOffset(ptr ,4*4); + + if (xcfOffset(ptr , 4*4, &(L->pixels.hierarchy)) != XCF_OK) { + xcffree(XCF.layers); + XCF.layers = XCF_PTR_EMPTY; + return XCF_ERROR; + } L->mask.tileptrs = 0 ; - L->mask.hierarchy = xcfOffset(ptr+4,4*4); + if (xcfOffset(ptr+4, 4*4, &(L->mask.hierarchy)) != XCF_OK) { + xcffree(XCF.layers); + XCF.layers = XCF_PTR_EMPTY; + return XCF_ERROR; + } computeDimensions(&L->dim); + } + return XCF_OK; } diff --git a/plugins/impex/xcf/3rdparty/xcftools/xcftools.h b/plugins/impex/xcf/3rdparty/xcftools/xcftools.h index 7b5eb5f00b..501067fbd9 100644 --- a/plugins/impex/xcf/3rdparty/xcftools/xcftools.h +++ b/plugins/impex/xcf/3rdparty/xcftools/xcftools.h @@ -1,201 +1,207 @@ /* Generic functions and macros for reading XCF files * * 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. */ #ifndef XCFTOOLS_H #define XCFTOOLS_H #include "config.h" #include "enums.h" #include #include #if defined(HAVE_GETTEXT) && defined(ENABLE_NLS) #include #define _(s) gettext(s) void nls_init(void); #else #define _(s) (s) #define nls_init() (void)0 #endif #define N_(s) (s) #if HAVE_INTTYPES_H # define __STDC_FORMAT_MACROS # include #else /* These legacy fall-backs will probably work on every system * that does not supply a inttypes.h ... */ typedef unsigned char uint8_t ; typedef unsigned long int uint32_t; typedef signed char int8_t ; typedef signed long int int32_t ; # define PRIX32 "lX" # define PRIu32 "lu" # define PRIXPTR "lX" #endif #if __GNUC__ # define __ATTRIBUTE__ __attribute__ #else # define __ATTRIBUTE__(x) #endif #if HAVE_NETINET_IN_H # include #elif HAVE_ARPA_INET_H # include #elif WORDS_BIGENDIAN # define ntohl(x) (x) #else static inline uint32_t ntohl(uint32_t a) { return (a << 24) + ((a & 0xFF00) << 8) + ((a >> 8) & 0xFF00) + (a >> 24) ; } #endif #ifndef HAVE_STRCASECMP #define strcasecmp strcmp #endif /* Read a single word value from the XCF file */ /* Use + instead of | because that allows LEA instructions */ #define xcfBE(a) ( ((uint32_t)xcf_file[(a) ] << 24) + \ ((uint32_t)xcf_file[(a)+1] << 16) + \ ((uint32_t)xcf_file[(a)+2] << 8 ) + \ ((uint32_t)xcf_file[(a)+3] ) ) #define xcfLE(a) ( ((uint32_t)xcf_file[(a) ] ) + \ ((uint32_t)xcf_file[(a)+1] << 8 ) + \ ((uint32_t)xcf_file[(a)+2] << 16) + \ ((uint32_t)xcf_file[(a)+3] << 24) ) #if defined(CAN_DO_UNALIGNED_WORDS) # define xcfL(a) ntohl(*(uint32_t *)(xcf_file + (a))) #else # define xcfL(a) ((a) & 3 ? xcfBE(a) : ntohl(*(uint32_t *)(xcf_file + (a)))) #endif /* ****************************************************************** */ /* The following are exported from am OS-specific source file; * io-unix.c on unixish systems. */ void read_or_mmap_xcf(const char* filename, const char *unzipper); void free_or_close_xcf(void); /* ****************************************************************** */ /* utils.c */ + +#define XCF_ERROR 1 +#define XCF_OK 0 +#define XCF_PTR_EMPTY 0 + + extern const char *progname ; extern int verboseFlag ; void *xcfmalloc(size_t size); void xcffree(void*); void FatalGeneric(int status,const char* format,...) - __ATTRIBUTE__((format(printf,2,3),noreturn)) ; + __ATTRIBUTE__((format(printf,2,3))) ; void FatalUnexpected(const char* format,...) - __ATTRIBUTE__((format(printf,1,2),noreturn)) ; + __ATTRIBUTE__((format(printf,1,2))) ; void FatalBadXCF(const char* format,...) - __ATTRIBUTE__((format(printf,1,2),noreturn)) ; + __ATTRIBUTE__((format(printf,1,2))) ; void FatalUnsupportedXCF(const char* format,...) - __ATTRIBUTE__((format(printf,1,2),noreturn)) ; + __ATTRIBUTE__((format(printf,1,2))) ; -void gpl_blurb(void) __ATTRIBUTE__((noreturn)); +void gpl_blurb(void); FILE* openout(const char*); -void closeout(FILE *,const char*); +int closeout(FILE *,const char*); struct rect { int t, b, l, r ; }; #define isSubrect(A,B) \ ((A).l >= (B).l && (A).r <= (B).r && (A).t >= (B).t && (A).b <= (B).b) #define disjointRects(A,B) \ ((A).l >= (B).r || (A).r <= (B).l || (A).t >= (B).b || (A).b <= (B).t) /* ****************************************************************** */ /* xcf-general.c */ extern uint8_t *xcf_file ; extern size_t xcf_length ; extern int use_utf8 ; -void xcfCheckspace(uint32_t addr,int spaceafter, const char *format,...) +int xcfCheckspace(uint32_t addr,int spaceafter, const char *format,...) __ATTRIBUTE__((format(printf,3,4))); -uint32_t xcfOffset(uint32_t addr,int spaceafter); +int xcfOffset(uint32_t addr,int spaceafter, uint32_t* apparent); -int xcfNextprop(uint32_t *master,uint32_t *body); +int xcfNextprop(uint32_t *master,uint32_t *body, PropType* type); const char* xcfString(uint32_t ptr,uint32_t *after); /* These are hardcoded in the Gimp sources: */ #define TILE_SHIFT 6 #define TILE_WIDTH (1<> TILE_SHIFT) struct tileDimensions { struct rect c ; unsigned width, height ; unsigned tilesx, tilesy ; unsigned ntiles ; }; /* computeDimensions assumes that width, height, c.l, and c.t are set */ void computeDimensions(struct tileDimensions *); struct xcfTiles { const struct _convertParams *params ; uint32_t *tileptrs ; uint32_t hierarchy ; }; struct xcfLayer { struct tileDimensions dim ; const char *name ; GimpLayerModeEffects mode ; GimpImageType type ; unsigned int opacity ; int isVisible, hasMask ; uint32_t propptr ; struct xcfTiles pixels ; struct xcfTiles mask ; int isGroup ; unsigned pathLength ; unsigned *path ; }; extern struct xcfImage { int version ; unsigned width, height ; GimpImageBaseType type ; XcfCompressionType compression ; int numLayers ; struct xcfLayer *layers ; uint32_t colormapptr ; } XCF ; -void getBasicXcfInfo(void); +int getBasicXcfInfo(void); #endif /* XCFTOOLS_H */ diff --git a/plugins/impex/xcf/kis_xcf_import.cpp b/plugins/impex/xcf/kis_xcf_import.cpp index 138c82e4e9..41c14801f8 100644 --- a/plugins/impex/xcf/kis_xcf_import.cpp +++ b/plugins/impex/xcf/kis_xcf_import.cpp @@ -1,317 +1,330 @@ /* * Copyright (c) 2009 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "kis_xcf_import.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kis_iterator_ng.h" #include "kis_types.h" #include extern "C" { #include "xcftools.h" #include "pixels.h" #define GET_RED(x) (x >> RED_SHIFT) #define GET_GREEN(x) (x >> GREEN_SHIFT) #define GET_BLUE(x) (x >> BLUE_SHIFT) #define GET_ALPHA(x) (x >> ALPHA_SHIFT) } QString layerModeG2K(GimpLayerModeEffects mode) { switch (mode) { case GIMP_NORMAL_MODE: return COMPOSITE_OVER; case GIMP_DISSOLVE_MODE: return COMPOSITE_DISSOLVE; case GIMP_MULTIPLY_MODE: return COMPOSITE_MULT; case GIMP_SCREEN_MODE: return COMPOSITE_SCREEN; case GIMP_OVERLAY_MODE: case GIMP_SOFTLIGHT_MODE: return COMPOSITE_OVERLAY; case GIMP_DIFFERENCE_MODE: return COMPOSITE_DIFF; case GIMP_ADDITION_MODE: return COMPOSITE_ADD; case GIMP_SUBTRACT_MODE: return COMPOSITE_SUBTRACT; case GIMP_DARKEN_ONLY_MODE: return COMPOSITE_DARKEN; case GIMP_LIGHTEN_ONLY_MODE: return COMPOSITE_LIGHTEN; case GIMP_HUE_MODE: return COMPOSITE_HUE_HSL; case GIMP_SATURATION_MODE: return COMPOSITE_SATURATION_HSV; case GIMP_COLOR_MODE: return COMPOSITE_COLOR_HSL; case GIMP_VALUE_MODE: return COMPOSITE_VALUE; case GIMP_DIVIDE_MODE: return COMPOSITE_DIVIDE; case GIMP_DODGE_MODE: return COMPOSITE_DODGE; case GIMP_BURN_MODE: return COMPOSITE_BURN; case GIMP_ERASE_MODE: return COMPOSITE_ERASE; case GIMP_REPLACE_MODE: return COMPOSITE_COPY; case GIMP_HARDLIGHT_MODE: return COMPOSITE_HARD_LIGHT; case GIMP_COLOR_ERASE_MODE: case GIMP_NORMAL_NOPARTIAL_MODE: case GIMP_ANTI_ERASE_MODE: case GIMP_GRAIN_EXTRACT_MODE: return COMPOSITE_GRAIN_EXTRACT; case GIMP_GRAIN_MERGE_MODE: return COMPOSITE_GRAIN_MERGE; case GIMP_BEHIND_MODE: break; } dbgFile << "Unknown mode: " << mode; return COMPOSITE_OVER; } struct Layer { KisLayerSP layer; int depth; KisMaskSP mask; }; KisGroupLayerSP findGroup(const QVector &layers, const Layer& layer, int i) { for (; i < layers.size(); ++i) { KisGroupLayerSP group = dynamic_cast(const_cast(layers[i].layer.data())); if (group && (layers[i].depth == layer.depth -1)) { return group; } } return 0; } void addLayers(const QVector &layers, KisImageSP image, int depth) { for(int i = 0; i < layers.size(); i++) { const Layer &layer = layers[i]; if (layer.depth == depth) { KisGroupLayerSP group = (depth == 0 ? image->rootLayer() : findGroup(layers, layer, i)); image->addNode(layer.layer, group); if (layer.mask) { image->addNode(layer.mask, layer.layer); } } } } K_PLUGIN_FACTORY_WITH_JSON(XCFImportFactory, "krita_xcf_import.json", registerPlugin();) KisXCFImport::KisXCFImport(QObject *parent, const QVariantList &) : KisImportExportFilter(parent) { } KisXCFImport::~KisXCFImport() { } -KisImportExportFilter::ConversionStatus KisXCFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) +KisImportExportErrorCode KisXCFImport::convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP /*configuration*/) { + int errorStatus; + dbgFile << "Start decoding file"; QByteArray data = io->readAll(); xcf_file = (uint8_t*)data.data(); xcf_length = data.size(); io->close(); // Decode the data - getBasicXcfInfo() ; - - if (XCF.version < 0 || XCF.version > 3) { - document->setErrorMessage(i18n("This XCF file is too new; Krita cannot support XCF files written by GIMP 2.9 or newer.")); - return KisImportExportFilter::UnsupportedVersion; + if (getBasicXcfInfo() != XCF_OK) { + if (XCF.version < 0 || XCF.version > 3) { + document->setErrorMessage(i18n("This XCF file is too new; Krita cannot support XCF files written by GIMP 2.9 or newer.")); + return ImportExportCodes::FormatFeaturesUnsupported; + } + return ImportExportCodes::FileFormatIncorrect; } - initColormap(); + if(initColormap() != XCF_OK) { + return ImportExportCodes::FileFormatIncorrect; + } dbgFile << XCF.version << "width = " << XCF.width << "height = " << XCF.height << "layers = " << XCF.numLayers; // Create the image KisImageSP image = new KisImage(document->createUndoStore(), XCF.width, XCF.height, KoColorSpaceRegistry::instance()->rgb8(), "built image"); QVector layers; uint maxDepth = 0; // Read layers for (int i = 0; i < XCF.numLayers; ++i) { Layer layer; xcfLayer& xcflayer = XCF.layers[i]; dbgFile << i << " name = " << xcflayer.name << " opacity = " << xcflayer.opacity << "group:" << xcflayer.isGroup << xcflayer.pathLength; dbgFile << ppVar(xcflayer.dim.width) << ppVar(xcflayer.dim.height) << ppVar(xcflayer.dim.tilesx) << ppVar(xcflayer.dim.tilesy) << ppVar(xcflayer.dim.ntiles) << ppVar(xcflayer.dim.c.t) << ppVar(xcflayer.dim.c.l) << ppVar(xcflayer.dim.c.r) << ppVar(xcflayer.dim.c.b); maxDepth = qMax(maxDepth, xcflayer.pathLength); bool isRgbA = false; // Select the color space const KoColorSpace* colorSpace = 0; switch (xcflayer.type) { case GIMP_INDEXED_IMAGE: case GIMP_INDEXEDA_IMAGE: case GIMP_RGB_IMAGE: case GIMP_RGBA_IMAGE: colorSpace = KoColorSpaceRegistry::instance()->rgb8(); isRgbA = true; break; case GIMP_GRAY_IMAGE: case GIMP_GRAYA_IMAGE: colorSpace = KoColorSpaceRegistry::instance()->colorSpace(GrayAColorModelID.id(), Integer8BitsColorDepthID.id(), ""); isRgbA = false; break; } // Create the layer KisLayerSP kisLayer; if (xcflayer.isGroup) { kisLayer = new KisGroupLayer(image, QString::fromUtf8(xcflayer.name), xcflayer.opacity); } else { kisLayer = new KisPaintLayer(image, QString::fromUtf8(xcflayer.name), xcflayer.opacity, colorSpace); } // Set some properties kisLayer->setCompositeOpId(layerModeG2K(xcflayer.mode)); kisLayer->setVisible(xcflayer.isVisible); kisLayer->disableAlphaChannel(xcflayer.mode != GIMP_NORMAL_MODE); layer.layer = kisLayer; layer.depth = xcflayer.pathLength; // Copy the data in the image - initLayer(&xcflayer); + if ((errorStatus = initLayer(&xcflayer)) != XCF_OK) { + return ImportExportCodes::FileFormatIncorrect; + } int left = xcflayer.dim.c.l; int top = xcflayer.dim.c.t; if (!xcflayer.isGroup) { // Copy the data; for (unsigned int x = 0; x < xcflayer.dim.width; x += TILE_WIDTH) { for (unsigned int y = 0; y < xcflayer.dim.height; y += TILE_HEIGHT) { rect want; want.l = x + left; want.t = y + top; want.b = want.t + TILE_HEIGHT; want.r = want.l + TILE_WIDTH; Tile* tile = getMaskOrLayerTile(&xcflayer.dim, &xcflayer.pixels, want); + if (tile == XCF_PTR_EMPTY) { + return ImportExportCodes::FileFormatIncorrect; + } KisHLineIteratorSP it = kisLayer->paintDevice()->createHLineIteratorNG(x, y, TILE_WIDTH); rgba* data = tile->pixels; for (int v = 0; v < TILE_HEIGHT; ++v) { if (isRgbA) { // RGB image do { KoBgrTraits::setRed(it->rawData(), GET_RED(*data)); KoBgrTraits::setGreen(it->rawData(), GET_GREEN(*data)); KoBgrTraits::setBlue(it->rawData(), GET_BLUE(*data)); KoBgrTraits::setOpacity(it->rawData(), quint8(GET_ALPHA(*data)), 1); ++data; } while (it->nextPixel()); } else { // Grayscale image do { it->rawData()[0] = GET_RED(*data); it->rawData()[1] = GET_ALPHA(*data); ++data; } while (it->nextPixel()); } it->nextRow(); } } } // Move the layer to its position kisLayer->paintDevice()->setX(left); kisLayer->paintDevice()->setY(top); } // Create the mask if (xcflayer.hasMask) { KisTransparencyMaskSP mask = new KisTransparencyMask(); layer.mask = mask; mask->initSelection(kisLayer); for (unsigned int x = 0; x < xcflayer.dim.width; x += TILE_WIDTH) { for (unsigned int y = 0; y < xcflayer.dim.height; y += TILE_HEIGHT) { rect want; want.l = x + left; want.t = y + top; want.b = want.t + TILE_HEIGHT; want.r = want.l + TILE_WIDTH; Tile* tile = getMaskOrLayerTile(&xcflayer.dim, &xcflayer.mask, want); + if (tile == XCF_PTR_EMPTY) { + return ImportExportCodes::FileFormatIncorrect; + } KisHLineIteratorSP it = mask->paintDevice()->createHLineIteratorNG(x, y, TILE_WIDTH); rgba* data = tile->pixels; for (int v = 0; v < TILE_HEIGHT; ++v) { do { it->rawData()[0] = GET_ALPHA(*data); ++data; } while (it->nextPixel()); it->nextRow(); } } } mask->paintDevice()->setX(left); mask->paintDevice()->setY(top); } dbgFile << xcflayer.pixels.tileptrs; layers.append(layer); } for (uint i = 0; i <= maxDepth; ++i) { addLayers(layers, image, i); } document->setCurrentImage(image); - return KisImportExportFilter::OK; + return ImportExportCodes::OK; } #include "kis_xcf_import.moc" diff --git a/plugins/impex/xcf/kis_xcf_import.h b/plugins/impex/xcf/kis_xcf_import.h index cfbe32ebf6..67683cf51b 100644 --- a/plugins/impex/xcf/kis_xcf_import.h +++ b/plugins/impex/xcf/kis_xcf_import.h @@ -1,40 +1,40 @@ /* * Copyright (c) 2009 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 program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef _KIS_XCF_IMPORT_H_ #define _KIS_XCF_IMPORT_H_ #include #include #include class KisDocument; class KisXCFImport : public KisImportExportFilter { Q_OBJECT public: KisXCFImport(QObject *parent, const QVariantList &); ~KisXCFImport() override; public: - KisImportExportFilter::ConversionStatus convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; + KisImportExportErrorCode convert(KisDocument *document, QIODevice *io, KisPropertiesConfigurationSP configuration = 0) override; }; #endif diff --git a/plugins/impex/xcf/tests/data/incorrectFormatFile.txt b/plugins/impex/xcf/tests/data/incorrectFormatFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/xcf/tests/data/readonlyFile.txt b/plugins/impex/xcf/tests/data/readonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/xcf/tests/data/writeonlyFile.txt b/plugins/impex/xcf/tests/data/writeonlyFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugins/impex/xcf/tests/kis_xcf_test.cpp b/plugins/impex/xcf/tests/kis_xcf_test.cpp index 25c1c90c2a..51546a467a 100644 --- a/plugins/impex/xcf/tests/kis_xcf_test.cpp +++ b/plugins/impex/xcf/tests/kis_xcf_test.cpp @@ -1,38 +1,62 @@ /* * Copyright (C) 2007 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_xcf_test.h" #include #include #include #include "filestest.h" #ifndef FILES_DATA_DIR #error "FILES_DATA_DIR not set. A directory with the data used for testing the importing of files in krita" #endif +const QString XcfMimetype = "image/x-xcf"; + + void KisXCFTest::testFiles() { TestUtil::testFiles(QString(FILES_DATA_DIR) + "/sources", QStringList(), QString(), 1); } + + +void KisXCFTest::testImportFromWriteonly() +{ + TestUtil::testImportFromWriteonly(QString(FILES_DATA_DIR), XcfMimetype); +} + +/* +void KisXCFTest::testExportToReadonly() +{ + TestUtil::testExportToReadonly(QString(FILES_DATA_DIR), XcfMimetype); +} +*/ + + +void KisXCFTest::testImportIncorrectFormat() +{ + TestUtil::testImportIncorrectFormat(QString(FILES_DATA_DIR), XcfMimetype); +} + + KISTEST_MAIN(KisXCFTest) diff --git a/plugins/impex/xcf/tests/kis_xcf_test.h b/plugins/impex/xcf/tests/kis_xcf_test.h index 6ca18ec23d..11e3f37c72 100644 --- a/plugins/impex/xcf/tests/kis_xcf_test.h +++ b/plugins/impex/xcf/tests/kis_xcf_test.h @@ -1,31 +1,36 @@ /* * Copyright (C) 2007 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. */ #ifndef _KIS_XCF_TEST_H_ #define _KIS_XCF_TEST_H_ #include class KisXCFTest : public QObject { Q_OBJECT private Q_SLOTS: void testFiles(); + + void testImportFromWriteonly(); + // You can't export to xcf + /* void testExportToReadonly(); */ + void testImportIncorrectFormat(); }; #endif diff --git a/sdk/tests/filestest.h b/sdk/tests/filestest.h index 014cff7a6d..940ebee00f 100644 --- a/sdk/tests/filestest.h +++ b/sdk/tests/filestest.h @@ -1,118 +1,299 @@ /* * Copyright (C) 2007 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. */ #ifndef FILESTEST #define FILESTEST #include "testutil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include +#include +#include +#include namespace TestUtil { -void testFiles(const QString& _dirname, const QStringList& exclusions, const QString &resultSuffix = QString(), int fuzzy = 0, int maxNumFailingPixels = 0) +void testFiles(const QString& _dirname, const QStringList& exclusions, const QString &resultSuffix = QString(), int fuzzy = 0, int maxNumFailingPixels = 0, bool showDebug = true) { QDir dirSources(_dirname); QStringList failuresFileInfo; QStringList failuresDocImage; QStringList failuresCompare; Q_FOREACH (QFileInfo sourceFileInfo, dirSources.entryInfoList()) { qDebug() << sourceFileInfo.fileName(); if (exclusions.indexOf(sourceFileInfo.fileName()) > -1) { continue; } if (!sourceFileInfo.isHidden() && !sourceFileInfo.isDir()) { QFileInfo resultFileInfo(QString(FILES_DATA_DIR) + "/results/" + sourceFileInfo.fileName() + resultSuffix + ".png"); if (!resultFileInfo.exists()) { failuresFileInfo << resultFileInfo.fileName(); continue; } KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); KisImportExportManager manager(doc); doc->setFileBatchMode(true); - KisImportExportFilter::ConversionStatus status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString()); + KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString()); Q_UNUSED(status); if (!doc->image()) { failuresDocImage << sourceFileInfo.fileName(); continue; } QString id = doc->image()->colorSpace()->id(); if (id != "GRAYA" && id != "GRAYAU16" && id != "RGBA" && id != "RGBA16") { dbgKrita << "Images need conversion"; doc->image()->convertImageColorSpace(KoColorSpaceRegistry::instance()->rgb8(), KoColorConversionTransformation::IntentAbsoluteColorimetric, KoColorConversionTransformation::NoOptimization); } qApp->processEvents(); doc->image()->waitForDone(); QImage sourceImage = doc->image()->projection()->convertToQImage(0, doc->image()->bounds()); QImage resultImage(resultFileInfo.absoluteFilePath()); resultImage = resultImage.convertToFormat(QImage::Format_ARGB32); sourceImage = sourceImage.convertToFormat(QImage::Format_ARGB32); QPoint pt; - if (!TestUtil::compareQImages(pt, resultImage, sourceImage, fuzzy, fuzzy, maxNumFailingPixels)) { + if (!TestUtil::compareQImages(pt, resultImage, sourceImage, fuzzy, fuzzy, maxNumFailingPixels, showDebug)) { failuresCompare << sourceFileInfo.fileName() + ": " + QString("Pixel (%1,%2) has different values").arg(pt.x()).arg(pt.y()).toLatin1(); sourceImage.save(sourceFileInfo.fileName() + ".png"); resultImage.save(resultFileInfo.fileName() + ".expected.png"); continue; } delete doc; } } if (failuresCompare.isEmpty() && failuresDocImage.isEmpty() && failuresFileInfo.isEmpty()) { return; } qWarning() << "Comparison failures: " << failuresCompare; qWarning() << "No image failures: " << failuresDocImage; qWarning() << "No comparison image: " << failuresFileInfo; QFAIL("Failed testing files"); } + +void prepareFile(QFileInfo sourceFileInfo, bool removePermissionToWrite, bool removePermissionToRead) +{ + + QFileDevice::Permissions permissionsBefore; + if (sourceFileInfo.exists()) { + permissionsBefore = QFile::permissions(sourceFileInfo.absoluteFilePath()); + ENTER_FUNCTION() << permissionsBefore; + } else { + QFile file(sourceFileInfo.absoluteFilePath()); + bool opened = file.open(QIODevice::ReadWrite); + if (!opened) { + qDebug() << "The file cannot be opened/created: " << file.error() << file.errorString(); + } + permissionsBefore = file.permissions(); + file.close(); + } + QFileDevice::Permissions permissionsNow = permissionsBefore; + if (removePermissionToRead) { + permissionsNow = permissionsBefore & + (~QFileDevice::ReadUser & ~QFileDevice::ReadOwner + & ~QFileDevice::ReadGroup & ~QFileDevice::ReadOther); + } + if (removePermissionToWrite) { + permissionsNow = permissionsBefore & + (~QFileDevice::WriteUser & ~QFileDevice::WriteOwner + & ~QFileDevice::WriteGroup & ~QFileDevice::WriteOther); + } + + QFile::setPermissions(sourceFileInfo.absoluteFilePath(), permissionsNow); + +} + +void restorePermissionsToReadAndWrite(QFileInfo sourceFileInfo) +{ + QFileDevice::Permissions permissionsNow = sourceFileInfo.permissions(); + QFileDevice::Permissions permissionsAfter = permissionsNow + | (QFileDevice::ReadUser | QFileDevice::ReadOwner + | QFileDevice::ReadGroup | QFileDevice::ReadOther) + | (QFileDevice::WriteUser | QFileDevice::WriteOwner + | QFileDevice::WriteGroup | QFileDevice::WriteOther); + QFile::setPermissions(sourceFileInfo.absoluteFilePath(), permissionsAfter); +} + + +void testImportFromWriteonly(const QString& _dirname, QString mimetype = "") +{ + QString writeonlyFilename = _dirname + "writeonlyFile.txt"; + QFileInfo sourceFileInfo(writeonlyFilename); + + prepareFile(sourceFileInfo, false, true); + + KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); + + KisImportExportManager manager(doc); + doc->setFileBatchMode(true); + + KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), mimetype); + qDebug() << "import result = " << status; + + QString failMessage = ""; + bool fail = false; + + if (status == ImportExportCodes::FileFormatIncorrect) { + qDebug() << "Make sure you set the correct mimetype in the test case."; + failMessage = "Incorrect status."; + fail = true; + } + + qApp->processEvents(); + + if (doc->image()) { + doc->image()->waitForDone(); + } + + delete doc; + + restorePermissionsToReadAndWrite(sourceFileInfo); + + QVERIFY(!status.isOk()); + if (fail) { + QFAIL(failMessage.toUtf8()); + } + +} + + +void testExportToReadonly(const QString& _dirname, QString mimetype = "", bool useDocumentExport=false) +{ + QString readonlyFilename = _dirname + "readonlyFile.txt"; + + QFileInfo sourceFileInfo(readonlyFilename); + prepareFile(sourceFileInfo, true, false); + + KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); + + KisImportExportManager manager(doc); + doc->setFileBatchMode(true); + + KisImportExportErrorCode status = ImportExportCodes::OK; + QString failMessage = ""; + bool fail = false; + + { + MaskParent p; + ENTER_FUNCTION() << doc->image(); + + doc->setCurrentImage(p.image); + + if (useDocumentExport) { + bool result = doc->exportDocumentSync(QUrl(sourceFileInfo.absoluteFilePath()), mimetype.toUtf8()); + status = result ? ImportExportCodes::OK : ImportExportCodes::Failure; + } else { + status = manager.exportDocument(sourceFileInfo.absoluteFilePath(), sourceFileInfo.absoluteFilePath(), mimetype.toUtf8()); + } + + qDebug() << "export result = " << status; + + if (!useDocumentExport && status == ImportExportCodes::FileFormatIncorrect) { + qDebug() << "Make sure you set the correct mimetype in the test case."; + failMessage = "Incorrect status."; + fail = true; + } + + + qApp->processEvents(); + + if (doc->image()) { + doc->image()->waitForDone(); + } + + } + delete doc; + + restorePermissionsToReadAndWrite(sourceFileInfo); + + QVERIFY(!status.isOk()); + if (fail) { + QFAIL(failMessage.toUtf8()); + } +} + + + +void testImportIncorrectFormat(const QString& _dirname, QString mimetype = "") +{ + QString incorrectFormatFilename = _dirname + "incorrectFormatFile.txt"; + QFileInfo sourceFileInfo(incorrectFormatFilename); + + prepareFile(sourceFileInfo, false, false); + + KisDocument *doc = qobject_cast(KisPart::instance()->createDocument()); + + KisImportExportManager manager(doc); + doc->setFileBatchMode(true); + + KisImportExportErrorCode status = manager.importDocument(sourceFileInfo.absoluteFilePath(), mimetype); + qDebug() << "import result = " << status; + + + qApp->processEvents(); + + if (doc->image()) { + doc->image()->waitForDone(); + } + + delete doc; + + QVERIFY(!status.isOk()); + QVERIFY(status == KisImportExportErrorCode(ImportExportCodes::FileFormatIncorrect) + || status == KisImportExportErrorCode(ImportExportCodes::ErrorWhileReading)); // in case the filter doesn't know if it can't read or just parse + +} + + + + + } #endif diff --git a/sdk/tests/qimage_test_util.h b/sdk/tests/qimage_test_util.h index e1d0a294ce..fffbb596fc 100644 --- a/sdk/tests/qimage_test_util.h +++ b/sdk/tests/qimage_test_util.h @@ -1,245 +1,246 @@ /* * Copyright (c) 2016 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 QIMAGE_TEST_UTIL_H #define QIMAGE_TEST_UTIL_H #ifdef FILES_OUTPUT_DIR #include #include namespace TestUtil { inline QString fetchExternalDataFileName(const QString relativeFileName) { static QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); static QString unittestsDataDirPath = "KRITA_UNITTESTS_DATA_DIR"; QString path; if (!env.contains(unittestsDataDirPath)) { warnKrita << "Environment variable" << unittestsDataDirPath << "is not set"; return QString(); } else { path = env.value(unittestsDataDirPath, ""); } QString filename = path + QDir::separator() + relativeFileName; return filename; } inline QString fetchDataFileLazy(const QString relativeFileName, bool externalTest = false) { if (externalTest) { return fetchExternalDataFileName(relativeFileName); } else { QString filename = QString(FILES_DATA_DIR) + QDir::separator() + relativeFileName; if (QFileInfo(filename).exists()) { return filename; } filename = QString(FILES_DEFAULT_DATA_DIR) + QDir::separator() + relativeFileName; if (QFileInfo(filename).exists()) { return filename; } } return QString(); } // quint8 arguments are automatically converted into int inline bool compareChannels(int ch1, int ch2, int fuzzy) { return qAbs(ch1 - ch2) <= fuzzy; } -inline bool compareQImages(QPoint & pt, const QImage & image1, const QImage & image2, int fuzzy = 0, int fuzzyAlpha = 0, int maxNumFailingPixels = 0) +inline bool compareQImages(QPoint & pt, const QImage & image1, const QImage & image2, int fuzzy = 0, int fuzzyAlpha = 0, int maxNumFailingPixels = 0, bool showDebug = true) { // QTime t; // t.start(); const int w1 = image1.width(); const int h1 = image1.height(); const int w2 = image2.width(); const int h2 = image2.height(); const int bytesPerLine = image1.bytesPerLine(); if (w1 != w2 || h1 != h2) { pt.setX(-1); pt.setY(-1); qDebug() << "Images have different sizes" << image1.size() << image2.size(); return false; } int numFailingPixels = 0; for (int y = 0; y < h1; ++y) { const QRgb * const firstLine = reinterpret_cast(image2.scanLine(y)); const QRgb * const secondLine = reinterpret_cast(image1.scanLine(y)); if (memcmp(firstLine, secondLine, bytesPerLine) != 0) { for (int x = 0; x < w1; ++x) { const QRgb a = firstLine[x]; const QRgb b = secondLine[x]; const bool same = compareChannels(qRed(a), qRed(b), fuzzy) && compareChannels(qGreen(a), qGreen(b), fuzzy) && compareChannels(qBlue(a), qBlue(b), fuzzy); const bool sameAlpha = compareChannels(qAlpha(a), qAlpha(b), fuzzyAlpha); const bool bothTransparent = sameAlpha && qAlpha(a)==0; if (!bothTransparent && (!same || !sameAlpha)) { pt.setX(x); pt.setY(y); numFailingPixels++; - qDebug() << " Different at" << pt - << "source" << qRed(a) << qGreen(a) << qBlue(a) << qAlpha(a) - << "dest" << qRed(b) << qGreen(b) << qBlue(b) << qAlpha(b) - << "fuzzy" << fuzzy - << "fuzzyAlpha" << fuzzyAlpha - << "(" << numFailingPixels << "of" << maxNumFailingPixels << "allowed )"; - + if (showDebug) { + qDebug() << " Different at" << pt + << "source" << qRed(a) << qGreen(a) << qBlue(a) << qAlpha(a) + << "dest" << qRed(b) << qGreen(b) << qBlue(b) << qAlpha(b) + << "fuzzy" << fuzzy + << "fuzzyAlpha" << fuzzyAlpha + << "(" << numFailingPixels << "of" << maxNumFailingPixels << "allowed )"; + } if (numFailingPixels > maxNumFailingPixels) { return false; } } } } } // qDebug() << "compareQImages time elapsed:" << t.elapsed(); // qDebug() << "Images are identical"; return true; } inline bool checkQImageImpl(bool externalTest, const QImage &srcImage, const QString &testName, const QString &prefix, const QString &name, int fuzzy, int fuzzyAlpha, int maxNumFailingPixels) { QImage image = srcImage.convertToFormat(QImage::Format_ARGB32); if (fuzzyAlpha == -1) { fuzzyAlpha = fuzzy; } QString filename(prefix + "_" + name + ".png"); QString dumpName(prefix + "_" + name + "_expected.png"); const QString standardPath = testName + QDir::separator() + prefix + QDir::separator() + filename; QString fullPath = fetchDataFileLazy(standardPath, externalTest); if (fullPath.isEmpty() || !QFileInfo(fullPath).exists()) { // Try without the testname subdirectory fullPath = fetchDataFileLazy(prefix + QDir::separator() + filename, externalTest); } if (fullPath.isEmpty() || !QFileInfo(fullPath).exists()) { // Try without the prefix subdirectory fullPath = fetchDataFileLazy(testName + QDir::separator() + filename, externalTest); } if (!QFileInfo(fullPath).exists()) { fullPath = ""; } bool canSkipExternalTest = fullPath.isEmpty() && externalTest; QImage ref(fullPath); bool valid = true; QPoint t; if(!compareQImages(t, image, ref, fuzzy, fuzzyAlpha, maxNumFailingPixels)) { bool saveStandardResults = true; if (canSkipExternalTest) { static QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); static QString writeUnittestsVar = "KRITA_WRITE_UNITTESTS"; int writeUnittests = env.value(writeUnittestsVar, "0").toInt(); if (writeUnittests) { QString path = fetchExternalDataFileName(standardPath); QFileInfo pathInfo(path); QDir directory; directory.mkpath(pathInfo.path()); qDebug() << "--- Saving reference image:" << name << path; image.save(path); saveStandardResults = false; } else { qDebug() << "--- External image not found. Skipping..." << name; } } else { qDebug() << "--- Wrong image:" << name; valid = false; } if (saveStandardResults) { image.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + filename); ref.save(QString(FILES_OUTPUT_DIR) + QDir::separator() + dumpName); } } return valid; } inline bool checkQImage(const QImage &image, const QString &testName, const QString &prefix, const QString &name, int fuzzy = 0, int fuzzyAlpha = -1, int maxNumFailingPixels = 0) { return checkQImageImpl(false, image, testName, prefix, name, fuzzy, fuzzyAlpha, maxNumFailingPixels); } inline bool checkQImageExternal(const QImage &image, const QString &testName, const QString &prefix, const QString &name, int fuzzy = 0, int fuzzyAlpha = -1, int maxNumFailingPixels = 0) { return checkQImageImpl(true, image, testName, prefix, name, fuzzy, fuzzyAlpha, maxNumFailingPixels); } } #endif // FILES_OUTPUT_DIR #endif // QIMAGE_TEST_UTIL_H