diff --git a/src/imageformats/exr.cpp b/src/imageformats/exr.cpp index 3f60515..b64dc9e 100644 --- a/src/imageformats/exr.cpp +++ b/src/imageformats/exr.cpp @@ -1,240 +1,241 @@ /* * KImageIO Routines to read (and perhaps in the future, write) images * in the high dynamic range EXR format. * Copyright (c) 2003, Brad Hards * * This library is distributed under the conditions of the GNU LGPL. */ #include "exr_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class K_IStream: public Imf::IStream { public: K_IStream(QIODevice *dev, const QByteArray &fileName): IStream(fileName.data()), m_dev(dev) { } bool read(char c[], int n) override; Imf::Int64 tellg() override; void seekg(Imf::Int64 pos) override; void clear() override; private: QIODevice *m_dev; }; bool K_IStream::read(char c[], int n) { qint64 result = m_dev->read(c, n); if (result > 0) { return true; } else if (result == 0) { throw Iex::InputExc("Unexpected end of file"); } else { // negative value { Iex::throwErrnoExc("Error in read", result); } return false; } Imf::Int64 K_IStream::tellg() { return m_dev->pos(); } void K_IStream::seekg(Imf::Int64 pos) { m_dev->seek(pos); } void K_IStream::clear() { // TODO } /* this does a conversion from the ILM Half (equal to Nvidia Half) * format into the normal 32 bit pixel format. Process is from the * ILM code. */ QRgb RgbaToQrgba(struct Imf::Rgba &imagePixel) { float r, g, b, a; // 1) Compensate for fogging by subtracting defog // from the raw pixel values. // Response: We work with defog of 0.0, so this is a no-op // 2) Multiply the defogged pixel values by // 2^(exposure + 2.47393). // Response: We work with exposure of 0.0. // (2^2.47393) is 5.55555 r = imagePixel.r * 5.55555; g = imagePixel.g * 5.55555; b = imagePixel.b * 5.55555; a = imagePixel.a * 5.55555; // 3) Values, which are now 1.0, are called "middle gray". // If defog and exposure are both set to 0.0, then // middle gray corresponds to a raw pixel value of 0.18. // In step 6, middle gray values will be mapped to an // intensity 3.5 f-stops below the display's maximum // intensity. // Response: no apparent content. // 4) Apply a knee function. The knee function has two // parameters, kneeLow and kneeHigh. Pixel values // below 2^kneeLow are not changed by the knee // function. Pixel values above kneeLow are lowered // according to a logarithmic curve, such that the // value 2^kneeHigh is mapped to 2^3.5 (in step 6, // this value will be mapped to the display's // maximum intensity). // Response: kneeLow = 0.0 (2^0.0 => 1); kneeHigh = 5.0 (2^5 =>32) if (r > 1.0) { r = 1.0 + Imath::Math::log((r - 1.0) * 0.184874 + 1) / 0.184874; } if (g > 1.0) { g = 1.0 + Imath::Math::log((g - 1.0) * 0.184874 + 1) / 0.184874; } if (b > 1.0) { b = 1.0 + Imath::Math::log((b - 1.0) * 0.184874 + 1) / 0.184874; } if (a > 1.0) { a = 1.0 + Imath::Math::log((a - 1.0) * 0.184874 + 1) / 0.184874; } // // 5) Gamma-correct the pixel values, assuming that the // screen's gamma is 0.4545 (or 1/2.2). r = Imath::Math::pow(r, 0.4545); g = Imath::Math::pow(g, 0.4545); b = Imath::Math::pow(b, 0.4545); a = Imath::Math::pow(a, 0.4545); // 6) Scale the values such that pixels middle gray // pixels are mapped to 84.66 (or 3.5 f-stops below // the display's maximum intensity). // // 7) Clamp the values to [0, 255]. return qRgba((unsigned char)(Imath::clamp(r * 84.66f, 0.f, 255.f)), (unsigned char)(Imath::clamp(g * 84.66f, 0.f, 255.f)), (unsigned char)(Imath::clamp(b * 84.66f, 0.f, 255.f)), (unsigned char)(Imath::clamp(a * 84.66f, 0.f, 255.f))); } EXRHandler::EXRHandler() { } bool EXRHandler::canRead() const { if (canRead(device())) { setFormat("exr"); return true; } return false; } bool EXRHandler::read(QImage *outImage) { try { int width, height; K_IStream istr(device(), QByteArray()); Imf::RgbaInputFile file(istr); Imath::Box2i dw = file.dataWindow(); width = dw.max.x - dw.min.x + 1; height = dw.max.y - dw.min.y + 1; + QImage image(width, height, QImage::Format_RGB32); + if (image.isNull()) { + qWarning() << "Failed to allocate image, invalid size?" << QSize(width, height); + return false; + } + Imf::Array2D pixels; pixels.resizeErase(height, width); file.setFrameBuffer(&pixels[0][0] - dw.min.x - dw.min.y * width, 1, width); file.readPixels(dw.min.y, dw.max.y); - QImage image(width, height, QImage::Format_RGB32); - if (image.isNull()) { - return false; - } - // somehow copy pixels into image for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { // copy pixels(x,y) into image(x,y) image.setPixel(x, y, RgbaToQrgba(pixels[y][x])); } } *outImage = image; return true; } catch (const std::exception &exc) { // qDebug() << exc.what(); return false; } } bool EXRHandler::canRead(QIODevice *device) { if (!device) { qWarning("EXRHandler::canRead() called with no device"); return false; } const QByteArray head = device->peek(4); return Imf::isImfMagic(head.data()); } QImageIOPlugin::Capabilities EXRPlugin::capabilities(QIODevice *device, const QByteArray &format) const { if (format == "exr") { return Capabilities(CanRead); } if (!format.isEmpty()) { return {}; } if (!device->isOpen()) { return {}; } Capabilities cap; if (device->isReadable() && EXRHandler::canRead(device)) { cap |= CanRead; } return cap; } QImageIOHandler *EXRPlugin::create(QIODevice *device, const QByteArray &format) const { QImageIOHandler *handler = new EXRHandler; handler->setDevice(device); handler->setFormat(format); return handler; } diff --git a/src/imageformats/pcx.cpp b/src/imageformats/pcx.cpp index a45ba96..fd376a0 100644 --- a/src/imageformats/pcx.cpp +++ b/src/imageformats/pcx.cpp @@ -1,699 +1,718 @@ /* This file is part of the KDE project Copyright (C) 2002-2005 Nadeem Hasan This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License (LGPL) as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ #include "pcx_p.h" #include #include #include #include #pragma pack(push,1) class RGB { public: quint8 r; quint8 g; quint8 b; static RGB from(const QRgb color) { RGB c; c.r = qRed(color); c.g = qGreen(color); c.b = qBlue(color); return c; } }; class Palette { public: void setColor(int i, const QRgb color) { RGB &c = rgb[ i ]; c.r = qRed(color); c.g = qGreen(color); c.b = qBlue(color); } QRgb color(int i) const { return qRgb(rgb[ i ].r, rgb[ i ].g, rgb[ i ].b); } class RGB rgb[ 16 ]; }; class PCXHEADER { public: PCXHEADER(); inline int width() const { return (XMax - XMin) + 1; } inline int height() const { return (YMax - YMin) + 1; } inline bool isCompressed() const { return (Encoding == 1); } quint8 Manufacturer; // Constant Flag, 10 = ZSoft .pcx quint8 Version; // Version information· // 0 = Version 2.5 of PC Paintbrush· // 2 = Version 2.8 w/palette information· // 3 = Version 2.8 w/o palette information· // 4 = PC Paintbrush for Windows(Plus for // Windows uses Ver 5)· // 5 = Version 3.0 and > of PC Paintbrush // and PC Paintbrush +, includes // Publisher's Paintbrush . Includes // 24-bit .PCX files· quint8 Encoding; // 1 = .PCX run length encoding quint8 Bpp; // Number of bits to represent a pixel // (per Plane) - 1, 2, 4, or 8· quint16 XMin; quint16 YMin; quint16 XMax; quint16 YMax; quint16 HDpi; quint16 YDpi; Palette ColorMap; quint8 Reserved; // Should be set to 0. quint8 NPlanes; // Number of color planes quint16 BytesPerLine; // Number of bytes to allocate for a scanline // plane. MUST be an EVEN number. Do NOT // calculate from Xmax-Xmin.· quint16 PaletteInfo; // How to interpret palette- 1 = Color/BW, // 2 = Grayscale ( ignored in PB IV/ IV + )· quint16 HScreenSize; // Horizontal screen size in pixels. New field // found only in PB IV/IV Plus quint16 VScreenSize; // Vertical screen size in pixels. New field // found only in PB IV/IV Plus }; #pragma pack(pop) static QDataStream &operator>>(QDataStream &s, RGB &rgb) { quint8 r, g, b; s >> r >> g >> b; rgb.r = r; rgb.g = g; rgb.b = b; return s; } static QDataStream &operator>>(QDataStream &s, Palette &pal) { for (int i = 0; i < 16; ++i) { s >> pal.rgb[ i ]; } return s; } static QDataStream &operator>>(QDataStream &s, PCXHEADER &ph) { quint8 m, ver, enc, bpp; s >> m >> ver >> enc >> bpp; ph.Manufacturer = m; ph.Version = ver; ph.Encoding = enc; ph.Bpp = bpp; quint16 xmin, ymin, xmax, ymax; s >> xmin >> ymin >> xmax >> ymax; ph.XMin = xmin; ph.YMin = ymin; ph.XMax = xmax; ph.YMax = ymax; quint16 hdpi, ydpi; s >> hdpi >> ydpi; ph.HDpi = hdpi; ph.YDpi = ydpi; Palette colorMap; quint8 res, np; s >> colorMap >> res >> np; ph.ColorMap = colorMap; ph.Reserved = res; ph.NPlanes = np; quint16 bytesperline; s >> bytesperline; ph.BytesPerLine = bytesperline; quint16 paletteinfo; s >> paletteinfo; ph.PaletteInfo = paletteinfo; quint16 hscreensize, vscreensize; s >> hscreensize; ph.HScreenSize = hscreensize; s >> vscreensize; ph.VScreenSize = vscreensize; // Skip the rest of the header quint8 byte; while (s.device()->pos() < 128) { s >> byte; } return s; } static QDataStream &operator<<(QDataStream &s, const RGB rgb) { s << rgb.r << rgb.g << rgb.b; return s; } static QDataStream &operator<<(QDataStream &s, const Palette &pal) { for (int i = 0; i < 16; ++i) { s << pal.rgb[ i ]; } return s; } static QDataStream &operator<<(QDataStream &s, const PCXHEADER &ph) { s << ph.Manufacturer; s << ph.Version; s << ph.Encoding; s << ph.Bpp; s << ph.XMin << ph.YMin << ph.XMax << ph.YMax; s << ph.HDpi << ph.YDpi; s << ph.ColorMap; s << ph.Reserved; s << ph.NPlanes; s << ph.BytesPerLine; s << ph.PaletteInfo; s << ph.HScreenSize; s << ph.VScreenSize; quint8 byte = 0; for (int i = 0; i < 54; ++i) { s << byte; } return s; } PCXHEADER::PCXHEADER() { // Initialize all data to zero QByteArray dummy(128, 0); dummy.fill(0); QDataStream s(&dummy, QIODevice::ReadOnly); s >> *this; } static void readLine(QDataStream &s, QByteArray &buf, const PCXHEADER &header) { quint32 i = 0; quint32 size = buf.size(); quint8 byte, count; if (header.isCompressed()) { // Uncompress the image data while (i < size) { count = 1; s >> byte; if (byte > 0xc0) { count = byte - 0xc0; s >> byte; } while (count-- && i < size) { buf[ i++ ] = byte; } } } else { // Image is not compressed (possible?) while (i < size) { s >> byte; buf[ i++ ] = byte; } } } static void readImage1(QImage &img, QDataStream &s, const PCXHEADER &header) { QByteArray buf(header.BytesPerLine, 0); img = QImage(header.width(), header.height(), QImage::Format_Mono); img.setColorCount(2); - if (img.isNull()) + if (img.isNull()) { + qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height()); return; + } for (int y = 0; y < header.height(); ++y) { if (s.atEnd()) { img = QImage(); return; } readLine(s, buf, header); uchar *p = img.scanLine(y); unsigned int bpl = qMin((quint16)((header.width() + 7) / 8), header.BytesPerLine); for (unsigned int x = 0; x < bpl; ++x) { p[ x ] = buf[x]; } } // Set the color palette img.setColor(0, qRgb(0, 0, 0)); img.setColor(1, qRgb(255, 255, 255)); } static void readImage4(QImage &img, QDataStream &s, const PCXHEADER &header) { QByteArray buf(header.BytesPerLine * 4, 0); QByteArray pixbuf(header.width(), 0); img = QImage(header.width(), header.height(), QImage::Format_Indexed8); img.setColorCount(16); + if (img.isNull()) { + qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height()); + return; + } for (int y = 0; y < header.height(); ++y) { if (s.atEnd()) { img = QImage(); return; } pixbuf.fill(0); readLine(s, buf, header); for (int i = 0; i < 4; i++) { quint32 offset = i * header.BytesPerLine; for (int x = 0; x < header.width(); ++x) if (buf[ offset + (x / 8) ] & (128 >> (x % 8))) { pixbuf[ x ] = (int)(pixbuf[ x ]) + (1 << i); } } uchar *p = img.scanLine(y); + if (!p) { + qWarning() << "Failed to get scanline for" << y << "might be out of bounds"; + } for (int x = 0; x < header.width(); ++x) { p[ x ] = pixbuf[ x ]; } } // Read the palette for (int i = 0; i < 16; ++i) { img.setColor(i, header.ColorMap.color(i)); } } static void readImage8(QImage &img, QDataStream &s, const PCXHEADER &header) { QByteArray buf(header.BytesPerLine, 0); img = QImage(header.width(), header.height(), QImage::Format_Indexed8); img.setColorCount(256); + if (img.isNull()) { + qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height()); + return; + } + for (int y = 0; y < header.height(); ++y) { if (s.atEnd()) { img = QImage(); return; } readLine(s, buf, header); uchar *p = img.scanLine(y); if (!p) return; unsigned int bpl = qMin(header.BytesPerLine, (quint16)header.width()); for (unsigned int x = 0; x < bpl; ++x) { p[ x ] = buf[ x ]; } } quint8 flag; s >> flag; // qDebug() << "Palette Flag: " << flag; if (flag == 12 && (header.Version == 5 || header.Version == 2)) { // Read the palette quint8 r, g, b; for (int i = 0; i < 256; ++i) { s >> r >> g >> b; img.setColor(i, qRgb(r, g, b)); } } } static void readImage24(QImage &img, QDataStream &s, const PCXHEADER &header) { QByteArray r_buf(header.BytesPerLine, 0); QByteArray g_buf(header.BytesPerLine, 0); QByteArray b_buf(header.BytesPerLine, 0); img = QImage(header.width(), header.height(), QImage::Format_RGB32); + if (img.isNull()) { + qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width(), header.height()); + return; + } + for (int y = 0; y < header.height(); ++y) { if (s.atEnd()) { img = QImage(); return; } readLine(s, r_buf, header); readLine(s, g_buf, header); readLine(s, b_buf, header); uint *p = (uint *)img.scanLine(y); for (int x = 0; x < header.width(); ++x) { p[ x ] = qRgb(r_buf[ x ], g_buf[ x ], b_buf[ x ]); } } } static void writeLine(QDataStream &s, QByteArray &buf) { quint32 i = 0; quint32 size = buf.size(); quint8 count, data; char byte; while (i < size) { count = 1; byte = buf[ i++ ]; while ((i < size) && (byte == buf[ i ]) && (count < 63)) { ++i; ++count; } data = byte; if (count > 1 || data >= 0xc0) { count |= 0xc0; s << count; } s << data; } } static void writeImage1(QImage &img, QDataStream &s, PCXHEADER &header) { img = img.convertToFormat(QImage::Format_Mono); header.Bpp = 1; header.NPlanes = 1; header.BytesPerLine = img.bytesPerLine(); s << header; QByteArray buf(header.BytesPerLine, 0); for (int y = 0; y < header.height(); ++y) { quint8 *p = img.scanLine(y); // Invert as QImage uses reverse palette for monochrome images? for (int i = 0; i < header.BytesPerLine; ++i) { buf[ i ] = ~p[ i ]; } writeLine(s, buf); } } static void writeImage4(QImage &img, QDataStream &s, PCXHEADER &header) { header.Bpp = 1; header.NPlanes = 4; header.BytesPerLine = header.width() / 8; for (int i = 0; i < 16; ++i) { header.ColorMap.setColor(i, img.color(i)); } s << header; QByteArray buf[ 4 ]; for (int i = 0; i < 4; ++i) { buf[ i ].resize(header.BytesPerLine); } for (int y = 0; y < header.height(); ++y) { quint8 *p = img.scanLine(y); for (int i = 0; i < 4; ++i) { buf[ i ].fill(0); } for (int x = 0; x < header.width(); ++x) { for (int i = 0; i < 4; ++i) if (*(p + x) & (1 << i)) { buf[ i ][ x / 8 ] = (int)(buf[ i ][ x / 8 ]) | 1 << (7 - x % 8); } } for (int i = 0; i < 4; ++i) { writeLine(s, buf[ i ]); } } } static void writeImage8(QImage &img, QDataStream &s, PCXHEADER &header) { header.Bpp = 8; header.NPlanes = 1; header.BytesPerLine = img.bytesPerLine(); s << header; QByteArray buf(header.BytesPerLine, 0); for (int y = 0; y < header.height(); ++y) { quint8 *p = img.scanLine(y); for (int i = 0; i < header.BytesPerLine; ++i) { buf[ i ] = p[ i ]; } writeLine(s, buf); } // Write palette flag quint8 byte = 12; s << byte; // Write palette for (int i = 0; i < 256; ++i) { s << RGB::from(img.color(i)); } } static void writeImage24(QImage &img, QDataStream &s, PCXHEADER &header) { header.Bpp = 8; header.NPlanes = 3; header.BytesPerLine = header.width(); s << header; QByteArray r_buf(header.width(), 0); QByteArray g_buf(header.width(), 0); QByteArray b_buf(header.width(), 0); for (int y = 0; y < header.height(); ++y) { uint *p = (uint *)img.scanLine(y); for (int x = 0; x < header.width(); ++x) { QRgb rgb = *p++; r_buf[ x ] = qRed(rgb); g_buf[ x ] = qGreen(rgb); b_buf[ x ] = qBlue(rgb); } writeLine(s, r_buf); writeLine(s, g_buf); writeLine(s, b_buf); } } PCXHandler::PCXHandler() { } bool PCXHandler::canRead() const { if (canRead(device())) { setFormat("pcx"); return true; } return false; } bool PCXHandler::read(QImage *outImage) { QDataStream s(device()); s.setByteOrder(QDataStream::LittleEndian); if (s.device()->size() < 128) { return false; } PCXHEADER header; s >> header; if (header.Manufacturer != 10 || s.atEnd()) { return false; } // int w = header.width(); // int h = header.height(); // qDebug() << "Manufacturer: " << header.Manufacturer; // qDebug() << "Version: " << header.Version; // qDebug() << "Encoding: " << header.Encoding; // qDebug() << "Bpp: " << header.Bpp; // qDebug() << "Width: " << w; // qDebug() << "Height: " << h; // qDebug() << "Window: " << header.XMin << "," << header.XMax << "," // << header.YMin << "," << header.YMax << endl; // qDebug() << "BytesPerLine: " << header.BytesPerLine; // qDebug() << "NPlanes: " << header.NPlanes; QImage img; if (header.Bpp == 1 && header.NPlanes == 1) { readImage1(img, s, header); } else if (header.Bpp == 1 && header.NPlanes == 4) { readImage4(img, s, header); } else if (header.Bpp == 8 && header.NPlanes == 1) { readImage8(img, s, header); } else if (header.Bpp == 8 && header.NPlanes == 3) { readImage24(img, s, header); } // qDebug() << "Image Bytes: " << img.numBytes(); // qDebug() << "Image Bytes Per Line: " << img.bytesPerLine(); // qDebug() << "Image Depth: " << img.depth(); if (!img.isNull()) { *outImage = img; return true; } else { return false; } } bool PCXHandler::write(const QImage &image) { QDataStream s(device()); s.setByteOrder(QDataStream::LittleEndian); QImage img = image; int w = img.width(); int h = img.height(); // qDebug() << "Width: " << w; // qDebug() << "Height: " << h; // qDebug() << "Depth: " << img.depth(); // qDebug() << "BytesPerLine: " << img.bytesPerLine(); // qDebug() << "Color Count: " << img.colorCount(); PCXHEADER header; header.Manufacturer = 10; header.Version = 5; header.Encoding = 1; header.XMin = 0; header.YMin = 0; header.XMax = w - 1; header.YMax = h - 1; header.HDpi = 300; header.YDpi = 300; header.Reserved = 0; header.PaletteInfo = 1; if (img.depth() == 1) { writeImage1(img, s, header); } else if (img.depth() == 8 && img.colorCount() <= 16) { writeImage4(img, s, header); } else if (img.depth() == 8) { writeImage8(img, s, header); } else if (img.depth() == 32) { writeImage24(img, s, header); } return true; } bool PCXHandler::canRead(QIODevice *device) { if (!device) { qWarning("PCXHandler::canRead() called with no device"); return false; } qint64 oldPos = device->pos(); char head[1]; qint64 readBytes = device->read(head, sizeof(head)); if (readBytes != sizeof(head)) { if (device->isSequential()) { while (readBytes > 0) { device->ungetChar(head[readBytes-- - 1]); } } else { device->seek(oldPos); } return false; } if (device->isSequential()) { while (readBytes > 0) { device->ungetChar(head[readBytes-- - 1]); } } else { device->seek(oldPos); } return qstrncmp(head, "\012", 1) == 0; } QImageIOPlugin::Capabilities PCXPlugin::capabilities(QIODevice *device, const QByteArray &format) const { if (format == "pcx") { return Capabilities(CanRead | CanWrite); } if (!format.isEmpty()) { return {}; } if (!device->isOpen()) { return {}; } Capabilities cap; if (device->isReadable() && PCXHandler::canRead(device)) { cap |= CanRead; } if (device->isWritable()) { cap |= CanWrite; } return cap; } QImageIOHandler *PCXPlugin::create(QIODevice *device, const QByteArray &format) const { QImageIOHandler *handler = new PCXHandler; handler->setDevice(device); handler->setFormat(format); return handler; } diff --git a/src/imageformats/pic.cpp b/src/imageformats/pic.cpp index 22bd8b4..6566e27 100644 --- a/src/imageformats/pic.cpp +++ b/src/imageformats/pic.cpp @@ -1,475 +1,481 @@ /* * Softimage PIC support for QImage. * * Copyright 1998 Halfdan Ingvarsson * Copyright 2007 Ruben Lopez * Copyright 2014 Alex Merry * * This library 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 of the License, or (at your option) any later version. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ---------------------------------------------------------------------------- */ /* This code is based on the GIMP-PIC plugin by Halfdan Ingvarsson, * and relicensed from GPL to LGPL to accommodate the KDE licensing policy * with his permission. */ #include "pic_p.h" #include "rle_p.h" #include #include #include #include #include #include #include /** * Reads a PIC file header from a data stream. * * @param s The data stream to read from. * @param channels Where the read header will be stored. * @returns @p s * * @relates PicHeader */ static QDataStream &operator>> (QDataStream &s, PicHeader &header) { s.setFloatingPointPrecision(QDataStream::SinglePrecision); s >> header.magic; s >> header.version; // the comment should be truncated to the first null byte char comment[81] = {}; s.readRawData(comment, 80); header.comment = QByteArray(comment); header.id.resize(4); const int bytesRead = s.readRawData(header.id.data(), 4); if (bytesRead != 4) { header.id.resize(bytesRead); } s >> header.width; s >> header.height; s >> header.ratio; qint16 fields; s >> fields; header.fields = static_cast(fields); qint16 pad; s >> pad; return s; } /** * Writes a PIC file header to a data stream. * * @param s The data stream to write to. * @param channels The header to write. * @returns @p s * * @relates PicHeader */ static QDataStream &operator<< (QDataStream &s, const PicHeader &header) { s.setFloatingPointPrecision(QDataStream::SinglePrecision); s << header.magic; s << header.version; char comment[80] = {}; strncpy(comment, header.comment.constData(), sizeof(comment)); s.writeRawData(comment, sizeof(comment)); char id[4] = {}; strncpy(id, header.id.constData(), sizeof(id)); s.writeRawData(id, sizeof(id)); s << header.width; s << header.height; s << header.ratio; s << quint16(header.fields); s << quint16(0); return s; } /** * Reads a series of channel descriptions from a data stream. * * If the stream contains more than 8 channel descriptions, the status of @p s * will be set to QDataStream::ReadCorruptData (note that more than 4 channels * - one for each component - does not really make sense anyway). * * @param s The data stream to read from. * @param channels The location to place the read channel descriptions; any * existing entries will be cleared. * @returns @p s * * @relates PicChannel */ static QDataStream &operator>> (QDataStream &s, QList &channels) { const unsigned maxChannels = 8; unsigned count = 0; quint8 chained = 1; channels.clear(); while (chained && count < maxChannels && s.status() == QDataStream::Ok) { PicChannel channel; s >> chained; s >> channel.size; s >> channel.encoding; s >> channel.code; channels << channel; ++count; } if (chained) { // too many channels! s.setStatus(QDataStream::ReadCorruptData); } return s; } /** * Writes a series of channel descriptions to a data stream. * * Note that the corresponding read operation will not read more than 8 channel * descriptions, although there should be no reason to have more than 4 channels * anyway. * * @param s The data stream to write to. * @param channels The channel descriptions to write. * @returns @p s * * @relates PicChannel */ static QDataStream &operator<< (QDataStream &s, const QList &channels) { Q_ASSERT(channels.size() > 0); for (int i = 0; i < channels.size() - 1; ++i) { s << quint8(1); // chained s << channels[i].size; s << quint8(channels[i].encoding); s << channels[i].code; } s << quint8(0); // chained s << channels.last().size; s << quint8(channels.last().encoding); s << channels.last().code; return s; } static bool readRow(QDataStream &stream, QRgb *row, quint16 width, const QList &channels) { for(const PicChannel &channel : channels) { auto readPixel = [&] (QDataStream &str) -> QRgb { quint8 red = 0; if (channel.code & RED) { str >> red; } quint8 green = 0; if (channel.code & GREEN) { str >> green; } quint8 blue = 0; if (channel.code & BLUE) { str >> blue; } quint8 alpha = 0; if (channel.code & ALPHA) { str >> alpha; } return qRgba(red, green, blue, alpha); }; auto updatePixel = [&] (QRgb oldPixel, QRgb newPixel) -> QRgb { return qRgba( qRed((channel.code & RED) ? newPixel : oldPixel), qGreen((channel.code & GREEN) ? newPixel : oldPixel), qBlue((channel.code & BLUE) ? newPixel : oldPixel), qAlpha((channel.code & ALPHA) ? newPixel : oldPixel)); }; if (channel.encoding == MixedRLE) { bool success = decodeRLEData(RLEVariant::PIC, stream, row, width, readPixel, updatePixel); if (!success) { qDebug() << "decodeRLEData failed"; return false; } } else if (channel.encoding == Uncompressed) { for (quint16 i = 0; i < width; ++i) { QRgb pixel = readPixel(stream); row[i] = updatePixel(row[i], pixel); } } else { // unknown encoding qDebug() << "Unknown encoding"; return false; } } if (stream.status() != QDataStream::Ok) { qDebug() << "DataStream status was" << stream.status(); } return stream.status() == QDataStream::Ok; } bool SoftimagePICHandler::canRead() const { if (!SoftimagePICHandler::canRead(device())) { return false; } setFormat("pic"); return true; } bool SoftimagePICHandler::read(QImage *image) { if (!readChannels()) { return false; } QImage::Format fmt = QImage::Format_RGB32; for (const PicChannel &channel : qAsConst(m_channels)) { if (channel.size != 8) { // we cannot read images that do not come in bytes qDebug() << "Channel size was" << channel.size; m_state = Error; return false; } if (channel.code & ALPHA) { fmt = QImage::Format_ARGB32; } } QImage img(m_header.width, m_header.height, fmt); + if (img.isNull()) { + qDebug() << "Failed to allocate image, invalid dimensions?" << QSize(m_header.width, m_header.height) << fmt; + return false; + } + img.fill(qRgb(0,0,0)); for (int y = 0; y < m_header.height; y++) { QRgb *row = reinterpret_cast(img.scanLine(y)); if (!readRow(m_dataStream, row, m_header.width, m_channels)) { qDebug() << "readRow failed"; m_state = Error; return false; } } *image = img; m_state = Ready; return true; } bool SoftimagePICHandler::write(const QImage &_image) { bool alpha = _image.hasAlphaChannel(); const QImage image = _image.convertToFormat( alpha ? QImage::Format_ARGB32 : QImage::Format_RGB32); if (image.width() < 0 || image.height() < 0) { qDebug() << "Image size invalid:" << image.width() << image.height(); return false; } if (image.width() > 65535 || image.height() > 65535) { qDebug() << "Image too big:" << image.width() << image.height(); // there are only two bytes for each dimension return false; } QDataStream stream(device()); stream << PicHeader(image.width(), image.height(), m_description); PicChannelEncoding encoding = m_compression ? MixedRLE : Uncompressed; QList channels; channels << PicChannel(encoding, RED | GREEN | BLUE); if (alpha) { channels << PicChannel(encoding, ALPHA); } stream << channels; for (int r = 0; r < image.height(); r++) { const QRgb *row = reinterpret_cast(image.scanLine(r)); /* Write the RGB part of the scanline */ auto rgbEqual = [] (QRgb p1, QRgb p2) -> bool { return qRed(p1) == qRed(p2) && qGreen(p1) == qGreen(p2) && qBlue(p1) == qBlue(p2); }; auto writeRgb = [] (QDataStream &str, QRgb pixel) -> void { str << quint8(qRed(pixel)) << quint8(qGreen(pixel)) << quint8(qBlue(pixel)); }; if (m_compression) { encodeRLEData(RLEVariant::PIC, stream, row, image.width(), rgbEqual, writeRgb); } else { for (int i = 0; i < image.width(); ++i) { writeRgb(stream, row[i]); } } /* Write the alpha channel */ if (alpha) { auto alphaEqual = [] (QRgb p1, QRgb p2) -> bool { return qAlpha(p1) == qAlpha(p2); }; auto writeAlpha = [] (QDataStream &str, QRgb pixel) -> void { str << quint8(qAlpha(pixel)); }; if (m_compression) { encodeRLEData(RLEVariant::PIC, stream, row, image.width(), alphaEqual, writeAlpha); } else { for (int i = 0; i < image.width(); ++i) { writeAlpha(stream, row[i]); } } } } return stream.status() == QDataStream::Ok; } bool SoftimagePICHandler::canRead(QIODevice *device) { char data[4]; if (device->peek(data, 4) != 4) { return false; } return qFromBigEndian(reinterpret_cast(data)) == PIC_MAGIC_NUMBER; } bool SoftimagePICHandler::readHeader() { if (m_state == Ready) { m_state = Error; m_dataStream.setDevice(device()); m_dataStream >> m_header; if (m_header.isValid() && m_dataStream.status() == QDataStream::Ok) { m_state = ReadHeader; } } + return m_state != Error; } bool SoftimagePICHandler::readChannels() { readHeader(); if (m_state == ReadHeader) { m_state = Error; m_dataStream >> m_channels; if (m_dataStream.status() == QDataStream::Ok) { m_state = ReadChannels; } } return m_state != Error; } void SoftimagePICHandler::setOption(ImageOption option, const QVariant &value) { switch (option) { case CompressionRatio: m_compression = value.toBool(); break; case Description: { m_description.clear(); const QStringList entries = value.toString().split(QStringLiteral("\n\n")); for (const QString &entry : entries) { if (entry.startsWith(QStringLiteral("Description: "))) { m_description = entry.mid(13).simplified().toUtf8(); } } break; } default: break; } } QVariant SoftimagePICHandler::option(ImageOption option) const { const_cast(this)->readHeader(); switch (option) { case Size: if (const_cast(this)->readHeader()) { return QSize(m_header.width, m_header.height); } else { return QVariant(); } case CompressionRatio: return m_compression; case Description: if (const_cast(this)->readHeader()) { QString descStr = QString::fromUtf8(m_header.comment); if (!descStr.isEmpty()) { return QString(QStringLiteral("Description: ") + descStr + QStringLiteral("\n\n")); } } return QString(); case ImageFormat: if (const_cast(this)->readChannels()) { for (const PicChannel &channel : qAsConst(m_channels)) { if (channel.code & ALPHA) { return QImage::Format_ARGB32; } } return QImage::Format_RGB32; } return QVariant(); default: return QVariant(); } } bool SoftimagePICHandler::supportsOption(ImageOption option) const { return (option == CompressionRatio || option == Description || option == ImageFormat || option == Size); } QImageIOPlugin::Capabilities SoftimagePICPlugin::capabilities(QIODevice *device, const QByteArray &format) const { if (format == "pic") { return Capabilities(CanRead | CanWrite); } if (!format.isEmpty()) { return {}; } if (!device->isOpen()) { return {}; } Capabilities cap; if (device->isReadable() && SoftimagePICHandler::canRead(device)) { cap |= CanRead; } if (device->isWritable()) { cap |= CanWrite; } return cap; } QImageIOHandler *SoftimagePICPlugin::create(QIODevice *device, const QByteArray &format) const { QImageIOHandler *handler = new SoftimagePICHandler(); handler->setDevice(device); handler->setFormat(format); return handler; } diff --git a/src/imageformats/psd.cpp b/src/imageformats/psd.cpp index 85f3aeb..e33271e 100644 --- a/src/imageformats/psd.cpp +++ b/src/imageformats/psd.cpp @@ -1,326 +1,341 @@ /* * Photoshop File Format support for QImage. * * Copyright 2003 Ignacio Castaño * Copyright 2015 Alex Merry * * This library 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 of the License, or (at your option) any later version. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* * This code is based on Thacher Ulrich PSD loading code released * into the public domain. See: http://tulrich.com/geekstuff/ */ /* * Documentation on this file format is available at * http://www.adobe.com/devnet-apps/photoshop/fileformatashtml/ */ #include "psd_p.h" #include "rle_p.h" #include #include #include typedef quint32 uint; typedef quint16 ushort; typedef quint8 uchar; namespace // Private. { enum ColorMode { CM_BITMAP = 0, CM_GRAYSCALE = 1, CM_INDEXED = 2, CM_RGB = 3, CM_CMYK = 4, CM_MULTICHANNEL = 7, CM_DUOTONE = 8, CM_LABCOLOR = 9 }; struct PSDHeader { uint signature; ushort version; uchar reserved[6]; ushort channel_count; uint height; uint width; ushort depth; ushort color_mode; }; static QDataStream &operator>> (QDataStream &s, PSDHeader &header) { s >> header.signature; s >> header.version; for (int i = 0; i < 6; i++) { s >> header.reserved[i]; } s >> header.channel_count; s >> header.height; s >> header.width; s >> header.depth; s >> header.color_mode; return s; } // Check that the header is a valid PSD. static bool IsValid(const PSDHeader &header) { if (header.signature != 0x38425053) { // '8BPS' return false; } return true; } // Check that the header is supported. static bool IsSupported(const PSDHeader &header) { if (header.version != 1) { return false; } if (header.channel_count > 16) { return false; } if (header.depth != 8) { return false; } if (header.color_mode != CM_RGB) { return false; } return true; } static void skip_section(QDataStream &s) { quint32 section_length; // Skip mode data. s >> section_length; s.skipRawData(section_length); } static quint8 readPixel(QDataStream &stream) { quint8 pixel; stream >> pixel; return pixel; } static QRgb updateRed(QRgb oldPixel, quint8 redPixel) { return qRgba(redPixel, qGreen(oldPixel), qBlue(oldPixel), qAlpha(oldPixel)); } static QRgb updateGreen(QRgb oldPixel, quint8 greenPixel) { return qRgba(qRed(oldPixel), greenPixel, qBlue(oldPixel), qAlpha(oldPixel)); } static QRgb updateBlue(QRgb oldPixel, quint8 bluePixel) { return qRgba(qRed(oldPixel), qGreen(oldPixel), bluePixel, qAlpha(oldPixel)); } static QRgb updateAlpha(QRgb oldPixel, quint8 alphaPixel) { return qRgba(qRed(oldPixel), qGreen(oldPixel), qBlue(oldPixel), alphaPixel); } typedef QRgb(*channelUpdater)(QRgb,quint8); // Load the PSD image. static bool LoadPSD(QDataStream &stream, const PSDHeader &header, QImage &img) { // Mode data skip_section(stream); // Image resources skip_section(stream); // Reserved data skip_section(stream); // Find out if the data is compressed. // Known values: // 0: no compression // 1: RLE compressed quint16 compression; stream >> compression; if (compression > 1) { qDebug() << "Unknown compression type"; return false; } quint32 channel_num = header.channel_count; QImage::Format fmt = QImage::Format_RGB32; // Clear the image. if (channel_num >= 4) { // Enable alpha. fmt = QImage::Format_ARGB32; // Ignore the other channels. channel_num = 4; } img = QImage(header.width, header.height, fmt); + if (img.isNull()) { + qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(header.width, header.height); + return false; + } img.fill(qRgb(0,0,0)); const quint32 pixel_count = header.height * header.width; + // Verify this, as this is used to write into the memory of the QImage + if (pixel_count > img.sizeInBytes() / sizeof(QRgb)) { + qWarning() << "Invalid pixel count!" << pixel_count << "bytes available:" << img.sizeInBytes(); + return false; + } + QRgb *image_data = reinterpret_cast(img.bits()); if (!image_data) { return false; } static const channelUpdater updaters[4] = { updateRed, updateGreen, updateBlue, updateAlpha }; if (compression) { // Skip row lengths. int skip_count = header.height * header.channel_count * sizeof(quint16); if (stream.skipRawData(skip_count) != skip_count) { return false; } for (unsigned short channel = 0; channel < channel_num; channel++) { bool success = decodeRLEData(RLEVariant::PackBits, stream, image_data, pixel_count, &readPixel, updaters[channel]); if (!success) { qDebug() << "decodeRLEData on channel" << channel << "failed"; return false; } } } else { for (unsigned short channel = 0; channel < channel_num; channel++) { for (unsigned i = 0; i < pixel_count; ++i) { image_data[i] = updaters[channel](image_data[i], readPixel(stream)); } // make sure we didn't try to read past the end of the stream if (stream.status() != QDataStream::Ok) { qDebug() << "DataStream status was" << stream.status(); return false; } } } return true; } } // Private PSDHandler::PSDHandler() { } bool PSDHandler::canRead() const { if (canRead(device())) { setFormat("psd"); return true; } return false; } bool PSDHandler::read(QImage *image) { QDataStream s(device()); s.setByteOrder(QDataStream::BigEndian); PSDHeader header; s >> header; // Check image file format. if (s.atEnd() || !IsValid(header)) { // qDebug() << "This PSD file is not valid."; return false; } // Check if it's a supported format. if (!IsSupported(header)) { // qDebug() << "This PSD file is not supported."; return false; } QImage img; if (!LoadPSD(s, header, img)) { // qDebug() << "Error loading PSD file."; return false; } *image = img; return true; } bool PSDHandler::canRead(QIODevice *device) { if (!device) { qWarning("PSDHandler::canRead() called with no device"); return false; } qint64 oldPos = device->pos(); char head[4]; qint64 readBytes = device->read(head, sizeof(head)); + if (readBytes < 0) { + qWarning() << "Read failed" << device->errorString(); + return false; + } + if (readBytes != sizeof(head)) { if (device->isSequential()) { while (readBytes > 0) { device->ungetChar(head[readBytes-- - 1]); } } else { device->seek(oldPos); } return false; } if (device->isSequential()) { while (readBytes > 0) { device->ungetChar(head[readBytes-- - 1]); } } else { device->seek(oldPos); } return qstrncmp(head, "8BPS", 4) == 0; } QImageIOPlugin::Capabilities PSDPlugin::capabilities(QIODevice *device, const QByteArray &format) const { if (format == "psd") { return Capabilities(CanRead); } if (!format.isEmpty()) { return {}; } if (!device->isOpen()) { return {}; } Capabilities cap; if (device->isReadable() && PSDHandler::canRead(device)) { cap |= CanRead; } return cap; } QImageIOHandler *PSDPlugin::create(QIODevice *device, const QByteArray &format) const { QImageIOHandler *handler = new PSDHandler; handler->setDevice(device); handler->setFormat(format); return handler; } diff --git a/src/imageformats/rgb.cpp b/src/imageformats/rgb.cpp index a288eef..77ac1bb 100644 --- a/src/imageformats/rgb.cpp +++ b/src/imageformats/rgb.cpp @@ -1,749 +1,778 @@ // kimgio module for SGI images // // Copyright (C) 2004 Melchior FRANZ // // This program is free software; you can redistribute it and/or // modify it under the terms of the Lesser GNU General Public License as // published by the Free Software Foundation; either version 2 of the // License, or (at your option) any later version. /* this code supports: * reading: * everything, except images with 1 dimension or images with * mapmode != NORMAL (e.g. dithered); Images with 16 bit * precision or more than 4 layers are stripped down. * writing: * Run Length Encoded (RLE) or Verbatim (uncompressed) * (whichever is smaller) * * Please report if you come across rgb/rgba/sgi/bw files that aren't * recognized. Also report applications that can't deal with images * saved by this filter. */ #include "rgb_p.h" #include #include #include #include class RLEData : public QVector { public: RLEData() {} RLEData(const uchar *d, uint l, uint o) : _offset(o) { for (uint i = 0; i < l; i++) { append(d[i]); } } bool operator<(const RLEData &) const; void write(QDataStream &s); uint offset() const { return _offset; } private: uint _offset; }; class RLEMap : public QMap { public: RLEMap() : _counter(0), _offset(0) {} uint insert(const uchar *d, uint l); QVector vector(); void setBaseOffset(uint o) { _offset = o; } private: uint _counter; uint _offset; }; class SGIImage { public: SGIImage(QIODevice *device); ~SGIImage(); bool readImage(QImage &); bool writeImage(const QImage &); private: enum { NORMAL, DITHERED, SCREEN, COLORMAP }; // colormap QIODevice *_dev; QDataStream _stream; quint8 _rle; quint8 _bpc; quint16 _dim; quint16 _xsize; quint16 _ysize; quint16 _zsize; quint32 _pixmin; quint32 _pixmax; char _imagename[80]; quint32 _colormap; quint32 *_starttab; quint32 *_lengthtab; QByteArray _data; QByteArray::Iterator _pos; RLEMap _rlemap; QVector _rlevector; uint _numrows; bool readData(QImage &); bool getRow(uchar *dest); void writeHeader(); void writeRle(); void writeVerbatim(const QImage &); bool scanData(const QImage &); uint compact(uchar *, uchar *); uchar intensity(uchar); }; SGIImage::SGIImage(QIODevice *io) : _starttab(nullptr), _lengthtab(nullptr) { _dev = io; _stream.setDevice(_dev); } SGIImage::~SGIImage() { delete[] _starttab; delete[] _lengthtab; } /////////////////////////////////////////////////////////////////////////////// bool SGIImage::getRow(uchar *dest) { int n, i; if (!_rle) { for (i = 0; i < _xsize; i++) { if (_pos >= _data.end()) { return false; } dest[i] = uchar(*_pos); _pos += _bpc; } return true; } for (i = 0; i < _xsize;) { if (_bpc == 2) { _pos++; } if (_pos >= _data.end()) { return false; } n = *_pos & 0x7f; if (!n) { break; } if (*_pos++ & 0x80) { for (; i < _xsize && _pos < _data.end() && n--; i++) { *dest++ = *_pos; _pos += _bpc; } } else { for (; i < _xsize && n--; i++) { *dest++ = *_pos; } _pos += _bpc; } } return i == _xsize; } bool SGIImage::readData(QImage &img) { QRgb *c; quint32 *start = _starttab; QByteArray lguard(_xsize, 0); uchar *line = (uchar *)lguard.data(); unsigned x, y; if (!_rle) { _pos = _data.begin(); } for (y = 0; y < _ysize; y++) { if (_rle) { _pos = _data.begin() + *start++; } if (!getRow(line)) { return false; } c = (QRgb *)img.scanLine(_ysize - y - 1); for (x = 0; x < _xsize; x++, c++) { *c = qRgb(line[x], line[x], line[x]); } } if (_zsize == 1) { return true; } if (_zsize != 2) { for (y = 0; y < _ysize; y++) { if (_rle) { _pos = _data.begin() + *start++; } if (!getRow(line)) { return false; } c = (QRgb *)img.scanLine(_ysize - y - 1); for (x = 0; x < _xsize; x++, c++) { *c = qRgb(qRed(*c), line[x], line[x]); } } for (y = 0; y < _ysize; y++) { if (_rle) { _pos = _data.begin() + *start++; } if (!getRow(line)) { return false; } c = (QRgb *)img.scanLine(_ysize - y - 1); for (x = 0; x < _xsize; x++, c++) { *c = qRgb(qRed(*c), qGreen(*c), line[x]); } } if (_zsize == 3) { return true; } } for (y = 0; y < _ysize; y++) { if (_rle) { _pos = _data.begin() + *start++; } if (!getRow(line)) { return false; } c = (QRgb *)img.scanLine(_ysize - y - 1); for (x = 0; x < _xsize; x++, c++) { *c = qRgba(qRed(*c), qGreen(*c), qBlue(*c), line[x]); } } return true; } bool SGIImage::readImage(QImage &img) { qint8 u8; qint16 u16; qint32 u32; // qDebug() << "reading rgb "; // magic _stream >> u16; if (u16 != 0x01da) { return false; } // verbatim/rle _stream >> _rle; // qDebug() << (_rle ? "RLE" : "verbatim"); if (_rle > 1) { return false; } // bytes per channel _stream >> _bpc; // qDebug() << "bytes per channel: " << int(_bpc); if (_bpc == 1) ; else if (_bpc == 2) { // qDebug() << "dropping least significant byte"; } else { return false; } // number of dimensions _stream >> _dim; // qDebug() << "dimensions: " << _dim; if (_dim < 1 || _dim > 3) { return false; } _stream >> _xsize >> _ysize >> _zsize >> _pixmin >> _pixmax >> u32; // qDebug() << "x: " << _xsize; // qDebug() << "y: " << _ysize; // qDebug() << "z: " << _zsize; // name _stream.readRawData(_imagename, 80); _imagename[79] = '\0'; _stream >> _colormap; // qDebug() << "colormap: " << _colormap; if (_colormap != NORMAL) { return false; // only NORMAL supported } for (int i = 0; i < 404; i++) { _stream >> u8; } if (_dim == 1) { // qDebug() << "1-dimensional images aren't supported yet"; return false; } if (_stream.atEnd()) { return false; } img = QImage(_xsize, _ysize, QImage::Format_RGB32); + if (img.isNull()) { + qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(_xsize, _ysize); + return false; + } if (_zsize == 0 ) return false; if (_zsize == 2 || _zsize == 4) { img = img.convertToFormat(QImage::Format_ARGB32); } else if (_zsize > 4) { // qDebug() << "using first 4 of " << _zsize << " channels"; // Only let this continue if it won't cause a int overflow later // this is most likely a broken file anyway if (_ysize > std::numeric_limits::max() / _zsize) return false; } _numrows = _ysize * _zsize; if (_rle) { uint l; _starttab = new quint32[_numrows]; for (l = 0; !_stream.atEnd() && l < _numrows; l++) { _stream >> _starttab[l]; _starttab[l] -= 512 + _numrows * 2 * sizeof(quint32); } for (; l < _numrows; l++) { _starttab[l] = 0; } _lengthtab = new quint32[_numrows]; for (l = 0; l < _numrows; l++) { _stream >> _lengthtab[l]; } } _data = _dev->readAll(); // sanity check if (_rle) for (uint o = 0; o < _numrows; o++) // don't change to greater-or-equal! if (_starttab[o] + _lengthtab[o] > (uint)_data.size()) { // qDebug() << "image corrupt (sanity check failed)"; return false; } if (!readData(img)) { // qDebug() << "image corrupt (incomplete scanline)"; return false; } return true; } /////////////////////////////////////////////////////////////////////////////// void RLEData::write(QDataStream &s) { for (int i = 0; i < size(); i++) { s << at(i); } } bool RLEData::operator<(const RLEData &b) const { uchar ac, bc; for (int i = 0; i < qMin(size(), b.size()); i++) { ac = at(i); bc = b[i]; if (ac != bc) { return ac < bc; } } return size() < b.size(); } uint RLEMap::insert(const uchar *d, uint l) { RLEData data = RLEData(d, l, _offset); Iterator it = find(data); if (it != end()) { return it.value(); } _offset += l; return QMap::insert(data, _counter++).value(); } QVector RLEMap::vector() { QVector v(size()); for (Iterator it = begin(); it != end(); ++it) { v.replace(it.value(), &it.key()); } return v; } uchar SGIImage::intensity(uchar c) { if (c < _pixmin) { _pixmin = c; } if (c > _pixmax) { _pixmax = c; } return c; } uint SGIImage::compact(uchar *d, uchar *s) { uchar *dest = d, *src = s, patt, *t, *end = s + _xsize; int i, n; while (src < end) { for (n = 0, t = src; t + 2 < end && !(*t == t[1] && *t == t[2]); t++) { n++; } while (n) { i = n > 126 ? 126 : n; n -= i; *dest++ = 0x80 | i; while (i--) { *dest++ = *src++; } } if (src == end) { break; } patt = *src++; for (n = 1; src < end && *src == patt; src++) { n++; } while (n) { i = n > 126 ? 126 : n; n -= i; *dest++ = i; *dest++ = patt; } } *dest++ = 0; return dest - d; } bool SGIImage::scanData(const QImage &img) { quint32 *start = _starttab; QByteArray lineguard(_xsize * 2, 0); QByteArray bufguard(_xsize, 0); uchar *line = (uchar *)lineguard.data(); uchar *buf = (uchar *)bufguard.data(); const QRgb *c; unsigned x, y; uint len; for (y = 0; y < _ysize; y++) { - c = reinterpret_cast(img.scanLine(_ysize - y - 1)); + const int yPos = _ysize - y - 1; // scanline doesn't do any sanity checking + if (yPos >= img.height()) { + qWarning() << "Failed to get scanline for" << yPos; + return false; + } + + c = reinterpret_cast(img.scanLine(yPos)); + for (x = 0; x < _xsize; x++) { buf[x] = intensity(qRed(*c++)); } len = compact(line, buf); *start++ = _rlemap.insert(line, len); } if (_zsize == 1) { return true; } if (_zsize != 2) { for (y = 0; y < _ysize; y++) { - c = reinterpret_cast(img.scanLine(_ysize - y - 1)); + const int yPos = _ysize - y - 1; + if (yPos >= img.height()) { + qWarning() << "Failed to get scanline for" << yPos; + return false; + } + + c = reinterpret_cast(img.scanLine(yPos)); for (x = 0; x < _xsize; x++) { buf[x] = intensity(qGreen(*c++)); } len = compact(line, buf); *start++ = _rlemap.insert(line, len); } for (y = 0; y < _ysize; y++) { - c = reinterpret_cast(img.scanLine(_ysize - y - 1)); + const int yPos = _ysize - y - 1; + if (yPos >= img.height()) { + qWarning() << "Failed to get scanline for" << yPos; + return false; + } + + c = reinterpret_cast(img.scanLine(yPos)); for (x = 0; x < _xsize; x++) { buf[x] = intensity(qBlue(*c++)); } len = compact(line, buf); *start++ = _rlemap.insert(line, len); } if (_zsize == 3) { return true; } } for (y = 0; y < _ysize; y++) { - c = reinterpret_cast(img.scanLine(_ysize - y - 1)); + const int yPos = _ysize - y - 1; + if (yPos >= img.height()) { + qWarning() << "Failed to get scanline for" << yPos; + return false; + } + + c = reinterpret_cast(img.scanLine(yPos)); for (x = 0; x < _xsize; x++) { buf[x] = intensity(qAlpha(*c++)); } len = compact(line, buf); *start++ = _rlemap.insert(line, len); } return true; } void SGIImage::writeHeader() { _stream << quint16(0x01da); _stream << _rle << _bpc << _dim; _stream << _xsize << _ysize << _zsize; _stream << _pixmin << _pixmax; _stream << quint32(0); for (int i = 0; i < 80; i++) { _imagename[i] = '\0'; } _stream.writeRawData(_imagename, 80); _stream << _colormap; for (int i = 0; i < 404; i++) { _stream << quint8(0); } } void SGIImage::writeRle() { _rle = 1; // qDebug() << "writing RLE data"; writeHeader(); uint i; // write start table for (i = 0; i < _numrows; i++) { _stream << quint32(_rlevector[_starttab[i]]->offset()); } // write length table for (i = 0; i < _numrows; i++) { _stream << quint32(_rlevector[_starttab[i]]->size()); } // write data for (i = 0; (int)i < _rlevector.size(); i++) { const_cast(_rlevector[i])->write(_stream); } } void SGIImage::writeVerbatim(const QImage &img) { _rle = 0; // qDebug() << "writing verbatim data"; writeHeader(); const QRgb *c; unsigned x, y; for (y = 0; y < _ysize; y++) { c = reinterpret_cast(img.scanLine(_ysize - y - 1)); for (x = 0; x < _xsize; x++) { _stream << quint8(qRed(*c++)); } } if (_zsize == 1) { return; } if (_zsize != 2) { for (y = 0; y < _ysize; y++) { c = reinterpret_cast(img.scanLine(_ysize - y - 1)); for (x = 0; x < _xsize; x++) { _stream << quint8(qGreen(*c++)); } } for (y = 0; y < _ysize; y++) { c = reinterpret_cast(img.scanLine(_ysize - y - 1)); for (x = 0; x < _xsize; x++) { _stream << quint8(qBlue(*c++)); } } if (_zsize == 3) { return; } } for (y = 0; y < _ysize; y++) { c = reinterpret_cast(img.scanLine(_ysize - y - 1)); for (x = 0; x < _xsize; x++) { _stream << quint8(qAlpha(*c++)); } } } bool SGIImage::writeImage(const QImage &image) { // qDebug() << "writing "; // TODO add filename QImage img = image; if (img.allGray()) { _dim = 2, _zsize = 1; } else { _dim = 3, _zsize = 3; } if (img.format() == QImage::Format_ARGB32) { _dim = 3, _zsize++; } img = img.convertToFormat(QImage::Format_RGB32); if (img.isNull()) { // qDebug() << "can't convert image to depth 32"; return false; } _bpc = 1; _xsize = img.width(); _ysize = img.height(); _pixmin = ~0u; _pixmax = 0; _colormap = NORMAL; _numrows = _ysize * _zsize; _starttab = new quint32[_numrows]; _rlemap.setBaseOffset(512 + _numrows * 2 * sizeof(quint32)); if (!scanData(img)) { // qDebug() << "this can't happen"; return false; } _rlevector = _rlemap.vector(); long verbatim_size = _numrows * _xsize; long rle_size = _numrows * 2 * sizeof(quint32); for (int i = 0; i < _rlevector.size(); i++) { rle_size += _rlevector[i]->size(); } // qDebug() << "minimum intensity: " << _pixmin; // qDebug() << "maximum intensity: " << _pixmax; // qDebug() << "saved scanlines: " << _numrows - _rlemap.size(); // qDebug() << "total savings: " << (verbatim_size - rle_size) << " bytes"; // qDebug() << "compression: " << (rle_size * 100.0 / verbatim_size) << '%'; if (verbatim_size <= rle_size) { writeVerbatim(img); } else { writeRle(); } return true; } /////////////////////////////////////////////////////////////////////////////// RGBHandler::RGBHandler() { } bool RGBHandler::canRead() const { if (canRead(device())) { setFormat("rgb"); return true; } return false; } bool RGBHandler::read(QImage *outImage) { SGIImage sgi(device()); return sgi.readImage(*outImage); } bool RGBHandler::write(const QImage &image) { SGIImage sgi(device()); return sgi.writeImage(image); } bool RGBHandler::canRead(QIODevice *device) { if (!device) { qWarning("RGBHandler::canRead() called with no device"); return false; } const qint64 oldPos = device->pos(); const QByteArray head = device->readLine(64); int readBytes = head.size(); if (device->isSequential()) { while (readBytes > 0) { device->ungetChar(head[readBytes-- - 1]); } } else { device->seek(oldPos); } return head.size() >= 4 && head.startsWith("\x01\xda") && (head[2] == 0 || head[2] == 1) && (head[3] == 1 || head[3] == 2); } /////////////////////////////////////////////////////////////////////////////// QImageIOPlugin::Capabilities RGBPlugin::capabilities(QIODevice *device, const QByteArray &format) const { if (format == "rgb" || format == "rgba" || format == "bw" || format == "sgi") { return Capabilities(CanRead | CanWrite); } if (!format.isEmpty()) { return {}; } if (!device->isOpen()) { return {}; } Capabilities cap; if (device->isReadable() && RGBHandler::canRead(device)) { cap |= CanRead; } if (device->isWritable()) { cap |= CanWrite; } return cap; } QImageIOHandler *RGBPlugin::create(QIODevice *device, const QByteArray &format) const { QImageIOHandler *handler = new RGBHandler; handler->setDevice(device); handler->setFormat(format); return handler; } diff --git a/src/imageformats/tga.cpp b/src/imageformats/tga.cpp index 6b0b600..bbd380e 100644 --- a/src/imageformats/tga.cpp +++ b/src/imageformats/tga.cpp @@ -1,493 +1,517 @@ /* This file is part of the KDE project Copyright (C) 2003 Dominik Seichter Copyright (C) 2004 Ignacio Castaño This program is free software; you can redistribute it and/or modify it under the terms of the Lesser GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. */ /* this code supports: * reading: * uncompressed and run length encoded indexed, grey and color tga files. * image types 1, 2, 3, 9, 10 and 11. * only RGB color maps with no more than 256 colors. * pixel formats 8, 16, 24 and 32. * writing: * uncompressed true color tga files */ #include "tga_p.h" #include #include #include #include typedef quint32 uint; typedef quint16 ushort; typedef quint8 uchar; namespace // Private. { // Header format of saved files. uchar targaMagic[12] = { 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; enum TGAType { TGA_TYPE_INDEXED = 1, TGA_TYPE_RGB = 2, TGA_TYPE_GREY = 3, TGA_TYPE_RLE_INDEXED = 9, TGA_TYPE_RLE_RGB = 10, TGA_TYPE_RLE_GREY = 11 }; #define TGA_INTERLEAVE_MASK 0xc0 #define TGA_INTERLEAVE_NONE 0x00 #define TGA_INTERLEAVE_2WAY 0x40 #define TGA_INTERLEAVE_4WAY 0x80 #define TGA_ORIGIN_MASK 0x30 #define TGA_ORIGIN_LEFT 0x00 #define TGA_ORIGIN_RIGHT 0x10 #define TGA_ORIGIN_LOWER 0x00 #define TGA_ORIGIN_UPPER 0x20 /** Tga Header. */ struct TgaHeader { uchar id_length; uchar colormap_type; uchar image_type; ushort colormap_index; ushort colormap_length; uchar colormap_size; ushort x_origin; ushort y_origin; ushort width; ushort height; uchar pixel_size; uchar flags; enum { SIZE = 18 }; // const static int SIZE = 18; }; static QDataStream &operator>> (QDataStream &s, TgaHeader &head) { s >> head.id_length; s >> head.colormap_type; s >> head.image_type; s >> head.colormap_index; s >> head.colormap_length; s >> head.colormap_size; s >> head.x_origin; s >> head.y_origin; s >> head.width; s >> head.height; s >> head.pixel_size; s >> head.flags; /*qDebug() << "id_length: " << head.id_length << " - colormap_type: " << head.colormap_type << " - image_type: " << head.image_type; qDebug() << "colormap_index: " << head.colormap_index << " - colormap_length: " << head.colormap_length << " - colormap_size: " << head.colormap_size; qDebug() << "x_origin: " << head.x_origin << " - y_origin: " << head.y_origin << " - width:" << head.width << " - height:" << head.height << " - pixelsize: " << head.pixel_size << " - flags: " << head.flags;*/ return s; } static bool IsSupported(const TgaHeader &head) { if (head.image_type != TGA_TYPE_INDEXED && head.image_type != TGA_TYPE_RGB && head.image_type != TGA_TYPE_GREY && head.image_type != TGA_TYPE_RLE_INDEXED && head.image_type != TGA_TYPE_RLE_RGB && head.image_type != TGA_TYPE_RLE_GREY) { return false; } if (head.image_type == TGA_TYPE_INDEXED || head.image_type == TGA_TYPE_RLE_INDEXED) { if (head.colormap_length > 256 || head.colormap_size != 24 || head.colormap_type != 1) { return false; } } if (head.image_type == TGA_TYPE_RGB || head.image_type == TGA_TYPE_GREY || head.image_type == TGA_TYPE_RLE_RGB || head.image_type == TGA_TYPE_RLE_GREY) { if (head.colormap_type != 0) { return false; } } if (head.width == 0 || head.height == 0) { return false; } if (head.pixel_size != 8 && head.pixel_size != 16 && head.pixel_size != 24 && head.pixel_size != 32) { return false; } return true; } struct Color555 { ushort b : 5; ushort g : 5; ushort r : 5; }; struct TgaHeaderInfo { bool rle; bool pal; bool rgb; bool grey; TgaHeaderInfo(const TgaHeader &tga) : rle(false), pal(false), rgb(false), grey(false) { switch (tga.image_type) { case TGA_TYPE_RLE_INDEXED: rle = true; Q_FALLTHROUGH(); // no break is intended! case TGA_TYPE_INDEXED: pal = true; break; case TGA_TYPE_RLE_RGB: rle = true; Q_FALLTHROUGH(); // no break is intended! case TGA_TYPE_RGB: rgb = true; break; case TGA_TYPE_RLE_GREY: rle = true; Q_FALLTHROUGH(); // no break is intended! case TGA_TYPE_GREY: grey = true; break; default: // Error, unknown image type. break; } } }; static bool LoadTGA(QDataStream &s, const TgaHeader &tga, QImage &img) { // Create image. img = QImage(tga.width, tga.height, QImage::Format_RGB32); + if (img.isNull()) { + qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height); + return false; + } TgaHeaderInfo info(tga); // Bits 0-3 are the numbers of alpha bits (can be zero!) const int numAlphaBits = tga.flags & 0xf; // However alpha exists only in the 32 bit format. if ((tga.pixel_size == 32) && (tga.flags & 0xf)) { img = QImage(tga.width, tga.height, QImage::Format_ARGB32); + if (img.isNull()) { + qWarning() << "Failed to allocate image, invalid dimensions?" << QSize(tga.width, tga.height); + return false; + } if (numAlphaBits > 8) { return false; } } uint pixel_size = (tga.pixel_size / 8); qint64 size = qint64(tga.width) * qint64(tga.height) * pixel_size; if (size < 1) { // qDebug() << "This TGA file is broken with size " << size; return false; } // Read palette. static const int max_palette_size = 768; char palette[max_palette_size]; if (info.pal) { // @todo Support palettes in other formats! const int palette_size = 3 * tga.colormap_length; if (palette_size > max_palette_size) { return false; } const int dataRead = s.readRawData(palette, palette_size); if (dataRead < 0) { return false; } if (dataRead < max_palette_size) { memset(&palette[dataRead], 0, max_palette_size - dataRead); } } // Allocate image. uchar *const image = reinterpret_cast(malloc(size)); if (!image) { return false; } bool valid = true; if (info.rle) { // Decode image. char *dst = (char *)image; + char *imgEnd = dst + size; qint64 num = size; - while (num > 0) { + while (num > 0 && valid) { if (s.atEnd()) { valid = false; break; } // Get packet header. uchar c; s >> c; uint count = (c & 0x7f) + 1; num -= count * pixel_size; if (num < 0) { valid = false; break; } if (c & 0x80) { // RLE pixels. assert(pixel_size <= 8); char pixel[8]; const int dataRead = s.readRawData(pixel, pixel_size); if (dataRead < (int)pixel_size) { memset(&pixel[dataRead], 0, pixel_size - dataRead); } do { + if (dst + pixel_size > imgEnd) { + qWarning() << "Trying to write out of bounds!" << ptrdiff_t(dst) << (ptrdiff_t(imgEnd) - ptrdiff_t(pixel_size)); + valid = false; + break; + } + memcpy(dst, pixel, pixel_size); dst += pixel_size; } while (--count); } else { // Raw pixels. count *= pixel_size; const int dataRead = s.readRawData(dst, count); if (dataRead < 0) { free(image); return false; } + + if ((uint)dataRead < count) { - memset(&dst[dataRead], 0, count - dataRead); + const size_t toCopy = count - dataRead; + if (&dst[dataRead] + toCopy > imgEnd) { + qWarning() << "Trying to write out of bounds!" << ptrdiff_t(image) << ptrdiff_t(&dst[dataRead]);; + valid = false; + break; + } + + memset(&dst[dataRead], 0, toCopy); } dst += count; } } } else { // Read raw image. const int dataRead = s.readRawData((char *)image, size); if (dataRead < 0) { free(image); return false; } if (dataRead < size) { memset(&image[dataRead], 0, size - dataRead); } } if (!valid) { free(image); return false; } // Convert image to internal format. int y_start, y_step, y_end; if (tga.flags & TGA_ORIGIN_UPPER) { y_start = 0; y_step = 1; y_end = tga.height; } else { y_start = tga.height - 1; y_step = -1; y_end = -1; } uchar *src = image; for (int y = y_start; y != y_end; y += y_step) { QRgb *scanline = (QRgb *) img.scanLine(y); if (info.pal) { // Paletted. for (int x = 0; x < tga.width; x++) { uchar idx = *src++; scanline[x] = qRgb(palette[3 * idx + 2], palette[3 * idx + 1], palette[3 * idx + 0]); } } else if (info.grey) { // Greyscale. for (int x = 0; x < tga.width; x++) { scanline[x] = qRgb(*src, *src, *src); src++; } } else { // True Color. if (tga.pixel_size == 16) { for (int x = 0; x < tga.width; x++) { Color555 c = *reinterpret_cast(src); scanline[x] = qRgb((c.r << 3) | (c.r >> 2), (c.g << 3) | (c.g >> 2), (c.b << 3) | (c.b >> 2)); src += 2; } } else if (tga.pixel_size == 24) { for (int x = 0; x < tga.width; x++) { scanline[x] = qRgb(src[2], src[1], src[0]); src += 3; } } else if (tga.pixel_size == 32) { for (int x = 0; x < tga.width; x++) { // ### TODO: verify with images having really some alpha data const uchar alpha = (src[3] << (8 - numAlphaBits)); scanline[x] = qRgba(src[2], src[1], src[0], alpha); src += 4; } } } } // Free image. free(image); return true; } } // namespace TGAHandler::TGAHandler() { } bool TGAHandler::canRead() const { if (canRead(device())) { setFormat("tga"); return true; } return false; } bool TGAHandler::read(QImage *outImage) { //qDebug() << "Loading TGA file!"; QDataStream s(device()); s.setByteOrder(QDataStream::LittleEndian); // Read image header. TgaHeader tga; s >> tga; s.device()->seek(TgaHeader::SIZE + tga.id_length); // Check image file format. if (s.atEnd()) { // qDebug() << "This TGA file is not valid."; return false; } // Check supported file types. if (!IsSupported(tga)) { // qDebug() << "This TGA file is not supported."; return false; } QImage img; bool result = LoadTGA(s, tga, img); if (result == false) { // qDebug() << "Error loading TGA file."; return false; } *outImage = img; return true; } bool TGAHandler::write(const QImage &image) { QDataStream s(device()); s.setByteOrder(QDataStream::LittleEndian); const QImage &img = image; const bool hasAlpha = (img.format() == QImage::Format_ARGB32); for (int i = 0; i < 12; i++) { s << targaMagic[i]; } // write header s << quint16(img.width()); // width s << quint16(img.height()); // height s << quint8(hasAlpha ? 32 : 24); // depth (24 bit RGB + 8 bit alpha) s << quint8(hasAlpha ? 0x24 : 0x20); // top left image (0x20) + 8 bit alpha (0x4) for (int y = 0; y < img.height(); y++) for (int x = 0; x < img.width(); x++) { const QRgb color = img.pixel(x, y); s << quint8(qBlue(color)); s << quint8(qGreen(color)); s << quint8(qRed(color)); if (hasAlpha) { s << quint8(qAlpha(color)); } } return true; } bool TGAHandler::canRead(QIODevice *device) { if (!device) { qWarning("TGAHandler::canRead() called with no device"); return false; } qint64 oldPos = device->pos(); QByteArray head = device->read(TgaHeader::SIZE); int readBytes = head.size(); if (device->isSequential()) { for (int pos = readBytes - 1; pos >= 0; --pos) { device->ungetChar(head[pos]); } } else { device->seek(oldPos); } if (readBytes < TgaHeader::SIZE) { return false; } QDataStream stream(head); stream.setByteOrder(QDataStream::LittleEndian); TgaHeader tga; stream >> tga; return IsSupported(tga); } QImageIOPlugin::Capabilities TGAPlugin::capabilities(QIODevice *device, const QByteArray &format) const { if (format == "tga") { return Capabilities(CanRead | CanWrite); } if (!format.isEmpty()) { return {}; } if (!device->isOpen()) { return {}; } Capabilities cap; if (device->isReadable() && TGAHandler::canRead(device)) { cap |= CanRead; } if (device->isWritable()) { cap |= CanWrite; } return cap; } QImageIOHandler *TGAPlugin::create(QIODevice *device, const QByteArray &format) const { QImageIOHandler *handler = new TGAHandler; handler->setDevice(device); handler->setFormat(format); return handler; }