/* Copyright 2006-2026 The QElectroTech Team This file is part of QElectroTech. QElectroTech 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. QElectroTech 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 QElectroTech. If not, see . */ #ifndef IMAGE_TRANSPARENT_COLOR_DIALOG_H #define IMAGE_TRANSPARENT_COLOR_DIALOG_H #include #include #include #include #include class QVBoxLayout; class QPushButton; class QMouseEvent; /** @brief The ClickableImageLabel class Displays an image scaled to fit a reasonable dialog size, but always samples the picked color from the original, full-resolution image at the corresponding coordinate -- never from the (possibly smoothly interpolated, and therefore color-blended) scaled-down pixmap being displayed, which would make the picked color subtly wrong for exactly the pixels near an edge where it matters most. */ class ClickableImageLabel : public QLabel { Q_OBJECT public: explicit ClickableImageLabel(const QImage &sourceImage, QWidget *parent = nullptr); /// The same downsampled image already computed for display -- /// reused as the live-preview source so a tolerance drag runs /// its per-tick colour-key pass against a small image instead /// of the full-resolution one, which on a large source (a /// scanned schematic background, several Mpx) made every /// intermediate slider tick visibly lag. resultPixmap() still /// computes the final, committed result from the true /// full-resolution source -- only the live preview is /// downsampled. QImage displayImage() const { return m_displayImage; } signals: void colorPicked(const QColor &color); protected: void mousePressEvent(QMouseEvent *event) override; private: QImage m_source; QImage m_displayImage; qreal m_displayScale = 1.0; }; /** @brief The ImageTransparentColorDialog class Lets the user click directly on a preview of the image to sample one or more colors -- each click adds to the set rather than replacing the previous pick -- each shown as its own row: a colour swatch, a slider for that colour's own tolerance right next to it, and a remove button. Each colour keeps its own tolerance rather than sharing one: a white background and a grey border rarely need the same looseness of match, and forcing one tolerance onto both meant either the background left ragged edges or the border ate into content near it. A live checkerboard-backed preview of the combined result updates as any slider moves. A self-contained modal dialog rather than a diagram-level "click the canvas to pick" interaction mode, since this needs neither undo-during-drag nor coexistence with other tools; it only ever needs a handful of clicks, evaluated against a pixmap the caller already has in hand. */ class ImageTransparentColorDialog : public QDialog { Q_OBJECT public: /// One picked colour and the tolerance it's individually keyed /// with -- the whole point of this being a struct rather than /// two parallel lists is that the two can never drift out of /// index alignment with each other. struct PickedColor { QColor color; int tolerance; }; /// @param basePixmap the pristine source to pick colours from -- /// the caller's responsibility to pass the true original, not /// an already colour-keyed result, or previously-transparent /// areas would show as plain background rather than a pickable /// surface, and re-picking the same colour would be a no-op. /// @param existingColors colours (with their individual /// tolerances) already keyed out of basePixmap in a previous /// session, shown as rows from the start rather than forcing /// them to be re-picked from scratch. explicit ImageTransparentColorDialog(const QPixmap &basePixmap, const QList &existingColors = {}, QWidget *parent = nullptr); /// The resulting pixmap: basePixmap unchanged if no colour is /// picked, colour-keyed against every picked colour (each at /// its own tolerance) otherwise. QPixmap resultPixmap() const; /// The final set of picked colours and their individual /// tolerances, for the caller to remember across dialog /// sessions -- may differ from existingColors if any were /// added, removed, or had their tolerance adjusted. QList pickedColors() const { return m_pickedColors; } /// Public so DiagramImageItem can re-derive its display pixmap /// directly (base + crop + these colours) without needing to /// re-open this dialog every time the crop region changes -- /// binary transparency within tolerance, not a smooth falloff: /// every pixel within a colour's own `tolerance` (0-100, mapped /// onto the maximum possible RGB distance) of that colour /// becomes fully transparent, everything else keeps its /// existing alpha untouched. Squared distance throughout, /// avoiding a sqrt per pixel; breaks out of the inner loop on /// the first matching colour, since further matches wouldn't /// change the outcome. static QImage applyColorKey(const QImage &source, const QList &keyColors); private slots: void onColorPicked(const QColor &color); private: void setToleranceForIndex(int index, int value); void removeColor(int index); void rebuildSwatches(); void updatePreview(); void updateOkEnabled(); static QPixmap onCheckerboard(const QImage &image); QImage m_sourceImage; QImage m_previewSourceImage; // downsampled -- see ClickableImageLabel::displayImage()'s comment for why QList m_pickedColors; bool m_startedWithColors = false; // whether existingColors was non-empty -- see updateOkEnabled()'s comment for why this matters int m_lastToleranceUsed = 10; // seeds a newly-picked colour's own tolerance, so successive picks in one session feel consistent rather than each resetting to some fixed default ClickableImageLabel *m_sourceLabel; QLabel *m_previewLabel; QVBoxLayout *m_swatchesLayout; // one row per picked colour, stacked vertically -- each row now carries its own slider, too wide to lay out side by side the way plain swatches once were QLabel *m_hintLabel; QPushButton *m_okButton; }; #endif // IMAGE_TRANSPARENT_COLOR_DIALOG_H