xref: /core/svx/source/core/graphichelper.cxx (revision e6211a4b3bd74f2bf392fd5cda7dea51552cd250)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  *   Licensed to the Apache Software Foundation (ASF) under one or more
12  *   contributor license agreements. See the NOTICE file distributed
13  *   with this work for additional information regarding copyright
14  *   ownership. The ASF licenses this file to you under the Apache
15  *   License, Version 2.0 (the "License"); you may not use this file
16  *   except in compliance with the License. You may obtain a copy of
17  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <vcl/graphicfilter.hxx>
21 #include <sfx2/docfile.hxx>
22 #include <sfx2/filedlghelper.hxx>
23 #include <svx/xoutbmp.hxx>
24 #include <svx/dialmgr.hxx>
25 #include <svx/graphichelper.hxx>
26 #include <svx/strings.hrc>
27 #include <comphelper/diagnose_ex.hxx>
28 #include <vcl/svapp.hxx>
29 #include <vcl/weld.hxx>
30 
31 #include <comphelper/processfactory.hxx>
32 #include <comphelper/propertyvalue.hxx>
33 
34 #include <com/sun/star/beans/XPropertySet.hpp>
35 #include <com/sun/star/beans/PropertyValue.hpp>
36 #include <com/sun/star/container/NoSuchElementException.hpp>
37 #include <com/sun/star/document/XExporter.hpp>
38 #include <com/sun/star/drawing/GraphicExportFilter.hpp>
39 #include <com/sun/star/drawing/XShape.hpp>
40 #include <com/sun/star/lang/XMultiServiceFactory.hpp>
41 #include <com/sun/star/lang/XComponent.hpp>
42 #include <com/sun/star/io/XInputStream.hpp>
43 #include <com/sun/star/ucb/SimpleFileAccess.hpp>
44 #include <com/sun/star/ui/dialogs/XFilePicker3.hpp>
45 #include <com/sun/star/ui/dialogs/TemplateDescription.hpp>
46 #include <com/sun/star/beans/XPropertyAccess.hpp>
47 #include <com/sun/star/task/ErrorCodeIOException.hpp>
48 #include <com/sun/star/graphic/XGraphic.hpp>
49 #include <com/sun/star/drawing/ShapeCollection.hpp>
50 
51 #include <map>
52 #include <frozen/bits/defines.h>
53 #include <frozen/bits/elsa_std.h>
54 #include <frozen/unordered_map.h>
55 
56 #include <unotools/streamwrap.hxx>
57 
58 using namespace css;
59 
60 namespace
61 {
62 
63 const auto constGfxTypeToExtension = frozen::make_unordered_map<GfxLinkType, OUString>(
64 {
65     { GfxLinkType::NativePng, u"png"_ustr },
66     { GfxLinkType::NativeGif, u"gif"_ustr },
67     { GfxLinkType::NativeTif, u"tif"_ustr },
68     { GfxLinkType::NativeWmf, u"wmf"_ustr },
69     { GfxLinkType::NativeMet, u"met"_ustr },
70     { GfxLinkType::NativePct, u"pct"_ustr },
71     { GfxLinkType::NativeJpg, u"jpg"_ustr },
72     { GfxLinkType::NativeBmp, u"bmp"_ustr },
73     { GfxLinkType::NativeSvg, u"svg"_ustr },
74     { GfxLinkType::NativePdf, u"pdf"_ustr },
75     { GfxLinkType::NativeWebp, u"webp"_ustr },
76 });
77 
78 const auto constVectorGraphicTypeToExtension = frozen::make_unordered_map<VectorGraphicDataType, OUString>(
79 {
80     { VectorGraphicDataType::Wmf, u"wmf"_ustr },
81     { VectorGraphicDataType::Emf, u"emf"_ustr },
82     { VectorGraphicDataType::Svg, u"svg"_ustr },
83 });
84 
85 const auto constGfxTypeToString = frozen::make_unordered_map<GfxLinkType, TranslateId>(
86 {
87     { GfxLinkType::NativePng, STR_IMAGE_PNG },
88     { GfxLinkType::NativeGif, STR_IMAGE_GIF },
89     { GfxLinkType::NativeTif, STR_IMAGE_TIFF },
90     { GfxLinkType::NativeWmf, STR_IMAGE_WMF },
91     { GfxLinkType::NativeMet, STR_IMAGE_MET },
92     { GfxLinkType::NativePct, STR_IMAGE_PCT },
93     { GfxLinkType::NativeJpg, STR_IMAGE_JPEG },
94     { GfxLinkType::NativeBmp, STR_IMAGE_BMP },
95     { GfxLinkType::NativeSvg, STR_IMAGE_SVG },
96     { GfxLinkType::NativePdf, STR_IMAGE_PNG },
97     { GfxLinkType::NativeWebp, STR_IMAGE_WEBP },
98 });
99 
100 } // end anonymous ns
101 
GetPreferredExtension(OUString & rExtension,const Graphic & rGraphic)102 void GraphicHelper::GetPreferredExtension( OUString& rExtension, const Graphic& rGraphic )
103 {
104     auto const & rVectorGraphicDataPtr(rGraphic.getVectorGraphicData());
105 
106     if (rVectorGraphicDataPtr && !rVectorGraphicDataPtr->getBinaryDataContainer().isEmpty())
107     {
108         auto eType = rVectorGraphicDataPtr->getType();
109         const auto iter = constVectorGraphicTypeToExtension.find(eType);
110         if (iter != constVectorGraphicTypeToExtension.end())
111             rExtension = iter->second;
112         else
113             rExtension = u"svg"_ustr; // not sure this makes sense but it is like this for a long time
114     }
115     else
116     {
117         auto eType = rGraphic.GetGfxLink().GetType();
118         const auto iter = constGfxTypeToExtension.find(eType);
119         if (iter != constGfxTypeToExtension.end())
120             rExtension = iter->second;
121         else
122             rExtension = u"png"_ustr; // not sure this makes sense but it is like this for a long time
123     }
124 }
125 
GetImageType(const Graphic & rGraphic)126 OUString GraphicHelper::GetImageType(const Graphic& rGraphic)
127 {
128     const auto& pGfxLink = rGraphic.GetSharedGfxLink();
129     if (pGfxLink)
130     {
131         auto iter = constGfxTypeToString.find(pGfxLink->GetType());
132         if (iter != constGfxTypeToString.end())
133             return SvxResId(iter->second);
134     }
135     return SvxResId(STR_IMAGE_UNKNOWN);
136 }
137 
138 namespace {
139 
140 
lcl_ExecuteFilterDialog(const uno::Sequence<beans::PropertyValue> & rPropsForDialog,uno::Sequence<beans::PropertyValue> & rFilterData)141 bool lcl_ExecuteFilterDialog(const uno::Sequence<beans::PropertyValue>& rPropsForDialog,
142                              uno::Sequence<beans::PropertyValue>& rFilterData)
143 {
144     bool bStatus = false;
145     try
146     {
147         uno::Reference<ui::dialogs::XExecutableDialog> xFilterDialog(
148                 comphelper::getProcessServiceFactory()->createInstance( u"com.sun.star.svtools.SvFilterOptionsDialog"_ustr ), uno::UNO_QUERY);
149         uno::Reference<beans::XPropertyAccess> xFilterProperties( xFilterDialog, uno::UNO_QUERY);
150 
151         if( xFilterDialog.is() && xFilterProperties.is() )
152         {
153             xFilterProperties->setPropertyValues( rPropsForDialog );
154             if( xFilterDialog->execute() )
155             {
156                 bStatus = true;
157                 const uno::Sequence<beans::PropertyValue> aPropsFromDialog = xFilterProperties->getPropertyValues();
158                 for ( const auto& rProp : aPropsFromDialog )
159                 {
160                     if (rProp.Name == "FilterData")
161                     {
162                         rProp.Value >>= rFilterData;
163                     }
164                 }
165             }
166         }
167     }
168     catch (container::NoSuchElementException const& exception)
169     {
170         // the filter name is unknown
171         throw task::ErrorCodeIOException(
172             ("lcl_ExecuteFilterDialog: NoSuchElementException"
173              " \"" + exception.Message + "\": ERRCODE_IO_ABORT"),
174             uno::Reference<uno::XInterface>(), sal_uInt32(ERRCODE_IO_INVALIDPARAMETER));
175     }
176     catch (const task::ErrorCodeIOException&)
177     {
178         throw;
179     }
180     catch (const uno::Exception&)
181     {
182         TOOLS_WARN_EXCEPTION("sfx.doc", "ignoring");
183     }
184 
185     return bStatus;
186 }
187 } // anonymous ns
188 
ExportGraphic(weld::Window * pParent,const Graphic & rGraphic,const OUString & rGraphicName)189 OUString GraphicHelper::ExportGraphic(weld::Window* pParent, const Graphic& rGraphic, const OUString& rGraphicName)
190 {
191     sfx2::FileDialogHelper aDialogHelper(ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION, FileDialogFlags::NONE, pParent);
192     uno::Reference<ui::dialogs::XFilePicker3> xFilePicker = aDialogHelper.GetFilePicker();
193 
194     // fish out the graphic's name
195     aDialogHelper.SetContext(sfx2::FileDialogHelper::ExportImage);
196     aDialogHelper.SetTitle( SvxResId(RID_SVXSTR_EXPORT_GRAPHIC_TITLE));
197     INetURLObject aURL;
198     aURL.SetSmartURL( rGraphicName );
199     aDialogHelper.SetFileName(aURL.GetLastName());
200 
201     GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
202     const sal_uInt16 nCount = rGraphicFilter.GetExportFormatCount();
203 
204     OUString aExtension(aURL.GetFileExtension());
205     if( aExtension.isEmpty() )
206     {
207         GetPreferredExtension( aExtension, rGraphic );
208     }
209 
210     aExtension = aExtension.toAsciiLowerCase();
211     sal_uInt16 nDefaultFilter = USHRT_MAX;
212 
213     for ( sal_uInt16 i = 0; i < nCount; i++ )
214     {
215         xFilePicker->appendFilter( rGraphicFilter.GetExportFormatName( i ), rGraphicFilter.GetExportWildcard( i ) );
216         OUString aFormatShortName = rGraphicFilter.GetExportFormatShortName( i );
217         if ( aFormatShortName.equalsIgnoreAsciiCase( aExtension ) )
218         {
219             nDefaultFilter = i;
220         }
221     }
222     if ( USHRT_MAX == nDefaultFilter )
223     {
224         // "wrong" extension?
225         GetPreferredExtension( aExtension, rGraphic );
226         for ( sal_uInt16 i = 0; i < nCount; ++i )
227             if ( aExtension == rGraphicFilter.GetExportFormatShortName( i ).toAsciiLowerCase() )
228             {
229                 nDefaultFilter =  i;
230                 break;
231             }
232     }
233 
234     if( USHRT_MAX != nDefaultFilter )
235     {
236         xFilePicker->setCurrentFilter( rGraphicFilter.GetExportFormatName( nDefaultFilter ) ) ;
237 
238         if( aDialogHelper.Execute() == ERRCODE_NONE )
239         {
240             OUString sPath(xFilePicker->getSelectedFiles()[0]);
241             if( !rGraphicName.isEmpty() &&
242                 nDefaultFilter == rGraphicFilter.GetExportFormatNumber( xFilePicker->getCurrentFilter()))
243             {
244                 // try to save the original graphic
245                 SfxMedium aIn( rGraphicName, StreamMode::READ | StreamMode::NOCREATE );
246                 if( aIn.GetInStream() && !aIn.GetInStream()->GetError() )
247                 {
248                     SfxMedium aOut( sPath, StreamMode::WRITE | StreamMode::SHARE_DENYNONE);
249                     if( aOut.GetOutStream() && !aOut.GetOutStream()->GetError())
250                     {
251                         aOut.GetOutStream()->WriteStream( *aIn.GetInStream() );
252                         if ( ERRCODE_NONE == aIn.GetErrorIgnoreWarning() )
253                         {
254                             aOut.Close();
255                             aOut.Commit();
256                             if ( ERRCODE_NONE == aOut.GetErrorIgnoreWarning() )
257                                 return sPath;
258                         }
259                     }
260                 }
261             }
262 
263             sal_uInt16 nFilter;
264             if ( !xFilePicker->getCurrentFilter().isEmpty() && rGraphicFilter.GetExportFormatCount() )
265             {
266                 nFilter = rGraphicFilter.GetExportFormatNumber( xFilePicker->getCurrentFilter() );
267             }
268             else
269             {
270                 nFilter = GRFILTER_FORMAT_DONTKNOW;
271             }
272             OUString aFilter( rGraphicFilter.GetExportFormatShortName( nFilter ) );
273 
274             if ( rGraphic.GetType() == GraphicType::Bitmap )
275             {
276                 Graphic aGraphic = rGraphic;
277                 uno::Reference<graphic::XGraphic> xGraphic = aGraphic.GetXGraphic();
278 
279                 OUString aExportFilter = rGraphicFilter.GetExportInternalFilterName(nFilter);
280 
281                 uno::Sequence<beans::PropertyValue> aPropsForDialog
282                 {
283                     comphelper::makePropertyValue(u"Graphic"_ustr, xGraphic),
284                     comphelper::makePropertyValue(u"FilterName"_ustr, aExportFilter)
285                 };
286 
287                 uno::Sequence<beans::PropertyValue> aFilterData;
288                 bool bStatus = lcl_ExecuteFilterDialog(aPropsForDialog, aFilterData);
289                 if (bStatus)
290                 {
291                     sal_Int32 nWidth = 0;
292                     sal_Int32 nHeight = 0;
293 
294                     for (const auto& rProp : aFilterData)
295                     {
296                         if (rProp.Name == "PixelWidth")
297                         {
298                             rProp.Value >>= nWidth;
299                         }
300                         else if (rProp.Name == "PixelHeight")
301                         {
302                             rProp.Value >>= nHeight;
303                         }
304                     }
305 
306                     // scaling must performed here because png/jpg writer s
307                     // do not take care of that.
308                     Size aSizePixel( aGraphic.GetSizePixel() );
309                     if( nWidth && nHeight &&
310                         ( ( nWidth != aSizePixel.Width() ) ||
311                           ( nHeight != aSizePixel.Height() ) ) )
312                     {
313                         Bitmap aBmp( aGraphic.GetBitmap() );
314                         // export: use highest quality
315                         aBmp.Scale( Size( nWidth, nHeight ), BmpScaleFlag::Lanczos );
316                         aGraphic = aBmp;
317                     }
318 
319                     XOutBitmap::WriteGraphic( aGraphic, sPath, aFilter,
320                                                 XOutFlags::DontExpandFilename |
321                                                 XOutFlags::DontAddExtension |
322                                                 XOutFlags::UseNativeIfPossible,
323                                                 nullptr, &aFilterData );
324                     return sPath;
325                 }
326             }
327             else
328             {
329                 XOutBitmap::WriteGraphic( rGraphic, sPath, aFilter,
330                                             XOutFlags::DontExpandFilename |
331                                             XOutFlags::DontAddExtension |
332                                             XOutFlags::UseNativeIfPossible );
333             }
334         }
335     }
336     return OUString();
337 }
338 
SaveShapeAsGraphicToPath(const uno::Reference<lang::XComponent> & xComponent,const uno::Reference<drawing::XShape> & xShape,const OUString & aExportMimeType,const OUString & sPath)339 void GraphicHelper::SaveShapeAsGraphicToPath(
340     const uno::Reference<lang::XComponent>& xComponent,
341     const uno::Reference<drawing::XShape>& xShape, const OUString& aExportMimeType,
342     const OUString& sPath)
343 {
344     const uno::Reference<uno::XComponentContext>& xContext(::comphelper::getProcessComponentContext());
345     uno::Reference<io::XInputStream> xGraphStream;
346 
347     if (xGraphStream.is())
348     {
349         uno::Reference<ucb::XSimpleFileAccess3> xFileAccess = ucb::SimpleFileAccess::create(xContext);
350         xFileAccess->writeFile(sPath, xGraphStream);
351     }
352     else if (xComponent.is() && aExportMimeType == "application/pdf")
353     {
354         uno::Reference<lang::XMultiServiceFactory> xMSF(xContext->getServiceManager(), uno::UNO_QUERY);
355         uno::Reference<document::XExporter> xExporter(
356             xMSF->createInstance(u"com.sun.star.comp.PDF.PDFFilter"_ustr), uno::UNO_QUERY);
357         xExporter->setSourceDocument(xComponent);
358 
359         uno::Reference<drawing::XShapes> xShapes
360             = drawing::ShapeCollection::create(comphelper::getProcessComponentContext());
361         xShapes->add(xShape);
362         uno::Sequence<beans::PropertyValue> aFilterData{
363             comphelper::makePropertyValue(u"Selection"_ustr, xShapes),
364         };
365         SvFileStream aStream(sPath, StreamMode::READWRITE | StreamMode::TRUNC);
366         uno::Reference<io::XOutputStream> xStream(new utl::OStreamWrapper(aStream));
367         uno::Sequence<beans::PropertyValue> aDescriptor
368         {
369             comphelper::makePropertyValue(u"FilterData"_ustr, aFilterData),
370             comphelper::makePropertyValue(u"OutputStream"_ustr, xStream)
371         };
372         uno::Reference<document::XFilter> xFilter(xExporter, uno::UNO_QUERY);
373         xFilter->filter(aDescriptor);
374     }
375     else
376     {
377         uno::Reference<drawing::XGraphicExportFilter> xGraphicExporter = drawing::GraphicExportFilter::create(xContext);
378 
379         uno::Sequence<beans::PropertyValue> aDescriptor{ comphelper::makePropertyValue(u"MediaType"_ustr,
380                                                                            aExportMimeType),
381                                              comphelper::makePropertyValue(u"URL"_ustr, sPath) };
382 
383         uno::Reference<lang::XComponent> xSourceDocument(xShape, uno::UNO_QUERY_THROW);
384         xGraphicExporter->setSourceDocument(xSourceDocument);
385         xGraphicExporter->filter(aDescriptor);
386     }
387 }
388 
SaveShapeAsGraphic(weld::Window * pParent,const uno::Reference<lang::XComponent> & xComponent,const uno::Reference<drawing::XShape> & xShape)389 void GraphicHelper::SaveShapeAsGraphic(weld::Window* pParent,
390                                        const uno::Reference<lang::XComponent>& xComponent,
391                                        const uno::Reference<drawing::XShape>& xShape)
392 {
393     try
394     {
395         uno::Reference<beans::XPropertySet> xShapeSet(xShape, uno::UNO_QUERY_THROW);
396 
397         sfx2::FileDialogHelper aDialogHelper(ui::dialogs::TemplateDescription::FILESAVE_AUTOEXTENSION, FileDialogFlags::NONE, pParent);
398         uno::Reference<ui::dialogs::XFilePicker3> xFilePicker = aDialogHelper.GetFilePicker();
399         aDialogHelper.SetContext(sfx2::FileDialogHelper::ExportImage);
400         aDialogHelper.SetTitle( SvxResId(RID_SVXSTR_SAVEAS_IMAGE) );
401 
402         // populate filter dialog filter list and select default filter to match graphic mime type
403 
404         GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter();
405         static constexpr OUStringLiteral aDefaultMimeType(u"image/png");
406         OUString aDefaultFormatName;
407         sal_uInt16 nCount = rGraphicFilter.GetExportFormatCount();
408 
409         std::map< OUString, OUString > aMimeTypeMap;
410 
411         for ( sal_uInt16 i = 0; i < nCount; i++ )
412         {
413             const OUString aExportFormatName( rGraphicFilter.GetExportFormatName( i ) );
414             const OUString aFilterMimeType( rGraphicFilter.GetExportFormatMediaType( i ) );
415             xFilePicker->appendFilter( aExportFormatName, rGraphicFilter.GetExportWildcard( i ) );
416             aMimeTypeMap[ aExportFormatName ] = aFilterMimeType;
417             if( aDefaultMimeType == aFilterMimeType )
418                 aDefaultFormatName = aExportFormatName;
419         }
420 
421         if( !aDefaultFormatName.isEmpty() )
422             xFilePicker->setCurrentFilter( aDefaultFormatName );
423 
424         // execute dialog
425 
426         if( aDialogHelper.Execute() == ERRCODE_NONE )
427         {
428             OUString sPath(xFilePicker->getSelectedFiles()[0]);
429             OUString aExportMimeType( aMimeTypeMap[xFilePicker->getCurrentFilter()] );
430 
431             GraphicHelper::SaveShapeAsGraphicToPath(xComponent, xShape, aExportMimeType, sPath);
432         }
433     }
434     catch (uno::Exception&)
435     {
436     }
437 }
438 
HasToSaveTransformedImage(weld::Widget * pWin)439 short GraphicHelper::HasToSaveTransformedImage(weld::Widget* pWin)
440 {
441     OUString aMsg(SvxResId(RID_SVXSTR_SAVE_MODIFIED_IMAGE));
442     std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pWin,
443                                               VclMessageType::Question, VclButtonsType::YesNo, aMsg));
444     return xBox->run();
445 }
446 
447 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
448