diff options
author | Qt Continuous Integration System <qt-info@nokia.com> | 2009-12-23 08:55:49 (GMT) |
---|---|---|
committer | Qt Continuous Integration System <qt-info@nokia.com> | 2009-12-23 08:55:49 (GMT) |
commit | 03ee83e15de7f5091d7322ac5765daec652b4b3e (patch) | |
tree | e92442160ea6df2cbae30621c1727b9a689675bc | |
parent | 3b705ed3fb5d485171ebd6132846ced88abd7187 (diff) | |
parent | 61f214e60a074cbaf2413b2c77ed5a4cfd638edb (diff) | |
download | Qt-03ee83e15de7f5091d7322ac5765daec652b4b3e.zip Qt-03ee83e15de7f5091d7322ac5765daec652b4b3e.tar.gz Qt-03ee83e15de7f5091d7322ac5765daec652b4b3e.tar.bz2 |
Merge branch '4.6' of scm.dev.nokia.troll.no:qt/oslo-staging-2 into 4.6-integration
* '4.6' of scm.dev.nokia.troll.no:qt/oslo-staging-2:
Implement ScaledClipRect in the jpeg image plugin
Add support for ClipRect to the jpeg image plugin
Use libjpeg's builtin scaler for implementing setScaledSize()
Use 3x3 PMV matrices rather than 4x4 in the GL2 engine
Handle broken shaders better in the GL2 engine's shader manager
Add GLfloat[2][2] & GLfloat[3][3] uniform setters to QGLShaderProgram
Fix build with neon instructions enabled but not set in mkspec
-rw-r--r-- | src/gui/painting/painting.pri | 2 | ||||
-rw-r--r-- | src/opengl/gl2paintengineex/qglengineshadermanager.cpp | 191 | ||||
-rw-r--r-- | src/opengl/gl2paintengineex/qglengineshadersource_p.h | 66 | ||||
-rw-r--r-- | src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp | 58 | ||||
-rw-r--r-- | src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h | 2 | ||||
-rw-r--r-- | src/opengl/qglshaderprogram.cpp | 67 | ||||
-rw-r--r-- | src/opengl/qglshaderprogram.h | 4 | ||||
-rw-r--r-- | src/plugins/imageformats/jpeg/qjpeghandler.cpp | 285 | ||||
-rw-r--r-- | src/plugins/imageformats/jpeg/qjpeghandler.h | 3 | ||||
-rw-r--r-- | tests/auto/qimagereader/tst_qimagereader.cpp | 10 |
10 files changed, 451 insertions, 237 deletions
diff --git a/src/gui/painting/painting.pri b/src/gui/painting/painting.pri index 0b1e79a..a6cc9c7 100644 --- a/src/gui/painting/painting.pri +++ b/src/gui/painting/painting.pri @@ -383,7 +383,7 @@ neon { DEFINES += QT_HAVE_NEON HEADERS += painting/qdrawhelper_neon_p.h SOURCES += painting/qdrawhelper_neon.cpp - QMAKE.CXXFLAGS *= -mfpu=neon + QMAKE_CXXFLAGS *= -mfpu=neon } contains(QT_CONFIG, zlib) { diff --git a/src/opengl/gl2paintengineex/qglengineshadermanager.cpp b/src/opengl/gl2paintengineex/qglengineshadermanager.cpp index 1187c2d..9d545b9 100644 --- a/src/opengl/gl2paintengineex/qglengineshadermanager.cpp +++ b/src/opengl/gl2paintengineex/qglengineshadermanager.cpp @@ -170,13 +170,15 @@ QGLEngineSharedShaders::QGLEngineSharedShaders(const QGLContext* context) source.append(qShaderSnippets[MainVertexShader]); source.append(qShaderSnippets[PositionOnlyVertexShader]); vertexShader = new QGLShader(QGLShader::Vertex, context, this); - vertexShader->compileSourceCode(source); + if (!vertexShader->compileSourceCode(source)) + qWarning("Vertex shader for simpleShaderProg (MainVertexShader & PositionOnlyVertexShader) failed to compile"); source.clear(); source.append(qShaderSnippets[MainFragmentShader]); source.append(qShaderSnippets[ShockingPinkSrcFragmentShader]); fragShader = new QGLShader(QGLShader::Fragment, context, this); - fragShader->compileSourceCode(source); + if (!fragShader->compileSourceCode(source)) + qWarning("Fragment shader for simpleShaderProg (MainFragmentShader & ShockingPinkSrcFragmentShader) failed to compile"); simpleShaderProg = new QGLShaderProgram(context, this); simpleShaderProg->addShader(vertexShader); @@ -193,13 +195,15 @@ QGLEngineSharedShaders::QGLEngineSharedShaders(const QGLContext* context) source.append(qShaderSnippets[MainWithTexCoordsVertexShader]); source.append(qShaderSnippets[UntransformedPositionVertexShader]); vertexShader = new QGLShader(QGLShader::Vertex, context, this); - vertexShader->compileSourceCode(source); + if (!vertexShader->compileSourceCode(source)) + qWarning("Vertex shader for blitShaderProg (MainWithTexCoordsVertexShader & UntransformedPositionVertexShader) failed to compile"); source.clear(); source.append(qShaderSnippets[MainFragmentShader]); source.append(qShaderSnippets[ImageSrcFragmentShader]); fragShader = new QGLShader(QGLShader::Fragment, context, this); - fragShader->compileSourceCode(source); + if (!fragShader->compileSourceCode(source)) + qWarning("Fragment shader for blitShaderProg (MainFragmentShader & ImageSrcFragmentShader) failed to compile"); blitShaderProg = new QGLShaderProgram(context, this); blitShaderProg->addShader(vertexShader); @@ -234,84 +238,95 @@ QGLEngineShaderProg *QGLEngineSharedShaders::findProgramInCache(const QGLEngineS } } - QByteArray source; - source.append(qShaderSnippets[prog.mainFragShader]); - source.append(qShaderSnippets[prog.srcPixelFragShader]); - if (prog.srcPixelFragShader == CustomImageSrcFragmentShader) - source.append(prog.customStageSource); - if (prog.compositionFragShader) - source.append(qShaderSnippets[prog.compositionFragShader]); - if (prog.maskFragShader) - source.append(qShaderSnippets[prog.maskFragShader]); - QGLShader* fragShader = new QGLShader(QGLShader::Fragment, ctxGuard.context(), this); - fragShader->compileSourceCode(source); - - source.clear(); - source.append(qShaderSnippets[prog.mainVertexShader]); - source.append(qShaderSnippets[prog.positionVertexShader]); - QGLShader* vertexShader = new QGLShader(QGLShader::Vertex, ctxGuard.context(), this); - vertexShader->compileSourceCode(source); + QGLShader *vertexShader = 0; + QGLShader *fragShader = 0; + QGLEngineShaderProg *newProg = 0; + bool success = false; + + do { + QByteArray source; + source.append(qShaderSnippets[prog.mainFragShader]); + source.append(qShaderSnippets[prog.srcPixelFragShader]); + if (prog.srcPixelFragShader == CustomImageSrcFragmentShader) + source.append(prog.customStageSource); + if (prog.compositionFragShader) + source.append(qShaderSnippets[prog.compositionFragShader]); + if (prog.maskFragShader) + source.append(qShaderSnippets[prog.maskFragShader]); + fragShader = new QGLShader(QGLShader::Fragment, ctxGuard.context(), this); + QByteArray description; +#if defined(QT_DEBUG) + // Name the shader for easier debugging + description.append("Fragment shader: main="); + description.append(snippetNameStr(prog.mainFragShader)); + description.append(", srcPixel="); + description.append(snippetNameStr(prog.srcPixelFragShader)); + if (prog.compositionFragShader) { + description.append(", composition="); + description.append(snippetNameStr(prog.compositionFragShader)); + } + if (prog.maskFragShader) { + description.append(", mask="); + description.append(snippetNameStr(prog.maskFragShader)); + } + fragShader->setObjectName(QString::fromLatin1(description)); +#endif + if (!fragShader->compileSourceCode(source)) { + qWarning() << "Warning:" << description << "failed to compile!"; + break; + } + source.clear(); + source.append(qShaderSnippets[prog.mainVertexShader]); + source.append(qShaderSnippets[prog.positionVertexShader]); + vertexShader = new QGLShader(QGLShader::Vertex, ctxGuard.context(), this); #if defined(QT_DEBUG) - // Name the shaders for easier debugging - QByteArray description; - description.append("Fragment shader: main="); - description.append(snippetNameStr(prog.mainFragShader)); - description.append(", srcPixel="); - description.append(snippetNameStr(prog.srcPixelFragShader)); - if (prog.compositionFragShader) { - description.append(", composition="); - description.append(snippetNameStr(prog.compositionFragShader)); - } - if (prog.maskFragShader) { - description.append(", mask="); - description.append(snippetNameStr(prog.maskFragShader)); - } - fragShader->setObjectName(QString::fromLatin1(description)); - - description.clear(); - description.append("Vertex shader: main="); - description.append(snippetNameStr(prog.mainVertexShader)); - description.append(", position="); - description.append(snippetNameStr(prog.positionVertexShader)); - vertexShader->setObjectName(QString::fromLatin1(description)); + // Name the shader for easier debugging + description.clear(); + description.append("Vertex shader: main="); + description.append(snippetNameStr(prog.mainVertexShader)); + description.append(", position="); + description.append(snippetNameStr(prog.positionVertexShader)); + vertexShader->setObjectName(QString::fromLatin1(description)); #endif + if (!vertexShader->compileSourceCode(source)) { + qWarning() << "Warning:" << description << "failed to compile!"; + break; + } - QGLEngineShaderProg* newProg = new QGLEngineShaderProg(prog); - - // If the shader program's not found in the cache, create it now. - newProg->program = new QGLShaderProgram(ctxGuard.context(), this); - newProg->program->addShader(vertexShader); - newProg->program->addShader(fragShader); - - // We have to bind the vertex attribute names before the program is linked: - newProg->program->bindAttributeLocation("vertexCoordsArray", QT_VERTEX_COORDS_ATTR); - if (newProg->useTextureCoords) - newProg->program->bindAttributeLocation("textureCoordArray", QT_TEXTURE_COORDS_ATTR); - if (newProg->useOpacityAttribute) - newProg->program->bindAttributeLocation("opacityArray", QT_OPACITY_ATTR); - - newProg->program->link(); - if (!newProg->program->isLinked()) { - QLatin1String none("none"); - QLatin1String br("\n"); - QString error; - error = QLatin1String("Shader program failed to link,") + newProg = new QGLEngineShaderProg(prog); + + // If the shader program's not found in the cache, create it now. + newProg->program = new QGLShaderProgram(ctxGuard.context(), this); + newProg->program->addShader(vertexShader); + newProg->program->addShader(fragShader); + + // We have to bind the vertex attribute names before the program is linked: + newProg->program->bindAttributeLocation("vertexCoordsArray", QT_VERTEX_COORDS_ATTR); + if (newProg->useTextureCoords) + newProg->program->bindAttributeLocation("textureCoordArray", QT_TEXTURE_COORDS_ATTR); + if (newProg->useOpacityAttribute) + newProg->program->bindAttributeLocation("opacityArray", QT_OPACITY_ATTR); + + newProg->program->link(); + if (!newProg->program->isLinked()) { + QLatin1String none("none"); + QLatin1String br("\n"); + QString error; + error = QLatin1String("Shader program failed to link,") #if defined(QT_DEBUG) - + br - + QLatin1String(" Shaders Used:") + br - + QLatin1String(" ") + vertexShader->objectName() + QLatin1String(": ") + br - + QLatin1String(vertexShader->sourceCode()) + br - + QLatin1String(" ") + fragShader->objectName() + QLatin1String(": ") + br - + QLatin1String(fragShader->sourceCode()) + br + + br + + QLatin1String(" Shaders Used:") + br + + QLatin1String(" ") + vertexShader->objectName() + QLatin1String(": ") + br + + QLatin1String(vertexShader->sourceCode()) + br + + QLatin1String(" ") + fragShader->objectName() + QLatin1String(": ") + br + + QLatin1String(fragShader->sourceCode()) + br #endif - + QLatin1String(" Error Log:\n") - + QLatin1String(" ") + newProg->program->log(); - qWarning() << error; - delete newProg; // Deletes the QGLShaderProgram in it's destructor - newProg = 0; - } - else { + + QLatin1String(" Error Log:\n") + + QLatin1String(" ") + newProg->program->log(); + qWarning() << error; + break; + } if (cachedPrograms.count() > 30) { // The cache is full, so delete the last 5 programs in the list. // These programs will be least used, as a program us bumped to @@ -323,6 +338,22 @@ QGLEngineShaderProg *QGLEngineSharedShaders::findProgramInCache(const QGLEngineS } cachedPrograms.insert(0, newProg); + + success = true; + } while (false); + + // Clean up everything if we weren't successful + if (!success) { + if (newProg) { + delete newProg; // Also deletes the QGLShaderProgram which in turn deletes the QGLShaders + newProg = 0; + } + else { + if (vertexShader) + delete vertexShader; + if (fragShader) + delete fragShader; + } } return newProg; @@ -364,6 +395,9 @@ QGLEngineShaderManager::~QGLEngineShaderManager() uint QGLEngineShaderManager::getUniformLocation(Uniform id) { + if (!currentShaderProg) + return 0; + QVector<uint> &uniformLocations = currentShaderProg->uniformLocations; if (uniformLocations.isEmpty()) uniformLocations.fill(GLuint(-1), NumUniforms); @@ -468,7 +502,10 @@ void QGLEngineShaderManager::removeCustomStage() QGLShaderProgram* QGLEngineShaderManager::currentProgram() { - return currentShaderProg->program; + if (currentShaderProg) + return currentShaderProg->program; + else + return simpleProgram(); } QGLShaderProgram* QGLEngineShaderManager::simpleProgram() diff --git a/src/opengl/gl2paintengineex/qglengineshadersource_p.h b/src/opengl/gl2paintengineex/qglengineshadersource_p.h index 2407979..46de124 100644 --- a/src/opengl/gl2paintengineex/qglengineshadersource_p.h +++ b/src/opengl/gl2paintengineex/qglengineshadersource_p.h @@ -99,12 +99,15 @@ static const char* const qglslMainWithTexCoordsAndOpacityVertexShader = "\ opacity = opacityArray; \ }"; +// NOTE: We let GL do the perspective correction so texture lookups in the fragment +// shader are also perspective corrected. static const char* const qglslPositionOnlyVertexShader = "\ - attribute highp vec4 vertexCoordsArray;\ - uniform highp mat4 pmvMatrix;\ + attribute highp vec2 vertexCoordsArray;\ + uniform highp mat3 pmvMatrix;\ void setPosition(void)\ {\ - gl_Position = pmvMatrix * vertexCoordsArray;\ + vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \ + gl_Position = vec4(transformedPos.xy, 0.0, transformedPos.z); \ }"; static const char* const qglslUntransformedPositionVertexShader = "\ @@ -116,20 +119,19 @@ static const char* const qglslUntransformedPositionVertexShader = "\ // Pattern Brush - This assumes the texture size is 8x8 and thus, the inverted size is 0.125 static const char* const qglslPositionWithPatternBrushVertexShader = "\ - attribute highp vec4 vertexCoordsArray; \ - uniform highp mat4 pmvMatrix; \ + attribute highp vec2 vertexCoordsArray; \ + uniform highp mat3 pmvMatrix; \ uniform mediump vec2 halfViewportSize; \ uniform highp vec2 invertedTextureSize; \ uniform highp mat3 brushTransform; \ varying highp vec2 patternTexCoords; \ void setPosition(void) { \ - gl_Position = pmvMatrix * vertexCoordsArray;\ - gl_Position.xy = gl_Position.xy / gl_Position.w; \ + vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \ + gl_Position.xy = transformedPos.xy / transformedPos.z; \ mediump vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \ - mediump vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1); \ + mediump vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1.0); \ mediump float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \ - gl_Position.xy = gl_Position.xy * invertedHTexCoordsZ; \ - gl_Position.w = invertedHTexCoordsZ; \ + gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \ patternTexCoords.xy = (hTexCoords.xy * 0.125) * invertedHTexCoordsZ; \ }"; @@ -147,20 +149,19 @@ static const char* const qglslPatternBrushSrcFragmentShader = "\ // Linear Gradient Brush static const char* const qglslPositionWithLinearGradientBrushVertexShader = "\ - attribute highp vec4 vertexCoordsArray; \ - uniform highp mat4 pmvMatrix; \ + attribute highp vec2 vertexCoordsArray; \ + uniform highp mat3 pmvMatrix; \ uniform mediump vec2 halfViewportSize; \ uniform highp vec3 linearData; \ uniform highp mat3 brushTransform; \ varying mediump float index; \ void setPosition() { \ - gl_Position = pmvMatrix * vertexCoordsArray;\ - gl_Position.xy = gl_Position.xy / gl_Position.w; \ + vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \ + gl_Position.xy = transformedPos.xy / transformedPos.z; \ mediump vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \ mediump vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1); \ mediump float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \ - gl_Position.xy = gl_Position.xy * invertedHTexCoordsZ; \ - gl_Position.w = invertedHTexCoordsZ; \ + gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \ index = (dot(linearData.xy, hTexCoords.xy) * linearData.z) * invertedHTexCoordsZ; \ }"; @@ -178,20 +179,19 @@ static const char* const qglslLinearGradientBrushSrcFragmentShader = "\ // Conical Gradient Brush static const char* const qglslPositionWithConicalGradientBrushVertexShader = "\ - attribute highp vec4 vertexCoordsArray;\ - uniform highp mat4 pmvMatrix;\ + attribute highp vec2 vertexCoordsArray;\ + uniform highp mat3 pmvMatrix;\ uniform mediump vec2 halfViewportSize; \ uniform highp mat3 brushTransform; \ varying highp vec2 A; \ void setPosition(void)\ {\ - gl_Position = pmvMatrix * vertexCoordsArray;\ - gl_Position.xy = gl_Position.xy / gl_Position.w; \ + vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \ + gl_Position.xy = transformedPos.xy / transformedPos.z; \ mediump vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \ mediump vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1); \ mediump float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \ - gl_Position.xy = gl_Position.xy * invertedHTexCoordsZ; \ - gl_Position.w = invertedHTexCoordsZ; \ + gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \ A = hTexCoords.xy * invertedHTexCoordsZ; \ }"; @@ -215,8 +215,8 @@ static const char* const qglslConicalGradientBrushSrcFragmentShader = "\n\ // Radial Gradient Brush static const char* const qglslPositionWithRadialGradientBrushVertexShader = "\ - attribute highp vec4 vertexCoordsArray;\ - uniform highp mat4 pmvMatrix;\ + attribute highp vec2 vertexCoordsArray;\ + uniform highp mat3 pmvMatrix;\ uniform mediump vec2 halfViewportSize; \ uniform highp mat3 brushTransform; \ uniform highp vec2 fmp; \ @@ -224,13 +224,12 @@ static const char* const qglslPositionWithRadialGradientBrushVertexShader = "\ varying highp vec2 A; \ void setPosition(void) \ {\ - gl_Position = pmvMatrix * vertexCoordsArray;\ - gl_Position.xy = gl_Position.xy / gl_Position.w; \ + vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \ + gl_Position.xy = transformedPos.xy / transformedPos.z; \ mediump vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \ mediump vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1); \ mediump float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \ - gl_Position.xy = gl_Position.xy * invertedHTexCoordsZ; \ - gl_Position.w = invertedHTexCoordsZ; \ + gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \ A = hTexCoords.xy * invertedHTexCoordsZ; \ b = 2.0 * dot(A, fmp); \ }"; @@ -253,20 +252,19 @@ static const char* const qglslRadialGradientBrushSrcFragmentShader = "\ // Texture Brush static const char* const qglslPositionWithTextureBrushVertexShader = "\ - attribute highp vec4 vertexCoordsArray; \ - uniform highp mat4 pmvMatrix; \ + attribute highp vec2 vertexCoordsArray; \ + uniform highp mat3 pmvMatrix; \ uniform mediump vec2 halfViewportSize; \ uniform highp vec2 invertedTextureSize; \ uniform highp mat3 brushTransform; \ varying highp vec2 textureCoords; \ void setPosition(void) { \ - gl_Position = pmvMatrix * vertexCoordsArray;\ - gl_Position.xy = gl_Position.xy / gl_Position.w; \ + vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \ + gl_Position.xy = transformedPos.xy / transformedPos.z; \ mediump vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \ mediump vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1); \ mediump float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \ - gl_Position.xy = gl_Position.xy * invertedHTexCoordsZ; \ - gl_Position.w = invertedHTexCoordsZ; \ + gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \ textureCoords.xy = (hTexCoords.xy * invertedTextureSize) * gl_Position.w; \ }"; diff --git a/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp b/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp index 8ca2fd4..f52ed92 100644 --- a/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp +++ b/src/opengl/gl2paintengineex/qpaintengineex_opengl2.cpp @@ -583,37 +583,37 @@ void QGL2PaintEngineExPrivate::updateMatrix() { // qDebug("QGL2PaintEngineExPrivate::updateMatrix()"); - // We set up the 4x4 transformation matrix on the vertex shaders to - // be the equivalent of glOrtho(0, w, h, 0, -1, 1) * transform: - // - // | 2/width 0 0 -1 | | m11 m21 0 dx | - // | 0 -2/height 0 1 | | m12 m22 0 dy | - // | 0 0 -1 0 | * | 0 0 1 0 | - // | 0 0 0 1 | | m13 m23 0 m33 | - // - // We expand out the multiplication to save the cost of a full 4x4 - // matrix multiplication as most of the components are trivial. const QTransform& transform = q->state()->matrix; - qreal wfactor = 2.0 / width; - qreal hfactor = -2.0 / height; - - pmvMatrix[0][0] = wfactor * transform.m11() - transform.m13(); - pmvMatrix[0][1] = hfactor * transform.m12() + transform.m13(); - pmvMatrix[0][2] = 0.0; - pmvMatrix[0][3] = transform.m13(); - pmvMatrix[1][0] = wfactor * transform.m21() - transform.m23(); - pmvMatrix[1][1] = hfactor * transform.m22() + transform.m23(); - pmvMatrix[1][2] = 0.0; - pmvMatrix[1][3] = transform.m23(); - pmvMatrix[2][0] = 0.0; - pmvMatrix[2][1] = 0.0; - pmvMatrix[2][2] = -1.0; - pmvMatrix[2][3] = 0.0; - pmvMatrix[3][0] = wfactor * transform.dx() - transform.m33(); - pmvMatrix[3][1] = hfactor * transform.dy() + transform.m33(); - pmvMatrix[3][2] = 0.0; - pmvMatrix[3][3] = transform.m33(); + // The projection matrix converts from Qt's coordinate system to GL's coordinate system + // * GL's viewport is 2x2, Qt's is width x height + // * GL has +y -> -y going from bottom -> top, Qt is the other way round + // * GL has [0,0] in the center, Qt has it in the top-left + // + // This results in the Projection matrix below, which is multiplied by the painter's + // transformation matrix, as shown below: + // + // Projection Matrix Painter Transform + // ------------------------------------------------ ------------------------ + // | 2.0 / width | 0.0 | -1.0 | | m11 | m21 | dx | + // | 0.0 | -2.0 / height | 1.0 | * | m12 | m22 | dy | + // | 0.0 | 0.0 | 1.0 | | m13 | m23 | m33 | + // ------------------------------------------------ ------------------------ + // + // NOTE: The resultant matrix is also transposed, as GL expects column-major matracies + + const GLfloat wfactor = 2.0f / width; + const GLfloat hfactor = -2.0f / height; + + pmvMatrix[0][0] = (wfactor * transform.m11()) - transform.m13(); + pmvMatrix[1][0] = (wfactor * transform.m21()) - transform.m23(); + pmvMatrix[2][0] = (wfactor * transform.dx() ) - transform.m33(); + pmvMatrix[0][1] = (hfactor * transform.m12()) + transform.m13(); + pmvMatrix[1][1] = (hfactor * transform.m22()) + transform.m23(); + pmvMatrix[2][1] = (hfactor * transform.dy() ) + transform.m33(); + pmvMatrix[0][2] = transform.m13(); + pmvMatrix[1][2] = transform.m23(); + pmvMatrix[2][2] = transform.m33(); // 1/10000 == 0.0001, so we have good enough res to cover curves // that span the entire widget... diff --git a/src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h b/src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h index 33ce24d..f1ec6e6 100644 --- a/src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h +++ b/src/opengl/gl2paintengineex/qpaintengineex_opengl2_p.h @@ -254,7 +254,7 @@ public: GLfloat staticVertexCoordinateArray[8]; GLfloat staticTextureCoordinateArray[8]; - GLfloat pmvMatrix[4][4]; + GLfloat pmvMatrix[3][3]; QGLEngineShaderManager* shaderManager; diff --git a/src/opengl/qglshaderprogram.cpp b/src/opengl/qglshaderprogram.cpp index b4191dc..f9737a56 100644 --- a/src/opengl/qglshaderprogram.cpp +++ b/src/opengl/qglshaderprogram.cpp @@ -2275,6 +2275,42 @@ void QGLShaderProgram::setUniformValue(const char *name, const QMatrix4x4& value \overload Sets the uniform variable at \a location in the current context + to a 2x2 matrix \a value. The matrix elements must be specified + in column-major order. + + \sa setAttributeValue() + \since 4.6.2 +*/ +void QGLShaderProgram::setUniformValue(int location, const GLfloat value[2][2]) +{ + Q_D(QGLShaderProgram); + Q_UNUSED(d); + if (location != -1) + glUniformMatrix2fv(location, 1, GL_FALSE, value[0]); +} + +/*! + \overload + + Sets the uniform variable at \a location in the current context + to a 3x3 matrix \a value. The matrix elements must be specified + in column-major order. + + \sa setAttributeValue() + \since 4.6.2 +*/ +void QGLShaderProgram::setUniformValue(int location, const GLfloat value[3][3]) +{ + Q_D(QGLShaderProgram); + Q_UNUSED(d); + if (location != -1) + glUniformMatrix3fv(location, 1, GL_FALSE, value[0]); +} + +/*! + \overload + + Sets the uniform variable at \a location in the current context to a 4x4 matrix \a value. The matrix elements must be specified in column-major order. @@ -2288,6 +2324,37 @@ void QGLShaderProgram::setUniformValue(int location, const GLfloat value[4][4]) glUniformMatrix4fv(location, 1, GL_FALSE, value[0]); } + +/*! + \overload + + Sets the uniform variable called \a name in the current context + to a 2x2 matrix \a value. The matrix elements must be specified + in column-major order. + + \sa setAttributeValue() + \since 4.6.2 +*/ +void QGLShaderProgram::setUniformValue(const char *name, const GLfloat value[2][2]) +{ + setUniformValue(uniformLocation(name), value); +} + +/*! + \overload + + Sets the uniform variable called \a name in the current context + to a 3x3 matrix \a value. The matrix elements must be specified + in column-major order. + + \sa setAttributeValue() + \since 4.6.2 +*/ +void QGLShaderProgram::setUniformValue(const char *name, const GLfloat value[3][3]) +{ + setUniformValue(uniformLocation(name), value); +} + /*! \overload diff --git a/src/opengl/qglshaderprogram.h b/src/opengl/qglshaderprogram.h index deeaee2..4eb80dd 100644 --- a/src/opengl/qglshaderprogram.h +++ b/src/opengl/qglshaderprogram.h @@ -216,6 +216,8 @@ public: void setUniformValue(int location, const QMatrix4x2& value); void setUniformValue(int location, const QMatrix4x3& value); void setUniformValue(int location, const QMatrix4x4& value); + void setUniformValue(int location, const GLfloat value[2][2]); + void setUniformValue(int location, const GLfloat value[3][3]); void setUniformValue(int location, const GLfloat value[4][4]); void setUniformValue(int location, const QTransform& value); @@ -242,6 +244,8 @@ public: void setUniformValue(const char *name, const QMatrix4x2& value); void setUniformValue(const char *name, const QMatrix4x3& value); void setUniformValue(const char *name, const QMatrix4x4& value); + void setUniformValue(const char *name, const GLfloat value[2][2]); + void setUniformValue(const char *name, const GLfloat value[3][3]); void setUniformValue(const char *name, const GLfloat value[4][4]); void setUniformValue(const char *name, const QTransform& value); diff --git a/src/plugins/imageformats/jpeg/qjpeghandler.cpp b/src/plugins/imageformats/jpeg/qjpeghandler.cpp index aa239ec..11608ef 100644 --- a/src/plugins/imageformats/jpeg/qjpeghandler.cpp +++ b/src/plugins/imageformats/jpeg/qjpeghandler.cpp @@ -90,9 +90,6 @@ public: QImage scale(); -protected: - int scaledWidth(void) const; - private: QImageSmoothScalerPrivate *d; virtual QRgb *scanLine(const int line = 0, const QImage *src = 0); @@ -140,11 +137,6 @@ void QImageSmoothScalerPrivate::setup(const int srcWidth, const int srcHeight, hasAlpha = hasAlphaChannel; } -int QImageSmoothScaler::scaledWidth() const -{ - return d->cols; -} - QImageSmoothScaler::~QImageSmoothScaler() { delete d; @@ -443,20 +435,18 @@ QImage QImageSmoothScaler::scale() class jpegSmoothScaler : public QImageSmoothScaler { public: - jpegSmoothScaler(struct jpeg_decompress_struct *info, int dstWidth, int dstHeight) - : QImageSmoothScaler(info->output_width, info->output_height, dstWidth, dstHeight) + jpegSmoothScaler(struct jpeg_decompress_struct *info, const QSize& dstSize, const QRect& clipRect) + : QImageSmoothScaler(clipRect.width(), clipRect.height(), + dstSize.width(), dstSize.height()) { - cinfo = info; - cols24Bit = scaledWidth() * 3; - - cacheHeight = 1; - imageCache = QImage( info->output_width, cacheHeight, QImage::Format_RGB32 ); + cinfo = info; + clip = clipRect; + imageCache = QImage(info->output_width, 1, QImage::Format_RGB32); } private: - int cols24Bit; + QRect clip; QImage imageCache; - int cacheHeight; struct jpeg_decompress_struct *cinfo; QRgb *scanLine(const int line = 0, const QImage *src = 0) @@ -468,33 +458,42 @@ private: Q_UNUSED(src); uchar* data = imageCache.bits(); + + // Read ahead if we haven't reached the first clipped scanline yet. + while (int(cinfo->output_scanline) < clip.y() && + cinfo->output_scanline < cinfo->output_height) + jpeg_read_scanlines(cinfo, &data, 1); + + // Read the next scanline. We assume that "line" + // will never be >= clip.height(). jpeg_read_scanlines(cinfo, &data, 1); - out = (QRgb*)imageCache.scanLine(0); + if (cinfo->output_scanline == cinfo->output_height) + jpeg_finish_decompress(cinfo); + + out = ((QRgb*)data) + clip.x(); // // The smooth scale algorithm only works on 32-bit images; // convert from (8|24) bits to 32. // if (cinfo->output_components == 1) { - in = (uchar*)out + scaledWidth(); - for (uint i = scaledWidth(); i--; ) { - in--; + in = data + clip.right(); + for (int i = clip.width(); i--; ) { out[i] = qRgb(*in, *in, *in); + in--; } - } else if (cinfo->out_color_space == JCS_CMYK) { - int cols32Bit = scaledWidth() * 4; - in = (uchar*)out + cols32Bit; - for (uint i = scaledWidth(); i--; ) { - in -= 4; - int k = in[3]; - out[i] = qRgb(k * in[0] / 255, k * in[1] / 255, k * in[2] / 255); - //out[i] = qRgb(in[0], in[1], in[2]); - } - } else { - in = (uchar*)out + cols24Bit; - for (uint i = scaledWidth(); i--; ) { - in -= 3; + } else if (cinfo->out_color_space == JCS_CMYK) { + in = data + clip.right() * 4; + for (int i = clip.width(); i--; ) { + int k = in[3]; + out[i] = qRgb(k * in[0] / 255, k * in[1] / 255, k * in[2] / 255); + in -= 4; + } + } else { + in = data + clip.right() * 3; + for (int i = clip.width(); i--; ) { out[i] = qRgb(in[0], in[1], in[2]); + in -= 3; } } @@ -693,7 +692,7 @@ static bool read_jpeg_format(QIODevice *device, QImage::Format &format) } static bool ensureValidImage(QImage *dest, struct jpeg_decompress_struct *info, - bool dummy = false) + const QSize& size) { QImage::Format format; switch (info->output_components) { @@ -708,13 +707,8 @@ static bool ensureValidImage(QImage *dest, struct jpeg_decompress_struct *info, return false; // unsupported format } - const QSize size(info->output_width, info->output_height); if (dest->size() != size || dest->format() != format) { - static uchar dummyImage[1]; - if (dummy) // Create QImage but don't read the pixels - *dest = QImage(dummyImage, size.width(), size.height(), format); - else - *dest = QImage(size, format); + *dest = QImage(size, format); if (format == QImage::Format_Indexed8) { dest->setColorCount(256); @@ -727,12 +721,9 @@ static bool ensureValidImage(QImage *dest, struct jpeg_decompress_struct *info, } static bool read_jpeg_image(QIODevice *device, QImage *outImage, - QSize scaledSize, int inQuality ) + QSize scaledSize, QRect scaledClipRect, + QRect clipRect, int inQuality ) { -#ifdef QT_NO_IMAGE_SMOOTHSCALE - Q_UNUSED( scaledSize ); -#endif - struct jpeg_decompress_struct cinfo; struct my_jpeg_source_mgr *iod_src = new my_jpeg_source_mgr(device); @@ -757,11 +748,53 @@ static bool read_jpeg_image(QIODevice *device, QImage *outImage, if (quality < 0) quality = 75; -#ifndef QT_NO_IMAGE_SMOOTHSCALE - // If high quality not required, shrink image during decompression - if (scaledSize.isValid() && !scaledSize.isEmpty() && quality < HIGH_QUALITY_THRESHOLD) { - cinfo.scale_denom = qMin(cinfo.image_width / scaledSize.width(), - cinfo.image_width / scaledSize.height()); + // If possible, merge the scaledClipRect into either scaledSize + // or clipRect to avoid doing a separate scaled clipping pass. + // Best results are achieved by clipping before scaling, not after. + if (!scaledClipRect.isEmpty()) { + if (scaledSize.isEmpty() && clipRect.isEmpty()) { + // No clipping or scaling before final clip. + clipRect = scaledClipRect; + scaledClipRect = QRect(); + } else if (scaledSize.isEmpty()) { + // Clipping, but no scaling: combine the clip regions. + scaledClipRect.translate(clipRect.topLeft()); + clipRect = scaledClipRect.intersected(clipRect); + scaledClipRect = QRect(); + } else if (clipRect.isEmpty()) { + // No clipping, but scaling: if we can map back to an + // integer pixel boundary, then clip before scaling. + if ((cinfo.image_width % scaledSize.width()) == 0 && + (cinfo.image_height % scaledSize.height()) == 0) { + int x = scaledClipRect.x() * cinfo.image_width / + scaledSize.width(); + int y = scaledClipRect.y() * cinfo.image_height / + scaledSize.height(); + int width = (scaledClipRect.right() + 1) * + cinfo.image_width / scaledSize.width() - x; + int height = (scaledClipRect.bottom() + 1) * + cinfo.image_height / scaledSize.height() - y; + clipRect = QRect(x, y, width, height); + scaledSize = scaledClipRect.size(); + scaledClipRect = QRect(); + } + } else { + // Clipping and scaling: too difficult to figure out, + // and not a likely use case, so do it the long way. + } + } + + // Determine the scale factor to pass to libjpeg for quick downscaling. + if (!scaledSize.isEmpty()) { + if (clipRect.isEmpty()) { + cinfo.scale_denom = + qMin(cinfo.image_width / scaledSize.width(), + cinfo.image_height / scaledSize.height()); + } else { + cinfo.scale_denom = + qMin(clipRect.width() / scaledSize.width(), + clipRect.height() / scaledSize.height()); + } if (cinfo.scale_denom < 2) { cinfo.scale_denom = 1; } else if (cinfo.scale_denom < 4) { @@ -772,9 +805,19 @@ static bool read_jpeg_image(QIODevice *device, QImage *outImage, cinfo.scale_denom = 8; } cinfo.scale_num = 1; + if (!clipRect.isEmpty()) { + // Correct the scale factor so that we clip accurately. + // It is recommended that the clip rectangle be aligned + // on an 8-pixel boundary for best performance. + while (cinfo.scale_denom > 1 && + ((clipRect.x() % cinfo.scale_denom) != 0 || + (clipRect.y() % cinfo.scale_denom) != 0 || + (clipRect.width() % cinfo.scale_denom) != 0 || + (clipRect.height() % cinfo.scale_denom) != 0)) { + cinfo.scale_denom /= 2; + } + } } -#endif - // If high quality not required, use fast decompression if( quality < HIGH_QUALITY_THRESHOLD ) { @@ -782,54 +825,102 @@ static bool read_jpeg_image(QIODevice *device, QImage *outImage, cinfo.do_fancy_upsampling = FALSE; } + (void) jpeg_calc_output_dimensions(&cinfo); - (void) jpeg_start_decompress(&cinfo); + // Determine the clip region to extract. + QRect imageRect(0, 0, cinfo.output_width, cinfo.output_height); + QRect clip; + if (clipRect.isEmpty()) { + clip = imageRect; + } else if (cinfo.scale_denom == 1) { + clip = clipRect.intersected(imageRect); + } else { + // The scale factor was corrected above to ensure that + // we don't miss pixels when we scale the clip rectangle. + clip = QRect(clipRect.x() / int(cinfo.scale_denom), + clipRect.y() / int(cinfo.scale_denom), + clipRect.width() / int(cinfo.scale_denom), + clipRect.height() / int(cinfo.scale_denom)); + clip = clip.intersected(imageRect); + } #ifndef QT_NO_IMAGE_SMOOTHSCALE - if (scaledSize.isValid() && scaledSize != QSize(cinfo.output_width, cinfo.output_height) + if (scaledSize.isValid() && scaledSize != clip.size() && quality >= HIGH_QUALITY_THRESHOLD) { - jpegSmoothScaler scaler(&cinfo, scaledSize.width(), scaledSize.height()); + (void) jpeg_start_decompress(&cinfo); + + jpegSmoothScaler scaler(&cinfo, scaledSize, clip); *outImage = scaler.scale(); } else #endif { - if (!ensureValidImage(outImage, &cinfo)) + // Allocate memory for the clipped QImage. + if (!ensureValidImage(outImage, &cinfo, clip.size())) longjmp(jerr.setjmp_buffer, 1); - uchar* data = outImage->bits(); - int bpl = outImage->bytesPerLine(); - while (cinfo.output_scanline < cinfo.output_height) { - uchar *d = data + cinfo.output_scanline * bpl; - (void) jpeg_read_scanlines(&cinfo, - &d, - 1); - } - (void) jpeg_finish_decompress(&cinfo); - - if (cinfo.output_components == 3) { - // Expand 24->32 bpp. - for (uint j=0; j<cinfo.output_height; j++) { - uchar *in = outImage->scanLine(j) + cinfo.output_width * 3; - QRgb *out = (QRgb*)outImage->scanLine(j); - - for (uint i=cinfo.output_width; i--;) { - in-=3; - out[i] = qRgb(in[0], in[1], in[2]); + // Avoid memcpy() overhead if grayscale with no clipping. + bool quickGray = (cinfo.output_components == 1 && + clip == imageRect); + if (!quickGray) { + // Ask the jpeg library to allocate a temporary row. + // The library will automatically delete it for us later. + // The libjpeg docs say we should do this before calling + // jpeg_start_decompress(). We can't use "new" here + // because we are inside the setjmp() block and an error + // in the jpeg input stream would cause a memory leak. + JSAMPARRAY rows = (cinfo.mem->alloc_sarray) + ((j_common_ptr)&cinfo, JPOOL_IMAGE, + cinfo.output_width * cinfo.output_components, 1); + + (void) jpeg_start_decompress(&cinfo); + + while (cinfo.output_scanline < cinfo.output_height) { + int y = int(cinfo.output_scanline) - clip.y(); + if (y >= clip.height()) + break; // We've read the entire clip region, so abort. + + (void) jpeg_read_scanlines(&cinfo, rows, 1); + + if (y < 0) + continue; // Haven't reached the starting line yet. + + if (cinfo.output_components == 3) { + // Expand 24->32 bpp. + uchar *in = rows[0] + clip.x() * 3; + QRgb *out = (QRgb*)outImage->scanLine(y); + for (int i = 0; i < clip.width(); ++i) { + *out++ = qRgb(in[0], in[1], in[2]); + in += 3; + } + } else if (cinfo.out_color_space == JCS_CMYK) { + // Convert CMYK->RGB. + uchar *in = rows[0] + clip.x() * 4; + QRgb *out = (QRgb*)outImage->scanLine(y); + for (int i = 0; i < clip.width(); ++i) { + int k = in[3]; + *out++ = qRgb(k * in[0] / 255, k * in[1] / 255, + k * in[2] / 255); + in += 4; + } + } else if (cinfo.output_components == 1) { + // Grayscale. + memcpy(outImage->scanLine(y), + rows[0] + clip.x(), clip.width()); } } - } else if (cinfo.out_color_space == JCS_CMYK) { - for (uint j = 0; j < cinfo.output_height; ++j) { - uchar *in = outImage->scanLine(j) + cinfo.output_width * 4; - QRgb *out = (QRgb*)outImage->scanLine(j); - - for (uint i = cinfo.output_width; i--; ) { - in-=4; - int k = in[3]; - out[i] = qRgb(k * in[0] / 255, k * in[1] / 255, k * in[2] / 255); - } + } else { + // Load unclipped grayscale data directly into the QImage. + (void) jpeg_start_decompress(&cinfo); + while (cinfo.output_scanline < cinfo.output_height) { + uchar *row = outImage->scanLine(cinfo.output_scanline); + (void) jpeg_read_scanlines(&cinfo, &row, 1); } } + + if (cinfo.output_scanline == cinfo.output_height) + (void) jpeg_finish_decompress(&cinfo); + if (cinfo.density_unit == 1) { outImage->setDotsPerMeterX(int(100. * cinfo.X_density / 2.54)); outImage->setDotsPerMeterY(int(100. * cinfo.Y_density / 2.54)); @@ -838,13 +929,15 @@ static bool read_jpeg_image(QIODevice *device, QImage *outImage, outImage->setDotsPerMeterY(int(100. * cinfo.Y_density)); } - if (scaledSize.isValid() && scaledSize != QSize(cinfo.output_width, cinfo.output_height)) + if (scaledSize.isValid() && scaledSize != clip.size()) *outImage = outImage->scaled(scaledSize, Qt::IgnoreAspectRatio, Qt::FastTransformation); } } jpeg_destroy_decompress(&cinfo); delete iod_src; + if (!scaledClipRect.isEmpty()) + *outImage = outImage->copy(scaledClipRect); return !outImage->isNull(); } @@ -1102,7 +1195,7 @@ bool QJpegHandler::read(QImage *image) { if (!canRead()) return false; - return read_jpeg_image(device(), image, scaledSize, quality); + return read_jpeg_image(device(), image, scaledSize, scaledClipRect, clipRect, quality); } bool QJpegHandler::write(const QImage &image) @@ -1113,9 +1206,9 @@ bool QJpegHandler::write(const QImage &image) bool QJpegHandler::supportsOption(ImageOption option) const { return option == Quality -#ifndef QT_NO_IMAGE_SMOOTHSCALE || option == ScaledSize -#endif + || option == ScaledClipRect + || option == ClipRect || option == Size || option == ImageFormat; } @@ -1124,10 +1217,12 @@ QVariant QJpegHandler::option(ImageOption option) const { if (option == Quality) { return quality; -#ifndef QT_NO_IMAGE_SMOOTHSCALE } else if (option == ScaledSize) { return scaledSize; -#endif + } else if (option == ScaledClipRect) { + return scaledClipRect; + } else if (option == ClipRect) { + return clipRect; } else if (option == Size) { if (canRead() && !device()->isSequential()) { qint64 pos = device()->pos(); @@ -1154,10 +1249,12 @@ void QJpegHandler::setOption(ImageOption option, const QVariant &value) { if (option == Quality) quality = value.toInt(); -#ifndef QT_NO_IMAGE_SMOOTHSCALE else if ( option == ScaledSize ) scaledSize = value.toSize(); -#endif + else if ( option == ScaledClipRect ) + scaledClipRect = value.toRect(); + else if ( option == ClipRect ) + clipRect = value.toRect(); } QByteArray QJpegHandler::name() const diff --git a/src/plugins/imageformats/jpeg/qjpeghandler.h b/src/plugins/imageformats/jpeg/qjpeghandler.h index 0a14a88..6870cd6 100644 --- a/src/plugins/imageformats/jpeg/qjpeghandler.h +++ b/src/plugins/imageformats/jpeg/qjpeghandler.h @@ -44,6 +44,7 @@ #include <QtGui/qimageiohandler.h> #include <QtCore/QSize> +#include <QtCore/QRect> QT_BEGIN_NAMESPACE @@ -67,6 +68,8 @@ public: private: int quality; QSize scaledSize; + QRect scaledClipRect; + QRect clipRect; }; QT_END_NAMESPACE diff --git a/tests/auto/qimagereader/tst_qimagereader.cpp b/tests/auto/qimagereader/tst_qimagereader.cpp index 15b1c1c..630cc03 100644 --- a/tests/auto/qimagereader/tst_qimagereader.cpp +++ b/tests/auto/qimagereader/tst_qimagereader.cpp @@ -322,7 +322,15 @@ void tst_QImageReader::setScaledSize_data() QTest::newRow("PPM: test") << "test.ppm" << QSize(10, 10) << QByteArray("ppm"); QTest::newRow("XBM: gnus") << "gnus" << QSize(200, 200) << QByteArray("xbm"); #ifdef QTEST_HAVE_JPEG - QTest::newRow("JPEG: beavis") << "beavis" << QSize(200, 200) << QByteArray("jpeg"); + QTest::newRow("JPEG: beavis A") << "beavis" << QSize(200, 200) << QByteArray("jpeg"); + QTest::newRow("JPEG: beavis B") << "beavis" << QSize(175, 175) << QByteArray("jpeg"); + QTest::newRow("JPEG: beavis C") << "beavis" << QSize(100, 100) << QByteArray("jpeg"); + QTest::newRow("JPEG: beavis D") << "beavis" << QSize(100, 200) << QByteArray("jpeg"); + QTest::newRow("JPEG: beavis E") << "beavis" << QSize(200, 100) << QByteArray("jpeg"); + QTest::newRow("JPEG: beavis F") << "beavis" << QSize(87, 87) << QByteArray("jpeg"); + QTest::newRow("JPEG: beavis G") << "beavis" << QSize(50, 45) << QByteArray("jpeg"); + QTest::newRow("JPEG: beavis H") << "beavis" << QSize(43, 43) << QByteArray("jpeg"); + QTest::newRow("JPEG: beavis I") << "beavis" << QSize(25, 25) << QByteArray("jpeg"); #endif // QTEST_HAVE_JPEG #ifdef QTEST_HAVE_GIF QTest::newRow("GIF: earth") << "earth" << QSize(200, 200) << QByteArray("gif"); |