From 3593380d9d88d0d73eae9ff87a02ae2d975333ea Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Fri, 27 Feb 2015 16:31:26 +0100 Subject: Simplify mirroring code and add tests for non-aliged 1 bit images Like it is done in Qt 5. Also add the autotest which was completely missing in Qt 4. Change-Id: Iaf89272b4e5b7f377c4b2f1ce929661f3d0edd9a Reviewed-by: Lars Knoll --- src/gui/image/qimage.cpp | 198 +++++++++++++++++++++------------------ tests/auto/qimage/tst_qimage.cpp | 116 +++++++++++++++++++++++ 2 files changed, 225 insertions(+), 89 deletions(-) diff --git a/src/gui/image/qimage.cpp b/src/gui/image/qimage.cpp index 800b886..965a1ba 100644 --- a/src/gui/image/qimage.cpp +++ b/src/gui/image/qimage.cpp @@ -4761,18 +4761,118 @@ QImage QImage::createMaskFromColor(QRgb color, Qt::MaskMode mode) const } -/* - This code is contributed by Philipp Lang, - GeneriCom Software Germany (www.generi.com) - under the terms of the QPL, Version 1.0 -*/ - /*! \fn QImage QImage::mirror(bool horizontal, bool vertical) const Use mirrored() instead. */ +template inline void do_mirror_data(QImageData *dst, QImageData *src, + int dstX0, int dstY0, + int dstXIncr, int dstYIncr, + int w, int h) +{ + if (dst == src) { + // When mirroring in-place, stop in the middle for one of the directions, since we + // are swapping the bytes instead of merely copying. + const int srcXEnd = dstX0 ? w / 2 : w; + const int srcYEnd = !dstX0 && dstY0 ? h / 2 : h; + for (int srcY = 0, dstY = dstY0; srcY < srcYEnd; ++srcY, dstY += dstYIncr) { + T *srcPtr = (T *) (src->data + srcY * src->bytes_per_line); + T *dstPtr = (T *) (dst->data + dstY * dst->bytes_per_line); + for (int srcX = 0, dstX = dstX0; srcX < srcXEnd; ++srcX, dstX += dstXIncr) + std::swap(srcPtr[srcX], dstPtr[dstX]); + } + } else { + for (int srcY = 0, dstY = dstY0; srcY < h; ++srcY, dstY += dstYIncr) { + T *srcPtr = (T *) (src->data + srcY * src->bytes_per_line); + T *dstPtr = (T *) (dst->data + dstY * dst->bytes_per_line); + for (int srcX = 0, dstX = dstX0; srcX < w; ++srcX, dstX += dstXIncr) + dstPtr[dstX] = srcPtr[srcX]; + } + } +} + +inline void do_mirror(QImageData *dst, QImageData *src, bool horizontal, bool vertical) +{ + Q_ASSERT(src->width == dst->width && src->height == dst->height && src->depth == dst->depth); + int w = src->width; + int h = src->height; + int depth = src->depth; + + if (src->depth == 1) { + w = (w + 7) / 8; // byte aligned width + depth = 8; + } + + int dstX0 = 0, dstXIncr = 1; + int dstY0 = 0, dstYIncr = 1; + if (horizontal) { + // 0 -> w-1, 1 -> w-2, 2 -> w-3, ... + dstX0 = w - 1; + dstXIncr = -1; + } + if (vertical) { + // 0 -> h-1, 1 -> h-2, 2 -> h-3, ... + dstY0 = h - 1; + dstYIncr = -1; + } + + switch (depth) { + case 32: + do_mirror_data(dst, src, dstX0, dstY0, dstXIncr, dstYIncr, w, h); + break; + case 24: + do_mirror_data(dst, src, dstX0, dstY0, dstXIncr, dstYIncr, w, h); + break; + case 16: + do_mirror_data(dst, src, dstX0, dstY0, dstXIncr, dstYIncr, w, h); + break; + case 8: + do_mirror_data(dst, src, dstX0, dstY0, dstXIncr, dstYIncr, w, h); + break; + default: + Q_ASSERT(false); + break; + } + + // The bytes are now all in the correct place. In addition, the bits in the individual + // bytes have to be flipped too when horizontally mirroring a 1 bit-per-pixel image. + if (horizontal && dst->depth == 1) { + Q_ASSERT(dst->format == QImage::Format_Mono || dst->format == QImage::Format_MonoLSB); + const int shift = 8 - (dst->width % 8); + const uchar *bitflip = qt_get_bitflip_array(); + for (int y = 0; y < h; ++y) { + uchar *begin = dst->data + y * dst->bytes_per_line; + uchar *end = begin + dst->bytes_per_line; + for (uchar *p = begin; p < end; ++p) { + *p = bitflip[*p]; + // When the data is non-byte aligned, an extra bit shift (of the number of + // unused bits at the end) is needed for the entire scanline. + if (shift != 8 && p != begin) { + if (dst->format == QImage::Format_Mono) { + for (int i = 0; i < shift; ++i) { + p[-1] <<= 1; + p[-1] |= (*p & (128 >> i)) >> (7 - i); + } + } else { + for (int i = 0; i < shift; ++i) { + p[-1] >>= 1; + p[-1] |= (*p & (1 << i)) << (7 - i); + } + } + } + } + if (shift != 8) { + if (dst->format == QImage::Format_Mono) + end[-1] <<= shift; + else + end[-1] >>= shift; + } + } + } +} + /*! Returns a mirror of the image, mirrored in the horizontal and/or the vertical direction depending on whether \a horizontal and \a @@ -4790,8 +4890,6 @@ QImage QImage::mirrored(bool horizontal, bool vertical) const if ((d->width <= 1 && d->height <= 1) || (!horizontal && !vertical)) return *this; - int w = d->width; - int h = d->height; // Create result image, copy colormap QImage result(d->width, d->height, d->format); QIMAGE_SANITYCHECK_MEMORY(result); @@ -4802,88 +4900,10 @@ QImage QImage::mirrored(bool horizontal, bool vertical) const result.d->colortable = d->colortable; result.d->has_alpha_clut = d->has_alpha_clut; + result.d->dpmx = d->dpmx; + result.d->dpmy = d->dpmy; - if (depth() == 1) - w = (w+7)/8; - int dxi = horizontal ? -1 : 1; - int dxs = horizontal ? w-1 : 0; - int dyi = vertical ? -1 : 1; - int dy = vertical ? h-1: 0; - - // 1 bit, 8 bit - if (d->depth == 1 || d->depth == 8) { - for (int sy = 0; sy < h; sy++, dy += dyi) { - quint8* ssl = (quint8*)(d->data + sy*d->bytes_per_line); - quint8* dsl = (quint8*)(result.d->data + dy*result.d->bytes_per_line); - int dx = dxs; - for (int sx = 0; sx < w; sx++, dx += dxi) - dsl[dx] = ssl[sx]; - } - } - // 16 bit - else if (d->depth == 16) { - for (int sy = 0; sy < h; sy++, dy += dyi) { - quint16* ssl = (quint16*)(d->data + sy*d->bytes_per_line); - quint16* dsl = (quint16*)(result.d->data + dy*result.d->bytes_per_line); - int dx = dxs; - for (int sx = 0; sx < w; sx++, dx += dxi) - dsl[dx] = ssl[sx]; - } - } - // 24 bit - else if (d->depth == 24) { - for (int sy = 0; sy < h; sy++, dy += dyi) { - quint24* ssl = (quint24*)(d->data + sy*d->bytes_per_line); - quint24* dsl = (quint24*)(result.d->data + dy*result.d->bytes_per_line); - int dx = dxs; - for (int sx = 0; sx < w; sx++, dx += dxi) - dsl[dx] = ssl[sx]; - } - } - // 32 bit - else if (d->depth == 32) { - for (int sy = 0; sy < h; sy++, dy += dyi) { - quint32* ssl = (quint32*)(d->data + sy*d->bytes_per_line); - quint32* dsl = (quint32*)(result.d->data + dy*result.d->bytes_per_line); - int dx = dxs; - for (int sx = 0; sx < w; sx++, dx += dxi) - dsl[dx] = ssl[sx]; - } - } - - // special handling of 1 bit images for horizontal mirroring - if (horizontal && d->depth == 1) { - int shift = width() % 8; - for (int y = h-1; y >= 0; y--) { - quint8* a0 = (quint8*)(result.d->data + y*d->bytes_per_line); - // Swap bytes - quint8* a = a0+dxs; - while (a >= a0) { - *a = bitflip[*a]; - a--; - } - // Shift bits if unaligned - if (shift != 0) { - a = a0+dxs; - quint8 c = 0; - if (format() == Format_MonoLSB) { - while (a >= a0) { - quint8 nc = *a << shift; - *a = (*a >> (8-shift)) | c; - --a; - c = nc; - } - } else { - while (a >= a0) { - quint8 nc = *a >> shift; - *a = (*a << (8-shift)) | c; - --a; - c = nc; - } - } - } - } - } + do_mirror(result.d, d, horizontal, vertical); return result; } diff --git a/tests/auto/qimage/tst_qimage.cpp b/tests/auto/qimage/tst_qimage.cpp index 7da70f7..5e4c358 100644 --- a/tests/auto/qimage/tst_qimage.cpp +++ b/tests/auto/qimage/tst_qimage.cpp @@ -150,6 +150,9 @@ private slots: void rgbSwapped_data(); void rgbSwapped(); + void mirrored_data(); + void mirrored(); + void deepCopyWhenPaintingActive(); void scaled_QTBUG19157(); }; @@ -2016,6 +2019,119 @@ void tst_QImage::rgbSwapped() QCOMPARE(memcmp(image.constBits(), imageSwappedTwice.constBits(), image.numBytes()), 0); } +void tst_QImage::mirrored_data() +{ + QTest::addColumn("format"); + QTest::addColumn("swap_vertical"); + QTest::addColumn("swap_horizontal"); + QTest::addColumn("width"); + QTest::addColumn("height"); + + QTest::newRow("Format_RGB32, vertical") << QImage::Format_RGB32 << true << false << 16 << 16; + QTest::newRow("Format_ARGB32, vertical") << QImage::Format_ARGB32 << true << false << 16 << 16; + QTest::newRow("Format_ARGB32_Premultiplied, vertical") << QImage::Format_ARGB32_Premultiplied << true << false << 16 << 16; + QTest::newRow("Format_RGB16, vertical") << QImage::Format_RGB16 << true << false << 16 << 16; + QTest::newRow("Format_ARGB8565_Premultiplied, vertical") << QImage::Format_ARGB8565_Premultiplied << true << false << 16 << 16; + QTest::newRow("Format_ARGB6666_Premultiplied, vertical") << QImage::Format_ARGB6666_Premultiplied << true << false << 16 << 16; + QTest::newRow("Format_ARGB4444_Premultiplied, vertical") << QImage::Format_ARGB4444_Premultiplied << true << false << 16 << 16; + QTest::newRow("Format_RGB666, vertical") << QImage::Format_RGB666 << true << false << 16 << 16; + QTest::newRow("Format_RGB555, vertical") << QImage::Format_RGB555 << true << false << 16 << 16; + QTest::newRow("Format_ARGB8555_Premultiplied, vertical") << QImage::Format_ARGB8555_Premultiplied << true << false << 16 << 16; + QTest::newRow("Format_RGB888, vertical") << QImage::Format_RGB888 << true << false << 16 << 16; + QTest::newRow("Format_RGB444, vertical") << QImage::Format_RGB444 << true << false << 16 << 16; + QTest::newRow("Format_Indexed8, vertical") << QImage::Format_Indexed8 << true << false << 16 << 16; + QTest::newRow("Format_Mono, vertical") << QImage::Format_Mono << true << false << 16 << 16; + QTest::newRow("Format_MonoLSB, vertical") << QImage::Format_MonoLSB << true << false << 16 << 16; + + QTest::newRow("Format_ARGB32_Premultiplied, horizontal") << QImage::Format_ARGB32_Premultiplied << false << true << 16 << 16; + QTest::newRow("Format_RGB888, horizontal") << QImage::Format_RGB888 << false << true << 16 << 16; + QTest::newRow("Format_RGB16, horizontal") << QImage::Format_RGB16 << false << true << 16 << 16; + QTest::newRow("Format_Indexed8, horizontal") << QImage::Format_Indexed8 << false << true << 16 << 16; + QTest::newRow("Format_Mono, horizontal") << QImage::Format_Mono << false << true << 16 << 16; + QTest::newRow("Format_MonoLSB, horizontal") << QImage::Format_MonoLSB << false << true << 16 << 16; + + QTest::newRow("Format_ARGB32_Premultiplied, horizontal+vertical") << QImage::Format_ARGB32_Premultiplied << true << true << 16 << 16; + QTest::newRow("Format_RGB888, horizontal+vertical") << QImage::Format_RGB888 << true << true << 16 << 16; + QTest::newRow("Format_RGB16, horizontal+vertical") << QImage::Format_RGB16 << true << true << 16 << 16; + QTest::newRow("Format_Indexed8, horizontal+vertical") << QImage::Format_Indexed8 << true << true << 16 << 16; + QTest::newRow("Format_Mono, horizontal+vertical") << QImage::Format_Mono << true << true << 16 << 16; + QTest::newRow("Format_MonoLSB, horizontal+vertical") << QImage::Format_MonoLSB << true << true << 16 << 16; + + QTest::newRow("Format_RGB32, vertical") << QImage::Format_RGB32 << true << false << 8 << 16; + QTest::newRow("Format_ARGB32, vertical") << QImage::Format_ARGB32 << true << false << 16 << 8; + QTest::newRow("Format_Mono, vertical, non-aligned") << QImage::Format_Mono << true << false << 19 << 25; + QTest::newRow("Format_MonoLSB, vertical, non-aligned") << QImage::Format_MonoLSB << true << false << 19 << 25; + + // Non-aligned horizontal 1-bit needs special handling so test this. + QTest::newRow("Format_Mono, horizontal, non-aligned") << QImage::Format_Mono << false << true << 13 << 17; + QTest::newRow("Format_Mono, horizontal, non-aligned") << QImage::Format_Mono << false << true << 19 << 25; + QTest::newRow("Format_Mono, horizontal+vertical, non-aligned") << QImage::Format_Mono << true << true << 25 << 47; + QTest::newRow("Format_Mono, horizontal+vertical, non-aligned") << QImage::Format_Mono << true << true << 21 << 16; + + QTest::newRow("Format_MonoLSB, horizontal, non-aligned") << QImage::Format_MonoLSB << false << true << 13 << 17; + QTest::newRow("Format_MonoLSB, horizontal, non-aligned") << QImage::Format_MonoLSB << false << true << 19 << 25; + QTest::newRow("Format_MonoLSB, horizontal+vertical, non-aligned") << QImage::Format_MonoLSB << true << true << 25 << 47; + QTest::newRow("Format_MonoLSB, horizontal+vertical, non-aligned") << QImage::Format_MonoLSB << true << true << 21 << 16; +} + +void tst_QImage::mirrored() +{ + QFETCH(QImage::Format, format); + QFETCH(bool, swap_vertical); + QFETCH(bool, swap_horizontal); + QFETCH(int, width); + QFETCH(int, height); + + QImage image(width, height, format); + + switch (format) { + case QImage::Format_Mono: + case QImage::Format_MonoLSB: + for (int i = 0; i < image.height(); ++i) { + ushort* scanLine = (ushort*)image.scanLine(i); + *scanLine = (i % 2) ? 0x5555U : 0xCCCCU; + } + break; + case QImage::Format_Indexed8: + for (int i = 0; i < image.height(); ++i) { + for (int j = 0; j < image.width(); ++j) { + image.setColor(i*16+j, qRgb(j*16, i*16, 0)); + image.setPixel(j, i, i*16+j); + } + } + break; + default: + for (int i = 0; i < image.height(); ++i) + for (int j = 0; j < image.width(); ++j) + image.setPixel(j, i, qRgb(j*16, i*16, 0)); + break; + } + + QImage imageMirrored = image.mirrored(swap_horizontal, swap_vertical); + + for (int i = 0; i < image.height(); ++i) { + int mirroredI = swap_vertical ? (image.height() - i - 1) : i; + for (int j = 0; j < image.width(); ++j) { + QRgb referenceColor = image.pixel(j, i); + int mirroredJ = swap_horizontal ? (image.width() - j - 1) : j; + QRgb mirroredColor = imageMirrored.pixel(mirroredJ, mirroredI); + QCOMPARE(mirroredColor, referenceColor); + } + } + + QImage imageMirroredTwice = imageMirrored.mirrored(swap_horizontal, swap_vertical); + + QCOMPARE(image, imageMirroredTwice); + + if (format != QImage::Format_Mono && format != QImage::Format_MonoLSB) + QCOMPARE(memcmp(image.constBits(), imageMirroredTwice.constBits(), image.byteCount()), 0); + else { + for (int i = 0; i < image.height(); ++i) + for (int j = 0; j < image.width(); ++j) + QCOMPARE(image.pixel(j,i), imageMirroredTwice.pixel(j,i)); + } +} + void tst_QImage::deepCopyWhenPaintingActive() { QImage image(64, 64, QImage::Format_ARGB32_Premultiplied); -- cgit v0.12