diff --git a/app/app.pro b/app/app.pro index 5ebdfa065..25b310639 100644 --- a/app/app.pro +++ b/app/app.pro @@ -59,6 +59,7 @@ INCLUDEPATH += \ PRECOMPILED_HEADER = src/app-pch.h HEADERS += \ + src/addtransparencytopaperdialog.h \ src/app-pch.h \ src/importlayersdialog.h \ src/importpositiondialog.h \ @@ -102,7 +103,8 @@ HEADERS += \ src/doubleprogressdialog.h \ src/colorslider.h \ src/checkupdatesdialog.h \ - src/presetdialog.h \ + src/bitmapcoloring.h \ + src/presetdialog.h \ src/repositionframesdialog.h \ src/commandlineparser.h \ src/commandlineexporter.h \ @@ -111,6 +113,7 @@ HEADERS += \ src/cameraoptionswidget.h SOURCES += \ + src/addtransparencytopaperdialog.cpp \ src/importlayersdialog.cpp \ src/importpositiondialog.cpp \ src/layeropacitydialog.cpp \ @@ -153,6 +156,7 @@ SOURCES += \ src/doubleprogressdialog.cpp \ src/colorslider.cpp \ src/checkupdatesdialog.cpp \ + src/bitmapcoloring.cpp \ src/presetdialog.cpp \ src/repositionframesdialog.cpp \ src/app_util.cpp \ @@ -163,6 +167,7 @@ SOURCES += \ src/cameraoptionswidget.cpp FORMS += \ + ui/addtransparencytopaperdialog.ui \ ui/cameraoptionswidget.ui \ ui/camerapropertiesdialog.ui \ ui/importimageseqpreview.ui \ @@ -191,6 +196,7 @@ FORMS += \ ui/filespage.ui \ ui/toolspage.ui \ ui/toolboxwidget.ui \ + ui/bitmapcoloringwidget.ui \ ui/presetdialog.ui GIT { diff --git a/app/data/app.qrc b/app/data/app.qrc index 1c78e052b..b7573e0e0 100644 --- a/app/data/app.qrc +++ b/app/data/app.qrc @@ -54,6 +54,9 @@ icons/new/svg/smudge_detailed.svg icons/new/svg/trash_detailed.svg icons/new/checkerboard_smaller.png + icons/blue.png + icons/green.png + icons/red.png icons/overlayCenter.png icons/overlayGoldenRatio.png icons/overlaySafe.png diff --git a/app/data/icons/blue.png b/app/data/icons/blue.png new file mode 100644 index 000000000..7bf25abce Binary files /dev/null and b/app/data/icons/blue.png differ diff --git a/app/data/icons/green.png b/app/data/icons/green.png new file mode 100644 index 000000000..f442c52d7 Binary files /dev/null and b/app/data/icons/green.png differ diff --git a/app/data/icons/red.png b/app/data/icons/red.png new file mode 100644 index 000000000..7d230c92d Binary files /dev/null and b/app/data/icons/red.png differ diff --git a/app/src/addtransparencytopaperdialog.cpp b/app/src/addtransparencytopaperdialog.cpp new file mode 100644 index 000000000..44ba86681 --- /dev/null +++ b/app/src/addtransparencytopaperdialog.cpp @@ -0,0 +1,250 @@ +#include "addtransparencytopaperdialog.h" +#include "ui_addtransparencytopaperdialog.h" + +#include +#include +#include + +#include "editor.h" +#include "layermanager.h" +#include "selectionmanager.h" +#include "layerbitmap.h" +#include "bitmapimage.h" + + +AddTransparencyToPaperDialog::AddTransparencyToPaperDialog(QDialog *parent) : + QDialog(parent), + ui(new Ui::AddTransparencyToPaperDialog) +{ + ui->setupUi(this); + ui->mainLayout->setStretchFactor(ui->optionsLayout, 1); + ui->mainLayout->setStretchFactor(ui->previewLayout, 20); + + connect(this, &QDialog::finished, this, &AddTransparencyToPaperDialog::closeDialog); + connect(ui->sb_treshold, static_cast(&QSpinBox::valueChanged), this, &AddTransparencyToPaperDialog::SpinboxChanged); + connect(ui->sliderThreshold, &QSlider::valueChanged, this, &AddTransparencyToPaperDialog::SliderChanged); + connect(ui->cb_Red, &QCheckBox::stateChanged, this, &AddTransparencyToPaperDialog::updateDrawing); + connect(ui->cb_Green, &QCheckBox::stateChanged, this, &AddTransparencyToPaperDialog::updateDrawing); + connect(ui->cb_Blue, &QCheckBox::stateChanged, this, &AddTransparencyToPaperDialog::updateDrawing); + connect(ui->btnCancel, &QPushButton::clicked, this, &AddTransparencyToPaperDialog::closeDialog); + connect(ui->btnApply, &QPushButton::clicked, this, &AddTransparencyToPaperDialog::traceScannedDrawings); + connect(ui->testTransparencyCheckbox, &QCheckBox::stateChanged, this, &AddTransparencyToPaperDialog::checkerStateChanged); + connect(ui->zoomSlider, &QSlider::valueChanged, this, &AddTransparencyToPaperDialog::zoomChanged); +} + +AddTransparencyToPaperDialog::~AddTransparencyToPaperDialog() +{ + delete ui; +} + +void AddTransparencyToPaperDialog::setCore(Editor *editor) +{ + mEditor = editor; +} + +void AddTransparencyToPaperDialog::initUI() +{ + if (mEditor->layers()->currentLayer()->type() != Layer::BITMAP) + this->setEnabled(false); + loadDrawing(mEditor->currentFrame()); + connect(mEditor->layers(), &LayerManager::currentLayerChanged, this, &AddTransparencyToPaperDialog::layerChanged); + connect(mEditor, &Editor::scrubbedTo, this, &AddTransparencyToPaperDialog::updateDrawing); + connect(mEditor, &Editor::currentFrameUpdated, this, &AddTransparencyToPaperDialog::updateDrawing); + + scene.setBackgroundBrush(Qt::white); + ui->preview->setScene(&scene); + ui->preview->show(); + + if (!mBitmap.bounds().isValid()) { + ui->btnApply->setEnabled(false); + } +} + +void AddTransparencyToPaperDialog::SpinboxChanged(int value) +{ + mThreshold = value; + ui->sliderThreshold->setValue(value); + updateDrawing(); +} + +void AddTransparencyToPaperDialog::SliderChanged(int value) +{ + mThreshold = value; + ui->sb_treshold->setValue(value); + updateDrawing(); +} + +void AddTransparencyToPaperDialog::checkerStateChanged(bool state) +{ + if (state) { + scene.setBackgroundBrush(QBrush(QImage(":/background/checkerboard.png"))); + } else { + scene.setBackgroundBrush(Qt::white); + } +} + +void AddTransparencyToPaperDialog::zoomChanged(int zoomLevel) +{ + mZoomLevel = zoomLevel; + updatePreview(); +} + +void AddTransparencyToPaperDialog::resizeEvent(QResizeEvent*) +{ + updatePreview(); +} + +void AddTransparencyToPaperDialog::updatePreview() +{ + QImage loadedImage = *mBitmap.image(); + + QSize previewSize = ui->preview->size()*mZoomLevel; + QSize size = mBitmap.size().scaled(previewSize, Qt::KeepAspectRatioByExpanding); + mPixmapFromImage = QPixmap(size); + mPixmapFromImage.fill(Qt::transparent); + + QPainter painter(&mPixmapFromImage); + + painter.drawImage(QRect(QPoint(0,0),QSize(size)), loadedImage, loadedImage.rect()); + mPreviewImageItem->setPixmap(mPixmapFromImage); + + scene.setSceneRect(QRect(QPoint(), previewSize)); +} + +void AddTransparencyToPaperDialog::loadDrawing(int frame) +{ + if (mEditor->layers()->currentLayer()->type() != Layer::BITMAP) { return; } + + LayerBitmap* layer = static_cast(mEditor->layers()->currentLayer()); + + if (!layer->keyExists(frame)) + { + if (!layer->keyExistsWhichCovers(frame)) + frame = layer->getPreviousKeyFramePosition(frame); + else + frame = layer->getNextKeyFramePosition(frame); + } + + ui->labShowingFrame->setText(tr("Previewing frame %1").arg(QString::number(frame))); + + BitmapImage* currentImage = layer->getBitmapImageAtFrame(frame); + + if (!currentImage) { return; } + + mBitmap = currentImage->copy(); + mBitmap.setThreshold(mThreshold); + + mBitmap = *mBitmap.scanToTransparent(&mBitmap, + ui->cb_Red->isChecked(), + ui->cb_Green->isChecked(), + ui->cb_Blue->isChecked()); + + if (mPreviewImageItem == nullptr) { + mPreviewImageItem = scene.addPixmap(mPixmapFromImage); + } else { + mPreviewImageItem->setPixmap(mPixmapFromImage); + } + + ui->btnApply->setEnabled(true); + + updatePreview(); +} + +void AddTransparencyToPaperDialog::updateDrawing() +{ + loadDrawing(mEditor->currentFrame()); +} + +void AddTransparencyToPaperDialog::layerChanged(int index) +{ + if (mEditor->layers()->getLayer(index)->type() == Layer::BITMAP) + { + this->setEnabled(true); + updateDrawing(); + } + else + { + this->setEnabled(false); + } +} + +void AddTransparencyToPaperDialog::traceScannedDrawings() +{ + if (mEditor->layers()->currentLayer()->type() != Layer::BITMAP) { return; } + + LayerBitmap* layer = static_cast(mEditor->layers()->currentLayer()); + BitmapImage* img = new BitmapImage(); + bool somethingSelected = mEditor->select()->somethingSelected(); + + if (ui->rbCurrentKeyframe->isChecked()) + { + int frame = mEditor->currentFrame(); + if (!layer->keyExists(frame)) + { + if (!layer->keyExistsWhichCovers(frame)) + frame = layer->getPreviousKeyFramePosition(frame); + else + frame = layer->getNextKeyFramePosition(frame); + } + mEditor->scrubTo(frame); + + if (somethingSelected) + { + mEditor->copy(); + layer->removeKeyFrame(frame); + layer->addNewKeyFrameAt(frame); + mEditor->paste(); + } + img = layer->getBitmapImageAtFrame(frame); + img->setThreshold(mThreshold); + img = img->scanToTransparent(img, + ui->cb_Red->isChecked(), + ui->cb_Green->isChecked(), + ui->cb_Blue->isChecked()); + img->modification(); + } + else + { + mEditor->setIsDoingRepeatColoring(true); + int count = mEditor->getAutoSaveCounter(); + QProgressDialog* mProgress = new QProgressDialog(tr("Tracing scanned drawings..."), tr("Abort"), 0, 100, this); + mProgress->setWindowModality(Qt::WindowModal); + mProgress->show(); + mProgress->setMaximum(layer->keyFrameCount()); + mProgress->setValue(0); + int keysThinned = 0; + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + for (int i = layer->firstKeyFramePosition(); i <= layer->getMaxKeyFramePosition(); i++) + { + if (layer->keyExists(i)) + { + mProgress->setValue(keysThinned++); + mEditor->scrubTo(i); + count++; + if (mProgress->wasCanceled()) + { + break; + } + if (somethingSelected) + { + mEditor->copy(); + layer->removeKeyFrame(i); + layer->addNewKeyFrameAt(i); + mEditor->paste(); + } + img = layer->getBitmapImageAtFrame(i); + img->setThreshold(mThreshold); + img = img->scanToTransparent(img, + ui->cb_Red->isChecked(), + ui->cb_Green->isChecked(), + ui->cb_Blue->isChecked()); + img->modification(); + } + } + mProgress->close(); + mEditor->setIsDoingRepeatColoring(false); + mEditor->setAutoSaveCounter(count); + } + if (ui->rbAllKeyframes->isChecked()) + emit closeDialog(); +} diff --git a/app/src/addtransparencytopaperdialog.h b/app/src/addtransparencytopaperdialog.h new file mode 100644 index 000000000..459ab2cfd --- /dev/null +++ b/app/src/addtransparencytopaperdialog.h @@ -0,0 +1,60 @@ +#ifndef ADDTRANSPARENCYTOPAPERDIALOG_H +#define ADDTRANSPARENCYTOPAPERDIALOG_H + +#include +#include + +#include "bitmapimage.h" + +class Editor; +class QGraphicsPixmapItem; + +namespace Ui { +class AddTransparencyToPaperDialog; +} + +class AddTransparencyToPaperDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AddTransparencyToPaperDialog(QDialog *parent = nullptr); + ~AddTransparencyToPaperDialog() override; + + void setCore(Editor* editor); + + void initUI(); + +signals: + void closeDialog(); + +protected: + void resizeEvent(QResizeEvent*) override; + +private slots: + void SpinboxChanged(int value); + void SliderChanged(int value); + void traceScannedDrawings(); + void updateDrawing(); + void layerChanged(int index); + void checkerStateChanged(bool state); + void zoomChanged(int zoomLevel); + +private: + void updatePreview(); + void loadDrawing(int frame); + + int mZoomLevel = 1; + + Ui::AddTransparencyToPaperDialog *ui; + + QGraphicsScene scene; + QGraphicsPixmapItem* mPreviewImageItem = nullptr; + + int mThreshold = 220; + BitmapImage mBitmap; + QPixmap mPixmapFromImage; + Editor* mEditor = nullptr; +}; + +#endif // ADDTRANSPARENCYTOPAPERDIALOG_H diff --git a/app/src/bitmapcoloring.cpp b/app/src/bitmapcoloring.cpp new file mode 100644 index 000000000..e064f92dd --- /dev/null +++ b/app/src/bitmapcoloring.cpp @@ -0,0 +1,779 @@ +/* + +Pencil - Traditional Animation Software +Copyright (C) 2012-2021 Matthew Chiawen Chang + +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; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ + +#include +#include +#include "bitmapcoloring.h" +#include "ui_bitmapcoloringwidget.h" +#include "editor.h" +#include "layerbitmap.h" +#include "layermanager.h" +#include "scribblearea.h" +#include "app_util.h" +#include "object.h" + + +BitmapColoring::BitmapColoring(Editor* editor, QWidget *parent) : + BaseDockWidget(parent) +{ + QWidget* innerWidget = new QWidget; + setWindowTitle(tr("Advanced Coloring")); + + ui = new Ui::BitmapColoringWidget; + ui->setupUi(innerWidget); + setWidget(innerWidget); + + mEditor = editor; + mScribblearea = mEditor->getScribbleArea(); + if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP) + mLayerBitmap = static_cast(mEditor->layers()->currentLayer()); + mBitmapImage = mLayerBitmap->getBitmapImageAtFrame(mEditor->currentFrame()); + checkRedBoxes(); + checkGreenBoxes(); + checkBlueBoxes(); + + connect(ui->cb2TraceRed, &QCheckBox::stateChanged, this, &BitmapColoring::checkRedBoxes); + connect(ui->cb2TraceGreen, &QCheckBox::stateChanged, this, &BitmapColoring::checkGreenBoxes); + connect(ui->cb2TraceBlue, &QCheckBox::stateChanged, this, &BitmapColoring::checkBlueBoxes); + connect(ui->cb3TraceAllKeyframes, &QCheckBox::stateChanged, this, &BitmapColoring::checkAllKeyframesBoxes); + connect(ui->btnResetTrace, &QPushButton::clicked, this, &BitmapColoring::resetColoringDock); + connect(ui->cbMethodSelector, QOverload::of(&QComboBox::currentIndexChanged), this, &BitmapColoring::enableTabs); + connect(ui->btnInfo, &QPushButton::clicked, this, &BitmapColoring::infoBox); + + // Prepare + connect(ui->tabWidget, &QTabWidget::tabBarClicked, this, &BitmapColoring::tabWidgetClicked); + connect(ui->btnPrepareLines, &QPushButton::clicked, this, &BitmapColoring::prepareAndTraceLines); + connect(ui->btnApplyTrace, &QPushButton::clicked, this, &BitmapColoring::traceLines); + // Thin + connect(ui->sbSpotAreas, QOverload::of(&QSpinBox::valueChanged), this, &BitmapColoring::setSpotArea); + connect(ui->cbSpotAreas, &QCheckBox::stateChanged, this, &BitmapColoring::updateFillSpotsButton); + connect(ui->btnFillAreas, &QPushButton::clicked, this, &BitmapColoring::fillSpotAreas); + connect(ui->btnApplyThin, &QPushButton::clicked, this, &BitmapColoring::thinLines); + // Finish + connect(ui->btnApplyBlend, &QPushButton::clicked, this, &BitmapColoring::blendLines); + updateTraceButtons(); + +} + +BitmapColoring::~BitmapColoring() +{ + delete ui; +} + +void BitmapColoring::initUI() +{ + updateUI(); +} + +void BitmapColoring::updateUI() +{ + if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP) + mLayerBitmap = static_cast(mEditor->layers()->currentLayer()); + if (mLayerBitmap == nullptr) { return; } + + if (mEditor->layers()->currentLayer()->type() == Layer::BITMAP && + ui->cbMethodSelector->currentIndex() > 0) + { + if (mLayerBitmap->getHasColorLayer()) + { + ui->tab1->setEnabled(true); + ui->tab2->setEnabled(false); + ui->tab3->setEnabled(false); + } + else if (mLayerBitmap->getIsColorLayer()) + { + ui->tab1->setEnabled(false); + ui->tab2->setEnabled(true); + ui->tab3->setEnabled(true); + } + else + { + ui->tab1->setEnabled(true); + ui->tab2->setEnabled(true); + ui->tab3->setEnabled(true); + } + } + else + { // If it is not a Bitmap Layer OR method not chosen - disable + ui->tab1->setEnabled(false); + ui->tab2->setEnabled(false); + ui->tab3->setEnabled(false); + } + mScribblearea->updateFrame(); +} + +void BitmapColoring::onVisibilityChanged(bool visibility) +{ + if (visibility) + updateUI(); +} + +void BitmapColoring::checkRedBoxes() +{ + if (ui->cb2TraceRed->isChecked()) + { + ui->cb2ThinRed->setChecked(ui->cb2TraceRed->isChecked()); + ui->cb2BlendRed->setChecked(ui->cb2TraceRed->isChecked()); + mRedChecked = true; + } + else + { + mRedChecked = false; + } + updateTraceButtons(); +} + +void BitmapColoring::checkGreenBoxes() +{ + if (ui->cb2TraceGreen->isChecked()) + { + ui->cb2ThinGreen->setChecked(ui->cb2TraceGreen->isChecked()); + ui->cb2BlendGreen->setChecked(ui->cb2TraceGreen->isChecked()); + mGreenChecked = true; + } + else + { + mGreenChecked = false; + } + updateTraceButtons(); +} + +void BitmapColoring::checkBlueBoxes() +{ + if (ui->cb2TraceBlue->isChecked()) + { + ui->cb2ThinBlue->setChecked(ui->cb2TraceBlue->isChecked()); + ui->cb2BlendBlue->setChecked(ui->cb2TraceBlue->isChecked()); + mBlueChecked = true; + } + else + { + mBlueChecked = false; + } + updateTraceButtons(); +} + +void BitmapColoring::checkAllKeyframesBoxes() +{ + ui->cbThinAllKeyframes->setChecked(ui->cb3TraceAllKeyframes->isChecked()); + ui->cb3BlendAllKeyframes->setChecked(ui->cb3TraceAllKeyframes->isChecked()); +} + +void BitmapColoring::tabWidgetClicked(int index) +{ + switch (index) + { + case 0: + updateTraceBoxes(); + break; + case 1: + updateThinBoxes(); + break; + case 2: + updateBlendBoxes(); + break; + default: + updateTraceBoxes(); + } +} + +void BitmapColoring::resetColoringDock() +{ + ui->cbMethodSelector->setCurrentIndex(0); + ui->cbSpotAreas->setChecked(false); + ui->sbSpotAreas->setValue(6); + ui->cb2TraceRed->setChecked(false); + ui->cb2TraceGreen->setChecked(false); + ui->cb2TraceBlue->setChecked(false); + ui->cb3TraceAllKeyframes->setChecked(false); +} + +void BitmapColoring::infoBox() +{ + QMessageBox::information(this, tr("Basics in Advanced Coloring") + ,tr("Advanced coloring is developed for users that use color-separation to achieve a shade-effect.\n" + "It is assumed that the animation is done with black/gray pencil,\n" + "and you can use red, blue and/or green for the color-separation.\n\n" + "In this example, we have a active layer called 'Bird'.\n" + "The Advanced coloring creates two new layers, called 'Bird_L' and 'Bird_C'.\n" + "'Bird_L' is a copy of the Line-art layer, and 'Bird_C' is the Coloring layer.\n" + "STEP 1: Trace:\n" + "Make sure that you are on the layer 'Bird'!\n" + "Check the relevant boxes for color-separation.\n" + "Check the 'all drawings' box, if it is all drawings on layer. It most times is.\n" + "Press 'Trace'\n" + "STEP 2: Thin:\n" + "Make sure that you are on the layer 'Bird_C'!\n" + "Here the traced lines are thinned to 1 pixel thickness.\n" + "To avoid 'holes' in your thinned line, you can run the 'Fill small areas' first.\n" + "Your original layer 'Bird' is un-altered through the process, and should be made hidden.\n" + "Press 'Thin'\n" + "STEP 3: Colorize:\n" + "Choose the Bucket-tool. Reference 'current layer', and method 'Replace'\n" + "NB! Uncheck 'Color tolerance' and 'Expand fill'!\n" + "STEP 4: Blend:\n" + "Press 'Blend and Finish', and the thinned lines will dissappear,\n" + "and be replaced with blending af neighboring colors. You're done!\n\n")); +} + +void BitmapColoring::enableTabs(int index) +{ + Q_UNUSED(index) + updateUI(); +} + +// public Trace funtions +void BitmapColoring::updateTraceBoxes() +{ + if (mLayerBitmap->getIsColorLayer()) + { + ui->tab1->setEnabled(false); + } + else if (ui->cbMethodSelector->currentIndex() > 0) + { + ui->tab1->setEnabled(true); + ui->gb2Trace->setEnabled(true); + } +} + +void BitmapColoring::traceLines() +{ + if (mLayerBitmap == nullptr || mLayerBitmap->type() != Layer::BITMAP) { return; } + + if (ui->cb3TraceAllKeyframes->isChecked()) + { + mEditor->setIsDoingRepeatColoring(true); + int count = mEditor->getAutoSaveCounter(); + QProgressDialog mProgress(tr("Tracing lines in bitmaps..."), tr("Abort"), 0, 100, this); + mProgress.setWindowModality(Qt::WindowModal); + mProgress.show(); + int keysToTrace = mLayerBitmap->keyFrameCount(); + mProgress.setMaximum(keysToTrace); + mProgress.setValue(0); + int keysTraced = 0; + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + + for (int i = mLayerBitmap->firstKeyFramePosition(); i <= mLayerBitmap->getMaxKeyFramePosition(); i++) + { + if (mLayerBitmap->keyExists(i)) + { + mProgress.setValue(keysTraced++); + mEditor->scrubTo(i); + trace(); + count++; + if (mProgress.wasCanceled()) + { + break; + } + } + } + mProgress.close(); + + // move colorLayer beneath Animation layer + while (mColLayer > mAnimLayer) + { + mEditor->object()->swapLayers(mColLayer, mColLayer - 1); + mColLayer--; + } + mAnimLayer++; + + mEditor->setIsDoingRepeatColoring(false); + mEditor->setAutoSaveCounter(count); + } + else if (mLayerBitmap->keyExists(mEditor->currentFrame())) + { + trace(); + qDebug() << "TRACE one " << mEditor->currentFrame(); + } + mEditor->deselectAll(); + if (ui->cb3TraceAllKeyframes->isChecked()) + { + ui->tabWidget->setCurrentIndex(1); + QMessageBox msgBox; + msgBox.setText(tr("Ready for thinning lines!")); + msgBox.exec(); + } +} + +void BitmapColoring::updateFillSpotsButton() +{ + if (ui->cbSpotAreas->isChecked()) + { + ui->btnFillAreas->setEnabled(true); + ui->btnApplyThin->setEnabled(false); + ui->labReminder->setText(tr("Fill areas blocks Thin button!")); + } + else + { + ui->btnFillAreas->setEnabled(false); + ui->btnApplyThin->setEnabled(true); + ui->labReminder->setText(""); + } +} + +void BitmapColoring::fillSpotAreas() +{ + if (mEditor->layers()->currentLayer()->type() != Layer::BITMAP) { return; } + + mLayerBitmap = static_cast(mEditor->layers()->currentLayer()); + + if (!ui->cbThinAllKeyframes->isChecked() && mLayerBitmap->keyExists(mEditor->currentFrame())) + { + mBitmapImage = mLayerBitmap->getBitmapImageAtFrame(mEditor->currentFrame()); + mBitmapImage->setSpotArea(ui->sbSpotAreas->value()); + mBitmapImage->fillSpotAreas(mBitmapImage); + mBitmapImage->modification(); + mEditor->scrubTo(mEditor->currentFrame()); + } + else + { + mEditor->setIsDoingRepeatColoring(true); + int count = mEditor->getAutoSaveCounter(); + QProgressDialog mProgress(tr("Fill small areas in bitmaps..."), tr("Abort"), 0, 100, this); + mProgress.setWindowModality(Qt::WindowModal); + mProgress.show(); + mProgress.setMaximum(mLayerBitmap->keyFrameCount()); + mProgress.setValue(0); + int keysThinned = 0; + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + for (int i = mLayerBitmap->firstKeyFramePosition(); i <= mLayerBitmap->getMaxKeyFramePosition(); i++) + { + if (mLayerBitmap->keyExists(i)) + { + mEditor->scrubTo(i); + mProgress.setValue(keysThinned++); + mBitmapImage = mLayerBitmap->getBitmapImageAtFrame(mEditor->currentFrame()); + mBitmapImage->setSpotArea(ui->sbSpotAreas->value()); + mBitmapImage->fillSpotAreas(mBitmapImage); + mBitmapImage->modification(); + count++; + } + if (mProgress.wasCanceled()) + { + break; + } + } + mProgress.close(); + mEditor->setIsDoingRepeatColoring(false); + mEditor->setAutoSaveCounter(count); + ui->cbSpotAreas->setChecked(false); + } + updateUI(); +} + +// public Thin functions +void BitmapColoring::updateThinBoxes() +{ + if (mLayerBitmap->getHasColorLayer()) + { + ui->tab2->setEnabled(false); + } + else if (ui->cbMethodSelector->currentIndex() > 0) + { + ui->tab2->setEnabled(true); + ui->gb2Thin->setEnabled(true); + } +} + +void BitmapColoring::setSpotArea(int size) +{ + mBitmapImage->setSpotArea(size); +} + +void BitmapColoring::thinLines() +{ + if (mLayerBitmap == nullptr) { return; } + + if (!ui->cbThinAllKeyframes->isChecked() && mLayerBitmap->keyExists(mEditor->currentFrame())) + { + thin(); + } + else + { + mEditor->setIsDoingRepeatColoring(true); + int count = mEditor->getAutoSaveCounter(); + QProgressDialog mProgress(tr("Thinning lines in bitmaps..."), tr("Abort"), 0, 100, this); + mProgress.setWindowModality(Qt::WindowModal); + mProgress.show(); + mProgress.setMaximum(mLayerBitmap->keyFrameCount()); + mProgress.setValue(0); + int keysThinned = 0; + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + for (int i = mLayerBitmap->firstKeyFramePosition(); i <= mLayerBitmap->getMaxKeyFramePosition(); i++) + { + if (mLayerBitmap->keyExists(i)) + { + mProgress.setValue(keysThinned++); + mEditor->scrubTo(i); + thin(); + count++; + } + if (mProgress.wasCanceled()) + { + break; + } + } + mProgress.close(); + mEditor->setIsDoingRepeatColoring(false); + mEditor->setAutoSaveCounter(count); + + ui->tabWidget->setCurrentIndex(2); + QMessageBox msgBox; + msgBox.setText(tr("Ready for coloring!")); + msgBox.exec(); + } + + mEditor->scrubTo(mEditor->currentFrame()); +} + +// public Blend functions +void BitmapColoring::updateBlendBoxes() +{ + if (mLayerBitmap->getHasColorLayer()) + { + ui->tab3->setEnabled(false); + } + else if (ui->cbMethodSelector->currentIndex() > 0) + { + ui->tab3->setEnabled(true); + } +} + +void BitmapColoring::blendLines() +{ + if (mLayerBitmap == nullptr) { return; } + + QString orgName = mLayerBitmap->name(); + LayerBitmap* artLayer = nullptr; + orgName.chop(2); + QString artLayerName = orgName + "_L"; + artLayer = static_cast(mEditor->layers()->findLayerByName(artLayerName)); + if (!artLayer) + { + artLayerName.chop(2); + artLayer = static_cast(mEditor->layers()->findLayerByName(artLayerName)); + } + if (artLayer == nullptr) { return; } + + artLayer->setVisible(false); + + if (!ui->cb3BlendAllKeyframes->isChecked() && mLayerBitmap->keyExists(mEditor->currentFrame())) + { + blend(artLayer); + } + else + { + mEditor->setIsDoingRepeatColoring(true); + int count = mEditor->getAutoSaveCounter(); + QProgressDialog progress(tr("Blending lines in bitmaps..."), tr("Abort"), 0, 100, this); + hideQuestionMark(progress); + progress.setWindowModality(Qt::WindowModal); + progress.show(); + int keysToBlend = mLayerBitmap->keyFrameCount(); + progress.setMaximum(keysToBlend); + int keysBlended = 0; + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + + for (int i = mLayerBitmap->firstKeyFramePosition(); i <= mLayerBitmap->getMaxKeyFramePosition(); i++) + { + if (mLayerBitmap->keyExists(i)) + { + mEditor->scrubTo(i); + count++; + progress.setValue(keysBlended++); + blend(artLayer); + if (progress.wasCanceled()) + { + break; + } + } + } + progress.close(); + mEditor->setIsDoingRepeatColoring(false); + mEditor->setAutoSaveCounter(count); + + ui->tabWidget->setCurrentIndex(0); + resetColoringDock(); + QMessageBox msgBox; + msgBox.setText(tr("Coloring finished!\nDialog reset...")); + msgBox.exec(); + } + mEditor->scrubTo(mEditor->currentFrame()); +} + +/* + * If drawings are made in Pencil2D, we need to preserve the originals by: + * - ...making a copy of the original layer, named 'name'_L (L for Line art) + * - ...making a new coloring layer named 'name'_C (C for Coloring) + * (unless copy and color layer already exists) +*/ +void BitmapColoring::prepareAndTraceLines() +{ + bool black; + ui->cbMethodSelector->currentIndex() == 1 ? black = false: black = true; + + LayerManager* lMgr = mEditor->layers(); + LayerBitmap* sourceLayer = mLayerBitmap; + QString orgName = mLayerBitmap->name(); + LayerBitmap* artLayer = nullptr; + LayerBitmap* colorLayer = nullptr; + + if (!lMgr->findLayerByName(orgName + "_L")) + { + colorLayer = lMgr->createBitmapLayer(orgName + "_C"); + artLayer = lMgr->createBitmapLayer(orgName + "_L"); + } + else + { + colorLayer = static_cast(lMgr->findLayerByName(orgName + "_C")); + artLayer = static_cast(lMgr->findLayerByName(orgName + "_L")); + } + Q_ASSERT(artLayer && colorLayer); + + artLayer->setHasColorLayer(true); + colorLayer->setIsColorLayer(true); + + if (ui->cb3TraceAllKeyframes->isChecked()) + { + for (int i = sourceLayer->firstKeyFramePosition(); i <= sourceLayer->getMaxKeyFramePosition(); i++) + { + if (sourceLayer->keyExists(i)) + { + mEditor->scrubTo(i); + lMgr->setCurrentLayer(sourceLayer); + copyFrame(sourceLayer, artLayer, i); + BitmapImage* image = artLayer->getBitmapImageAtFrame(i); + image->prepDrawing(image, + mRedChecked, + mGreenChecked, + mBlueChecked); + copyFrame(artLayer, colorLayer, i); + BitmapImage* colorImage = colorLayer->getBitmapImageAtFrame(i); + colorImage->traceLine(colorImage, + black, + mRedChecked, + mGreenChecked, + mBlueChecked); + } + } + ui->tabWidget->setCurrentIndex(1); + QMessageBox msgBox; + msgBox.setText(tr("Ready for thinning lines!")); + msgBox.exec(); + } + else + { + int i = mEditor->currentFrame(); + + if (sourceLayer->keyExists(i)) + { + lMgr->setCurrentLayer(sourceLayer); + copyFrame(sourceLayer, artLayer, i); + BitmapImage* image = artLayer->getBitmapImageAtFrame(i); + image->prepDrawing(image, + mRedChecked, + mGreenChecked, + mBlueChecked); + copyFrame(artLayer, colorLayer, i); + BitmapImage* colorImage = colorLayer->getBitmapImageAtFrame(i); + colorImage->traceLine(colorImage, + black, + mRedChecked, + mGreenChecked, + mBlueChecked); + } + } + lMgr->setCurrentLayer(colorLayer); + + mEditor->scrubTo(mEditor->currentFrame()); +} + +// protected functions +void BitmapColoring::prepareLines() +{ + // Method selector can be 0, 1 or 2. 0 = No method selected + if (ui->cbMethodSelector->currentIndex() < 1) + { + return; + } + + LayerManager* lMgr = mEditor->layers(); + LayerBitmap* colorLayer = nullptr; + bool black; + ui->cbMethodSelector->currentIndex() == 1 ? black = false: black = true; + // Method selector 1 = Coloring on same layer + if (ui->cbMethodSelector->currentIndex() == 1) + { + colorLayer = mLayerBitmap; + } + + // Method selector 2 = Coloring on separate layer + else + { + QString orgName = mLayerBitmap->name(); + // is it a copy or is it a prepared scanned drawing? + if (orgName.endsWith("_L")) + orgName.chop(2); + if (!mLayerBitmap->getHasColorLayer()) + { + mAnimLayer = mEditor->currentLayerIndex(); // necessary since new layer becomes currentlayer + colorLayer = lMgr->createBitmapLayer(orgName + "_C"); + mColLayer = mEditor->object()->getLayerCount() - 1; + lMgr->setCurrentLayer(mAnimLayer); + mLayerBitmap->setHasColorLayer(true); + colorLayer->setIsColorLayer(true); + } + else + { + colorLayer = static_cast(lMgr->findLayerByName(orgName + "_C")); + } + } + Q_ASSERT(colorLayer); + + if (ui->cbMethodSelector->currentIndex() == 2) + { + copyFrame(mLayerBitmap, colorLayer, mEditor->currentFrame()); + } + mBitmapImage = colorLayer->getBitmapImageAtFrame(mEditor->currentFrame()); + if (mBitmapImage == nullptr) + { + nonValidBitmap(mEditor->currentFrame()); + return; + } + + mBitmapImage->traceLine(mBitmapImage, + black, + mRedChecked, + mGreenChecked, + mBlueChecked); +} + +void BitmapColoring::trace() +{ + mBitmapImage = mLayerBitmap->getBitmapImageAtFrame(mEditor->currentFrame()); + if (mBitmapImage == nullptr) + { + nonValidBitmap(mEditor->currentFrame()); + return; + } + + prepareLines(); + mEditor->backup("Trace lines"); +} + +void BitmapColoring::thin() +{ + bool black; + ui->cbMethodSelector->currentIndex() == 1 ? black = false: black = true; + mBitmapImage = mLayerBitmap->getBitmapImageAtFrame(mEditor->currentFrame()); + if (mBitmapImage == nullptr) + { + nonValidBitmap(mEditor->currentFrame()); + return; + } + + mBitmapImage->toThinLine(mBitmapImage, + black, + ui->cb2ThinRed->isChecked(), + ui->cb2ThinGreen->isChecked(), + ui->cb2ThinBlue->isChecked()); + mEditor->backup("Thin lines"); + updateUI(); +} + +void BitmapColoring::blend(LayerBitmap *artLayer) +{ + bool black; + ui->cbMethodSelector->currentIndex() == 1 ? black = false: black = true; + mBitmapImage = mLayerBitmap->getBitmapImageAtFrame(mEditor->currentFrame()); + if (mBitmapImage == nullptr) + { + nonValidBitmap(mEditor->currentFrame()); + return; + } + + mBitmapImage->blendLines(mLayerBitmap->getBitmapImageAtFrame(mEditor->currentFrame()), + black, + ui->cb2BlendRed->isChecked(), + ui->cb2BlendGreen->isChecked(), + ui->cb2BlendBlue->isChecked()); + mEditor->backup("Blend lines"); + if (ui->cbMethodSelector->currentIndex() == 2 && artLayer != nullptr) + { + mBitmapImage = artLayer->getBitmapImageAtFrame(mEditor->currentFrame()); + if (mBitmapImage == nullptr) + { + nonValidBitmap(mEditor->currentFrame()); + return; + } + + mBitmapImage->eraseRedGreenBlueLines(mBitmapImage); + mEditor->backup("Blend lines"); + } + updateUI(); +} + +void BitmapColoring::nonValidBitmap(int frame) +{ + QMessageBox msgBox; + msgBox.setText(tr("Frame %1 is not valid!\nAborting frame...").arg(frame)); + msgBox.exec(); +} + +void BitmapColoring::updateTraceButtons() +{ + if (ui->cb2TraceRed->isChecked() || ui->cb2TraceGreen->isChecked() || ui->cb2TraceBlue->isChecked()) + { + ui->btnPrepareLines->setEnabled(true); + ui->btnApplyTrace->setEnabled(false); + } + else + { + ui->btnPrepareLines->setEnabled(false); + ui->btnApplyTrace->setEnabled(true); + } +} + +void BitmapColoring::copyFrame(LayerBitmap *fromLayer, LayerBitmap *toLayer, int frame) +{ + if (fromLayer->keyExists(frame)) + { + KeyFrame* keyframe = fromLayer->getKeyFrameAt(frame); + KeyFrame* dupKey = keyframe->clone(); + if (toLayer->keyExists(frame) && toLayer->keyFrameCount() == 1) + { + int i = toLayer->firstKeyFramePosition(); + if (i == frame) + { + toLayer->addKeyFrame(frame + 1, dupKey); + toLayer->removeKeyFrame(frame); + toLayer->moveKeyFrame(frame + 1, -1); + } else + { + toLayer->addKeyFrame(frame, dupKey); + toLayer->removeKeyFrame(i); + } + } + else + { + toLayer->removeKeyFrame(frame); + toLayer->addKeyFrame(frame, dupKey); + } + toLayer->setModified(frame, true); + toLayer->getKeyFrameAt(frame)->modification(); + } + +} diff --git a/app/src/bitmapcoloring.h b/app/src/bitmapcoloring.h new file mode 100644 index 000000000..c6f2191b7 --- /dev/null +++ b/app/src/bitmapcoloring.h @@ -0,0 +1,75 @@ +#ifndef BITMAPCOLORING_H +#define BITMAPCOLORING_H + +#include "basedockwidget.h" + +class Object; +class BitmapImage; +class Editor; +class LayerBitmap; +class ScribbleArea; + + +namespace Ui +{ +class BitmapColoringWidget; +} + +class BitmapColoring : public BaseDockWidget +{ + Q_OBJECT + +public: + explicit BitmapColoring(Editor* editor, QWidget *parent); + ~BitmapColoring() override; + + void initUI() override; + void updateUI() override; + void onVisibilityChanged(bool visibility); + +private slots: + void checkRedBoxes(); + void checkGreenBoxes(); + void checkBlueBoxes(); + void checkAllKeyframesBoxes(); + void tabWidgetClicked(int index); + void resetColoringDock(); + void infoBox(); + void enableTabs(int index); + // 1: Trace + void updateTraceBoxes(); + void prepareAndTraceLines(); + void traceLines(); + // 2: Thin + void updateFillSpotsButton(); + void fillSpotAreas(); + void updateThinBoxes(); + void setSpotArea(int size); + void thinLines(); + // 3: Blend + void updateBlendBoxes(); + void blendLines(); + +private: + void prepareLines(); + void trace(); + void thin(); + void blend(LayerBitmap* artLayer); + void nonValidBitmap(int frame); + void updateTraceButtons(); + void copyFrame(LayerBitmap *fromLayer, LayerBitmap *toLayer, int frame); + + Ui::BitmapColoringWidget* ui = nullptr; + Editor* mEditor = nullptr; + ScribbleArea* mScribblearea = nullptr; + LayerBitmap* mLayerBitmap = nullptr; + BitmapImage* mBitmapImage = nullptr; + int mAnimLayer = 0; // Animation layer index + int mColLayer = 0; // Coloring layer index + bool mRedChecked = false; + bool mGreenChecked = false; + bool mBlueChecked = false; + +}; + +#endif // BITMAPCOLORING_H diff --git a/app/src/mainwindow2.cpp b/app/src/mainwindow2.cpp index f59aaa32c..560b6edb6 100644 --- a/app/src/mainwindow2.cpp +++ b/app/src/mainwindow2.cpp @@ -64,8 +64,10 @@ GNU General Public License for more details. #include "preferencesdialog.h" #include "timeline.h" #include "toolbox.h" +#include "bitmapcoloring.h" #include "onionskinwidget.h" #include "pegbaralignmentdialog.h" +#include "addtransparencytopaperdialog.h" #include "repositionframesdialog.h" //#include "preview.h" @@ -171,6 +173,9 @@ void MainWindow2::createDockWidgets() mToolBox = new ToolBoxWidget(this); mToolBox->setObjectName("ToolBox"); + mBitmapColoring = new BitmapColoring(mEditor, this); + mBitmapColoring->setObjectName("BitmapColoring"); + mDockWidgets << mTimeLine << mColorBox @@ -178,7 +183,8 @@ void MainWindow2::createDockWidgets() << mColorPalette << mOnionSkinWidget << mToolOptions - << mToolBox; + << mToolBox + << mBitmapColoring; mStartIcon = QIcon(":icons/controls/play.png"); mStopIcon = QIcon(":icons/controls/stop.png"); @@ -199,6 +205,7 @@ void MainWindow2::createDockWidgets() addDockWidget(Qt::RightDockWidgetArea, mColorBox); addDockWidget(Qt::RightDockWidgetArea, mColorInspector); addDockWidget(Qt::RightDockWidgetArea, mColorPalette); + addDockWidget(Qt::RightDockWidgetArea, mBitmapColoring); addDockWidget(Qt::LeftDockWidgetArea, mToolBox); addDockWidget(Qt::LeftDockWidgetArea, mToolOptions); addDockWidget(Qt::LeftDockWidgetArea, mOnionSkinWidget); @@ -225,7 +232,10 @@ void MainWindow2::createDockWidgets() for (BaseDockWidget* w : mDockWidgets) { w->setFloating(false); - w->show(); + if (w != mBitmapColoring) + { + w->show(); + } w->updateUI(); } } @@ -275,6 +285,7 @@ void MainWindow2::createMenus() connect(ui->actionFlip_X, &QAction::triggered, mCommands, &ActionCommands::flipSelectionX); connect(ui->actionFlip_Y, &QAction::triggered, mCommands, &ActionCommands::flipSelectionY); connect(ui->actionPegbarAlignment, &QAction::triggered, this, &MainWindow2::openPegAlignDialog); + connect(ui->actionAdd_Transparency_to_paper, &QAction::triggered, this, &MainWindow2::openAddTranspToPaperDialog); connect(ui->actionSelect_All, &QAction::triggered, mCommands, &ActionCommands::selectAll); connect(ui->actionDeselect_All, &QAction::triggered, mCommands, &ActionCommands::deselectAll); connect(ui->actionReposition_Selected_Frames, &QAction::triggered, this, &MainWindow2::openRepositionDialog); @@ -432,6 +443,7 @@ void MainWindow2::createMenus() mColorPalette->toggleViewAction(), mTimeLine->toggleViewAction(), mColorInspector->toggleViewAction(), + mBitmapColoring->toggleViewAction(), mOnionSkinWidget->toggleViewAction() }; @@ -523,6 +535,26 @@ void MainWindow2::openLayerOpacityDialog() }); } +void MainWindow2::openAddTranspToPaperDialog() +{ + if (mAddTranspToPaper == nullptr) + { + mAddTranspToPaper = new AddTransparencyToPaperDialog(); + mAddTranspToPaper->setCore(mEditor); + mAddTranspToPaper->initUI(); + mAddTranspToPaper->setWindowFlag(Qt::WindowStaysOnTopHint); + mAddTranspToPaper->show(); + + connect(mAddTranspToPaper, &AddTransparencyToPaperDialog::closeDialog, [=] { + mAddTranspToPaper->deleteLater(); + mAddTranspToPaper = nullptr; + }); + + } else { + mAddTranspToPaper->raise(); + } +} + void MainWindow2::openRepositionDialog() { if (mEditor->layers()->currentLayer()->getSelectedFramesByPos().count() < 2) @@ -694,6 +726,23 @@ bool MainWindow2::openObject(const QString& strFilePath) setWindowModified(false); ui->statusBar->updateModifiedStatus(false); + // identify color layers + for (int i = 1; i < mEditor->layers()->count(); i++) + { + Layer* color = mEditor->layers()->getLayer(i); + if (color->type() == Layer::BITMAP && color->name().endsWith("_C")) + { + QString tmp = color->name(); + tmp.chop(2); + Layer* org = mEditor->layers()->findLayerByName(tmp); + if (org != nullptr) + { + color->setIsColorLayer(true); + org->setHasColorLayer(true); + } + } + } + progress.setValue(progress.maximum()); updateSaveState(); @@ -1502,6 +1551,8 @@ void MainWindow2::makeConnections(Editor* pEditor, TimeLine* pTimeline) connect(pEditor->layers(), &LayerManager::currentLayerChanged, this, &MainWindow2::updateLayerMenu); connect(pEditor->layers(), &LayerManager::currentLayerChanged, mToolOptions, &ToolOptionWidget::updateUI); + connect(pEditor->layers(), &LayerManager::currentLayerChanged, mBitmapColoring, &BitmapColoring::updateUI); + connect(mBitmapColoring, &QDockWidget::visibilityChanged, mBitmapColoring, &BitmapColoring::onVisibilityChanged); } void MainWindow2::makeConnections(Editor*, OnionSkinWidget*) diff --git a/app/src/mainwindow2.h b/app/src/mainwindow2.h index a9f9f7a0c..64f8444f3 100644 --- a/app/src/mainwindow2.h +++ b/app/src/mainwindow2.h @@ -19,8 +19,6 @@ GNU General Public License for more details. #define MAINWINDOW2_H #include -#include "preferencemanager.h" - template class QList; class QActionGroup; @@ -30,6 +28,7 @@ class Editor; class ScribbleArea; class BaseDockWidget; class ColorPaletteWidget; +class BitmapColoring; class OnionSkinWidget; class ToolOptionWidget; class TimeLine; @@ -44,6 +43,7 @@ class ImportImageSeqDialog; class BackupElement; class LayerOpacityDialog; class PegBarAlignmentDialog; +class AddTransparencyToPaperDialog; class RepositionFramesDialog; class StatusBar; enum class SETTING; @@ -71,6 +71,7 @@ public slots: void openRepositionDialog(); void closeRepositionDialog(); void openLayerOpacityDialog(); + void openAddTranspToPaperDialog(); void currentLayerChanged(); void selectionChanged(); void viewFlipped(); @@ -162,6 +163,7 @@ private slots: //PreviewWidget* mPreview = nullptr; TimeLine* mTimeLine = nullptr; ColorInspector* mColorInspector = nullptr; + BitmapColoring* mBitmapColoring = nullptr; OnionSkinWidget* mOnionSkinWidget = nullptr; QToolBar* mMainToolbar = nullptr; QToolBar* mViewToolbar = nullptr; @@ -173,6 +175,7 @@ private slots: PegBarAlignmentDialog* mPegAlign = nullptr; RepositionFramesDialog* mReposDialog = nullptr; LayerOpacityDialog* mLayerOpacityDialog = nullptr; + AddTransparencyToPaperDialog* mAddTranspToPaper = nullptr; void createToolbars(); private: diff --git a/app/src/timeline.cpp b/app/src/timeline.cpp index af5bd22c1..bf7119fa4 100644 --- a/app/src/timeline.cpp +++ b/app/src/timeline.cpp @@ -173,6 +173,7 @@ void TimeLine::initUI() QGridLayout* rightLayout = new QGridLayout(); rightLayout->addWidget(rightToolBar, 0, 0); + rightLayout->setAlignment(Qt::AlignLeft); rightLayout->addWidget(mTracks, 1, 0); rightLayout->setContentsMargins(0, 0, 0, 0); rightLayout->setSpacing(0); diff --git a/app/ui/addtransparencytopaperdialog.ui b/app/ui/addtransparencytopaperdialog.ui new file mode 100644 index 000000000..a8420ab84 --- /dev/null +++ b/app/ui/addtransparencytopaperdialog.ui @@ -0,0 +1,370 @@ + + + AddTransparencyToPaperDialog + + + + 0 + 0 + 671 + 375 + + + + Add transparency to Paper + + + + + + + + + + + + + + Threshold + + + + + + + Qt::Horizontal + + + + 13 + 19 + + + + + + + + + + + + 150 + + + 245 + + + 220 + + + Qt::Horizontal + + + + + + + Color pick values above treshold, will become transparent + + + 150 + + + 245 + + + 220 + + + + + + + + + + + Qt::Horizontal + + + + + + + 0 + + + + + Zoom + + + + + + + 1 + + + 10 + + + Qt::Horizontal + + + + + + + + + Qt::Horizontal + + + + + + + + + Trace Red + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + :/icons/red.png + + + + + + + + + + + Trace Green + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + :/icons/green.png + + + + + + + + + + + Trace Blue + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + :/icons/blue.png + + + + + + + + + Qt::Horizontal + + + + + + + Apply to: + + + + + + + Current Keyframe + + + + + + + All Keyframes on layer + + + true + + + + + + + Qt::Horizontal + + + + + + + Test transparency + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Close + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Apply + + + true + + + + + + + + + + + + + (Previewing frame: ) + + + + + + + + 0 + 0 + + + + + 400 + 300 + + + + QAbstractScrollArea::AdjustToContents + + + QGraphicsView::NoAnchor + + + + + + + + + + + + + + diff --git a/app/ui/bitmapcoloringwidget.ui b/app/ui/bitmapcoloringwidget.ui new file mode 100644 index 000000000..abe9e598d --- /dev/null +++ b/app/ui/bitmapcoloringwidget.ui @@ -0,0 +1,851 @@ + + + BitmapColoringWidget + + + + 0 + 0 + 282 + 485 + + + + Advanced Coloring + + + + + + + + Methods: + + + + + + + Colorize on Same or Separate layer? + + + + Choose method... + + + + + On the Same Layer + + + + + On a Separate Layer + + + + + + + + + + + + Info + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + + + + Trace + + + + + + Filters + + + + 2 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + + Trace Red + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + :/icons/red.png + + + + + + + + + + + Trace Green + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + :/icons/green.png + + + + + + + + + + + Trace Blue + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + :/icons/blue.png + + + + + + + + + + + + Repeat Process + + + + 2 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + All keyframes + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + <html><head/><body><p>Color separation lines must be prepared for coloring.<br/>Color separation, made in Pencil2d, are not prepared.<br/></p></body></html> + + + Prepare lines + Trace... + + + + + + + + + + + Reset + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Trace... + + + + + + + + + Qt::Vertical + + + + 20 + 19 + + + + + + + + + Thin + + + + + + Fill small areas? + + + + 2 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + + Size of area to fill (1-15) + + + px + + + 1 + + + 15 + + + 6 + + + + + + + false + + + Fill areas + + + + + + + Fill small, unwanted areas in lines + + + Fill + + + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Filters + + + + 2 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + + true + + + Thin Red + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + :/icons/red.png + + + + + + + + + + + true + + + Thin Green + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + :/icons/green.png + + + + + + + + + + + true + + + Thin Blue + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + :/icons/blue.png + + + + + + + + + + + + Repeat Process + + + + 2 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + All keyframes + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Thin... + + + + + + + + + Qt::Vertical + + + + 20 + 59 + + + + + + + + + Blend + + + + 2 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + Filters + + + + 2 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + + true + + + Blend Red + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + :/icons/red.png + + + + + + + + + + + true + + + Blend Green + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + :/icons/green.png + + + + + + + + + + + true + + + Blend Blue + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + :/icons/blue.png + + + + + + + + + + + + Repeat Process + + + + 2 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + All keyframes + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Blend and Finish... + + + + + + + + + Qt::Vertical + + + + 20 + 39 + + + + + + + + + + + + + + + diff --git a/app/ui/mainwindow2.ui b/app/ui/mainwindow2.ui index 50b108997..57752c313 100644 --- a/app/ui/mainwindow2.ui +++ b/app/ui/mainwindow2.ui @@ -107,6 +107,13 @@ + + + Prepare scanned drawings + + + + @@ -121,7 +128,7 @@ - + @@ -1078,6 +1085,16 @@ Reset Rotation + + + Peg bar Alignment + + + + + Add Transparency to paper + + Add Exposure diff --git a/core_lib/src/graphics/bitmap/bitmapimage.cpp b/core_lib/src/graphics/bitmap/bitmapimage.cpp index e2be3641e..f49f1b401 100644 --- a/core_lib/src/graphics/bitmap/bitmapimage.cpp +++ b/core_lib/src/graphics/bitmap/bitmapimage.cpp @@ -728,6 +728,546 @@ void BitmapImage::drawPath(QPainterPath path, QPen pen, QBrush brush, modification(); } +BitmapImage* BitmapImage::scanToTransparent(BitmapImage *img, bool redEnabled, bool greenEnabled, bool blueEnabled) +{ + Q_ASSERT(img != nullptr); + + QRgb rgba = img->constScanLine(img->left(), img->top()); + if (qAlpha(rgba) == 0) + return img; + + for (int x = img->left(); x <= img->right(); x++) + { + for (int y = img->top(); y <= img->bottom(); y++) + { + rgba = img->constScanLine(x, y); + + int grayValue = qGray(rgba); + int redValue = qRed(rgba); + int greenValue = qGreen(rgba); + int blueValue = qBlue(rgba); + int alphaValue = qAlpha(rgba); + if (alphaValue == 0) + break; + if (grayValue >= mThreshold) + { // IF Threshold or above + img->scanLine(x, y, transp); + } + else if(redValue > greenValue + COLORDIFF && redValue > blueValue + COLORDIFF && redValue > grayValue + GRAYSCALEDIFF) + { // IF Red line + if (redEnabled) + { + img->scanLine(x, y, redline); + } + else + { + img->scanLine(x, y, transp); + } + } + else if(greenValue > redValue + COLORDIFF && greenValue > blueValue + COLORDIFF && greenValue > grayValue + GRAYSCALEDIFF) + { // IF Green line + if (greenEnabled) + { + img->scanLine(x, y, greenline); + } + else + { + img->scanLine(x, y, transp); + } + } + else if(blueValue > redValue + COLORDIFF && blueValue > greenValue + COLORDIFF && blueValue > grayValue + GRAYSCALEDIFF) + { // IF Blue line + if (blueEnabled) + { + img->scanLine(x, y, blueline); + } + else + { + img->scanLine(x, y, transp); + } + } + else + { // okay, so it is in grayscale graduation area + if( grayValue >= mLowThreshold && grayValue < mThreshold) + { + qreal factor = qreal(mThreshold - grayValue) / qreal(mThreshold - mLowThreshold); + img->scanLine(x , y, qRgba(0, 0, 0, static_cast(mThreshold * factor))); + } + else + { + img->scanLine(x , y, blackline); + } + } + } + } + img->modification(); + return img; +} + +BitmapImage* BitmapImage::prepDrawing(BitmapImage* img, bool redEnabled, bool greenEnabled, bool blueEnabled) +{ + Q_ASSERT(img != nullptr); + + for (int x = img->left(); x <= img->right(); x++) + { + for (int y = img->top(); y <= img->bottom(); y++) + { + QRgb rgba = img->constScanLine(x, y); + if (qAlpha(rgba) > 0) + { + int redValue = qRed(rgba); + int greenValue = qGreen(rgba); + int blueValue = qBlue(rgba); + + if(redValue > greenValue + COLORDIFF && redValue > blueValue + COLORDIFF) + { // IF Red line + if (redEnabled) + { + img->scanLine(x, y, redline); + } + else + { + img->scanLine(x, y, transp); + } + } + + else if(greenValue > redValue + COLORDIFF && greenValue > blueValue + COLORDIFF) + { // IF Green line + if (greenEnabled) + { + img->scanLine(x, y, greenline); + } + else + { + img->scanLine(x, y, transp); + } + } + else if(blueValue > redValue + COLORDIFF && blueValue > greenValue + COLORDIFF) + { // IF Blue line + if (blueEnabled) + { + img->scanLine(x, y, blueline); + } + else + { + img->scanLine(x, y, transp); + } + } + } + + } + } + img->modification(); + return img; +} + +void BitmapImage::traceLine(BitmapImage* img, bool blackEnabled, bool redEnabled, bool greenEnabled, bool blueEnabled) +{ + Q_ASSERT(img != nullptr); + + QRgb rgba; + for (int x = img->left(); x <= img->right(); x++) + { + for (int y = img->top(); y <= img->bottom(); y++) + { + rgba = img->constScanLine(x, y); + + int redValue = qRed(rgba); + int greenValue = qGreen(rgba); + int blueValue = qBlue(rgba); + int alphaValue = qAlpha(rgba); + if (alphaValue > 0) + { + if(redValue > greenValue && redValue > blueValue) + { + if(redEnabled) + img->scanLine(x, y, redline); + else + img->scanLine(x, y, transp); + } + else if(blueValue > redValue && blueValue > greenValue) + { + if(blueEnabled) + img->scanLine(x, y, blueline); + else + img->scanLine(x, y, transp); + } + else if(greenValue > redValue && greenValue > blueValue) + { + if(greenEnabled) + img->scanLine(x, y, greenline); + else + img->scanLine(x, y, transp); + } + else + { + if (blackEnabled && alphaValue > TRANSP_THRESHOLD) + { + img->scanLine(x, y, blackline); + } + else if (blackEnabled) + img->scanLine(x, y, transp); + } + } + } + } + img->modification(); +} + +void BitmapImage::eraseRedGreenBlueLines(BitmapImage *img) +{ + Q_ASSERT(img != nullptr); + + QRgb rgba; + for (int x = img->left(); x <= img->right(); x++) + { + for (int y = img->top(); y <= img->bottom(); y++) + { + rgba = img->constScanLine(x, y); + if (rgba == redline || rgba == greenline || rgba == blueline) + { + img->scanLine(x, y, transp); + } + } + } + img->modification(); +} + +void BitmapImage::fillSpotAreas(BitmapImage *img) +{ + Q_ASSERT(img != nullptr); + + // fill areas size 'area' or less with appropriate color + QVector points; + points.clear(); + QRgb active; + QRgb previous = blackline; + for (int x = img->left() + 1; x < img->right(); x++) + { + for (int y = img->top() + 1; y < img->bottom(); y++) + { + active = img->constScanLine(x, y); + if (qAlpha(active) == 0) + { + points.append(QPoint(x, y)); + int areaSize = fillWithColor(QPoint(x, y), transp, rosa, img); + if (areaSize <= mSpotArea) + { // replace rosa with last color + fillWithColor(points.last(), rosa, previous, img); + points.removeLast(); + } + } + previous = active; + } + } + // replace rosa with trans + while (!points.isEmpty()) { + fillWithColor(points[0], rosa, transp, img); + points.removeFirst(); + } + img->modification(); +} + +void BitmapImage::toThinLine(BitmapImage * img, bool black, bool red, bool green, bool blue) +{ + Q_ASSERT(img != nullptr); + + bool N = true, E = true, S = true, W = true, pixelRemoved, search; + + QList colors; + if (black) colors.append(blackline); + if (red) colors.append(redline); + if (green) colors.append(greenline); + if (blue) colors.append(blueline); + + QRgb pixColor; + + while (N || E || S || W) + { + if (N) // from NORTH + { + // set 'pixelRemoved' to false. 'pixelRemoved' is set to true whenever a pixel is removed + pixelRemoved = false; + // 'search' is true while pixels are transparent + // when blackline pixel is found, 'search' is set to false until next transparent pixel + search = true; + for (int x = img->left(); x < img->right(); x++) + { + for (int y = img->top(); y < img->bottom(); y++) + { + if (search) + { + pixColor = img->constScanLine(x,y); + if (qAlpha(pixColor) > 0) + { + search = false; + if (qAlpha(img->constScanLine(x,y+1)) > 0 && colors.contains(pixColor)) + { + if ((qAlpha(img->constScanLine(x-1,y-1)) == 0 && qAlpha(img->constScanLine(x+1,y-1)) == 0) && + (qAlpha(img->constScanLine(x+1, y)) > 0 || qAlpha(img->constScanLine(x-1, y)) >0 || + qAlpha(img->constScanLine(x+1, y+1)) > 0 || qAlpha(img->constScanLine(x-1, y+1)) >0)) + { + img->scanLine(x, y, transp); + pixelRemoved = true; + } + else if ((qAlpha(img->constScanLine(x-1,y-1)) > 0 && qAlpha(img->constScanLine(x+1,y-1)) > 0) && + (qAlpha(img->constScanLine(x+1,y)) > 0 && qAlpha(img->constScanLine(x-1,y)) > 0)) + { + img->scanLine(x, y, transp); + pixelRemoved = true; + } + else if ((qAlpha(img->constScanLine(x-1,y-1)) > 0 && qAlpha(img->constScanLine(x-1,y)) > 0) || + (qAlpha(img->constScanLine(x+1,y-1)) > 0 && qAlpha(img->constScanLine(x+1,y)) > 0)) + { + img->scanLine(x, y, transp); + pixelRemoved = true; + } + } + } + } + else + { + if (qAlpha(img->constScanLine(x,y)) == 0) + search = true; + } + } + } + N = pixelRemoved; // if none is removed, N = false + } + if (E) // from EAST + { + pixelRemoved = false; + search = true; + for (int y = img->top(); y < img->bottom(); y++) + { + for (int x = img->right(); x > img->left(); x--) + { + if (search) + { + pixColor = img->constScanLine(x,y); + if (qAlpha(pixColor) > 0) + { + search = false; + if (qAlpha(img->constScanLine(x-1,y)) > 0 && colors.contains(pixColor)) + { + if ((qAlpha(img->constScanLine(x+1,y-1)) == 0 && qAlpha(img->constScanLine(x+1,y+1)) == 0) && + (qAlpha(img->constScanLine(x,y-1)) > 0 || qAlpha(img->constScanLine(x,y+1)) >0 || + qAlpha(img->constScanLine(x-1,y-1)) > 0 || qAlpha(img->constScanLine(x-1,y+1)) >0)) + { + img->scanLine(x, y, transp); + pixelRemoved = true; + } + else if ((qAlpha(img->constScanLine(x+1,y+1)) > 0 && qAlpha(img->constScanLine(x+1,y-1)) > 0) && + (qAlpha(img->constScanLine(x,y+1)) > 0 && qAlpha(img->constScanLine(x,y-1)) > 0)) + { + img->scanLine(x, y, transp); + pixelRemoved = true; + } + else if ((qAlpha(img->constScanLine(x+1,y+1)) > 0 && qAlpha(img->constScanLine(x,y+1)) > 0) || + (qAlpha(img->constScanLine(x+1,y-1)) > 0 && qAlpha(img->constScanLine(x,y-1)) > 0)) + { + img->scanLine(x, y, transp); + pixelRemoved = true; + } + } + } + } + else + { + if (qAlpha(img->constScanLine(x,y)) == 0) + search = true; + } + } + } + E = pixelRemoved; // if none is removed, E = false + } + if (S) // from SOUTH + { + pixelRemoved = false; + search = true; + for (int x = img->left(); x < img->right(); x++) + { + for (int y = img->bottom(); y > img->top(); y--) + { + if (search) + { + pixColor = img->constScanLine(x,y); + if (qAlpha(pixColor) > 0) + { + search = false; + if (qAlpha(img->constScanLine(x,y-1)) > 0 && colors.contains(pixColor)) + { + if ((qAlpha(img->constScanLine(x-1,y+1)) == 0 && qAlpha(img->constScanLine(x+1,y+1)) == 0) && + (qAlpha(img->constScanLine(x-1, y)) > 0 || qAlpha(img->constScanLine(x+1, y)) >0 || + qAlpha(img->constScanLine(x-1, y-1)) > 0 || qAlpha(img->constScanLine(x+1, y-1)) >0)) + { + img->scanLine(x, y, transp); + pixelRemoved = true; + } + else if ((qAlpha(img->constScanLine(x-1,y+1)) > 0 && qAlpha(img->constScanLine(x+1,y+1)) > 0) && + (qAlpha(img->constScanLine(x+1,y)) > 0 && qAlpha(img->constScanLine(x-1,y)) > 0)) + { + img->scanLine(x, y, transp); + pixelRemoved = true; + } + else if ((qAlpha(img->constScanLine(x-1,y+1)) > 0 && qAlpha(img->constScanLine(x-1,y)) > 0) || + (qAlpha(img->constScanLine(x+1,y+1)) > 0 && qAlpha(img->constScanLine(x+1,y)) > 0)) + { + img->scanLine(x, y, transp); + pixelRemoved = true; + } + } + } + } + else + { + if (qAlpha(img->constScanLine(x,y)) == 0) + search = true; + } + } + } + S = pixelRemoved; // if none is removed, S = false + } + if (W) // from WEST + { + pixelRemoved = false; + search = true; + for (int y = img->top(); y <= img->bottom(); y++) + { + for (int x = img->left(); x < img->right(); x++) + { + if (search) + { + pixColor = img->constScanLine(x,y); + if (qAlpha(pixColor) > 0) + { + search = false; + if (qAlpha(img->constScanLine(x+1,y)) > 0 && colors.contains(pixColor)) + { + if ((qAlpha(img->constScanLine(x-1,y-1)) == 0 && qAlpha(img->constScanLine(x-1,y+1)) == 0) && + (qAlpha(img->constScanLine(x,y-1)) > 0 || qAlpha(img->constScanLine(x,y+1)) >0 || + qAlpha(img->constScanLine(x+1,y-1)) > 0 || qAlpha(img->constScanLine(x+1,y+1)) >0)) + { + img->scanLine(x, y, transp); + pixelRemoved = true; + } + else if ((qAlpha(img->constScanLine(x-1,y+1)) > 0 && qAlpha(img->constScanLine(x-1,y-1)) > 0) && + (qAlpha(img->constScanLine(x,y+1)) > 0 && qAlpha(img->constScanLine(x,y-1)) > 0)) + { + img->scanLine(x, y, transp); + pixelRemoved = true; + } + else if ((qAlpha(img->constScanLine(x-1,y+1)) > 0 && qAlpha(img->constScanLine(x,y+1)) > 0) || + (qAlpha(img->constScanLine(x-1,y-1)) > 0 && qAlpha(img->constScanLine(x,y-1)) > 0)) + { + img->scanLine(x, y, transp); + pixelRemoved = true; + } + } + } + } + else + { + if (qAlpha(img->constScanLine(x,y)) == 0) + search = true; + } + } + } + W = pixelRemoved; // if none is removed, W = false + } + } + img->modification(); +} + +void BitmapImage::blendLines(BitmapImage *img, bool black, bool red, bool green, bool blue) +{ + Q_ASSERT(img != nullptr); + + int r, g, b, a; //red, green, blue, alpha + QList points; // QPoints to add in calculation + QList rgblist; // QRgb's that should be excluded + if (black) rgblist << blackline; + if (red) rgblist << redline; + if (green) rgblist << greenline; + if (blue) rgblist << blueline; + for (int x = img->left(); x <= img->right(); x++) + { + for (int y = img->top(); y <= img->bottom(); y++) + { + points.clear(); + r=0; g=0; b=0; a=0; + if (rgblist.contains(img->constScanLine(x,y))) + { + if (!rgblist.contains(img->pixel(x-1, y-1))) points.append(QPoint(x-1, y-1)); + if (!rgblist.contains(img->pixel(x-1, y ))) points.append(QPoint(x-1, y )); + if (!rgblist.contains(img->pixel(x-1, y+1))) points.append(QPoint(x-1, y+1)); + if (!rgblist.contains(img->pixel(x , y-1))) points.append(QPoint(x , y-1)); + if (!rgblist.contains(img->pixel(x , y+1))) points.append(QPoint(x , y+1)); + if (!rgblist.contains(img->pixel(x+1, y-1))) points.append(QPoint(x+1, y-1)); + if (!rgblist.contains(img->pixel(x+1, y ))) points.append(QPoint(x+1, y )); + if (!rgblist.contains(img->pixel(x+1, y+1))) points.append(QPoint(x+1, y+1)); + for (int i = 0; i < points.size(); i++) + { + r += static_cast(qPow(qRed(img->pixel(points.at(i))), 2)); + g += static_cast(qPow(qGreen(img->pixel(points.at(i))), 2)); + b += static_cast(qPow(qBlue(img->pixel(points.at(i))), 2)); + a += static_cast(qPow(qAlpha(img->pixel(points.at(i))), 2)); + } + r = static_cast(sqrt(r/points.size())); + g = static_cast(sqrt(g/points.size())); + b = static_cast(sqrt(b/points.size())); + a = static_cast(sqrt(a/points.size())); + img->scanLine(x, y, qRgba(r, g, b, a)); + } + } + } + img->modification(); +} + +int BitmapImage::fillWithColor(QPoint point, QRgb orgColor, QRgb newColor, BitmapImage *img) +{ + Q_ASSERT(img != nullptr); + + QList fillList; + fillList.clear(); + // fill first pixel + img->scanLine(point.x(), point.y(), newColor); + int pixels = 1; + fillList.append(point); + + QRect rect = img->bounds(); + while (!fillList.isEmpty()) + { + QPoint tmp = fillList.takeFirst(); + if (rect.contains(QPoint(tmp.x() + 1, tmp.y())) && img->pixel(QPoint(tmp.x() + 1, tmp.y())) == orgColor) + { + img->scanLine(tmp.x() + 1, tmp.y(), newColor); + fillList.append(QPoint(tmp.x() + 1, tmp.y())); + pixels++; + } + if (rect.contains(QPoint(tmp.x(), tmp.y() + 1)) && img->pixel(QPoint(tmp.x(), tmp.y() + 1)) == orgColor) + { + img->scanLine(tmp.x(), tmp.y() + 1, newColor); + fillList.append(QPoint(tmp.x(), tmp.y() + 1)); + pixels++; + } + if (rect.contains(QPoint(tmp.x() - 1, tmp.y())) && img->pixel(QPoint(tmp.x() - 1, tmp.y())) == orgColor) + { + img->scanLine(tmp.x() - 1, tmp.y(), newColor); + fillList.append(QPoint(tmp.x() - 1, tmp.y())); + pixels++; + } + if (rect.contains(QPoint(tmp.x(), tmp.y() - 1)) && img->pixel(QPoint(tmp.x(), tmp.y() - 1)) == orgColor) + { + img->scanLine(tmp.x(), tmp.y() - 1, newColor); + fillList.append(QPoint(tmp.x(), tmp.y() - 1)); + pixels++; + } + } + img->modification(); + return pixels; +} + Status BitmapImage::writeFile(const QString& filename) { if (!mImage.isNull()) diff --git a/core_lib/src/graphics/bitmap/bitmapimage.h b/core_lib/src/graphics/bitmap/bitmapimage.h index ead411bfb..7c96191f2 100644 --- a/core_lib/src/graphics/bitmap/bitmapimage.h +++ b/core_lib/src/graphics/bitmap/bitmapimage.h @@ -24,10 +24,16 @@ GNU General Public License for more details. class TiledBuffer; - class BitmapImage : public KeyFrame { public: + const QRgb transp = qRgba(0, 0, 0, 0); + const QRgb rosa = qRgba(255,230,230,255); + const QRgb blackline = qRgba(1, 1, 1, 255); + const QRgb redline = qRgba(254,0,0,255); + const QRgb greenline = qRgba(0,254,0,255); + const QRgb blueline = qRgba(0,0,254,255); + BitmapImage(); BitmapImage(const BitmapImage&); BitmapImage(const QRect &rectangle, const QColor& color); @@ -107,6 +113,16 @@ class BitmapImage : public KeyFrame QRect& bounds() { autoCrop(); return mBounds; } + // coloring methods + BitmapImage* scanToTransparent(BitmapImage* bitmapimage, bool redEnabled, bool greenEnabled, bool blueEnabled); + BitmapImage* prepDrawing(BitmapImage* img, bool redEnabled, bool greenEnabled, bool blueEnabled); + void traceLine(BitmapImage* bitmapimage, bool blackEnabled, bool redEnabled, bool greenEnabled, bool blueEnabled); + void eraseRedGreenBlueLines(BitmapImage* img); + void fillSpotAreas(BitmapImage* img); + void toThinLine(BitmapImage* colorImage, bool black, bool red, bool green, bool blue); + void blendLines(BitmapImage* bitmapimage, bool black, bool red, bool green, bool blue); + int fillWithColor(QPoint point, QRgb orgColor, QRgb newColor, BitmapImage* img); + /** Determines if the BitmapImage is minimally bounded. * * A BitmapImage is minimally bounded if all edges contain @@ -124,6 +140,9 @@ class BitmapImage : public KeyFrame Status writeFile(const QString& filename); +public slots: + void setThreshold(int threshold) { mThreshold = threshold; } + void setSpotArea(int spotArea) { mSpotArea = spotArea; } /** Compare colors for the purposes of flood filling * * Calculates the Eulcidian difference of the RGB channels @@ -181,6 +200,14 @@ class BitmapImage : public KeyFrame /** @see isMinimallyBounded() */ bool mMinBound = true; bool mEnableAutoCrop = false; + + int mSpotArea = 6; + int mThreshold = 200; + const int mLowThreshold = 30; // threshold for images to be given transparency + const int COLORDIFF = 5; // difference in color values to decide color + const int GRAYSCALEDIFF = 15; // difference in grasycale values to decide color + const int TRANSP_THRESHOLD = 60;// threshold when tracing black for two layer coloring + qreal mOpacity = 1.0; }; diff --git a/core_lib/src/interface/editor.cpp b/core_lib/src/interface/editor.cpp index 7c4d5bc60..6bf6c2d05 100644 --- a/core_lib/src/interface/editor.cpp +++ b/core_lib/src/interface/editor.cpp @@ -505,7 +505,7 @@ void Editor::clearUndoStack() void Editor::updateAutoSaveCounter() { - if (mIsAutosave == false) + if (mIsAutosave == false || mIsDoingRepeatInColoring) return; mAutosaveCounter++; @@ -1140,6 +1140,7 @@ void Editor::scrubTo(int frame) emit updateTimeLineCached(); // needs to update the timeline to update onion skin positions } mObject->updateActiveFrames(frame); + emit scrubbedTo(frame); } void Editor::scrubForward() diff --git a/core_lib/src/interface/editor.h b/core_lib/src/interface/editor.h index eef05b266..48b2d8cde 100644 --- a/core_lib/src/interface/editor.h +++ b/core_lib/src/interface/editor.h @@ -154,14 +154,20 @@ class Editor : public QObject void objectLoaded(); void fpsChanged(int fps); + void scrubbedTo(int frame); void needSave(); void needDisplayInfo(const QString& title, const QString& body); void needDisplayInfoNoTitle(const QString& body); + + // Something was updated on the current frame, notify receivers + void currentFrameUpdated(); + void canCopyChanged(bool enabled); void canPasteChanged(bool enabled); + public: //slots /** Will call update() and update the canvas @@ -228,6 +234,10 @@ class Editor : public QObject void dontAskAutoSave(bool b) { mAutosaveNeverAskAgain = b; } bool autoSaveNeverAskAgain() const { return mAutosaveNeverAskAgain; } void resetAutoSaveCounter(); + int getAutoSaveCounter() { return mAutosaveCounter; } + void setAutoSaveCounter(int count) { mAutosaveCounter = count; } + void setIsDoingRepeatColoring(bool b) { mIsDoingRepeatInColoring = b; } + private: Status importBitmapImage(const QString&, int space = 0); @@ -266,6 +276,7 @@ class Editor : public QObject int mAutosaveNumber = 12; int mAutosaveCounter = 0; bool mAutosaveNeverAskAgain = false; + bool mIsDoingRepeatInColoring = false; void makeConnections(); KeyFrame* addKeyFrame(int layerNumber, int frameNumber); diff --git a/core_lib/src/interface/scribblearea.h b/core_lib/src/interface/scribblearea.h index c31ffca13..3761db879 100644 --- a/core_lib/src/interface/scribblearea.h +++ b/core_lib/src/interface/scribblearea.h @@ -30,7 +30,6 @@ GNU General Public License for more details. #include #include "movemode.h" -#include "log.h" #include "pencildef.h" #include "bitmapimage.h" #include "canvaspainter.h" diff --git a/core_lib/src/managers/layermanager.cpp b/core_lib/src/managers/layermanager.cpp index 927988f4b..3ddd378cc 100644 --- a/core_lib/src/managers/layermanager.cpp +++ b/core_lib/src/managers/layermanager.cpp @@ -324,6 +324,16 @@ Status LayerManager::deleteLayer(int index) } Q_ASSERT(object()->getLayerCount() >= 2); + // resets layer flag, if color layer is deleted + if (layer->getIsColorLayer()) + { + QString s = layer->name(); + s.chop(2); + Layer* artLayer = findLayerByName(s); + if (artLayer != nullptr) + artLayer->setHasColorLayer(false); + } + // current layer is the last layer && we are deleting it if (index == object()->getLayerCount() - 1 && index == currentLayerIndex()) diff --git a/core_lib/src/structure/layer.h b/core_lib/src/structure/layer.h index bbb857cf8..3dbce2eed 100644 --- a/core_lib/src/structure/layer.h +++ b/core_lib/src/structure/layer.h @@ -66,6 +66,11 @@ class Layer : public QObject bool visible() const { return mVisible; } void setVisible(bool b) { mVisible = b; } + void setHasColorLayer(bool b) { mHasColorLayer = b; } + bool getHasColorLayer() { return mHasColorLayer; } + void setIsColorLayer(bool b) { mIsColorLayer = b; } + bool getIsColorLayer() { return mIsColorLayer; } + /** Get selected keyframe positions sorted by position */ QList selectedKeyFramesPositions() const { return mSelectedFrames_byPosition; } @@ -180,6 +185,8 @@ class Layer : public QObject int mId = 0; bool mVisible = true; QString mName; + bool mHasColorLayer = false; + bool mIsColorLayer = false; std::map> mKeyFrames;