diff --git a/src/tools/ksvg2icns/ksvg2icns.cpp b/src/tools/ksvg2icns/ksvg2icns.cpp index 27ed66f..d0a5883 100644 --- a/src/tools/ksvg2icns/ksvg2icns.cpp +++ b/src/tools/ksvg2icns/ksvg2icns.cpp @@ -1,162 +1,168 @@ /****************************************************************************** * Copyright 2014 Harald Fernengel * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU Lesser General Public * * * * License as published by the Free Software Foundation; either * * version 2.1 of the License, or (at your option) version 3, or any * * later version accepted by the membership of KDE e.V. (or its * * successor approved by the membership of KDE e.V.), which shall * * act as a proxy defined in Section 6 of version 3 of the license. * * * * This library 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 * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with this library. If not, see * * . * * * ******************************************************************************/ /* This tool converts an svg to a Mac OS X icns file. * Note: Requires the 'iconutil' Mac OS X binary */ #include #include #include #include -#include +#include #include #include #include "../../../kiconthemes_version.h" #include #define EXIT_ON_ERROR(isOk, ...) \ do { \ if (!(isOk)) { \ fprintf(stderr, __VA_ARGS__); \ return 1; \ } \ } while (false); static bool writeImage(QSvgRenderer &svg, int size, const QString &outFile1, const QString &outFile2 = QString()) { QImage out(size, size, QImage::Format_ARGB32); out.fill(Qt::transparent); QPainter painter(&out); svg.render(&painter); painter.end(); if (!out.save(outFile1)) { fprintf(stderr, "Unable to write %s\n", qPrintable(outFile1)); return false; } if (!outFile2.isEmpty() && !out.save(outFile2)) { fprintf(stderr, "Unable to write %s\n", qPrintable(outFile2)); return false; } return true; } int main(int argc, char *argv[]) { - QGuiApplication app(argc, argv); + // it turns out we don't actually need to be a QGuiApplication in order + // to use QPainter on a QImage. Downgrading to a QCoreApplication should + // make us usable on headless (CI) servers and remove some QPA-related + // runtime overhead. + // Alternatively we create a QGuiApplication after setting QT_QPA_PLATFORM: + // qputenv("QT_QPA_PLATFORM", QByteArrayLiteral("minimal")); + QCoreApplication app(argc, argv); app.setApplicationName("ksvg2icns"); app.setApplicationVersion(KICONTHEMES_VERSION_STRING); QCommandLineParser parser; parser.setApplicationDescription(app.translate("main", "Creates an icns file from an svg image")); parser.addPositionalArgument("iconname", app.translate("main", "The svg icon to convert")); parser.addHelpOption(); parser.process(app); if (parser.positionalArguments().isEmpty()) { parser.showHelp(); return 1; } bool isOk; // create a temporary dir to create an iconset QTemporaryDir tmpDir("ksvg2icns"); tmpDir.setAutoRemove(true); isOk = tmpDir.isValid(); EXIT_ON_ERROR(isOk, "Unable to create temporary directory\n"); isOk = QDir(tmpDir.path()).mkdir("out.iconset"); EXIT_ON_ERROR(isOk, "Unable to create out.iconset directory\n"); const QString outPath = tmpDir.path() + "/out.iconset"; const QStringList &args = app.arguments(); EXIT_ON_ERROR(args.size() == 2, "Usage: %s svg-image\n", qPrintable(args.value(0))); const QString &svgFileName = args.at(1); // open the actual svg file QSvgRenderer svg; isOk = svg.load(svgFileName); EXIT_ON_ERROR(isOk, "Unable to load %s\n", qPrintable(svgFileName)); // The sizes are from: // https://developer.apple.com/library/mac/documentation/GraphicsAnimation/Conceptual/HighResolutionOSX/Optimizing/Optimizing.html struct OutFiles { int size; QString out1; QString out2; }; // create the pngs in various resolutions const OutFiles outFiles[] = { { 1024, outPath + "/icon_512x512@2x.png", QString() }, { 512, outPath + "/icon_512x512.png", outPath + "/icon_256x256@2x.png" }, { 256, outPath + "/icon_256x256.png", outPath + "/icon_128x128@2x.png" }, { 128, outPath + "/icon_128x128.png", QString() }, { 64, outPath + "/icon_32x32@2x.png", QString() }, { 32, outPath + "/icon_32x32.png", outPath + "/icon_16x16@2x.png" }, { 16, outPath + "/icon_16x16.png", QString() } }; for (size_t i = 0; i < sizeof(outFiles) / sizeof(OutFiles); ++i) { isOk = writeImage(svg, outFiles[i].size, outFiles[i].out1, outFiles[i].out2); if (!isOk) { return 1; } } // convert the iconset to icns using the "iconutil" command const QString outIcns = QFileInfo(svgFileName).baseName() + ".icns"; const QStringList utilArgs = QStringList() << "-c" << "icns" << "-o" << outIcns << outPath; QProcess iconUtil; iconUtil.setProgram("iconUtil"); iconUtil.setArguments(utilArgs); iconUtil.start("iconutil", utilArgs, QIODevice::ReadOnly); isOk = iconUtil.waitForFinished(-1); EXIT_ON_ERROR(isOk, "Unable to launch iconutil: %s\n", qPrintable(iconUtil.errorString())); EXIT_ON_ERROR(iconUtil.exitStatus() == QProcess::NormalExit, "iconutil crashed!\n"); EXIT_ON_ERROR(iconUtil.exitCode() == 0, "iconutil returned %d\n", iconUtil.exitCode()); return 0; } diff --git a/src/tools/ksvg2ico/CMakeLists.txt b/src/tools/ksvg2ico/CMakeLists.txt new file mode 100644 index 0000000..57e8ceb --- /dev/null +++ b/src/tools/ksvg2ico/CMakeLists.txt @@ -0,0 +1,20 @@ +find_package(Png2Ico) +if (Png2Ico_FOUND) + if (Png2Ico_HAS_RCFILE_ARGUMENT) + # not used at the moment + add_definitions(-DPNG2ICO_HAS_RCFILE_ARGUMENT) + endif() + # act as if there's a png2ico utility that supports both options + if (Png2Ico_HAS_COLORS_ARGUMENT) + # using --colors appears counter-productive + # add_definitions(-DPNG2ICO_SUPPORTS_MULTIPLE_COLOR_DEPTHS) + endif() +else() + message(WARNING "Unable to find the png2ico utility - ksvg2ico will miss its runtime dependency") +endif() + +add_executable(ksvg2ico ksvg2ico.cpp qicohandler.cpp) +ecm_mark_nongui_executable(ksvg2ico) +target_link_libraries(ksvg2ico Qt5::Gui Qt5::Svg) + +install(TARGETS ksvg2ico ${KF5_INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/tools/ksvg2ico/ksvg2ico.cpp b/src/tools/ksvg2ico/ksvg2ico.cpp new file mode 100644 index 0000000..480d339 --- /dev/null +++ b/src/tools/ksvg2ico/ksvg2ico.cpp @@ -0,0 +1,166 @@ +/****************************************************************************** + * Copyright (C) 2007 Ralf Habacker * + * Copyright (C) 2017 René J.V. Bertin * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2.1 of the License, or (at your option) version 3, or any * + * later version accepted by the membership of KDE e.V. (or its * + * successor approved by the membership of KDE e.V.), which shall * + * act as a proxy defined in Section 6 of version 3 of the license. * + * * + * This library 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 * + * Lesser General Public License for more details. * + * * + * You should have received a copy of the GNU Lesser General Public * + * License along with this library. If not, see * + * . * + * * + ******************************************************************************/ + +/* + svg to ico format converter, adapted from KDEWin's sgv2ico tool. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../../../kiconthemes_version.h" +// load our private QtIcoHandler copy, adapted from Qt 5.7.1 +#include "qicohandler.h" + +bool verbose = false; +bool debug = false; + +#include + +#define EXIT_ON_ERROR(isOk, ...) \ + do { \ + if (!(isOk)) { \ + fprintf(stderr, __VA_ARGS__); \ + return 1; \ + } \ + } while (false); + +bool svg2png(QSvgRenderer &renderer, const QString &outFile, int width, int height, QVector &imgList) +{ + QImage img(width, height, QImage::Format_ARGB32_Premultiplied); + img.fill(0); + + QPainter p(&img); + renderer.render(&p); + img.save(outFile, "PNG"); + imgList += img.convertToFormat(QImage::Format_ARGB32, Qt::ColorOnly|Qt::DiffuseAlphaDither|Qt::AvoidDither); + imgList += img.convertToFormat(QImage::Format_Indexed8, Qt::ColorOnly|Qt::DiffuseAlphaDither|Qt::AvoidDither); + return true; +} + +int main(int argc, char **argv) +{ + QGuiApplication app(argc, argv); + + QString rcFileName = ""; + + app.setApplicationName("ksvg2ico"); + app.setApplicationVersion(KICONTHEMES_VERSION_STRING); + QCommandLineParser parser; + parser.setApplicationDescription(app.translate("main", "Creates an ico file from an SVG image")); + parser.addPositionalArgument("input.sgv[z]", app.translate("main", "The SVG icon to convert")); + parser.addPositionalArgument("output.ico", app.translate("main", "The name of the resulting ico file")); + const QCommandLineOption verboseOption(QStringLiteral("verbose"), QStringLiteral("print execution details")); + const QCommandLineOption debugOption(QStringLiteral("debug"), + QStringLiteral("print debugging information and don't delete temporary files")); + const QCommandLineOption rcFileOption(QStringLiteral("rcfile"), + QStringLiteral("generate the named rc file for the icon"), + "rcfile", rcFileName); + parser.addOption(verboseOption); + parser.addOption(debugOption); + parser.addOption(rcFileOption); + parser.addHelpOption(); + parser.addVersionOption(); + + parser.process(app); + if (parser.positionalArguments().isEmpty()) { + parser.showHelp(); + return 1; + } + + bool isOk; + + if (parser.isSet(verboseOption)) { + verbose = true; + } + if (parser.isSet(debugOption)) { + debug = true; + } + + QString svgFile = parser.positionalArguments().at(0); + QString icoFile = parser.positionalArguments().at(1); + if (parser.isSet(rcFileOption)) { + rcFileName = parser.value(rcFileOption); + } + + QString pngFile = icoFile; + pngFile.replace(".ico", "-%1.png"); + + // create a temporary dir to create an iconset + QTemporaryDir tmpDir("ksvg2ico"); + tmpDir.setAutoRemove(!debug); + + isOk = tmpDir.isValid(); + EXIT_ON_ERROR(isOk, "Unable to create temporary directory\n"); + + QSvgRenderer renderer; + isOk = renderer.load(svgFile); + EXIT_ON_ERROR(isOk, "Unable to load %s\n", qPrintable(svgFile)); + + QVector imgList; + // generate PNG versions up to the largest size the ico format is guaranteed to handle: + foreach (int d, QVector({16, 32, 48, 64, 128, 256})) { + const QString pngSize = tmpDir.path() + "/" + pngFile.arg(d); + if (verbose) { + qDebug() << "converting" << svgFile << "to" << pngSize; + } + svg2png(renderer, pngSize, d, d, imgList); + } + if (debug || verbose) { + qWarning() << "Creating" << icoFile << "from:"; + foreach (const auto &img, imgList) { + qWarning() << img; + } + } + + QFile f(icoFile); + isOk = f.open(QIODevice::WriteOnly); + EXIT_ON_ERROR(isOk, "Can not open %s for writing", qPrintable(icoFile)); + + QtIcoHandler ico(&f); + isOk = ico.write(imgList); + f.close(); + EXIT_ON_ERROR(isOk, "Failure writing ico file %s\n", qPrintable(icoFile)); + + if (!rcFileName.isEmpty()) { + QFile rcFile(rcFileName); + if (!rcFile.open(QIODevice::WriteOnly)) { + EXIT_ON_ERROR(false, "Can not open %s for writing", qPrintable(rcFileName )); + } + QTextStream ts(&rcFile); + ts << QString( "IDI_ICON1 ICON DISCARDABLE \"%1\"\n" ).arg(icoFile); + rcFile.close(); + } + return !isOk; +} diff --git a/src/tools/ksvg2ico/qicohandler.cpp b/src/tools/ksvg2ico/qicohandler.cpp new file mode 100644 index 0000000..481eac2 --- /dev/null +++ b/src/tools/ksvg2ico/qicohandler.cpp @@ -0,0 +1,966 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \class QtIcoHandler + \since 4.4 + \brief The QtIcoHandler class provides support for the ICO image format. + \internal +*/ + + + +#include "qicohandler.h" +#include +#include +#include +#include +#include + +// These next two structs represent how the icon information is stored +// in an ICO file. +typedef struct +{ + quint8 bWidth; // Width of the image + quint8 bHeight; // Height of the image (actual height, not times 2) + quint8 bColorCount; // Number of colors in image (0 if >=8bpp) [ not ture ] + quint8 bReserved; // Reserved + quint16 wPlanes; // Color Planes + quint16 wBitCount; // Bits per pixel + quint32 dwBytesInRes; // how many bytes in this resource? + quint32 dwImageOffset; // where in the file is this image +} ICONDIRENTRY, *LPICONDIRENTRY; +#define ICONDIRENTRY_SIZE 16 + +typedef struct +{ + quint16 idReserved; // Reserved + quint16 idType; // resource type (1 for icons, 2 for cursors) + quint16 idCount; // how many images? + ICONDIRENTRY idEntries[1]; // the entries for each image +} ICONDIR, *LPICONDIR; +#define ICONDIR_SIZE 6 // Exclude the idEntries field + +typedef struct { // BMP information header + quint32 biSize; // size of this struct + quint32 biWidth; // pixmap width + quint32 biHeight; // pixmap height (specifies the combined height of the XOR and AND masks) + quint16 biPlanes; // should be 1 + quint16 biBitCount; // number of bits per pixel + quint32 biCompression; // compression method + quint32 biSizeImage; // size of image + quint32 biXPelsPerMeter; // horizontal resolution + quint32 biYPelsPerMeter; // vertical resolution + quint32 biClrUsed; // number of colors used + quint32 biClrImportant; // number of important colors +} BMP_INFOHDR ,*LPBMP_INFOHDR; +#define BMP_INFOHDR_SIZE 40 + +class ICOReader +{ +public: + ICOReader(QIODevice * iodevice); +#ifdef QT_ICO_READ_SUPPORT + int count(); + QImage iconAt(int index); + static bool canRead(QIODevice *iodev); + + static QVector read(QIODevice *device); + bool readIconEntry(int index, ICONDIRENTRY * iconEntry); +#endif + static bool write(QIODevice *device, const QVector &images); + + +#ifdef QT_ICO_READ_SUPPORT +private: + bool readHeader(); + + bool readBMPHeader(quint32 imageOffset, BMP_INFOHDR * header); + void findColorInfo(QImage & image); + void readColorTable(QImage & image); + + void readBMP(QImage & image); + void read1BitBMP(QImage & image); + void read4BitBMP(QImage & image); + void read8BitBMP(QImage & image); + void read16_24_32BMP(QImage & image); + + struct IcoAttrib + { + int nbits; + int ncolors; + int h; + int w; + int depth; + } icoAttrib; + + QIODevice * iod; + qint64 startpos; + bool headerRead; + ICONDIR iconDir; +#endif +}; + +#ifdef QT_ICO_READ_SUPPORT +// Data readers and writers that takes care of alignment and endian stuff. +static bool readIconDirEntry(QIODevice *iodev, ICONDIRENTRY *iconDirEntry) +{ + if (iodev) { + uchar tmp[ICONDIRENTRY_SIZE]; + if (iodev->read((char*)tmp, ICONDIRENTRY_SIZE) == ICONDIRENTRY_SIZE) { + iconDirEntry->bWidth = tmp[0]; + iconDirEntry->bHeight = tmp[1]; + iconDirEntry->bColorCount = tmp[2]; + iconDirEntry->bReserved = tmp[3]; + + iconDirEntry->wPlanes = qFromLittleEndian(&tmp[4]); + iconDirEntry->wBitCount = qFromLittleEndian(&tmp[6]); + iconDirEntry->dwBytesInRes = qFromLittleEndian(&tmp[8]); + iconDirEntry->dwImageOffset = qFromLittleEndian(&tmp[12]); + return true; + } + } + return false; +} +#endif + +static bool writeIconDirEntry(QIODevice *iodev, const ICONDIRENTRY &iconEntry) +{ + if (iodev) { + uchar tmp[ICONDIRENTRY_SIZE]; + tmp[0] = iconEntry.bWidth; + tmp[1] = iconEntry.bHeight; + tmp[2] = iconEntry.bColorCount; + tmp[3] = iconEntry.bReserved; + qToLittleEndian(iconEntry.wPlanes, &tmp[4]); + qToLittleEndian(iconEntry.wBitCount, &tmp[6]); + qToLittleEndian(iconEntry.dwBytesInRes, &tmp[8]); + qToLittleEndian(iconEntry.dwImageOffset, &tmp[12]); + return iodev->write((char*)tmp, ICONDIRENTRY_SIZE) == ICONDIRENTRY_SIZE; + } + + return false; +} + +#ifdef QT_ICO_READ_SUPPORT +static bool readIconDir(QIODevice *iodev, ICONDIR *iconDir) +{ + if (iodev) { + uchar tmp[ICONDIR_SIZE]; + if (iodev->read((char*)tmp, ICONDIR_SIZE) == ICONDIR_SIZE) { + iconDir->idReserved = qFromLittleEndian(&tmp[0]); + iconDir->idType = qFromLittleEndian(&tmp[2]); + iconDir->idCount = qFromLittleEndian(&tmp[4]); + return true; + } + } + return false; +} +#endif + +static bool writeIconDir(QIODevice *iodev, const ICONDIR &iconDir) +{ + if (iodev) { + uchar tmp[6]; + qToLittleEndian(iconDir.idReserved, tmp); + qToLittleEndian(iconDir.idType, &tmp[2]); + qToLittleEndian(iconDir.idCount, &tmp[4]); + return iodev->write((char*)tmp, 6) == 6; + } + return false; +} + +#ifdef QT_ICO_READ_SUPPORT +static bool readBMPInfoHeader(QIODevice *iodev, BMP_INFOHDR *pHeader) +{ + if (iodev) { + uchar header[BMP_INFOHDR_SIZE]; + if (iodev->read((char*)header, BMP_INFOHDR_SIZE) == BMP_INFOHDR_SIZE) { + pHeader->biSize = qFromLittleEndian(&header[0]); + pHeader->biWidth = qFromLittleEndian(&header[4]); + pHeader->biHeight = qFromLittleEndian(&header[8]); + pHeader->biPlanes = qFromLittleEndian(&header[12]); + pHeader->biBitCount = qFromLittleEndian(&header[14]); + pHeader->biCompression = qFromLittleEndian(&header[16]); + pHeader->biSizeImage = qFromLittleEndian(&header[20]); + pHeader->biXPelsPerMeter = qFromLittleEndian(&header[24]); + pHeader->biYPelsPerMeter = qFromLittleEndian(&header[28]); + pHeader->biClrUsed = qFromLittleEndian(&header[32]); + pHeader->biClrImportant = qFromLittleEndian(&header[36]); + return true; + } + } + return false; +} +#endif + +static bool writeBMPInfoHeader(QIODevice *iodev, const BMP_INFOHDR &header) +{ + if (iodev) { + uchar tmp[BMP_INFOHDR_SIZE]; + qToLittleEndian(header.biSize, &tmp[0]); + qToLittleEndian(header.biWidth, &tmp[4]); + qToLittleEndian(header.biHeight, &tmp[8]); + qToLittleEndian(header.biPlanes, &tmp[12]); + qToLittleEndian(header.biBitCount, &tmp[14]); + qToLittleEndian(header.biCompression, &tmp[16]); + qToLittleEndian(header.biSizeImage, &tmp[20]); + qToLittleEndian(header.biXPelsPerMeter, &tmp[24]); + qToLittleEndian(header.biYPelsPerMeter, &tmp[28]); + qToLittleEndian(header.biClrUsed, &tmp[32]); + qToLittleEndian(header.biClrImportant, &tmp[36]); + + return iodev->write((char*)tmp, BMP_INFOHDR_SIZE) == BMP_INFOHDR_SIZE; + } + return false; +} + + +#ifdef QT_ICO_READ_SUPPORT +ICOReader::ICOReader(QIODevice * iodevice) + : iod(iodevice) + , startpos(0) + , headerRead(false) +{ +} +#else +ICOReader::ICOReader(QIODevice *) +{ +} +#endif + +#ifdef QT_ICO_READ_SUPPORT +int ICOReader::count() +{ + if (readHeader()) + return iconDir.idCount; + return 0; +} + +bool ICOReader::canRead(QIODevice *iodev) +{ + bool isProbablyICO = false; + if (iodev) { + qint64 oldPos = iodev->pos(); + + ICONDIR ikonDir; + if (readIconDir(iodev, &ikonDir)) { + qint64 readBytes = ICONDIR_SIZE; + if (readIconDirEntry(iodev, &ikonDir.idEntries[0])) { + readBytes += ICONDIRENTRY_SIZE; + // ICO format does not have a magic identifier, so we read 6 different values, which will hopefully be enough to identify the file. + if ( ikonDir.idReserved == 0 + && (ikonDir.idType == 1 || ikonDir.idType == 2) + && ikonDir.idEntries[0].bReserved == 0 + && (ikonDir.idEntries[0].wPlanes <= 1 || ikonDir.idType == 2) + && (ikonDir.idEntries[0].wBitCount <= 32 || ikonDir.idType == 2) // Bits per pixel + && ikonDir.idEntries[0].dwBytesInRes >= 40 // Must be over 40, since sizeof (infoheader) == 40 + ) { + isProbablyICO = true; + } + + if (iodev->isSequential()) { + // Our structs might be padded due to alignment, so we need to fetch each member before we ungetChar() ! + quint32 tmp = ikonDir.idEntries[0].dwImageOffset; + iodev->ungetChar((tmp >> 24) & 0xff); + iodev->ungetChar((tmp >> 16) & 0xff); + iodev->ungetChar((tmp >> 8) & 0xff); + iodev->ungetChar(tmp & 0xff); + + tmp = ikonDir.idEntries[0].dwBytesInRes; + iodev->ungetChar((tmp >> 24) & 0xff); + iodev->ungetChar((tmp >> 16) & 0xff); + iodev->ungetChar((tmp >> 8) & 0xff); + iodev->ungetChar(tmp & 0xff); + + tmp = ikonDir.idEntries[0].wBitCount; + iodev->ungetChar((tmp >> 8) & 0xff); + iodev->ungetChar(tmp & 0xff); + + tmp = ikonDir.idEntries[0].wPlanes; + iodev->ungetChar((tmp >> 8) & 0xff); + iodev->ungetChar(tmp & 0xff); + + iodev->ungetChar(ikonDir.idEntries[0].bReserved); + iodev->ungetChar(ikonDir.idEntries[0].bColorCount); + iodev->ungetChar(ikonDir.idEntries[0].bHeight); + iodev->ungetChar(ikonDir.idEntries[0].bWidth); + } + } + + if (iodev->isSequential()) { + // Our structs might be padded due to alignment, so we need to fetch each member before we ungetChar() ! + quint32 tmp = ikonDir.idCount; + iodev->ungetChar((tmp >> 8) & 0xff); + iodev->ungetChar(tmp & 0xff); + + tmp = ikonDir.idType; + iodev->ungetChar((tmp >> 8) & 0xff); + iodev->ungetChar(tmp & 0xff); + + tmp = ikonDir.idReserved; + iodev->ungetChar((tmp >> 8) & 0xff); + iodev->ungetChar(tmp & 0xff); + } + } + if (!iodev->isSequential()) iodev->seek(oldPos); + } + + return isProbablyICO; +} + +bool ICOReader::readHeader() +{ + if (iod && !headerRead) { + startpos = iod->pos(); + if (readIconDir(iod, &iconDir)) { + if (iconDir.idReserved == 0 && (iconDir.idType == 1 || iconDir.idType == 2)) + headerRead = true; + } + } + + return headerRead; +} + +bool ICOReader::readIconEntry(int index, ICONDIRENTRY *iconEntry) +{ + if (readHeader()) { + if (iod->seek(startpos + ICONDIR_SIZE + (index * ICONDIRENTRY_SIZE))) { + return readIconDirEntry(iod, iconEntry); + } + } + return false; +} + +bool ICOReader::readBMPHeader(quint32 imageOffset, BMP_INFOHDR * header) +{ + if (iod) { + if (iod->seek(startpos + imageOffset)) { + if (readBMPInfoHeader(iod, header)) { + return true; + } + } + } + return false; +} + +void ICOReader::findColorInfo(QImage & image) +{ + if (icoAttrib.ncolors > 0) { + // set color table + readColorTable(image); + } else if (icoAttrib.nbits == 16) { + // don't support RGB values for 15/16 bpp + image = QImage(); + } +} + +void ICOReader::readColorTable(QImage & image) +{ + if (iod) { + image.setColorCount(icoAttrib.ncolors); + uchar rgb[4]; + for (int i=0; iread((char*)rgb, 4) != 4) { + image = QImage(); + break; + } + image.setColor(i, qRgb(rgb[2],rgb[1],rgb[0])); + } + } else { + image = QImage(); + } +} + +void ICOReader::readBMP(QImage & image) +{ + if (icoAttrib.nbits == 1) { + // 1 bit BMP image + read1BitBMP(image); + } else if (icoAttrib.nbits == 4) { + // 4 bit BMP image + read4BitBMP(image); + } else if (icoAttrib.nbits == 8) { + read8BitBMP(image); + } else if (icoAttrib.nbits == 16 || icoAttrib.nbits == 24 || icoAttrib.nbits == 32 ) { + // 16,24,32 bit BMP image + read16_24_32BMP(image); + } +} + + +/** + * NOTE: A 1 bit BMP is only flipped vertically, and not horizontally like all other color depths! + * (This is the same with the bitmask) + * + */ +void ICOReader::read1BitBMP(QImage & image) +{ + if (iod) { + int h = image.height(); + int bpl = image.bytesPerLine(); + + while (--h >= 0) { + if (iod->read((char*)image.scanLine(h),bpl) != bpl) { + image = QImage(); + break; + } + } + } else { + image = QImage(); + } +} + +void ICOReader::read4BitBMP(QImage & image) +{ + if (iod) { + + int h = icoAttrib.h; + int buflen = ((icoAttrib.w+7)/8)*4; + uchar *buf = new uchar[buflen]; + Q_CHECK_PTR(buf); + + while (--h >= 0) { + if (iod->read((char*)buf,buflen) != buflen) { + image = QImage(); + break; + } + uchar *p = image.scanLine(h); + uchar *b = buf; + for (int i=0; i> 4; + *p++ = *b++ & 0x0f; + } + if (icoAttrib.w & 1) { + // the last nibble + *p = *b >> 4; + } + } + + delete [] buf; + + } else { + image = QImage(); + } +} + +void ICOReader::read8BitBMP(QImage & image) +{ + if (iod) { + int h = icoAttrib.h; + int bpl = image.bytesPerLine(); + + while (--h >= 0) { + if (iod->read((char *)image.scanLine(h), bpl) != bpl) { + image = QImage(); + break; + } + } + } else { + image = QImage(); + } +} + +void ICOReader::read16_24_32BMP(QImage & image) +{ + if (iod) { + int h = icoAttrib.h; + QRgb *p; + QRgb *end; + uchar *buf = new uchar[image.bytesPerLine()]; + int bpl = ((icoAttrib.w*icoAttrib.nbits+31)/32)*4; + uchar *b; + + while (--h >= 0) { + p = (QRgb *)image.scanLine(h); + end = p + icoAttrib.w; + if (iod->read((char *)buf, bpl) != bpl) { + image = QImage(); + break; + } + b = buf; + while (p < end) { + if (icoAttrib.nbits == 24) + *p++ = qRgb(*(b+2), *(b+1), *b); + else if (icoAttrib.nbits == 32) + *p++ = qRgba(*(b+2), *(b+1), *b, *(b+3)); + b += icoAttrib.nbits/8; + } + } + + delete[] buf; + + } else { + image = QImage(); + } +} + +static const char icoOrigDepthKey[] = "_q_icoOrigDepth"; + +QImage ICOReader::iconAt(int index) +{ + QImage img; + + if (count() > index) { // forces header to be read + + ICONDIRENTRY iconEntry; + if (readIconEntry(index, &iconEntry)) { + + static const uchar pngMagicData[] = { 137, 80, 78, 71, 13, 10, 26, 10 }; + + iod->seek(iconEntry.dwImageOffset); + + const QByteArray pngMagic = QByteArray::fromRawData((const char*)pngMagicData, sizeof(pngMagicData)); + const bool isPngImage = (iod->read(pngMagic.size()) == pngMagic); + + if (isPngImage) { + iod->seek(iconEntry.dwImageOffset); + QImage image = QImage::fromData(iod->read(iconEntry.dwBytesInRes), "png"); + image.setText(QLatin1String(icoOrigDepthKey), QString::number(iconEntry.wBitCount)); + return image; + } + + BMP_INFOHDR header; + if (readBMPHeader(iconEntry.dwImageOffset, &header)) { + icoAttrib.nbits = header.biBitCount ? header.biBitCount : iconEntry.wBitCount; + + switch (icoAttrib.nbits) { + case 32: + case 24: + case 16: + icoAttrib.depth = 32; + break; + case 8: + case 4: + icoAttrib.depth = 8; + break; + default: + icoAttrib.depth = 1; + } + if (icoAttrib.depth == 32) // there's no colormap + icoAttrib.ncolors = 0; + else // # colors used + icoAttrib.ncolors = header.biClrUsed ? header.biClrUsed : 1 << icoAttrib.nbits; + if (icoAttrib.ncolors > 256) //color table can't be more than 256 + return img; + icoAttrib.w = iconEntry.bWidth; + if (icoAttrib.w == 0) // means 256 pixels + icoAttrib.w = header.biWidth; + icoAttrib.h = iconEntry.bHeight; + if (icoAttrib.h == 0) // means 256 pixels + icoAttrib.h = header.biHeight/2; + + QImage::Format format = QImage::Format_ARGB32; + if (icoAttrib.nbits == 24) + format = QImage::Format_RGB32; + else if (icoAttrib.ncolors == 2 && icoAttrib.depth == 1) + format = QImage::Format_Mono; + else if (icoAttrib.ncolors > 0) + format = QImage::Format_Indexed8; + + QImage image(icoAttrib.w, icoAttrib.h, format); + if (!image.isNull()) { + findColorInfo(image); + if (!image.isNull()) { + readBMP(image); + if (!image.isNull()) { + QImage mask(image.width(), image.height(), QImage::Format_Mono); + if (!mask.isNull()) { + mask.setColorCount(2); + mask.setColor(0, qRgba(255,255,255,0xff)); + mask.setColor(1, qRgba(0 ,0 ,0 ,0xff)); + read1BitBMP(mask); + if (!mask.isNull()) { + img = image; + img.setAlphaChannel(mask); + // (Luckily, it seems that setAlphaChannel() does not ruin the alpha values + // of partially transparent pixels in those icons that have that) + } + } + } + } + } + img.setText(QLatin1String(icoOrigDepthKey), QString::number(iconEntry.wBitCount)); + } + } + } + + return img; +} + + +/*! + Reads all the icons from the given \a device, and returns them as + a list of QImage objects. + + Each image has an alpha channel that represents the mask from the + corresponding icon. + + \sa write() +*/ +QVector ICOReader::read(QIODevice *device) +{ + QVector images; + + ICOReader reader(device); + const int N = reader.count(); + images.reserve(N); + for (int i = 0; i < N; i++) + images += reader.iconAt(i); + + return images; +} +#endif + +/*! + Writes all the QImages in the \a images list to the given \a + device. Returns \c true if the images are written successfully; + otherwise returns \c false. + + The first image in the list is stored as the first icon in the + device, and is therefore used as the default icon by applications. + The alpha channel of each image is converted to a mask for each + corresponding icon. + + \sa read() +*/ +bool ICOReader::write(QIODevice *device, const QVector &images) +{ + bool retValue = false; + + if (images.count()) { + + qint64 origOffset = device->pos(); + + ICONDIR id; + id.idReserved = 0; + id.idType = 1; + id.idCount = images.count(); + + ICONDIRENTRY * entries = new ICONDIRENTRY[id.idCount]; + BMP_INFOHDR * bmpHeaders = new BMP_INFOHDR[id.idCount]; + QByteArray * imageData = new QByteArray[id.idCount]; + + for (int i=0; i 256 || image.height() > 256) + { + image = image.scaled(256, 256, Qt::KeepAspectRatio, Qt::SmoothTransformation); + } + QImage maskImage(image.width(), image.height(), QImage::Format_Mono); + image = image.convertToFormat(QImage::Format_ARGB32); + + if (image.hasAlphaChannel()) { + maskImage = image.createAlphaMask(); + } else { + maskImage.fill(0xff); + } + maskImage = maskImage.convertToFormat(QImage::Format_Mono); + + int nbits = 32; + int bpl_bmp = ((image.width()*nbits+31)/32)*4; + + entries[i].bColorCount = 0; + entries[i].bReserved = 0; + entries[i].wBitCount = nbits; + entries[i].bHeight = image.height() < 256 ? image.height() : 0; // 0 means 256 + entries[i].bWidth = image.width() < 256 ? image.width() : 0; // 0 means 256 + entries[i].dwBytesInRes = BMP_INFOHDR_SIZE + (bpl_bmp * image.height()) + + (maskImage.bytesPerLine() * maskImage.height()); + entries[i].wPlanes = 1; + if (i == 0) + entries[i].dwImageOffset = origOffset + ICONDIR_SIZE + + (id.idCount * ICONDIRENTRY_SIZE); + else + entries[i].dwImageOffset = entries[i-1].dwImageOffset + entries[i-1].dwBytesInRes; + + bmpHeaders[i].biBitCount = entries[i].wBitCount; + bmpHeaders[i].biClrImportant = 0; + bmpHeaders[i].biClrUsed = entries[i].bColorCount; + bmpHeaders[i].biCompression = 0; + bmpHeaders[i].biHeight = entries[i].bHeight ? entries[i].bHeight * 2 : 256 * 2; // 2 is for the mask + bmpHeaders[i].biPlanes = entries[i].wPlanes; + bmpHeaders[i].biSize = BMP_INFOHDR_SIZE; + bmpHeaders[i].biSizeImage = entries[i].dwBytesInRes - BMP_INFOHDR_SIZE; + bmpHeaders[i].biWidth = entries[i].bWidth ? entries[i].bWidth : 256; + bmpHeaders[i].biXPelsPerMeter = 0; + bmpHeaders[i].biYPelsPerMeter = 0; + + QBuffer buffer(&imageData[i]); + buffer.open(QIODevice::WriteOnly); + + uchar *buf = new uchar[bpl_bmp]; + uchar *b; + memset( buf, 0, bpl_bmp ); + int y; + for (y = image.height() - 1; y >= 0; y--) { // write the image bits + // 32 bits + QRgb *p = (QRgb *)image.scanLine(y); + QRgb *end = p + image.width(); + b = buf; + int x = 0; + while (p < end) { + *b++ = qBlue(*p); + *b++ = qGreen(*p); + *b++ = qRed(*p); + *b++ = qAlpha(*p); + if (qAlpha(*p) > 0) // Even mostly transparent pixels must not be masked away + maskImage.setPixel(x, y, Qt::color1); // (i.e. createAlphaMask() takes away too much) + p++; + x++; + } + buffer.write((char*)buf, bpl_bmp); + } + delete[] buf; + + maskImage.invertPixels(); // seems as though it needs this + // NOTE! !! The mask is only flipped vertically - not horizontally !! + for (y = maskImage.height() - 1; y >= 0; y--) + buffer.write((char*)maskImage.scanLine(y), maskImage.bytesPerLine()); + + } + + if (writeIconDir(device, id)) { + int i; + bool bOK = true; + for (i = 0; i < id.idCount && bOK; i++) { + bOK = writeIconDirEntry(device, entries[i]); + } + if (bOK) { + for (i = 0; i < id.idCount && bOK; i++) { + bOK = writeBMPInfoHeader(device, bmpHeaders[i]); + bOK &= (device->write(imageData[i]) == (int) imageData[i].size()); + } + retValue = bOK; + } + } + + delete [] entries; + delete [] bmpHeaders; + delete [] imageData; + + } + return retValue; +} + +/*! + Constructs an instance of QtIcoHandler initialized to use \a device. +*/ +QtIcoHandler::QtIcoHandler(QIODevice *device) +{ + setDevice(device); +#ifdef QT_ICO_READ_SUPPORT + m_currentIconIndex = 0; + m_pICOReader = new ICOReader(device); +#endif +} + +/*! + Destructor for QtIcoHandler. +*/ +QtIcoHandler::~QtIcoHandler() +{ +#ifdef QT_ICO_READ_SUPPORT + delete m_pICOReader; +#endif +} + +QVariant QtIcoHandler::option(ImageOption option) const +{ +#ifdef QT_ICO_READ_SUPPORT + if (option == Size || option == ImageFormat) { + ICONDIRENTRY iconEntry; + if (m_pICOReader->readIconEntry(m_currentIconIndex, &iconEntry)) { + switch (option) { + case Size: + return QSize(iconEntry.bWidth ? iconEntry.bWidth : 256, + iconEntry.bHeight ? iconEntry.bHeight : 256); + + case ImageFormat: + switch (iconEntry.wBitCount) { + case 2: + return QImage::Format_Mono; + case 24: + return QImage::Format_RGB32; + case 32: + return QImage::Format_ARGB32; + default: + return QImage::Format_Indexed8; + } + break; + default: + break; + } + } + } +#else + Q_UNUSED(option); +#endif + return QVariant(); +} + +bool QtIcoHandler::supportsOption(ImageOption option) const +{ + return (option == Size || option == ImageFormat); +} + +/*! + * Verifies if some values (magic bytes) are set as expected in the header of the file. + * If the magic bytes were found, it is assumed that the QtIcoHandler can read the file. + * + */ +bool QtIcoHandler::canRead() const +{ + bool bCanRead = false; +#ifdef QT_ICO_READ_SUPPORT + QIODevice *device = QImageIOHandler::device(); + if (device) { + bCanRead = ICOReader::canRead(device); + if (bCanRead) + setFormat("ico"); + } else { + qWarning("QtIcoHandler::canRead() called with no device"); + } +#endif + return bCanRead; +} + +/*! This static function is used by the plugin code, and is provided for convenience only. + \a device must be an opened device with pointing to the start of the header data of the ICO file. +*/ +bool QtIcoHandler::canRead(QIODevice *device) +{ + Q_ASSERT(device); +#ifdef QT_ICO_READ_SUPPORT + return ICOReader::canRead(device); +#else + return false; +#endif +} + +/*! \reimp + +*/ +bool QtIcoHandler::read(QImage *image) +{ + bool bSuccess = false; +#ifdef QT_ICO_READ_SUPPORT + QImage img = m_pICOReader->iconAt(m_currentIconIndex); + + // Make sure we only write to \a image when we succeed. + if (!img.isNull()) { + bSuccess = true; + *image = img; + } +#else + Q_UNUSED(image); +#endif + return bSuccess; +} + +/*! \reimp + +*/ +bool QtIcoHandler::write(const QImage &image) +{ + QIODevice *device = QImageIOHandler::device(); + QVector imgs; + imgs.append(image); + return ICOReader::write(device, imgs); +} + +bool QtIcoHandler::write(const QVector &images) +{ + QIODevice *device = QImageIOHandler::device(); + return ICOReader::write(device, images); +} + +/*! + * Return the common identifier of the format. + * For ICO format this will return "ico". + */ +QByteArray QtIcoHandler::name() const +{ + return "ico"; +} + + +/*! \reimp + +*/ +int QtIcoHandler::imageCount() const +{ +#ifdef QT_ICO_READ_SUPPORT + return m_pICOReader->count(); +#else + return 0; +#endif +} + +/*! \reimp + +*/ +bool QtIcoHandler::jumpToImage(int imageNumber) +{ +#ifdef QT_ICO_READ_SUPPORT + if (imageNumber < imageCount()) { + m_currentIconIndex = imageNumber; + return true; + } +#else + Q_UNUSED(imageNumber); +#endif + return false; +} + +/*! \reimp + +*/ +bool QtIcoHandler::jumpToNextImage() +{ +#ifdef QT_ICO_READ_SUPPORT + return jumpToImage(m_currentIconIndex + 1); +#else + return false; +#endif +} diff --git a/src/tools/ksvg2ico/qicohandler.h b/src/tools/ksvg2ico/qicohandler.h new file mode 100644 index 0000000..2f35322 --- /dev/null +++ b/src/tools/ksvg2ico/qicohandler.h @@ -0,0 +1,79 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** 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-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QTICOHANDLER_H +#define QTICOHANDLER_H + +#include +#include + +#ifdef QT_ICO_READ_SUPPORT +class ICOReader; +#endif + +class QtIcoHandler: public QImageIOHandler +{ +public: + QtIcoHandler(QIODevice *device); + virtual ~QtIcoHandler(); + + bool canRead() const Q_DECL_OVERRIDE; + bool read(QImage *image) Q_DECL_OVERRIDE; + bool write(const QImage &image) Q_DECL_OVERRIDE; + bool write(const QVector &images); + + QByteArray name() const Q_DECL_OVERRIDE; + + int imageCount() const Q_DECL_OVERRIDE; + bool jumpToImage(int imageNumber) Q_DECL_OVERRIDE; + bool jumpToNextImage() Q_DECL_OVERRIDE; + + static bool canRead(QIODevice *device); + + bool supportsOption(ImageOption option) const Q_DECL_OVERRIDE; + QVariant option(ImageOption option) const Q_DECL_OVERRIDE; + +#ifdef QT_ICO_READ_SUPPORT +private: + int m_currentIconIndex; + ICOReader *m_pICOReader; +#endif +}; + +#endif /* QTICOHANDLER_H */ +