File indexing completed on 2024-04-28 15:39:40
0001 /************************************************************************ 0002 * * 0003 * This file is part of Kooka, a scanning/OCR application using * 0004 * Qt <http://www.qt.io> and KDE Frameworks <http://www.kde.org>. * 0005 * * 0006 * Copyright (C) 2000-2016 Klaas Freitag <freitag@suse.de> * 0007 * Jonathan Marten <jjm@keelhaul.me.uk> * 0008 * * 0009 * Kooka is free software; you can redistribute it and/or modify it * 0010 * under the terms of the GNU Library General Public License as * 0011 * published by the Free Software Foundation and appearing in the * 0012 * file COPYING included in the packaging of this file; either * 0013 * version 2 of the License, or (at your option) any later version. * 0014 * * 0015 * As a special exception, permission is given to link this program * 0016 * with any version of the KADMOS OCR/ICR engine (a product of * 0017 * reRecognition GmbH, Kreuzlingen), and distribute the resulting * 0018 * executable without including the source code for KADMOS in the * 0019 * source distribution. * 0020 * * 0021 * This program is distributed in the hope that it will be useful, * 0022 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 0023 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 0024 * GNU General Public License for more details. * 0025 * * 0026 * You should have received a copy of the GNU General Public * 0027 * License along with this program; see the file COPYING. If * 0028 * not, see <http://www.gnu.org/licenses/>. * 0029 * * 0030 ************************************************************************/ 0031 0032 #include "abstractocrengine.h" 0033 0034 #include <qdir.h> 0035 #include <qfileinfo.h> 0036 #include <qprocess.h> 0037 #include <qtemporaryfile.h> 0038 0039 #include <qtextdocument.h> 0040 #include <qtextcursor.h> 0041 0042 #include <kwidgetsaddons_version.h> 0043 #include <kmessagebox.h> 0044 #include <klocalizedstring.h> 0045 #include <kcolorscheme.h> 0046 #include <kconfigskeleton.h> 0047 0048 #include "imagecanvas.h" 0049 #include "imageformat.h" 0050 0051 #include "abstractocrdialogue.h" 0052 #include "ocr_logging.h" 0053 0054 0055 // Constructor/destructor and external engine creation 0056 // --------------------------------------------------- 0057 0058 AbstractOcrEngine::AbstractOcrEngine(QObject *pnt, const char *name) 0059 : AbstractPlugin(pnt), 0060 m_ocrProcess(nullptr), 0061 m_ocrRunning(false), 0062 m_ocrDialog(nullptr), 0063 m_imgCanvas(nullptr), 0064 m_document(nullptr), 0065 m_cursor(nullptr), 0066 m_currHighlight(-1), 0067 m_trackingActive(false) 0068 { 0069 setObjectName(name); 0070 qCDebug(OCR_LOG) << objectName(); 0071 0072 m_parent = nullptr; 0073 0074 m_resolvedBW = false; // have not examined image yet 0075 } 0076 0077 0078 AbstractOcrEngine::~AbstractOcrEngine() 0079 { 0080 qCDebug(OCR_LOG) << objectName(); 0081 if (m_ocrProcess!=nullptr) delete m_ocrProcess; 0082 if (m_ocrDialog!=nullptr) delete m_ocrDialog; 0083 } 0084 0085 0086 /* 0087 * This is called to introduce a new image, usually if the user clicks on a 0088 * new image either in the gallery or on the thumbnailview. 0089 */ 0090 void AbstractOcrEngine::setImage(ScanImage::Ptr img) 0091 { 0092 m_introducedImage = img; // shared copy of original 0093 m_resolvedBW = false; // invalidate previous result 0094 0095 if (m_ocrDialog!=nullptr) m_ocrDialog->introduceImage(m_introducedImage); 0096 m_trackingActive = false; 0097 } 0098 0099 0100 /* 0101 * Starts the visual OCR process. Depending on the OCR engine, this function creates 0102 * a new dialog, and shows it. 0103 */ 0104 bool AbstractOcrEngine::openOcrDialogue(QWidget *pnt) 0105 { 0106 if (m_ocrRunning) { 0107 KMessageBox::error(pnt, i18n("OCR is already in progress")); 0108 return (false); 0109 } 0110 0111 m_parent = pnt; 0112 m_errorText.clear(); // ready for new messages 0113 0114 m_ocrDialog = createOcrDialogue(this, pnt); 0115 Q_ASSERT(m_ocrDialog!=nullptr); 0116 0117 if (!m_ocrDialog->setupGui()) 0118 { 0119 const QString msg = collectErrorMessages(i18n("OCR could not be started."), 0120 i18n("Check the OCR engine selection and settings.")); 0121 int result = KMessageBox::warningContinueCancel(pnt, msg, 0122 i18n("OCR Setup Error"), 0123 KGuiItem(i18n("Configure OCR..."))); 0124 if (result==KMessageBox::Continue) emit openOcrPrefs(); 0125 return (false); // with no OCR dialogue 0126 } 0127 0128 connect(m_ocrDialog, &AbstractOcrDialogue::signalOcrStart, this, &AbstractOcrEngine::slotStartOCR); 0129 connect(m_ocrDialog, &AbstractOcrDialogue::signalOcrStop, this, &AbstractOcrEngine::slotStopOCR); 0130 connect(m_ocrDialog, &QDialog::rejected, this, &AbstractOcrEngine::slotClose); 0131 0132 m_ocrDialog->introduceImage(m_introducedImage); 0133 m_ocrDialog->show(); 0134 0135 // TODO: m_ocrActive would better reflect the function (if indeed useful at all) 0136 m_ocrRunning = true; 0137 return (true); 0138 } 0139 0140 0141 /* Called by "Close" used while OCR is not in progress */ 0142 void AbstractOcrEngine::slotClose() 0143 { 0144 stopOcrProcess(false); 0145 } 0146 0147 0148 /* Called by "Stop" used while OCR is in progress */ 0149 void AbstractOcrEngine::slotStopOCR() 0150 { 0151 Q_ASSERT(m_ocrDialog!=nullptr); 0152 0153 stopOcrProcess(true); 0154 m_ocrDialog->enableGUI(false); // enable controls again 0155 } 0156 0157 0158 /* Called by "Start" used while OCR is not in progress */ 0159 void AbstractOcrEngine::slotStartOCR() 0160 { 0161 Q_ASSERT(m_ocrDialog!=nullptr); 0162 0163 m_ocrDialog->enableGUI(true); // disable controls while running 0164 m_ocrDialog->show(); // just in case it got closed 0165 0166 createOcrProcess(m_ocrDialog, m_introducedImage); 0167 } 0168 0169 0170 void AbstractOcrEngine::stopOcrProcess(bool tellUser) 0171 { 0172 if (m_ocrProcess!=nullptr && m_ocrProcess->state()==QProcess::Running) 0173 { 0174 // If the process is to be killed, there is no point allowing 0175 // it to call slotProcessExited(). The finishedOcr() below 0176 // will clean up. 0177 disconnect(m_ocrProcess, nullptr, this, nullptr); 0178 0179 qCDebug(OCR_LOG) << "Killing OCR process" << m_ocrProcess->processId(); 0180 m_ocrProcess->kill(); 0181 if (tellUser) KMessageBox::error(m_parent, i18n("The OCR process was stopped")); 0182 } 0183 0184 finishedOcr(false); 0185 } 0186 0187 0188 /** 0189 * This method should be called by the engine specific finish slots. 0190 * It does the engine independent cleanups like re-enabling buttons etc. 0191 */ 0192 void AbstractOcrEngine::finishedOcr(bool success) 0193 { 0194 if (m_ocrDialog!=nullptr) m_ocrDialog->enableGUI(false); 0195 0196 if (success) 0197 { 0198 if (!m_ocrResultFile.isEmpty() && // there is a result image 0199 m_imgCanvas!=nullptr) // and we can display it 0200 { 0201 // Load the result image from its file. 0202 // The QSharedPointer passed to ImageCanvas::newImage() will 0203 // retain the image and delete it when it is no longer needed. 0204 ScanImage *resultImage = new ScanImage(QUrl::fromLocalFile(m_ocrResultFile)); 0205 qCDebug(OCR_LOG) << "Result image from" << m_ocrResultFile << "size" << resultImage->size(); 0206 m_imgCanvas->newImage(ScanImage::Ptr(resultImage), true); 0207 m_imgCanvas->setReadOnly(true); // display on image canvas 0208 m_trackingActive = true; // handle clicks on image 0209 } 0210 0211 // Do this after loading the result image above. Then when the signal 0212 // reaches KookaView::slotOcrResultAvailable() which then calls 0213 // updateSelectionState(), the image canvas has the image loaded and 0214 // the image view actions are correctly enabled. 0215 emit newOCRResultText(); // send out the text result 0216 0217 /* now it is time to invoke the dictionary if required */ 0218 // TODO: readOnlyEditor needed here? Also done in finishResultDocument() 0219 emit readOnlyEditor(false); // user can now edit 0220 if (m_ocrDialog != nullptr) 0221 { 0222 emit setSpellCheckConfig(m_ocrDialog->customSpellConfigFile()); 0223 0224 bool doSpellcheck = m_ocrDialog->wantInteractiveSpellCheck(); 0225 bool bgSpellcheck = m_ocrDialog->wantBackgroundSpellCheck(); 0226 emit startSpellCheck(doSpellcheck, bgSpellcheck); 0227 } 0228 } 0229 0230 if (m_ocrDialog!=nullptr) m_ocrDialog->hide(); // close the dialogue 0231 m_ocrRunning = false; 0232 removeTempFiles(); 0233 0234 qCDebug(OCR_LOG) << "OCR finished"; 0235 } 0236 0237 0238 void AbstractOcrEngine::removeTempFiles() 0239 { 0240 bool retain = m_ocrDialog->keepTempFiles(); 0241 qCDebug(OCR_LOG) << "retain?" << retain; 0242 0243 QStringList temps = tempFiles(retain); // get files used by engine 0244 if (!m_ocrResultFile.isEmpty()) temps << m_ocrResultFile; // plus our result image 0245 if (!m_ocrStderrLog.isEmpty()) temps << m_ocrStderrLog; // and our standard error log 0246 if (temps.join("").isEmpty()) return; // no temporary files to remove 0247 0248 if (retain) 0249 { 0250 QString s = xi18nc("@info", "The following OCR temporary files are retained for debugging:<nl/><nl/>"); 0251 for (QStringList::const_iterator it = temps.constBegin(); it != temps.constEnd(); ++it) 0252 { 0253 const QString file = (*it); 0254 if (file.isEmpty()) continue; 0255 0256 QUrl u = QUrl::fromLocalFile(file); 0257 s += xi18nc("@info", "<filename><link url=\"%1\">%2</link></filename><nl/>", u.url(), file); 0258 } 0259 0260 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0) 0261 if (KMessageBox::questionTwoActions(m_parent, s, 0262 #else 0263 if (KMessageBox::questionYesNo(m_parent, s, 0264 #endif 0265 i18n("OCR Temporary Files"), 0266 KStandardGuiItem::del(), 0267 KStandardGuiItem::close(), 0268 QString(), 0269 KMessageBox::AllowLink) 0270 #if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 100, 0) 0271 == KMessageBox::PrimaryAction) { 0272 #else 0273 == KMessageBox::Yes) { 0274 #endif 0275 retain = false; 0276 } 0277 } 0278 0279 if (!retain) { 0280 for (QStringList::const_iterator it = temps.constBegin(); it != temps.constEnd(); ++it) { 0281 if ((*it).isEmpty()) { 0282 continue; 0283 } 0284 0285 QString tf = (*it); 0286 QFileInfo fi(tf); 0287 if (!fi.exists()) { // what happened? 0288 //qCDebug(OCR_LOG) << "does not exist:" << tf; 0289 } else if (fi.isDir()) { 0290 //qCDebug(OCR_LOG) << "temp dir" << tf; 0291 QDir(tf).removeRecursively(); // recursive deletion 0292 } else { 0293 //qCDebug(OCR_LOG) << "temp file" << tf; 0294 QFile::remove(tf); // just a simple file 0295 } 0296 } 0297 } 0298 } 0299 0300 0301 // Filtering mouse events on the image viewer 0302 // ------------------------------------------ 0303 void AbstractOcrEngine::setImageCanvas(ImageCanvas *canvas) 0304 { 0305 m_imgCanvas = canvas; 0306 connect(m_imgCanvas, &ImageCanvas::doubleClicked, this, &AbstractOcrEngine::slotImagePosition); 0307 } 0308 0309 0310 void AbstractOcrEngine::slotImagePosition(const QPoint &p) 0311 { 0312 if (!m_trackingActive) return; // not interested 0313 0314 // ImageCanvas did the coordinate conversion. 0315 // OcrResEdit does all of the rest of the work. 0316 emit selectWord(p); 0317 } 0318 0319 0320 // Highlighting/scrolling the result text 0321 // -------------------------------------- 0322 void AbstractOcrEngine::slotHighlightWord(const QRect &r) 0323 { 0324 if (m_imgCanvas == nullptr) { 0325 return; 0326 } 0327 0328 if (m_currHighlight > -1) { 0329 m_imgCanvas->removeHighlight(m_currHighlight); 0330 } 0331 m_currHighlight = -1; 0332 0333 if (!m_trackingActive) { 0334 return; // not highlighting 0335 } 0336 if (!r.isValid()) { 0337 return; // word rectangle invalid 0338 } 0339 0340 KColorScheme sch(QPalette::Active, KColorScheme::Selection); 0341 QColor col = sch.background(KColorScheme::NegativeBackground).color(); 0342 m_imgCanvas->setHighlightStyle(ImageCanvas::HighlightBox, QPen(col, 2)); 0343 m_currHighlight = m_imgCanvas->addHighlight(r, true); 0344 } 0345 0346 void AbstractOcrEngine::slotScrollToWord(const QRect &r) 0347 { 0348 if (m_imgCanvas == nullptr) { 0349 return; 0350 } 0351 0352 if (m_currHighlight > -1) { 0353 m_imgCanvas->removeHighlight(m_currHighlight); 0354 } 0355 m_currHighlight = -1; 0356 0357 if (!m_trackingActive) { 0358 return; // not highlighting 0359 } 0360 0361 KColorScheme sch(QPalette::Active, KColorScheme::Selection); 0362 QColor col = sch.background(KColorScheme::NeutralBackground).color(); 0363 m_imgCanvas->setHighlightStyle(ImageCanvas::HighlightUnderline, QPen(col, 2)); 0364 m_currHighlight = m_imgCanvas->addHighlight(r, true); 0365 } 0366 0367 0368 // Assembling the OCR results 0369 // -------------------------- 0370 void AbstractOcrEngine::setTextDocument(QTextDocument *doc) 0371 { 0372 m_document = doc; 0373 } 0374 0375 0376 QTextDocument *AbstractOcrEngine::startResultDocument() 0377 { 0378 m_document->setUndoRedoEnabled(false); 0379 m_document->clear(); 0380 m_wordCount = 0; 0381 0382 m_cursor = new QTextCursor(m_document); 0383 0384 emit readOnlyEditor(true); // read only while updating 0385 return (m_document); 0386 } 0387 0388 void AbstractOcrEngine::finishResultDocument() 0389 { 0390 qCDebug(OCR_LOG) << "words" << m_wordCount << "lines" << m_document->blockCount() << "chars" << m_document->characterCount(); 0391 0392 if (m_cursor != nullptr) delete m_cursor; 0393 emit readOnlyEditor(false); // now let user edit it 0394 } 0395 0396 void AbstractOcrEngine::startLine() 0397 { 0398 if (verboseDebug()) { 0399 qCDebug(OCR_LOG); 0400 } 0401 if (!m_cursor->atStart()) { 0402 m_cursor->insertBlock(QTextBlockFormat(), QTextCharFormat()); 0403 } 0404 } 0405 0406 void AbstractOcrEngine::finishLine() 0407 { 0408 } 0409 0410 void AbstractOcrEngine::addWord(const QString &word, const OcrWordData &data) 0411 { 0412 if (verboseDebug()) { 0413 qCDebug(OCR_LOG) << "word" << word << "len" << word.length() 0414 << "rect" << data.property(OcrWordData::Rectangle) 0415 << "alts" << data.property(OcrWordData::Alternatives); 0416 } 0417 0418 if (!m_cursor->atBlockStart()) { 0419 m_cursor->insertText(" ", QTextCharFormat()); 0420 } 0421 m_cursor->insertText(word, data); 0422 ++m_wordCount; 0423 } 0424 0425 0426 QString AbstractOcrEngine::tempFileName(const QString &suffix, const QString &baseName) 0427 { 0428 QString protoName = QDir::tempPath()+'/'+baseName+"_XXXXXX"; 0429 if (!suffix.isEmpty()) protoName += "."+suffix; 0430 0431 QTemporaryFile tmpFile(protoName); 0432 tmpFile.setAutoRemove(false); 0433 0434 if (!tmpFile.open()) 0435 { 0436 qCDebug(OCR_LOG) << "error creating temporary file" << protoName; 0437 setErrorText(xi18nc("@info", "Cannot create temporary file <filename>%1</filename>", protoName)); 0438 return (QString()); 0439 } 0440 0441 QString tmpName = QFile::encodeName(tmpFile.fileName()); 0442 tmpFile.close(); // just want its name 0443 return (tmpName); 0444 } 0445 0446 0447 QString AbstractOcrEngine::tempSaveImage(ScanImage::Ptr img, const ImageFormat &format, int colors) 0448 { 0449 if (img.isNull()) return (QString()); // no image to save 0450 0451 QString tmpName = tempFileName(format.extension(), "imagetemp"); 0452 0453 ScanImage::Ptr tmpImg = img; // original image 0454 if (colors!=-1 && img->depth()!=colors) // need to convert it? 0455 { 0456 QImage::Format newfmt; 0457 switch (colors) 0458 { 0459 case 1: newfmt = QImage::Format_Mono; 0460 break; 0461 0462 case 8: newfmt = QImage::Format_Indexed8; 0463 break; 0464 0465 case 24: newfmt = QImage::Format_RGB888; 0466 break; 0467 0468 case 32: newfmt = QImage::Format_RGB32; 0469 break; 0470 0471 default: qCWarning(OCR_LOG) << "bad colour depth" << colors; 0472 return (QString()); 0473 } 0474 0475 tmpImg.clear(); // lose reference to original 0476 tmpImg.reset(new ScanImage(img->convertToFormat(newfmt))); 0477 } // update with converted image 0478 0479 qCDebug(OCR_LOG) << "saving to" << tmpName << "in format" << format; 0480 if (!tmpImg->save(tmpName, format.name())) 0481 { 0482 qCDebug(OCR_LOG) << "error saving to" << tmpName; 0483 setErrorText(xi18nc("@info", "Cannot save image to temporary file <filename>%1</filename>", tmpName)); 0484 tmpName.clear(); 0485 } 0486 0487 return (tmpName); 0488 } 0489 0490 0491 bool AbstractOcrEngine::verboseDebug() const 0492 { 0493 return (m_ocrDialog->verboseDebug()); 0494 } 0495 0496 0497 QString AbstractOcrEngine::findExecutable(QString (*settingFunc)(), KConfigSkeletonItem *settingItem) 0498 { 0499 QString exec = (*settingFunc)(); // get current setting 0500 if (exec.isEmpty()) settingItem->setDefault(); // if null, apply default 0501 exec = (*settingFunc)(); // and get new setting 0502 Q_ASSERT(!exec.isEmpty()); // should now have something 0503 qCDebug(OCR_LOG) << "configured/default" << exec; 0504 0505 if (!QDir::isAbsolutePath(exec)) // not specified absolute path 0506 { 0507 const QString pathExec = QStandardPaths::findExecutable(exec); 0508 if (pathExec.isEmpty()) // try to find executable 0509 { 0510 qCDebug(OCR_LOG) << "no" << exec << "found on PATH"; 0511 setErrorText(xi18nc("@info", "The executable <command>%1</command> could not be found on <envar>PATH</envar>.")); 0512 return (QString()); 0513 } 0514 exec = pathExec; 0515 } 0516 0517 QFileInfo fi(exec); // now check it is usable 0518 if (!fi.exists() || fi.isDir() || !fi.isExecutable()) 0519 { 0520 qCDebug(OCR_LOG) << "configured" << exec << "not usable"; 0521 setErrorText(xi18nc("@info", "The executable <filename>%1</filename> does not exist or is not usable.", fi.absoluteFilePath())); 0522 return (QString()); 0523 } 0524 0525 qCDebug(OCR_LOG) << "found" << exec; 0526 return (exec); 0527 } 0528 0529 0530 QString AbstractOcrEngine::collectErrorMessages(const QString &starter, const QString &ender) 0531 { 0532 // Any error message(s) in m_errorText will already have been converted 0533 // from KUIT markup to HTML by xi18nc() or similar. So all that is 0534 // needed is to build the rest of the error message in rich text also. 0535 // There will be some spurious <html> tags around each separate message 0536 // which has been converted, but they don't seem to cause any problem. 0537 0538 m_errorText.prepend(QString()); 0539 m_errorText.prepend(starter); 0540 m_errorText.prepend("<html>"); 0541 0542 m_errorText.append(QString()); 0543 m_errorText.append(ender); 0544 m_errorText.append("</html>"); 0545 0546 return (m_errorText.join("<br/>")); 0547 } 0548 0549 0550 QProcess *AbstractOcrEngine::initOcrProcess() 0551 { 0552 if (m_ocrProcess!=nullptr) delete m_ocrProcess; // kill old process if still there 0553 0554 m_ocrProcess = new QProcess(); // start new OCR process 0555 Q_CHECK_PTR(m_ocrProcess); 0556 qCDebug(OCR_LOG); 0557 0558 m_ocrProcess->setStandardInputFile(QProcess::nullDevice()); 0559 0560 m_ocrProcess->setProcessChannelMode(QProcess::SeparateChannels); 0561 m_ocrStderrLog = tempFileName("stderr.log"); 0562 m_ocrProcess->setStandardErrorFile(m_ocrStderrLog); 0563 0564 return (m_ocrProcess); 0565 } 0566 0567 0568 bool AbstractOcrEngine::runOcrProcess() 0569 { 0570 qCDebug(OCR_LOG) << "Running OCR," << m_ocrProcess->program() << m_ocrProcess->arguments(); 0571 connect(m_ocrProcess, QOverload<int,QProcess::ExitStatus>::of(&QProcess::finished), this, &AbstractOcrEngine::slotProcessExited); 0572 0573 m_ocrProcess->start(); 0574 if (!m_ocrProcess->waitForStarted(5000)) 0575 { 0576 qCWarning(OCR_LOG) << "Error starting OCR process"; 0577 return (false); 0578 } 0579 0580 return (true); 0581 } 0582 0583 0584 void AbstractOcrEngine::slotProcessExited(int exitCode, QProcess::ExitStatus exitStatus) 0585 { 0586 qCDebug(OCR_LOG) << "exit code" << exitCode << "status" << exitStatus; 0587 0588 bool success = (exitStatus==QProcess::NormalExit && exitCode==0); 0589 if (!success) // OCR command failed 0590 { 0591 if (exitStatus==QProcess::CrashExit) 0592 { 0593 setErrorText(xi18nc("@info", "Command <command>%1</command> crashed with exit status <numid>%2</numid>", 0594 m_ocrProcess->program(), exitCode)); 0595 } 0596 else 0597 { 0598 setErrorText(xi18nc("@info", "Command <command>%1</command> exited with status <numid>%2</numid>", 0599 m_ocrProcess->program(), exitCode)); 0600 } 0601 0602 const QString msg = collectErrorMessages(xi18nc("@info", "Running the OCR process failed."), 0603 xi18nc("@info", "More information may be available in its <link url=\"%1\">standard error</link> log file.", 0604 QUrl::fromLocalFile(m_ocrStderrLog).url())); 0605 0606 KMessageBox::error(m_parent, msg, i18n("OCR Command Failed"), KMessageBox::AllowLink); 0607 } 0608 else // OCR command succeeded 0609 { 0610 success = finishedOcrProcess(m_ocrProcess); // process the OCR results 0611 if (!success) // OCR processing failed 0612 { 0613 const QString msg = collectErrorMessages(xi18nc("@info", "Processing the OCR results failed."), QString()); 0614 KMessageBox::error(m_parent, msg, i18n("OCR Processing Failed"), KMessageBox::AllowLink); 0615 } 0616 } 0617 0618 finishedOcr(success); 0619 } 0620 0621 0622 bool AbstractOcrEngine::isBW() 0623 { 0624 if (m_resolvedBW) return (m_isBW); // have already resolved this 0625 0626 m_isBW = false; // assume so to start 0627 0628 const int cols = m_introducedImage->colorCount(); // colour count for indexed images 0629 if (cols>0) // not a true colour image 0630 { 0631 m_isBW = (cols<=2); // only this many palette entries 0632 } 0633 else // image is true colour 0634 { 0635 if (m_introducedImage->allGray()) // could possibly be BW 0636 { 0637 int othercols = 0; // colours not white or black 0638 0639 const int w = m_introducedImage->width(); 0640 const int h = m_introducedImage->height(); 0641 for (int x = 0; x<w; ++x) 0642 { 0643 for (int y = 0; y<h; ++y) 0644 { 0645 const QRgb col = m_introducedImage->pixel(x, y); 0646 const int r = qRed(col); 0647 if (r!=0 && r!=1 && r!=255) ++othercols; 0648 } 0649 } 0650 0651 m_isBW = (othercols==0); // no other colours in image 0652 } 0653 } 0654 0655 qDebug() << "cols" << cols << "format" << m_introducedImage->format() << "->" << m_isBW; 0656 m_resolvedBW = true; // note result now resolved 0657 return (m_isBW); 0658 }