diff --git a/krita/plugins/formats/psd/psd_loader.cpp b/krita/plugins/formats/psd/psd_loader.cpp index 687bacca0a..c0724b89ef 100644 --- a/krita/plugins/formats/psd/psd_loader.cpp +++ b/krita/plugins/formats/psd/psd_loader.cpp @@ -1,354 +1,356 @@ /* * 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 "kis_resource_server_provider.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_job = 0; m_stop = false; } PSDLoader::~PSDLoader() { } KisImageBuilder_Result PSDLoader::decode(const KUrl& uri) { // open the file QFile f(uri.toLocalFile()); if (!f.exists()) { return KisImageBuilder_RESULT_NOT_EXIST; } if (!f.open(QIODevice::ReadOnly)) { return KisImageBuilder_RESULT_FAILURE; } dbgFile << "pos:" << f.pos(); PSDHeader header; if (!header.read(&f)) { dbgFile << "failed reading header: " << header.error; return KisImageBuilder_RESULT_FAILURE; } dbgFile << header; dbgFile << "Read header. pos:" << f.pos(); PSDColorModeBlock colorModeBlock(header.colormode); if (!colorModeBlock.read(&f)) { dbgFile << "failed reading colormode block: " << colorModeBlock.error; return KisImageBuilder_RESULT_FAILURE; } dbgFile << "Read color mode block. pos:" << f.pos(); PSDImageResourceSection resourceSection; if (!resourceSection.read(&f)) { dbgFile << "failed image reading resource section: " << resourceSection.error; return KisImageBuilder_RESULT_FAILURE; } dbgFile << "Read image resource section. pos:" << f.pos(); PSDLayerMaskSection layerSection(header); if (!layerSection.read(&f)) { dbgFile << "failed reading layer/mask section: " << layerSection.error; return KisImageBuilder_RESULT_FAILURE; } dbgFile << "Read layer/mask section. " << layerSection.nLayers << "layers. pos:" << f.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; } // 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; } // Creating the KisImage m_image = new KisImage(m_doc->createUndoStore(), header.width, header.height, cs, f.fileName()); 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) { 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 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" << f.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(&f, 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; } // More than one layer, so now construct the Krita image from the info we read. QStack groupStack; groupStack.push(m_image->rootLayer()); 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")) { + 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.isEmpty()) { KisGroupLayerSP groupLayer = groupStack.pop(); 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 goupr // layer, so flip it if (compositeOp == COMPOSITE_PASS_THROUGH) { compositeOp = COMPOSITE_OVER; groupLayer->setPassThroughMode(true); } groupLayer->setCompositeOp(compositeOp); newLayer = groupLayer; } } else { KisPaintLayerSP layer = new KisPaintLayer(m_image, layerRecord->layerName, layerRecord->opacity); layer->setCompositeOp(psd_blendmode_to_composite_op(layerRecord->blendModeKey)); const QDomDocument &styleXml = layerRecord->infoBlocks.layerStyleXml; if (!styleXml.isNull()) { allStylesXml << LayerStyleMapping(styleXml, layer); } if (!layerRecord->readPixelData(&f, layer->paintDevice())) { dbgFile << "failed reading channels for layer: " << layerRecord->layerName << layerRecord->error; return KisImageBuilder_RESULT_FAILURE; } if (!groupStack.isEmpty()) { m_image->addNode(layer, groupStack.top()); } else { m_image->addNode(layer, m_image->root()); } layer->setVisible(layerRecord->visible); newLayer = layer; } foreach(ChannelInfo *channelInfo, layerRecord->channelInfoRecords) { if (channelInfo->channelId < -1) { KisTransparencyMaskSP mask = new KisTransparencyMask(); mask->setName(i18n("Transparency Mask")); mask->initSelection(newLayer); if (!layerRecord->readMask(&f, mask->paintDevice(), channelInfo)) { dbgFile << "failed reading masks for layer: " << layerRecord->layerName << layerRecord->error; } m_image->addNode(mask, newLayer); } } } const QVector &embeddedPatterns = layerSection.globalInfoSection.embeddedPatterns; KisAslLayerStyleSerializer serializer; if (!embeddedPatterns.isEmpty()) { foreach (const QDomDocument &doc, embeddedPatterns) { serializer.registerPSDPattern(doc); } } QVector allStylesForServer; if (!allStylesXml.isEmpty()) { 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 { qWarning() << "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_ASSERT_RECOVER_NOOP(!collection->valid()); collection->setLayerStyles(allStylesForServer); KIS_ASSERT_RECOVER_NOOP(collection->valid()); KoResourceServer *server = KisResourceServerProvider::instance()->layerStyleCollectionServer(); server->addResource(collection, false); } m_image->unlock(); return KisImageBuilder_RESULT_OK; } KisImageBuilder_Result PSDLoader::buildImage(const KUrl& uri) { if (uri.isEmpty()) return KisImageBuilder_RESULT_NO_URI; if (!KIO::NetAccess::exists(uri, KIO::NetAccess::SourceSide, qApp->activeWindow())) { return KisImageBuilder_RESULT_NOT_EXIST; } // We're not set up to handle asynchronous loading at the moment. KisImageBuilder_Result result = KisImageBuilder_RESULT_FAILURE; QString tmpFile; if (KIO::NetAccess::download(uri, tmpFile, qApp->activeWindow())) { KUrl uriTF; uriTF.setPath( tmpFile ); result = decode(uriTF); KIO::NetAccess::removeTempFile(tmpFile); } return result; } KisImageWSP PSDLoader::image() { return m_image; } void PSDLoader::cancel() { m_stop = true; } #include "psd_loader.moc" diff --git a/krita/plugins/formats/psd/tests/data/test_krita_psd_from_opencanvas.psd b/krita/plugins/formats/psd/tests/data/test_krita_psd_from_opencanvas.psd new file mode 100644 index 0000000000..1cd011fb58 Binary files /dev/null and b/krita/plugins/formats/psd/tests/data/test_krita_psd_from_opencanvas.psd differ diff --git a/krita/plugins/formats/psd/tests/kis_psd_test.cpp b/krita/plugins/formats/psd/tests/kis_psd_test.cpp index b833241ce5..d22cf85ee1 100644 --- a/krita/plugins/formats/psd/tests/kis_psd_test.cpp +++ b/krita/plugins/formats/psd/tests/kis_psd_test.cpp @@ -1,243 +1,253 @@ /* * 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" 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()); manager.setBatchMode(true); KisImportExportFilter::ConversionStatus status; QString s = manager.importDocument(sourceFileInfo.absoluteFilePath(), QString(), status); qDebug() << s; Q_ASSERT(doc->image()); } QSharedPointer openPsdDocument(const QFileInfo &fileInfo) { QSharedPointer doc(qobject_cast(KisPart::instance()->createDocument())); KisImportExportManager manager(doc.data()); manager.setBatchMode(true); KisImportExportFilter::ConversionStatus status; QString s = manager.importDocument(fileInfo.absoluteFilePath(), QString(), 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")); doc->setBackupFile(false); doc->setOutputMimeType("image/vnd.adobe.photoshop"); QFileInfo dstFileInfo(QDir::currentPath() + QDir::separator() + "test_tmask.psd"); bool retval = doc->saveAs(KUrl(dstFileInfo.absoluteFilePath())); 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")); 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 = dynamic_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 = dynamic_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 = dynamic_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 = dynamic_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->setBackupFile(false); doc->setOutputMimeType("image/vnd.adobe.photoshop"); QFileInfo dstFileInfo(QDir::currentPath() + QDir::separator() + "test_save_styles.psd"); bool retval = doc->saveAs(KUrl(dstFileInfo.absoluteFilePath())); 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 = dynamic_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()); +} QTEST_KDEMAIN(KisPSDTest, GUI) #include "kis_psd_test.moc" diff --git a/krita/plugins/formats/psd/tests/kis_psd_test.h b/krita/plugins/formats/psd/tests/kis_psd_test.h index 9e854aada0..9f010c9439 100644 --- a/krita/plugins/formats/psd/tests/kis_psd_test.h +++ b/krita/plugins/formats/psd/tests/kis_psd_test.h @@ -1,41 +1,43 @@ /* * 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(); }; #endif