QJpegHandler: add an embedded text support
texts for the textKeys are stored each in a separate COM(ment) section so that the maximum text's size is almost 65KB Task-number: QTBUG-10568 Task-number: QTBUG-111 Change-Id: I7d693741e10e5d78d497cb0af448160077350bb2 Reviewed-by: aavit <qt_aavit@ovi.com>bb10
parent
2380765a2b
commit
3cfcceae43
|
|
@ -528,7 +528,40 @@ inline my_jpeg_destination_mgr::my_jpeg_destination_mgr(QIODevice *device)
|
|||
}
|
||||
|
||||
|
||||
static bool write_jpeg_image(const QImage &image, QIODevice *device, int sourceQuality)
|
||||
static inline void set_text(const QImage &image, j_compress_ptr cinfo, const QString &description)
|
||||
{
|
||||
QMap<QString, QString> text;
|
||||
foreach (const QString &key, image.textKeys()) {
|
||||
if (!key.isEmpty())
|
||||
text.insert(key, image.text(key));
|
||||
}
|
||||
foreach (const QString &pair, description.split(QLatin1String("\n\n"))) {
|
||||
int index = pair.indexOf(QLatin1Char(':'));
|
||||
if (index >= 0 && pair.indexOf(QLatin1Char(' ')) < index) {
|
||||
QString s = pair.simplified();
|
||||
if (!s.isEmpty())
|
||||
text.insert(QLatin1String("Description"), s);
|
||||
} else {
|
||||
QString key = pair.left(index);
|
||||
if (!key.simplified().isEmpty())
|
||||
text.insert(key, pair.mid(index + 2).simplified());
|
||||
}
|
||||
}
|
||||
if (text.isEmpty())
|
||||
return;
|
||||
|
||||
for (QMap<QString, QString>::ConstIterator it = text.constBegin(); it != text.constEnd(); ++it) {
|
||||
QByteArray comment = it.key().toLatin1();
|
||||
if (!comment.isEmpty())
|
||||
comment += ": ";
|
||||
comment += it.value().toLatin1();
|
||||
if (comment.length() > 65530)
|
||||
comment.truncate(65530);
|
||||
jpeg_write_marker(cinfo, JPEG_COM, (JOCTET *)comment.constData(), comment.size());
|
||||
}
|
||||
}
|
||||
|
||||
static bool write_jpeg_image(const QImage &image, QIODevice *device, int sourceQuality, const QString &description)
|
||||
{
|
||||
bool success = false;
|
||||
const QVector<QRgb> cmap = image.colorTable();
|
||||
|
|
@ -600,6 +633,8 @@ static bool write_jpeg_image(const QImage &image, QIODevice *device, int sourceQ
|
|||
jpeg_start_compress(&cinfo, true);
|
||||
#endif
|
||||
|
||||
set_text(image, &cinfo, description);
|
||||
|
||||
row_pointer[0] = new uchar[cinfo.image_width*cinfo.input_components];
|
||||
int w = cinfo.image_width;
|
||||
while (cinfo.next_scanline < cinfo.image_height) {
|
||||
|
|
@ -734,6 +769,9 @@ public:
|
|||
QSize scaledSize;
|
||||
QRect scaledClipRect;
|
||||
QRect clipRect;
|
||||
QString description;
|
||||
QStringList readTexts;
|
||||
|
||||
struct jpeg_decompress_struct info;
|
||||
struct my_jpeg_source_mgr * iod_src;
|
||||
struct my_error_mgr err;
|
||||
|
|
@ -760,6 +798,8 @@ bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device)
|
|||
err.output_message = my_output_message;
|
||||
|
||||
if (!setjmp(err.setjmp_buffer)) {
|
||||
jpeg_save_markers(&info, JPEG_COM, 0xFFFF);
|
||||
|
||||
#if defined(Q_OS_UNIXWARE)
|
||||
(void) jpeg_read_header(&info, B_TRUE);
|
||||
#else
|
||||
|
|
@ -773,6 +813,27 @@ bool QJpegHandlerPrivate::readJpegHeader(QIODevice *device)
|
|||
|
||||
format = QImage::Format_Invalid;
|
||||
read_jpeg_format(format, &info);
|
||||
|
||||
for (jpeg_saved_marker_ptr marker = info.marker_list; marker != NULL; marker = marker->next) {
|
||||
if (marker->marker == JPEG_COM) {
|
||||
QString key, value;
|
||||
QString s = QString::fromLatin1((const char *)marker->data, marker->data_length);
|
||||
int index = s.indexOf(QLatin1String(": "));
|
||||
if (index == -1 || s.indexOf(QLatin1Char(' ')) < index) {
|
||||
key = QLatin1String("Description");
|
||||
value = s;
|
||||
} else {
|
||||
key = s.left(index);
|
||||
value = s.mid(index + 2);
|
||||
}
|
||||
if (!description.isEmpty())
|
||||
description += QLatin1String("\n\n");
|
||||
description += key + QLatin1String(": ") + value.simplified();
|
||||
readTexts.append(key);
|
||||
readTexts.append(value);
|
||||
}
|
||||
}
|
||||
|
||||
state = ReadHeader;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -793,9 +854,16 @@ bool QJpegHandlerPrivate::read(QImage *image)
|
|||
|
||||
if(state == ReadHeader)
|
||||
{
|
||||
bool success = read_jpeg_image(image, scaledSize, scaledClipRect, clipRect, quality, &info, &err);
|
||||
state = success ? Ready : Error;
|
||||
return success;
|
||||
bool success = read_jpeg_image(image, scaledSize, scaledClipRect, clipRect, quality, &info, &err);
|
||||
if (success) {
|
||||
for (int i = 0; i < readTexts.size()-1; i+=2)
|
||||
image->setText(readTexts.at(i), readTexts.at(i+1));
|
||||
|
||||
state = Ready;
|
||||
return true;
|
||||
}
|
||||
|
||||
state = Error;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -863,7 +931,7 @@ bool QJpegHandler::read(QImage *image)
|
|||
|
||||
bool QJpegHandler::write(const QImage &image)
|
||||
{
|
||||
return write_jpeg_image(image, device(), d->quality);
|
||||
return write_jpeg_image(image, device(), d->quality, d->description);
|
||||
}
|
||||
|
||||
bool QJpegHandler::supportsOption(ImageOption option) const
|
||||
|
|
@ -872,6 +940,7 @@ bool QJpegHandler::supportsOption(ImageOption option) const
|
|||
|| option == ScaledSize
|
||||
|| option == ScaledClipRect
|
||||
|| option == ClipRect
|
||||
|| option == Description
|
||||
|| option == Size
|
||||
|| option == ImageFormat;
|
||||
}
|
||||
|
|
@ -887,6 +956,9 @@ QVariant QJpegHandler::option(ImageOption option) const
|
|||
return d->scaledClipRect;
|
||||
case ClipRect:
|
||||
return d->clipRect;
|
||||
case Description:
|
||||
d->readJpegHeader(device());
|
||||
return d->description;
|
||||
case Size:
|
||||
d->readJpegHeader(device());
|
||||
return d->size;
|
||||
|
|
@ -894,8 +966,10 @@ QVariant QJpegHandler::option(ImageOption option) const
|
|||
d->readJpegHeader(device());
|
||||
return d->format;
|
||||
default:
|
||||
return QVariant();
|
||||
break;
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void QJpegHandler::setOption(ImageOption option, const QVariant &value)
|
||||
|
|
@ -913,6 +987,9 @@ void QJpegHandler::setOption(ImageOption option, const QVariant &value)
|
|||
case ClipRect:
|
||||
d->clipRect = value.toRect();
|
||||
break;
|
||||
case Description:
|
||||
d->description = value.toString();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -923,7 +1000,4 @@ QByteArray QJpegHandler::name() const
|
|||
return "jpeg";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
QT_END_NAMESPACE
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 2.3 KiB |
|
|
@ -62,6 +62,7 @@
|
|||
<file>images/corrupt.svg</file>
|
||||
<file>images/corrupt.svgz</file>
|
||||
<file>images/qtbug13653-no_eoi.jpg</file>
|
||||
<file>images/txts.jpg</file>
|
||||
<file>images/txts.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
|
|||
|
|
@ -75,8 +75,8 @@ public:
|
|||
virtual ~tst_QImageReader();
|
||||
|
||||
public slots:
|
||||
void init();
|
||||
void cleanup();
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
|
||||
private slots:
|
||||
void getSetCheck();
|
||||
|
|
@ -201,13 +201,15 @@ tst_QImageReader::~tst_QImageReader()
|
|||
|
||||
}
|
||||
|
||||
void tst_QImageReader::init()
|
||||
void tst_QImageReader::initTestCase()
|
||||
{
|
||||
prefix = QFINDTESTDATA("images/");
|
||||
if (prefix.isEmpty())
|
||||
QFAIL("Can't find images directory!");
|
||||
QVERIFY(m_temporaryDir.isValid());
|
||||
}
|
||||
|
||||
void tst_QImageReader::cleanup()
|
||||
void tst_QImageReader::cleanupTestCase()
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -1772,6 +1774,11 @@ void tst_QImageReader::readText_data()
|
|||
QTest::newRow("png, zTXt before img") << "txts.png" << "Comment" << "Some compressed text.";
|
||||
QTest::newRow("png, tEXt after img") << "txts.png" << "Disclaimer" << "For testing only.";
|
||||
QTest::newRow("png, zTXt after img") << "txts.png" << "Description" << "Rendered by Persistence of Vision (tm) Ray Tracer";
|
||||
|
||||
QTest::newRow("jpg, JPEG_COM Title") << "txts.jpg" << "Title" << "JPG";
|
||||
QTest::newRow("jpg, JPEG_COM Comment") << "txts.jpg" << "Comment" << "Some non-compressed text.";
|
||||
QTest::newRow("jpg, JPEG_COM Disclaimer") << "txts.jpg" << "Disclaimer" << "For testing only.";
|
||||
QTest::newRow("jpg, JPEG_COM Description") << "txts.jpg" << "Description" << "Rendered by Persistence of Vision (tm) Ray Tracer";
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1789,30 +1796,37 @@ void tst_QImageReader::readText()
|
|||
|
||||
void tst_QImageReader::preserveTexts_data()
|
||||
{
|
||||
QTest::addColumn<QString>("fileName");
|
||||
QTest::addColumn<QString>("text");
|
||||
|
||||
QTest::newRow("Simple") << "simpletext";
|
||||
QTest::newRow("Whitespace") << " A text with whitespace ";
|
||||
QTest::newRow("Newline") << "A text\nwith newlines\n";
|
||||
QTest::newRow("Double newlines") << "A text\n\nwith double newlines\n\n";
|
||||
QTest::newRow("Long") << QString("A rather long text, at least after many repetitions. ").repeated(100);
|
||||
QString latin1set;
|
||||
int c;
|
||||
for(c = 0x20; c <= 0x7e; c++)
|
||||
for (int c = 0x20; c <= 0x7e; c++)
|
||||
latin1set.append(QLatin1Char(c));
|
||||
for(c = 0xa0; c <= 0xff; c++)
|
||||
for (int c = 0xa0; c <= 0xff; c++)
|
||||
latin1set.append(QLatin1Char(c));
|
||||
QTest::newRow("All Latin1 chars") << latin1set;
|
||||
|
||||
QStringList fileNames;
|
||||
fileNames << QLatin1String(":/images/kollada.png")
|
||||
<< QLatin1String(":/images/txts.jpg");
|
||||
foreach (const QString &fileName, fileNames) {
|
||||
QTest::newRow("Simple") << fileName << "simpletext";
|
||||
QTest::newRow("Whitespace") << fileName << " A text with whitespace ";
|
||||
QTest::newRow("Newline") << fileName << "A text\nwith newlines\n";
|
||||
QTest::newRow("Double newlines") << fileName << "A text\n\nwith double newlines\n\n";
|
||||
QTest::newRow("Long") << fileName << QString("A rather long text, at least after many repetitions. ").repeated(100);
|
||||
QTest::newRow("All Latin1 chars") << fileName << latin1set;
|
||||
#if 0
|
||||
// Depends on iTXt support in libpng
|
||||
QTest::newRow("Multibyte string") << QString::fromUtf8("\341\233\222\341\233\226\341\232\251\341\232\271\341\232\242\341\233\232\341\232\240");
|
||||
// Depends on iTXt support in libpng
|
||||
QTest::newRow("Multibyte string") << fileName << QString::fromUtf8("\341\233\222\341\233\226\341\232\251\341\232\271\341\232\242\341\233\232\341\232\240");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void tst_QImageReader::preserveTexts()
|
||||
{
|
||||
QFETCH(QString, fileName);
|
||||
QByteArray format = fileName.right(3).toLatin1();
|
||||
QFETCH(QString, text);
|
||||
QString key("testkey");
|
||||
QString key2("testkey2");
|
||||
|
|
@ -1820,26 +1834,26 @@ void tst_QImageReader::preserveTexts()
|
|||
QString key3("testkey3");
|
||||
QString text3("Some more other text.");
|
||||
|
||||
QImage img(":/images/kollada.png");
|
||||
QImage img(fileName);
|
||||
img.setText(key, text);
|
||||
img.setText(key2, text2);
|
||||
QBuffer buf;
|
||||
buf.open(QIODevice::WriteOnly);
|
||||
QVERIFY(img.save(&buf, "png"));
|
||||
QVERIFY(img.save(&buf, format.constData()));
|
||||
buf.close();
|
||||
QImage stored = QImage::fromData(buf.buffer(), "png");
|
||||
QImage stored = QImage::fromData(buf.buffer(), format.constData());
|
||||
QCOMPARE(stored.text(key), text);
|
||||
QCOMPARE(stored.text(key2), text2);
|
||||
|
||||
QImage img2(":/images/kollada.png");
|
||||
QImage img2(fileName);
|
||||
img2.setText(key3, text3);
|
||||
QBuffer buf2;
|
||||
QImageWriter w(&buf2, "png");
|
||||
QImageWriter w(&buf2, format);
|
||||
w.setText(key, text);
|
||||
w.setText(key2, text2);
|
||||
QVERIFY(w.write(img2));
|
||||
buf2.close();
|
||||
QImageReader r(&buf2, "png");
|
||||
QImageReader r(&buf2, format);
|
||||
QCOMPARE(r.text(key), text.simplified());
|
||||
QCOMPARE(r.text(key2), text2.simplified());
|
||||
QCOMPARE(r.text(key3), text3.simplified());
|
||||
|
|
|
|||
Loading…
Reference in New Issue