diff --git a/CMakeLists.txt b/CMakeLists.txt index ae930d6c888..f1a9dc8c50a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -427,6 +427,7 @@ if(WITH_CORE) set(QT_VERSION_BASE "Qt5") set(HAS_KDE_QT5_PDF_TRANSFORM_FIX FALSE CACHE BOOL "Using KDE's Qt 5.15 fork with the PDF brush transform fix") set(HAS_KDE_QT5_SMALL_CAPS_FIX FALSE CACHE BOOL "Using KDE's Qt 5.15 fork with the QFont::SmallCaps fix") + set(HAS_KDE_QT5_FONT_STRETCH_FIX FALSE CACHE BOOL "Using KDE's Qt 5.15 fork with the QFont stretch fix") endif() # Use Qt5SerialPort optionally for GPS diff --git a/cmake_templates/qgsconfig.h.in b/cmake_templates/qgsconfig.h.in index c58524b00c6..e34c0ea9702 100644 --- a/cmake_templates/qgsconfig.h.in +++ b/cmake_templates/qgsconfig.h.in @@ -106,6 +106,7 @@ #cmakedefine HAS_KDE_QT5_PDF_TRANSFORM_FIX #cmakedefine HAS_KDE_QT5_SMALL_CAPS_FIX +#cmakedefine HAS_KDE_QT5_FONT_STRETCH_FIX #endif diff --git a/python/core/auto_generated/labeling/qgspallabeling.sip.in b/python/core/auto_generated/labeling/qgspallabeling.sip.in index efdeaf238e9..d1de565e6c6 100644 --- a/python/core/auto_generated/labeling/qgspallabeling.sip.in +++ b/python/core/auto_generated/labeling/qgspallabeling.sip.in @@ -135,6 +135,7 @@ Contains settings for how a map layer will be labeled. FontLetterSpacing, FontWordSpacing, FontBlendMode, + FontStretchFactor, // text formatting MultiLineWrapChar, diff --git a/python/core/auto_generated/textrenderer/qgstextformat.sip.in b/python/core/auto_generated/textrenderer/qgstextformat.sip.in index 1ddc66bd7f5..1bb25212a72 100644 --- a/python/core/auto_generated/textrenderer/qgstextformat.sip.in +++ b/python/core/auto_generated/textrenderer/qgstextformat.sip.in @@ -346,6 +346,36 @@ Sets the text's opacity. opaque) .. seealso:: :py:func:`opacity` +%End + + int stretchFactor() const; +%Docstring +Returns the text's stretch factor. + +The stretch factor matches a condensed or expanded version of the font or applies a stretch +transform that changes the width of all characters in the font by factor percent. + +For example, a factor of 150 results in all characters in the font being 1.5 times +(ie. 150%) wider. The minimum stretch factor is 1, and the maximum stretch factor is 4000. + +.. seealso:: :py:func:`setStretchFactor` + +.. versionadded:: 3.24 +%End + + void setStretchFactor( int factor ); +%Docstring +Sets the text's stretch ``factor``. + +The stretch factor matches a condensed or expanded version of the font or applies a stretch +transform that changes the width of all characters in the font by factor percent. + +For example, setting ``factor`` to 150 results in all characters in the font being 1.5 times +(ie. 150%) wider. The minimum stretch factor is 1, and the maximum stretch factor is 4000. + +.. seealso:: :py:func:`stretchFactor` + +.. versionadded:: 3.24 %End QPainter::CompositionMode blendMode() const; diff --git a/src/core/labeling/qgspallabeling.cpp b/src/core/labeling/qgspallabeling.cpp index b0b5a6239a2..f3cbfc8ef21 100644 --- a/src/core/labeling/qgspallabeling.cpp +++ b/src/core/labeling/qgspallabeling.cpp @@ -135,6 +135,7 @@ void QgsPalLayerSettings::initPropertyDefinitions() { QgsPalLayerSettings::FontSizeUnit, QgsPropertyDefinition( "FontSizeUnit", QObject::tr( "Font size units" ), QgsPropertyDefinition::RenderUnits, origin ) }, { QgsPalLayerSettings::FontTransp, QgsPropertyDefinition( "FontTransp", QObject::tr( "Text transparency" ), QgsPropertyDefinition::Opacity, origin ) }, { QgsPalLayerSettings::FontOpacity, QgsPropertyDefinition( "FontOpacity", QObject::tr( "Text opacity" ), QgsPropertyDefinition::Opacity, origin ) }, + { QgsPalLayerSettings::FontStretchFactor, QgsPropertyDefinition( "FontStretchFactor", QObject::tr( "Font stretch factor" ), QgsPropertyDefinition::IntegerPositiveGreaterZero, origin ) }, { QgsPalLayerSettings::FontCase, QgsPropertyDefinition( "FontCase", QgsPropertyDefinition::DataTypeString, QObject::tr( "Font case" ), QObject::tr( "string " ) + QStringLiteral( "[NoChange|Upper|
Lower|Title|Capitalize|SmallCaps|AllSmallCaps]" ), origin ) }, { QgsPalLayerSettings::FontLetterSpacing, QgsPropertyDefinition( "FontLetterSpacing", QObject::tr( "Letter spacing" ), QgsPropertyDefinition::Double, origin ) }, { QgsPalLayerSettings::FontWordSpacing, QgsPropertyDefinition( "FontWordSpacing", QObject::tr( "Word spacing" ), QgsPropertyDefinition::Double, origin ) }, @@ -3142,6 +3143,13 @@ void QgsPalLayerSettings::parseTextStyle( QFont &labelFont, labelFont.setStrikeOut( strikeout ); } + // data defined stretch + if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::FontStretchFactor ) ) + { + context.expressionContext().setOriginalValueVariable( mFormat.stretchFactor() ); + labelFont.setStretch( mDataDefinedProperties.valueAsInt( QgsPalLayerSettings::FontStretchFactor, context.expressionContext(), mFormat.stretchFactor() ) ); + } + // data defined underline font style? if ( mDataDefinedProperties.isActive( QgsPalLayerSettings::Underline ) ) { diff --git a/src/core/labeling/qgspallabeling.h b/src/core/labeling/qgspallabeling.h index c092eac453b..b06d49edd56 100644 --- a/src/core/labeling/qgspallabeling.h +++ b/src/core/labeling/qgspallabeling.h @@ -238,6 +238,7 @@ class CORE_EXPORT QgsPalLayerSettings FontLetterSpacing = 28, //!< Letter spacing FontWordSpacing = 29, //!< Word spacing FontBlendMode = 30, //!< Text blend mode + FontStretchFactor = 113, //!< Font stretch factor, since QGIS 3.24 // text formatting MultiLineWrapChar = 31, diff --git a/src/core/textrenderer/qgstextformat.cpp b/src/core/textrenderer/qgstextformat.cpp index 79663a7cff4..77ccb559f3b 100644 --- a/src/core/textrenderer/qgstextformat.cpp +++ b/src/core/textrenderer/qgstextformat.cpp @@ -290,6 +290,17 @@ void QgsTextFormat::setOpacity( double opacity ) d->opacity = opacity; } +int QgsTextFormat::stretchFactor() const +{ + return d->textFont.stretch(); +} + +void QgsTextFormat::setStretchFactor( int factor ) +{ + d->isValid = true; + d->textFont.setStretch( factor ); +} + QPainter::CompositionMode QgsTextFormat::blendMode() const { return d->blendMode; @@ -551,6 +562,7 @@ void QgsTextFormat::readXml( const QDomElement &elem, const QgsReadWriteContext { d->opacity = ( textStyleElem.attribute( QStringLiteral( "textOpacity" ) ).toDouble() ); } + d->textFont.setStretch( textStyleElem.attribute( QStringLiteral( "stretchFactor" ), QStringLiteral( "100" ) ).toInt() ); d->orientation = QgsTextRendererUtils::decodeTextOrientation( textStyleElem.attribute( QStringLiteral( "textOrientation" ) ) ); d->previewBackgroundColor = QgsSymbolLayerUtils::decodeColor( textStyleElem.attribute( QStringLiteral( "previewBkgrdColor" ), QgsSymbolLayerUtils::encodeColor( Qt::white ) ) ); @@ -655,6 +667,7 @@ QDomElement QgsTextFormat::writeXml( QDomDocument &doc, const QgsReadWriteContex textStyleElem.setAttribute( QStringLiteral( "fontWordSpacing" ), d->textFont.wordSpacing() ); textStyleElem.setAttribute( QStringLiteral( "fontKerning" ), d->textFont.kerning() ); textStyleElem.setAttribute( QStringLiteral( "textOpacity" ), d->opacity ); + textStyleElem.setAttribute( QStringLiteral( "stretchFactor" ), d->textFont.stretch() ); textStyleElem.setAttribute( QStringLiteral( "textOrientation" ), QgsTextRendererUtils::encodeTextOrientation( d->orientation ) ); textStyleElem.setAttribute( QStringLiteral( "blendMode" ), QgsPainting::getBlendModeEnum( d->blendMode ) ); textStyleElem.setAttribute( QStringLiteral( "multilineHeight" ), d->multilineHeight ); @@ -964,6 +977,16 @@ void QgsTextFormat::updateDataDefinedProperties( QgsRenderContext &context ) } } + if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::FontStretchFactor ) ) + { + context.expressionContext().setOriginalValueVariable( d->textFont.stretch() ); + const QVariant val = d->mDataDefinedProperties.value( QgsPalLayerSettings::FontStretchFactor, context.expressionContext(), d->textFont.stretch() ); + if ( !val.isNull() ) + { + d->textFont.setStretch( val.toInt() ); + } + } + if ( d->mDataDefinedProperties.isActive( QgsPalLayerSettings::TextOrientation ) ) { const QString encoded = QgsTextRendererUtils::encodeTextOrientation( d->orientation ); diff --git a/src/core/textrenderer/qgstextformat.h b/src/core/textrenderer/qgstextformat.h index 953e5ff92d1..446049d422f 100644 --- a/src/core/textrenderer/qgstextformat.h +++ b/src/core/textrenderer/qgstextformat.h @@ -324,6 +324,34 @@ class CORE_EXPORT QgsTextFormat */ void setOpacity( double opacity ); + /** + * Returns the text's stretch factor. + * + * The stretch factor matches a condensed or expanded version of the font or applies a stretch + * transform that changes the width of all characters in the font by factor percent. + * + * For example, a factor of 150 results in all characters in the font being 1.5 times + * (ie. 150%) wider. The minimum stretch factor is 1, and the maximum stretch factor is 4000. + * + * \see setStretchFactor() + * \since QGIS 3.24 + */ + int stretchFactor() const; + + /** + * Sets the text's stretch \a factor. + * + * The stretch factor matches a condensed or expanded version of the font or applies a stretch + * transform that changes the width of all characters in the font by factor percent. + * + * For example, setting \a factor to 150 results in all characters in the font being 1.5 times + * (ie. 150%) wider. The minimum stretch factor is 1, and the maximum stretch factor is 4000. + * + * \see stretchFactor() + * \since QGIS 3.24 + */ + void setStretchFactor( int factor ); + /** * Returns the blending mode used for drawing the text. * \see setBlendMode() diff --git a/src/gui/qgstextformatwidget.cpp b/src/gui/qgstextformatwidget.cpp index 66b38691734..92c78cce8d4 100644 --- a/src/gui/qgstextformatwidget.cpp +++ b/src/gui/qgstextformatwidget.cpp @@ -329,6 +329,12 @@ void QgsTextFormatWidget::initWidget() connect( mBackgroundEffectWidget, &QgsEffectStackCompactWidget::changed, this, &QgsTextFormatWidget::updatePreview ); mBackgroundEffectWidget->setPaintEffect( mBackgroundEffect.get() ); +#ifndef HAS_KDE_QT5_FONT_STRETCH_FIX + mLabelStretch->hide(); + mSpinStretch->hide(); + mFontStretchDDBtn->hide(); +#endif + setDockMode( false ); QList widgets; @@ -366,6 +372,7 @@ void QgsTextFormatWidget::initWidget() << mFontStyleComboBox << mTextOrientationComboBox << mTextOpacityWidget + << mSpinStretch << mFontWordSpacingSpinBox << mFormatNumChkBx << mFormatNumDecimalsSpnBx @@ -582,6 +589,10 @@ void QgsTextFormatWidget::toggleDDButtons( bool visible ) const auto buttons = findChildren< QgsPropertyOverrideButton * >(); for ( QgsPropertyOverrideButton *button : buttons ) { +#ifndef HAS_KDE_QT5_FONT_STRETCH_FIX + if ( button == mFontStretchDDBtn ) + continue; // always hidden +#endif button->setVisible( visible ); } } @@ -693,6 +704,7 @@ void QgsTextFormatWidget::populateDataDefinedButtons() registerDataDefinedButton( mFontLetterSpacingDDBtn, QgsPalLayerSettings::FontLetterSpacing ); registerDataDefinedButton( mFontWordSpacingDDBtn, QgsPalLayerSettings::FontWordSpacing ); registerDataDefinedButton( mFontBlendModeDDBtn, QgsPalLayerSettings::FontBlendMode ); + registerDataDefinedButton( mFontStretchDDBtn, QgsPalLayerSettings::FontStretchFactor ); // text formatting registerDataDefinedButton( mWrapCharDDBtn, QgsPalLayerSettings::MultiLineWrapChar ); @@ -887,6 +899,7 @@ void QgsTextFormatWidget::updateWidgetForFormat( const QgsTextFormat &format ) mRefFont = format.font(); mFontSizeSpinBox->setValue( format.size() ); btnTextColor->setColor( format.color() ); + whileBlocking( mSpinStretch )->setValue( format.stretchFactor() ); mTextOpacityWidget->setOpacity( format.opacity() ); comboBlendMode->setBlendMode( format.blendMode() ); mTextOrientationComboBox->setCurrentIndex( mTextOrientationComboBox->findData( format.orientation() ) ); @@ -1025,6 +1038,7 @@ QgsTextFormat QgsTextFormatWidget::format( bool includeDataDefinedProperties ) c format.setSize( mFontSizeSpinBox->value() ); format.setNamedStyle( mFontStyleComboBox->currentText() ); format.setOpacity( mTextOpacityWidget->opacity() ); + format.setStretchFactor( mSpinStretch->value() ); format.setBlendMode( comboBlendMode->blendMode() ); format.setSizeUnit( mFontSizeUnitWidget->unit() ); format.setSizeMapUnitScale( mFontSizeUnitWidget->getMapUnitScale() ); diff --git a/src/ui/qgstextformatwidgetbase.ui b/src/ui/qgstextformatwidgetbase.ui index f6a9375f07d..c0189c59579 100644 --- a/src/ui/qgstextformatwidgetbase.ui +++ b/src/ui/qgstextformatwidgetbase.ui @@ -109,7 +109,7 @@ 0 0 - 480 + 499 300 @@ -654,8 +654,8 @@ 0 0 - 455 - 414 + 485 + 429 @@ -1217,8 +1217,8 @@ font-style: italic; 0 0 - 427 - 931 + 471 + 742 @@ -1228,23 +1228,150 @@ font-style: italic; 6 - - + + + + true + - + 0 0 - - Type case + + + 16777215 + 16777215 + + + + Capitalization style of text - - + + + + + + + 0 + 0 + + + + word + + + + + + + + 0 + 0 + + + + Space in pixels or map units, relative to size unit choice + + + 4 + + + -1000.000000000000000 + + + 999999999.000000000000000 + + + 0.100000000000000 + + + true + + + + - + + + + Text orientation + + + + + + + + + + 0 + 0 + + + + letter + + + + + + + + 0 + 0 + + + + Space in pixels or map units, relative to size unit choice + + + 4 + + + -1000.000000000000000 + + + 999999999.000000000000000 + + + 0.100000000000000 + + + true + + + + + + + + + Spacing + + + + + + + Blend mode + + + + + + + If enabled, the label text will automatically be modified using a preset list of substitutes + + + Apply label text substitutes + + + + false @@ -1257,7 +1384,31 @@ font-style: italic; - + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1557,107 +1708,33 @@ font-style: italic; - - + + + + + 0 + 0 + + - + Type case - - - - Text orientation + + + + Qt::Vertical - - - - - - - - - 0 - 0 - - - - letter - - - - - - - - 0 - 0 - - - - Space in pixels or map units, relative to size unit choice - - - 4 - - - -1000.000000000000000 - - - 999999999.000000000000000 - - - 0.100000000000000 - - - true - - - - - - - - - + + + 20 + 0 + - + - - - - Blend mode - - - - - - - If enabled, the label text will automatically be modified using a preset list of substitutes - - - Apply label text substitutes - - - - - - - - - - - - - - - - - - - - - + 6 @@ -1765,74 +1842,21 @@ font-style: italic; - - - - - - - 0 - 0 - - - - word - - - - - - - - 0 - 0 - - - - Space in pixels or map units, relative to size unit choice - - - 4 - - - -1000.000000000000000 - - - 999999999.000000000000000 - - - 0.100000000000000 - - - true - - - - - - - - - true - - - - 0 - 0 - - - - - 16777215 - 16777215 - - - - Capitalization style of text + + + + - + + + + + + + + 6 @@ -2034,40 +2058,46 @@ font-style: italic; - - - - Qt::Vertical - - - - 20 - 0 - - - - - - - - Spacing - - - - - - - - - - - + Enable kerning + + + + + + + Stretch + + + + + + + % + + + 1 + + + 4000 + + + 100 + + + + + + + + + + @@ -2101,8 +2131,8 @@ font-style: italic; 0 0 - 338 - 401 + 299 + 308 @@ -2447,8 +2477,8 @@ font-style: italic; 0 0 - 338 - 351 + 296 + 291 @@ -2725,8 +2755,8 @@ font-style: italic; 0 0 - 513 - 1053 + 438 + 753 @@ -3476,8 +3506,8 @@ font-style: italic; 0 0 - 356 - 592 + 324 + 457 @@ -3904,8 +3934,8 @@ font-style: italic; 0 0 - 181 - 227 + 159 + 211 @@ -4054,8 +4084,8 @@ font-style: italic; 0 0 - 510 - 2074 + 472 + 1690 @@ -5757,8 +5787,8 @@ font-style: italic; 0 0 - 461 - 858 + 430 + 708 @@ -6700,6 +6730,8 @@ font-style: italic; mFontLetterSpacingDDBtn mFontWordSpacingSpinBox mFontWordSpacingDDBtn + mSpinStretch + mFontStretchDDBtn mKerningCheckBox mTextOrientationComboBox mTextOrientationDDBtn @@ -6926,6 +6958,7 @@ font-style: italic; mLimitLabelSpinBox mMinSizeSpinBox mFitInsidePolygonCheckBox + mCoordRotationUnitComboBox diff --git a/tests/src/python/test_qgstextrenderer.py b/tests/src/python/test_qgstextrenderer.py index 640429bec2a..a5645527c72 100644 --- a/tests/src/python/test_qgstextrenderer.py +++ b/tests/src/python/test_qgstextrenderer.py @@ -162,6 +162,10 @@ class PyQgsTextRenderer(unittest.TestCase): t.setFamilies(['Arial', 'Comic Sans']) self.assertTrue(t.isValid()) + t = QgsTextFormat() + t.setStretchFactor(110) + self.assertTrue(t.isValid()) + t = QgsTextFormat() t.dataDefinedProperties().setProperty(QgsPalLayerSettings.Bold, QgsProperty.fromValue(True)) self.assertTrue(t.isValid()) @@ -707,6 +711,9 @@ class PyQgsTextRenderer(unittest.TestCase): s.setPreviewBackgroundColor(QColor(100, 150, 200)) s.setOrientation(QgsTextFormat.VerticalOrientation) s.setAllowHtmlFormatting(True) + + s.setStretchFactor(110) + s.dataDefinedProperties().setProperty(QgsPalLayerSettings.Bold, QgsProperty.fromExpression('1>2')) return s @@ -808,6 +815,10 @@ class PyQgsTextRenderer(unittest.TestCase): s.setFamilies(['Times New Roman']) self.assertNotEqual(s, s2) + s = self.createFormatSettings() + s.setStretchFactor(120) + self.assertNotEqual(s, s2) + def checkTextFormat(self, s): """ test QgsTextFormat """ self.assertTrue(s.buffer().enabled()) @@ -835,6 +846,10 @@ class PyQgsTextRenderer(unittest.TestCase): self.assertTrue(s.allowHtmlFormatting()) self.assertEqual(s.dataDefinedProperties().property(QgsPalLayerSettings.Bold).expressionString(), '1>2') + if int(QT_VERSION_STR.split('.')[0]) > 6 or ( + int(QT_VERSION_STR.split('.')[0]) == 6 and int(QT_VERSION_STR.split('.')[1]) >= 3): + self.assertEqual(s.stretchFactor(), 110) + def testFormatGettersSetters(self): s = self.createFormatSettings() self.checkTextFormat(s) @@ -1295,6 +1310,13 @@ class PyQgsTextRenderer(unittest.TestCase): f.updateDataDefinedProperties(context) self.assertEqual(f.blendMode(), QPainter.CompositionMode_ColorBurn) + if int(QT_VERSION_STR.split('.')[0]) > 6 or ( + int(QT_VERSION_STR.split('.')[0]) == 6 and int(QT_VERSION_STR.split('.')[1]) >= 3): + # stretch + f.dataDefinedProperties().setProperty(QgsPalLayerSettings.FontStretchFactor, QgsProperty.fromExpression("135")) + f.updateDataDefinedProperties(context) + self.assertEqual(f.stretchFactor(), 135) + def testFontFoundFromLayer(self): layer = createEmptyLayer() layer.setCustomProperty('labeling/fontFamily', 'asdasd') @@ -1367,6 +1389,12 @@ class PyQgsTextRenderer(unittest.TestCase): qfont = s.toQFont() self.assertAlmostEqual(qfont.pointSizeF(), 360.0, 2) + if int(QT_VERSION_STR.split('.')[0]) > 6 or ( + int(QT_VERSION_STR.split('.')[0]) == 6 and int(QT_VERSION_STR.split('.')[1]) >= 3): + s.setStretchFactor(115) + qfont = s.toQFont() + self.assertEqual(qfont.stretch(), 115) + def testFontMetrics(self): """ Test calculating font metrics from scaled text formats @@ -1514,7 +1542,6 @@ class PyQgsTextRenderer(unittest.TestCase): format.setCapitalization(Qgis.Capitalization.SmallCaps) format.setSize(30) assert self.checkRender(format, 'mixed_small_caps', text=['Small Caps']) - assert False @unittest.skipIf(int(QT_VERSION_STR.split('.')[0]) < 6 or (int(QT_VERSION_STR.split('.')[0]) == 6 and int(QT_VERSION_STR.split('.')[1]) < 3), 'Too old Qt') def testDrawAllSmallCaps(self): @@ -1523,7 +1550,22 @@ class PyQgsTextRenderer(unittest.TestCase): format.setSize(30) format.setCapitalization(Qgis.Capitalization.AllSmallCaps) assert self.checkRender(format, 'all_small_caps', text=['Small Caps']) - assert False + + @unittest.skipIf(int(QT_VERSION_STR.split('.')[0]) < 6 or (int(QT_VERSION_STR.split('.')[0]) == 6 and int(QT_VERSION_STR.split('.')[1]) < 3), 'Too old Qt') + def testDrawStretch(self): + format = QgsTextFormat() + format.setFont(getTestFont('bold')) + format.setSize(30) + format.setStretchFactor(150) + assert self.checkRender(format, 'stretch_expand') + + @unittest.skipIf(int(QT_VERSION_STR.split('.')[0]) < 6 or (int(QT_VERSION_STR.split('.')[0]) == 6 and int(QT_VERSION_STR.split('.')[1]) < 3), 'Too old Qt') + def testDrawStretchCondense(self): + format = QgsTextFormat() + format.setFont(getTestFont('bold')) + format.setSize(30) + format.setStretchFactor(50) + assert self.checkRender(format, 'stretch_condense') def testDrawBackgroundDisabled(self): format = QgsTextFormat() diff --git a/tests/testdata/control_images/text_renderer/stretch_condense/stretch_condense.png b/tests/testdata/control_images/text_renderer/stretch_condense/stretch_condense.png new file mode 100644 index 00000000000..3f84bce9b51 Binary files /dev/null and b/tests/testdata/control_images/text_renderer/stretch_condense/stretch_condense.png differ diff --git a/tests/testdata/control_images/text_renderer/stretch_expand/stretch_expand.png b/tests/testdata/control_images/text_renderer/stretch_expand/stretch_expand.png new file mode 100644 index 00000000000..25758af82f7 Binary files /dev/null and b/tests/testdata/control_images/text_renderer/stretch_expand/stretch_expand.png differ