mirror of
https://github.com/qelectrotech/qelectrotech-source-mirror.git
synced 2026-10-01 23:54:13 +02:00
Fix transparency export (not possible for jpeg).
This commit is contained in:
@@ -31,10 +31,14 @@
|
||||
#include "../QetGraphicsItemModeler/qetgraphicshandleritem.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QBuffer>
|
||||
#include <QFileDialog>
|
||||
#include <QFileInfo>
|
||||
#include <QGraphicsSceneContextMenuEvent>
|
||||
#include <QImageWriter>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QTextStream>
|
||||
|
||||
/**
|
||||
@brief DiagramImageItem::DiagramImageItem
|
||||
@@ -1029,6 +1033,177 @@ void DiagramImageItem::restoreAspectRatio()
|
||||
diagram()->undoStack().push(undo);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramImageItem::saveImageAs
|
||||
Saves the currently displayed pixmap (crop and colour-keyed
|
||||
transparency already applied -- what the item actually looks like
|
||||
on the diagram, not the pristine original) to an arbitrary file on
|
||||
disk.
|
||||
*/
|
||||
void DiagramImageItem::saveImageAs()
|
||||
{
|
||||
saveImagePixmapAs(pixmap_, tr("Enregistrer l'image sous..."), !m_transparent_colors.isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramImageItem::saveOriginalImageAs
|
||||
Saves m_base_pixmap -- the true, pristine original, before any crop
|
||||
or colour-keyed transparency -- rather than the item's current,
|
||||
possibly-cropped-and-keyed display pixmap. The only way to recover
|
||||
the un-cropped, un-keyed source once either of those has actually
|
||||
been applied, short of undoing every edit back to the point it was
|
||||
first inserted or replaced.
|
||||
*/
|
||||
void DiagramImageItem::saveOriginalImageAs()
|
||||
{
|
||||
saveImagePixmapAs(m_base_pixmap, tr("Enregistrer l'image d'origine sous..."), false);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramImageItem::saveImagePixmapAs
|
||||
Shared by saveImageAs() and saveOriginalImageAs(): prompts for a
|
||||
destination, resolves whichever format was actually intended, warns
|
||||
before silently dropping transparency, and writes the file. A
|
||||
read-only export, not an edit: doesn't touch diagram()'s undo stack,
|
||||
and works even on a read-only diagram, unlike every other action in
|
||||
this item's context menu.
|
||||
@param pixmap the pixmap to save -- pixmap_ or m_base_pixmap
|
||||
@param dialogTitle distinguishes the two callers in the save dialog's own title bar
|
||||
@param hasTransparency whether `pixmap` has colour-keyed transparency worth warning about losing (never true for the pristine original, which predates any such keying)
|
||||
*/
|
||||
void DiagramImageItem::saveImagePixmapAs(const QPixmap &pixmap, const QString &dialogTitle, bool hasTransparency)
|
||||
{
|
||||
QWidget *parentWidget = (diagram() && !diagram()->views().isEmpty()) ? diagram()->views().first() : nullptr;
|
||||
|
||||
// Filter text -> extension. Built once and used both to construct
|
||||
// the dialog's filter list and, below, to resolve whichever one the
|
||||
// person actually had selected -- rather than only ever falling
|
||||
// back to a single hardcoded format regardless of their choice,
|
||||
// which is what this used to do (getSaveFileName() doesn't reliably
|
||||
// auto-append the selected filter's extension on every platform,
|
||||
// and the previous version's fallback ignored the selected filter
|
||||
// entirely, defaulting to PNG even when JPEG or BMP had been
|
||||
// explicitly chosen).
|
||||
const QList<QPair<QString, QString>> filters = {
|
||||
{tr("Image PNG (*.png)"), QStringLiteral("png")},
|
||||
{tr("Image JPEG (*.jpg *.jpeg)"), QStringLiteral("jpg")},
|
||||
{tr("Image BMP (*.bmp)"), QStringLiteral("bmp")},
|
||||
// SVG here always means a raster image wrapped in an SVG
|
||||
// container (an <image> element embedding this same pixmap as
|
||||
// base64 PNG), never a true vector export -- this item only
|
||||
// ever holds raster data, even when originally inserted from an
|
||||
// SVG file, since that file was rasterized once at import time
|
||||
// and its vector information is already gone by the time this
|
||||
// runs.
|
||||
{tr("Image SVG (*.svg)"), QStringLiteral("svg")},
|
||||
};
|
||||
QStringList filterStrings;
|
||||
for (const auto &f : filters)
|
||||
filterStrings << f.first;
|
||||
filterStrings << tr("Tous les fichiers (*)");
|
||||
|
||||
QString selectedFilter;
|
||||
QString path = QFileDialog::getSaveFileName(
|
||||
parentWidget, dialogTitle, QString(), filterStrings.join(QLatin1String(";;")), &selectedFilter);
|
||||
if (path.isEmpty())
|
||||
return;
|
||||
|
||||
// Resolve the target format: prefer an extension already present
|
||||
// and actually writable, otherwise fall back to whichever filter
|
||||
// was selected in the dialog (not a fixed default), so the format
|
||||
// picked in the dropdown is the one that's actually honoured even
|
||||
// when the typed name carries no extension of its own.
|
||||
QString suffix = QFileInfo(path).suffix().toLower();
|
||||
const bool suffixIsSvg = (suffix == QLatin1String("svg"));
|
||||
const bool suffixIsWritableRaster = QImageWriter::supportedImageFormats().contains(suffix.toUtf8());
|
||||
if (!suffixIsSvg && !suffixIsWritableRaster)
|
||||
{
|
||||
QString fallbackExt = QStringLiteral("png");
|
||||
for (const auto &f : filters)
|
||||
if (f.first == selectedFilter) { fallbackExt = f.second; break; }
|
||||
path += QLatin1Char('.') + fallbackExt;
|
||||
suffix = fallbackExt;
|
||||
}
|
||||
|
||||
// JPEG and BMP have no usable alpha channel here -- Qt's writers for
|
||||
// both silently drop it. That alone wouldn't be too bad (the
|
||||
// warning already tells the person transparency won't survive),
|
||||
// except pixmap_ itself has already lost the true colour
|
||||
// information wherever it's transparent by this point: QPixmap
|
||||
// stores its data premultiplied internally, which zeroes out RGB
|
||||
// at alpha=0 the moment applyColorKey()'s QImage result gets
|
||||
// wrapped into a QPixmap -- confirmed directly, not assumed
|
||||
// (QPixmap::fromImage() on a white, alpha=0 pixel reliably comes
|
||||
// back black on toImage()). Saving pixmap_ as-is to a format with
|
||||
// no alpha channel doesn't fall back to the original picture, it
|
||||
// reveals that already-lost black. The fix below substitutes the
|
||||
// cropped base pixmap for the save in this specific case -- it has
|
||||
// the same, correct RGB values everywhere applyColorKey() left a
|
||||
// pixel opaque (colour-keying only ever changes alpha, never RGB),
|
||||
// so it's an exact reproduction of what the image looked like
|
||||
// before any colour was ever keyed out, not an approximation.
|
||||
const bool formatPreservesAlpha = (suffix == QLatin1String("png") || suffix == QLatin1String("svg"));
|
||||
QPixmap toSave = pixmap;
|
||||
if (hasTransparency && !formatPreservesAlpha)
|
||||
{
|
||||
if (QMessageBox::warning(parentWidget,
|
||||
tr("Transparence non conservée"),
|
||||
tr("Ce format ne prend pas en charge la transparence : l'image sera enregistrée "
|
||||
"telle qu'elle était avant l'application de la couleur transparente. Continuer ?"),
|
||||
QMessageBox::Yes | QMessageBox::Cancel) != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
toSave = m_base_pixmap.copy(m_crop_rect);
|
||||
}
|
||||
|
||||
bool ok;
|
||||
if (suffix == QLatin1String("svg"))
|
||||
ok = writeRasterAsSvg(toSave, path);
|
||||
else
|
||||
ok = toSave.save(path);
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
QMessageBox::warning(parentWidget,
|
||||
tr("Échec de l'enregistrement"),
|
||||
tr("Impossible d'enregistrer l'image à cet emplacement."));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramImageItem::writeRasterAsSvg
|
||||
Wraps `pixmap` as a base64-embedded PNG inside a minimal, valid SVG
|
||||
document -- the closest this can honestly offer to "save as SVG"
|
||||
given this item only ever holds raster data (see the note in
|
||||
saveImagePixmapAs()). Opens in any SVG viewer at the pixmap's own
|
||||
pixel size, but is not, and cannot be, a vector re-export.
|
||||
@param pixmap the raster image to embed
|
||||
@param path destination file path
|
||||
@return whether the file was written successfully
|
||||
*/
|
||||
bool DiagramImageItem::writeRasterAsSvg(const QPixmap &pixmap, const QString &path)
|
||||
{
|
||||
QByteArray pngData;
|
||||
QBuffer buffer(&pngData);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
if (!pixmap.save(&buffer, "PNG"))
|
||||
return false;
|
||||
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
|
||||
return false;
|
||||
|
||||
QTextStream out(&file);
|
||||
out << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||
<< "<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" "
|
||||
<< "width=\"" << pixmap.width() << "\" height=\"" << pixmap.height() << "\" "
|
||||
<< "viewBox=\"0 0 " << pixmap.width() << ' ' << pixmap.height() << "\">\n"
|
||||
<< " <image width=\"" << pixmap.width() << "\" height=\"" << pixmap.height() << "\" "
|
||||
<< "xlink:href=\"data:image/png;base64," << QString::fromLatin1(pngData.toBase64()) << "\"/>\n"
|
||||
<< "</svg>\n";
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief DiagramImageItem::itemChange
|
||||
*/
|
||||
@@ -1382,6 +1557,12 @@ void DiagramImageItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
|
||||
QAction *replace = menu.data()->addAction(tr("Remplacer l'image..."));
|
||||
connect(replace, &QAction::triggered, this, &DiagramImageItem::replaceImage);
|
||||
|
||||
QAction *saveAs = menu.data()->addAction(tr("Enregistrer l'image sous..."));
|
||||
connect(saveAs, &QAction::triggered, this, &DiagramImageItem::saveImageAs);
|
||||
|
||||
QAction *saveOriginalAs = menu.data()->addAction(tr("Enregistrer l'image d'origine sous..."));
|
||||
connect(saveOriginalAs, &QAction::triggered, this, &DiagramImageItem::saveOriginalImageAs);
|
||||
|
||||
QAction *transparentColor = menu.data()->addAction(tr("Couleur transparente..."));
|
||||
connect(transparentColor, &QAction::triggered, this, &DiagramImageItem::setTransparentColor);
|
||||
|
||||
|
||||
@@ -143,6 +143,10 @@ class DiagramImageItem : public QetGraphicsItem {
|
||||
void setTransparentColor();
|
||||
void crop();
|
||||
void restoreAspectRatio();
|
||||
void saveImageAs();
|
||||
void saveOriginalImageAs();
|
||||
void saveImagePixmapAs(const QPixmap &pixmap, const QString &dialogTitle, bool hasTransparency);
|
||||
static bool writeRasterAsSvg(const QPixmap &pixmap, const QString &path);
|
||||
static QPixmap computeDisplayPixmap(const QPixmap &base, const QRect &cropRect, const QList<ImageTransparentColorDialog::PickedColor> &colors);
|
||||
|
||||
void toggleHandleMode();
|
||||
|
||||
Reference in New Issue
Block a user