blob: def9cdb95e54bdbb6bb489e6357d57b9d4e48b62 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtWinExtras/QtWin>
#include <QtWidgets/QAction>
#include <QtWidgets/QApplication>
#include <QtWidgets/QDialogButtonBox>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QLabel>
#include <QtWidgets/QMainWindow>
#include <QtWidgets/QMenu>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QShortcut>
#include <QtWidgets/QVBoxLayout>
#include <QtGui/QImage>
#include <QtGui/QPainter>
#include <QtGui/QPaintEvent>
#include <QtCore/QCommandLineOption>
#include <QtCore/QCommandLineParser>
#include <QtCore/QDebug>
#include <QtCore/QTimer>
#include <QtCore/qt_windows.h>
static void formatData(QDebug d, const void *data, int size)
{
QDebugStateSaver saver(d);
d.noquote();
d.nospace();
d << "\nData: " << QByteArray(reinterpret_cast<const char *>(data), qMin(20, size)).toHex();
if (size > 20)
d << "...";
d << "\n 0000000011111111222222223333333344444444";
}
QDebug operator<<(QDebug d, const BITMAP &b)
{
QDebugStateSaver saver(d);
d.nospace();
d << "BITMAP(type=" << b.bmType << ", " << b.bmWidth << 'x' << b.bmHeight
<< ", widthBytes=" << b.bmWidthBytes << ", planes=" << b.bmPlanes
<< ", bitsPixel=" << b.bmBitsPixel << ", bits=" << b.bmBits << ')';
return d;
}
QDebug operator<<(QDebug d, const BITMAPINFOHEADER &bih)
{
QDebugStateSaver saver(d);
d.nospace();
d << "BITMAPINFOHEADER(" << bih.biWidth << 'x' << qAbs(bih.biHeight)
<< (bih.biHeight < 0 ? ", top-down" : ", bottom-up")
<< ", planes=" << bih.biPlanes << ", bitCount=" << bih.biBitCount
<< ", compression=" << bih.biCompression << ", size="
<< bih.biSizeImage << ')';
return d;
}
static void formatImage(QDebug d, const QImage &image)
{
QDebugStateSaver s(d);
d.noquote();
d.nospace();
d << image;
if (const int colorTableSize = image.colorCount()) {
QVector<QRgb> colorTable = image.colorTable();
d << " Color table: " << colorTableSize << " (" << showbase << hex; // 256 by standard
int c = 0;
for ( ; c < qMin(8, colorTableSize); ++c) {
if (c)
d << ", ";
d << colorTable[c];
}
if (c < colorTableSize)
d << "...";
d << ')' << noshowbase << dec;
}
formatData(d, image.constBits(), image.byteCount());
}
enum ParseOptionResult {
OptionError,
OptionUnset,
OptionSet
};
static ParseOptionResult parseIntOption(const QCommandLineParser &parser, const QCommandLineOption &option,
int minValue, int maxValue, int *target)
{
if (!parser.isSet(option))
return OptionUnset;
const QString spec = parser.value(option);
bool ok;
const int value = spec.toInt(&ok);
if (!ok || value < minValue || value > maxValue) {
qWarning() << "Invalid value" << spec << "for" << option.names();
return OptionError;
}
*target = value;
return OptionSet;
}
template <typename Enum>
static ParseOptionResult parseEnumOption(const QCommandLineParser &parser, const QCommandLineOption &option,
Enum minValue, Enum maxValue, Enum *target)
{
int intValue;
const ParseOptionResult result = parseIntOption(parser, option, int(minValue), int(maxValue), &intValue);
if (result == OptionSet)
*target = static_cast<Enum>(intValue);
return result;
}
// Display a QImage in a dialog.
class PreviewDialog : public QDialog
{
public:
explicit PreviewDialog(const QImage &image, QWidget *parent = nullptr);
};
PreviewDialog::PreviewDialog(const QImage &image, QWidget *parent) : QDialog(parent)
{
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
QString description;
QDebug(&description) << image.size() << ", format=" << image.format();
QLabel *descriptionLabel = new QLabel(description, this);
descriptionLabel->setWordWrap(true);
auto *layout = new QVBoxLayout(this);
layout->addWidget(descriptionLabel);
auto *hLayout = new QHBoxLayout;
QLabel *label = new QLabel(this);
label->setFrameShape(QFrame::Box);
label->setPixmap(QPixmap::fromImage(image));
hLayout->addStretch();
hLayout->addWidget(label);
hLayout->addStretch();
layout->addLayout(hLayout);
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this);
connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
layout->addWidget(buttonBox);
}
// Widget that paints a HBITMAP using GDI API in WM_PAINT.
class PaintWidget : public QWidget
{
Q_OBJECT
public:
explicit PaintWidget(HBITMAP hBitmap, QWidget *p = nullptr) : QWidget(p), m_hBitmap(hBitmap) { }
bool nativeEvent(const QByteArray &eventType, void *message, long *result) override;
public slots:
void saveBitmap();
void convertBack();
protected:
void contextMenuEvent(QContextMenuEvent *) override;
private:
const HBITMAP m_hBitmap;
};
bool PaintWidget::nativeEvent(const QByteArray &eventType, void *messageIn, long *result)
{
MSG *message = reinterpret_cast<MSG *>(messageIn);
if (message->message != WM_PAINT)
return QWidget::nativeEvent(eventType, message, result);
PAINTSTRUCT paintStruct;
BITMAP bitmap;
const HDC hdc = BeginPaint(message->hwnd, &paintStruct);
SelectObject(hdc, GetStockObject(BLACK_PEN));
Rectangle(hdc, 1, 1, width() - 1, height() - 1);
const HDC hdcMem = CreateCompatibleDC(hdc);
const HGDIOBJ oldBitmap = SelectObject(hdcMem, m_hBitmap);
GetObject(m_hBitmap, sizeof(bitmap), &bitmap);
{
QDebug d = qDebug();
d << __FUNCTION__ << bitmap;
formatData(d, bitmap.bmBits, bitmap.bmHeight * bitmap.bmWidthBytes);
}
BitBlt(hdc, 5, 5, bitmap.bmWidth, bitmap.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, oldBitmap);
DeleteDC(hdcMem);
EndPaint(message->hwnd, &paintStruct);
return true;
}
void PaintWidget::convertBack()
{
QImage image = QtWin::imageFromHBITMAP(m_hBitmap);
formatImage(qDebug(), image);
auto *dialog = new PreviewDialog(image, this);
dialog->setAttribute(Qt::WA_DeleteOnClose);
dialog->setModal(false);
dialog->setWindowTitle(QLatin1String("QImage - Qt ") + QLatin1String(QT_VERSION_STR));
dialog->show();
}
void PaintWidget::saveBitmap()
{
QImage image = QtWin::imageFromHBITMAP(m_hBitmap);
formatImage(qDebug(), image);
QFileDialog fileDialog(this);
fileDialog.setAcceptMode(QFileDialog::AcceptSave);
fileDialog.setMimeTypeFilters(QStringList(QStringLiteral("image/png")));
fileDialog.setDefaultSuffix(QStringLiteral("png"));
fileDialog.selectFile(QStringLiteral("test.png"));
while (fileDialog.exec() == QDialog::Accepted) {
const QString fileName = fileDialog.selectedFiles().first();
if (image.save(fileName)) {
qDebug().noquote() << "saved" << QDir::toNativeSeparators(fileName);
break;
}
qWarning().noquote() << "Could not save" << QDir::toNativeSeparators(fileName);
}
}
void PaintWidget::contextMenuEvent(QContextMenuEvent *e)
{
QMenu contextMenu;
contextMenu.addAction(QStringLiteral("Convert into QImage"), this, &PaintWidget::convertBack);
QAction *saveAction = contextMenu.addAction(QStringLiteral("Save"), this, &PaintWidget::saveBitmap);
saveAction->setShortcut(Qt::CTRL + Qt::Key_S);
contextMenu.exec(e->globalPos());
}
static const char description[] =
"\nCreates a HBITMAP from a QImage either passed as file name or by drawing in a\n"
"format determined by a command line option and draws it onto a native window\n"
"for comparison. Provides a context menu for converting the HBITMAP back to a\n"
"QImage and saving that for checking the reverse conversion.";
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QCoreApplication::setApplicationName("imageconversion");
QCoreApplication::setOrganizationName("QtProject");
QCoreApplication::setApplicationVersion(QT_VERSION_STR);
QCommandLineParser parser;
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
parser.setApplicationDescription("Qt Windows Extras Image Conversion Tester");
parser.addHelpOption();
parser.addVersionOption();
parser.setApplicationDescription(description);
const QCommandLineOption formatOption(QStringList{"format", "f"},
"QImage format", "format");
parser.addOption(formatOption);
const QCommandLineOption colorOption(QStringList{"color", "c"},
"Fill color", "color-spec");
parser.addOption(colorOption);
const QCommandLineOption globalColorOption(QStringList{"globalColor", "g"},
"Fill color (global color enum value)", "global-color");
parser.addOption(globalColorOption);
const QCommandLineOption widthOption(QStringList{"width", "w"},
"Width", "width");
parser.addOption(widthOption);
const QCommandLineOption heightOption(QStringList{"height", "h"},
"Height", "height");
parser.addOption(heightOption);
const QCommandLineOption previewOption(QStringList{"preview", "p"},
"Show a preview");
parser.addOption(previewOption);
parser.addPositionalArgument("file", "The image file to open.");
parser.process(app);
QColor defaultColor(Qt::red);
if (parser.isSet(colorOption)) {
const QString spec = parser.value(colorOption);
defaultColor = QColor(spec);
if (!defaultColor.isValid()) {
qWarning() << "Invalid color specification" << spec;
return -1;
}
} else {
// Color 0: color0, 1: color1, 2: black, 3: white, 7:red, 9: blue, 8: green
Qt::GlobalColor globalColor = Qt::color0;
if (!parseEnumOption(parser, globalColorOption, Qt::black, Qt::transparent, &globalColor))
return -1;
if (globalColor != Qt::color0)
defaultColor = QColor(defaultColor);
}
// Format: 1: mono, 3: Indexed8, 7: RGB 16, 11: RGB555, 13: RGB888
QImage::Format targetFormat = QImage::Format_ARGB32_Premultiplied;
if (!parseEnumOption(parser, formatOption, QImage::Format_Mono, QImage::Format_Grayscale8, &targetFormat))
return -1;
// Can't paint on indexed nor mono, need transform?
const QImage::Format drawFormat = targetFormat == QImage::Format_Indexed8
|| targetFormat == QImage::Format_Mono || targetFormat == QImage::Format_MonoLSB
? QImage::Format_ARGB32_Premultiplied : targetFormat;
if (targetFormat == QImage::Format_Mono || targetFormat == QImage::Format_MonoLSB)
defaultColor = Qt::white;
int width = 73;
int height = 57;
if (!parseIntOption(parser, widthOption, 1, 2000, &width) || !parseIntOption(parser, heightOption, 1, 2000, &height))
return -1;
const bool preview = parser.isSet(previewOption);
QImage image;
if (!parser.positionalArguments().isEmpty()) {
QString fileName = parser.positionalArguments().constFirst();
image = QImage(fileName);
if (image.isNull() || image.size().isEmpty()) {
qWarning().noquote() << "Image load fail" << QDir::toNativeSeparators(fileName);
return -1;
}
}
if (image.isNull()) {
qDebug() << "Default image color=" << defaultColor
<< showbase << hex << defaultColor.rgba() << noshowbase << dec
<< ", format=" << drawFormat;
image = QImage(width, height, drawFormat);
image.fill(defaultColor);
QPainter painter(&image);
painter.drawLine(0, 0, image.width(), image.height());
}
if (image.format() != targetFormat) {
qDebug() << "Converting " << image.format() << targetFormat;
image = image.convertToFormat(targetFormat);
}
formatImage(qDebug(), image);
const HBITMAP bitmap = QtWin::imageToHBITMAP(image);
if (!bitmap) {
qWarning() << "Failed to create HBITMAP";
return -1;
}
int exitCode = 0;
{
PaintWidget paintWidget(bitmap);
auto *quitShortcut = new QShortcut(&paintWidget);
quitShortcut->setKey(Qt::CTRL + Qt::Key_Q);
quitShortcut->setContext(Qt::ApplicationShortcut);
QObject::connect(quitShortcut, &QShortcut::activated, qApp, &QCoreApplication::quit);
paintWidget.setWindowTitle(QLatin1String("HBITMAP - Qt ") + QLatin1String(QT_VERSION_STR));
paintWidget.show();
if (preview) {
auto *dialog = new PreviewDialog(image);
dialog->setModal(false);
dialog->setWindowTitle(QLatin1String("QImage - Qt ") + QLatin1String(QT_VERSION_STR));
dialog->move(paintWidget.frameGeometry().topRight() + QPoint(50, 0));
dialog->show();
}
exitCode = app.exec();
}
DeleteObject(bitmap);
return exitCode;
}
#include "main.moc"