/src/libreoffice/svx/source/xoutdev/_xoutbmp.cxx
Line | Count | Source |
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 <sal/config.h> |
21 | | #include <sal/log.hxx> |
22 | | |
23 | | #include <comphelper/base64.hxx> |
24 | | #include <comphelper/graphicmimetype.hxx> |
25 | | #include <tools/debug.hxx> |
26 | | #include <vcl/virdev.hxx> |
27 | | #include <sfx2/docfile.hxx> |
28 | | #include <svx/svdpntv.hxx> |
29 | | #include <svx/xoutbmp.hxx> |
30 | | #include <vcl/graphicfilter.hxx> |
31 | | #include <vcl/cvtgrf.hxx> |
32 | | #include <vcl/gdimtf.hxx> |
33 | | #include <vcl/svapp.hxx> |
34 | | |
35 | | #include <UnoGraphicExporter.hxx> |
36 | | |
37 | | #include <memory> |
38 | | |
39 | | #include <com/sun/star/beans/XPropertySet.hpp> |
40 | | |
41 | | constexpr OUStringLiteral FORMAT_SVG = u"svg"; |
42 | | constexpr OUStringLiteral FORMAT_WMF = u"wmf"; |
43 | | constexpr OUString FORMAT_EMF = u"emf"_ustr; |
44 | | constexpr OUStringLiteral FORMAT_PDF = u"pdf"; |
45 | | |
46 | | constexpr OUString FORMAT_BMP = u"bmp"_ustr; |
47 | | constexpr OUString FORMAT_GIF = u"gif"_ustr; |
48 | | constexpr OUStringLiteral FORMAT_JPG = u"jpg"; |
49 | | constexpr OUString FORMAT_PNG = u"png"_ustr; |
50 | | constexpr OUStringLiteral FORMAT_TIF = u"tif"; |
51 | | constexpr OUStringLiteral FORMAT_WEBP = u"webp"; |
52 | | |
53 | | using namespace com::sun::star; |
54 | | |
55 | | Animation XOutBitmap::MirrorAnimation( const Animation& rAnimation, bool bHMirr, bool bVMirr ) |
56 | 0 | { |
57 | 0 | Animation aNewAnim( rAnimation ); |
58 | |
|
59 | 0 | if( bHMirr || bVMirr ) |
60 | 0 | { |
61 | 0 | const Size& rGlobalSize = aNewAnim.GetDisplaySizePixel(); |
62 | 0 | BmpMirrorFlags nMirrorFlags = BmpMirrorFlags::NONE; |
63 | |
|
64 | 0 | if( bHMirr ) |
65 | 0 | nMirrorFlags |= BmpMirrorFlags::Horizontal; |
66 | |
|
67 | 0 | if( bVMirr ) |
68 | 0 | nMirrorFlags |= BmpMirrorFlags::Vertical; |
69 | |
|
70 | 0 | for( sal_uInt16 i = 0, nCount = aNewAnim.Count(); i < nCount; i++ ) |
71 | 0 | { |
72 | 0 | AnimationFrame aAnimationFrame( aNewAnim.Get( i ) ); |
73 | | |
74 | | // mirror the Bitmap |
75 | 0 | aAnimationFrame.maBitmap.Mirror( nMirrorFlags ); |
76 | | |
77 | | // Adjust the positions inside the whole bitmap |
78 | 0 | if( bHMirr ) |
79 | 0 | aAnimationFrame.maPositionPixel.setX(rGlobalSize.Width() - aAnimationFrame.maPositionPixel.X() - |
80 | 0 | aAnimationFrame.maSizePixel.Width()); |
81 | |
|
82 | 0 | if( bVMirr ) |
83 | 0 | aAnimationFrame.maPositionPixel.setY(rGlobalSize.Height() - aAnimationFrame.maPositionPixel.Y() - |
84 | 0 | aAnimationFrame.maSizePixel.Height()); |
85 | |
|
86 | 0 | aNewAnim.Replace(aAnimationFrame, i); |
87 | 0 | } |
88 | 0 | } |
89 | |
|
90 | 0 | return aNewAnim; |
91 | 0 | } |
92 | | |
93 | | Graphic XOutBitmap::MirrorGraphic( const Graphic& rGraphic, const BmpMirrorFlags nMirrorFlags ) |
94 | 0 | { |
95 | 0 | Graphic aRetGraphic; |
96 | |
|
97 | 0 | if( nMirrorFlags != BmpMirrorFlags::NONE ) |
98 | 0 | { |
99 | 0 | if( rGraphic.IsAnimated() ) |
100 | 0 | { |
101 | 0 | aRetGraphic = MirrorAnimation( rGraphic.GetAnimation(), |
102 | 0 | bool( nMirrorFlags & BmpMirrorFlags::Horizontal ), |
103 | 0 | bool( nMirrorFlags & BmpMirrorFlags::Vertical ) ); |
104 | 0 | } |
105 | 0 | else |
106 | 0 | { |
107 | 0 | Bitmap aBmp( rGraphic.GetBitmap() ); |
108 | 0 | aBmp.Mirror( nMirrorFlags ); |
109 | 0 | aRetGraphic = aBmp; |
110 | 0 | } |
111 | 0 | } |
112 | 0 | else |
113 | 0 | aRetGraphic = rGraphic; |
114 | |
|
115 | 0 | return aRetGraphic; |
116 | 0 | } |
117 | | |
118 | | static OUString match(std::u16string_view filter, const OUString& expected, bool matchEmpty = true) |
119 | 0 | { |
120 | 0 | return (matchEmpty && filter.empty()) || expected.equalsIgnoreAsciiCase(filter) ? expected |
121 | 0 | : OUString(); |
122 | 0 | } |
123 | | |
124 | | static OUString isKnownVectorFormat(const Graphic& rGraphic, std::u16string_view rFilter) |
125 | 0 | { |
126 | 0 | const auto& pData(rGraphic.getVectorGraphicData()); |
127 | 0 | if (!pData || pData->getBinaryDataContainer().getSize() == 0) |
128 | 0 | return {}; |
129 | | |
130 | 0 | if (FORMAT_EMF.equalsIgnoreAsciiCase(rFilter) |
131 | 0 | && (pData->getType() == VectorGraphicDataType::Emf || rGraphic.GetGfxLink().IsEMF())) |
132 | 0 | return FORMAT_EMF; |
133 | | |
134 | | // Does the filter name match the original format? |
135 | 0 | switch (pData->getType()) |
136 | 0 | { |
137 | 0 | case VectorGraphicDataType::Svg: |
138 | 0 | return match(rFilter, FORMAT_SVG, false); |
139 | 0 | case VectorGraphicDataType::Wmf: |
140 | 0 | return match(rFilter, FORMAT_WMF, false); |
141 | 0 | case VectorGraphicDataType::Emf: |
142 | 0 | break; |
143 | 0 | case VectorGraphicDataType::Pdf: |
144 | 0 | return match(rFilter, FORMAT_PDF, false); |
145 | 0 | } |
146 | | |
147 | 0 | return {}; |
148 | 0 | } |
149 | | |
150 | | static OUString isKnownRasterFormat(const GfxLink& rLink, std::u16string_view rFilter) |
151 | 0 | { |
152 | | // tdf#60684: use native format if possible but it must correspond to filter name |
153 | | // or no specific format has been required |
154 | | // without this, you may save for example file with png extension but jpg content |
155 | 0 | switch (rLink.GetType()) |
156 | 0 | { |
157 | 0 | case GfxLinkType::NativeGif: |
158 | 0 | return match(rFilter, FORMAT_GIF); |
159 | | |
160 | | // #i15508# added BMP type for better exports (no call/trigger found, prob used in HTML export) |
161 | 0 | case GfxLinkType::NativeBmp: |
162 | 0 | return match(rFilter, FORMAT_BMP); |
163 | | |
164 | 0 | case GfxLinkType::NativeJpg: |
165 | 0 | return match(rFilter, FORMAT_JPG); |
166 | 0 | case GfxLinkType::NativePng: |
167 | 0 | return match(rFilter, FORMAT_PNG); |
168 | 0 | case GfxLinkType::NativeTif: |
169 | 0 | return match(rFilter, FORMAT_TIF); |
170 | 0 | case GfxLinkType::NativeWebp: |
171 | 0 | return match(rFilter, FORMAT_WEBP); |
172 | 0 | default: |
173 | 0 | return {}; |
174 | 0 | } |
175 | 0 | } |
176 | | |
177 | | ErrCode XOutBitmap::WriteGraphic( const Graphic& rGraphic, OUString& rFileName, |
178 | | const OUString& rFilterName, const XOutFlags nFlags, |
179 | | const Size* pMtfSize_100TH_MM, |
180 | | const css::uno::Sequence< css::beans::PropertyValue >* pFilterData, |
181 | | OUString* pMediaType ) |
182 | 0 | { |
183 | 0 | if( rGraphic.GetType() == GraphicType::NONE ) |
184 | 0 | return ERRCODE_NONE; |
185 | | |
186 | 0 | INetURLObject aURL( rFileName ); |
187 | 0 | GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); |
188 | |
|
189 | 0 | DBG_ASSERT( aURL.GetProtocol() != INetProtocol::NotValid, "XOutBitmap::WriteGraphic(...): invalid URL" ); |
190 | | |
191 | | // calculate correct file name |
192 | 0 | if( !( nFlags & XOutFlags::DontExpandFilename ) ) |
193 | 0 | { |
194 | 0 | OUString aStr( OUString::number( rGraphic.GetChecksum(), 16 ) ); |
195 | 0 | if ( aStr[0] == '-' ) |
196 | 0 | aStr = OUString::Concat("m") + aStr.subView(1); |
197 | 0 | OUString aName = aURL.getBase() + "_" + aURL.getExtension() + "_" + aStr; |
198 | 0 | aURL.setBase( aName ); |
199 | 0 | } |
200 | | |
201 | | // #i121128# use shortcut to write Vector Graphic Data data in original form (if possible) |
202 | 0 | if (OUString aExt = isKnownVectorFormat(rGraphic, rFilterName); !aExt.isEmpty()) |
203 | 0 | { |
204 | 0 | if (!(nFlags & XOutFlags::DontAddExtension)) |
205 | 0 | aURL.setExtension(aExt); |
206 | |
|
207 | 0 | rFileName = aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE); |
208 | 0 | if (pMediaType) |
209 | 0 | if (auto xGraphic = rGraphic.GetXGraphic().query<css::beans::XPropertySet>()) |
210 | 0 | xGraphic->getPropertyValue(u"MimeType"_ustr) >>= *pMediaType; |
211 | |
|
212 | 0 | SfxMedium aMedium(aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE), StreamMode::WRITE | StreamMode::SHARE_DENYNONE | StreamMode::TRUNC); |
213 | 0 | SvStream* pOStm = aMedium.GetOutStream(); |
214 | |
|
215 | 0 | if (pOStm) |
216 | 0 | { |
217 | 0 | rGraphic.getVectorGraphicData()->getBinaryDataContainer().writeToStream(*pOStm); |
218 | 0 | aMedium.Commit(); |
219 | |
|
220 | 0 | if (!aMedium.GetErrorIgnoreWarning()) |
221 | 0 | return ERRCODE_NONE; |
222 | 0 | } |
223 | 0 | } |
224 | | |
225 | 0 | if( ( nFlags & XOutFlags::UseNativeIfPossible ) && |
226 | 0 | !( nFlags & XOutFlags::MirrorHorz ) && |
227 | 0 | !( nFlags & XOutFlags::MirrorVert ) && |
228 | 0 | ( rGraphic.GetType() != GraphicType::GdiMetafile ) && rGraphic.IsGfxLink() ) |
229 | 0 | { |
230 | | // try to write native link |
231 | 0 | const GfxLink aGfxLink( rGraphic.GetGfxLink() ); |
232 | 0 | if (OUString aExt = isKnownRasterFormat(aGfxLink, rFilterName); !aExt.isEmpty()) |
233 | 0 | { |
234 | 0 | if( !(nFlags & XOutFlags::DontAddExtension) ) |
235 | 0 | aURL.setExtension( aExt ); |
236 | 0 | rFileName = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ); |
237 | 0 | if (pMediaType) |
238 | 0 | if (auto xGraphic = rGraphic.GetXGraphic().query<css::beans::XPropertySet>()) |
239 | 0 | xGraphic->getPropertyValue(u"MimeType"_ustr) >>= *pMediaType; |
240 | |
|
241 | 0 | SfxMedium aMedium(aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE), StreamMode::WRITE | StreamMode::SHARE_DENYNONE | StreamMode::TRUNC); |
242 | 0 | SvStream* pOStm = aMedium.GetOutStream(); |
243 | |
|
244 | 0 | if( pOStm && aGfxLink.GetDataSize() && aGfxLink.GetData() ) |
245 | 0 | { |
246 | 0 | pOStm->WriteBytes(aGfxLink.GetData(), aGfxLink.GetDataSize()); |
247 | 0 | aMedium.Commit(); |
248 | |
|
249 | 0 | if( !aMedium.GetErrorIgnoreWarning() ) |
250 | 0 | return ERRCODE_NONE; |
251 | 0 | } |
252 | 0 | } |
253 | 0 | } |
254 | | |
255 | 0 | OUString aFilter( rFilterName ); |
256 | 0 | bool bTransparent = rGraphic.IsTransparent(), bAnimated = rGraphic.IsAnimated(); |
257 | 0 | bool bWriteTransGrf = ( aFilter.equalsIgnoreAsciiCase( "transgrf" ) ) || |
258 | 0 | ( aFilter.equalsIgnoreAsciiCase( "gif" ) ) || |
259 | 0 | ( nFlags & XOutFlags::UseGifIfPossible ) || |
260 | 0 | ( ( nFlags & XOutFlags::UseGifIfSensible ) && ( bAnimated || bTransparent ) ); |
261 | | |
262 | | // get filter and extension |
263 | 0 | if( bWriteTransGrf ) |
264 | 0 | aFilter = FORMAT_GIF; |
265 | |
|
266 | 0 | sal_uInt16 nFilter = rFilter.GetExportFormatNumberForShortName( aFilter ); |
267 | |
|
268 | 0 | if( GRFILTER_FORMAT_NOTFOUND == nFilter ) |
269 | 0 | { |
270 | 0 | nFilter = rFilter.GetExportFormatNumberForShortName( FORMAT_PNG ); |
271 | |
|
272 | 0 | if( GRFILTER_FORMAT_NOTFOUND == nFilter ) |
273 | 0 | nFilter = rFilter.GetExportFormatNumberForShortName( FORMAT_BMP ); |
274 | 0 | } |
275 | |
|
276 | 0 | if( GRFILTER_FORMAT_NOTFOUND != nFilter ) |
277 | 0 | { |
278 | 0 | Graphic aGraphic; |
279 | |
|
280 | 0 | if (bAnimated) |
281 | 0 | aGraphic = rGraphic; |
282 | 0 | else if (rGraphic.GetType() == GraphicType::GdiMetafile |
283 | 0 | && rGraphic.GetGDIMetaFile().GetActionSize()) |
284 | 0 | { |
285 | 0 | Size aSize; |
286 | 0 | const Size* pSize = nullptr; |
287 | 0 | if (pMtfSize_100TH_MM) |
288 | 0 | { |
289 | 0 | aSize = Application::GetDefaultDevice()->LogicToPixel(*pMtfSize_100TH_MM, |
290 | 0 | MapMode(MapUnit::Map100thMM)); |
291 | 0 | pSize = &aSize; |
292 | 0 | } |
293 | 0 | aGraphic = GetBitmapFromMetaFile(rGraphic.GetGDIMetaFile(), pSize); |
294 | 0 | } |
295 | 0 | else if (pMtfSize_100TH_MM && (rGraphic.GetType() != GraphicType::Bitmap)) |
296 | 0 | { |
297 | 0 | if (bWriteTransGrf) |
298 | 0 | { |
299 | 0 | ScopedVclPtrInstance< VirtualDevice > pVDev(DeviceFormat::WITH_ALPHA); |
300 | 0 | const Size aSize(pVDev->LogicToPixel(*pMtfSize_100TH_MM, MapMode(MapUnit::Map100thMM))); |
301 | |
|
302 | 0 | if( pVDev->SetOutputSizePixel( aSize ) ) |
303 | 0 | { |
304 | 0 | rGraphic.Draw(*pVDev, Point(), aSize); |
305 | 0 | aGraphic = pVDev->GetBitmap( Point(), aSize ); |
306 | 0 | } |
307 | 0 | else |
308 | 0 | aGraphic = rGraphic.GetBitmap(); |
309 | 0 | } |
310 | 0 | else |
311 | 0 | { |
312 | 0 | ScopedVclPtrInstance< VirtualDevice > pVDev; |
313 | 0 | const Size aSize(pVDev->LogicToPixel(*pMtfSize_100TH_MM, MapMode(MapUnit::Map100thMM))); |
314 | |
|
315 | 0 | if( pVDev->SetOutputSizePixel( aSize ) ) |
316 | 0 | { |
317 | 0 | rGraphic.Draw(*pVDev, Point(), aSize); |
318 | 0 | aGraphic = pVDev->GetBitmap(Point(), aSize); |
319 | 0 | } |
320 | 0 | else |
321 | 0 | aGraphic = rGraphic.GetBitmap(); |
322 | 0 | } |
323 | 0 | } |
324 | 0 | else |
325 | 0 | aGraphic = rGraphic.GetBitmap(); |
326 | | |
327 | | // mirror? |
328 | 0 | if( ( nFlags & XOutFlags::MirrorHorz ) || ( nFlags & XOutFlags::MirrorVert ) ) |
329 | 0 | { |
330 | 0 | BmpMirrorFlags nBmpMirrorFlags = BmpMirrorFlags::NONE; |
331 | 0 | if( nFlags & XOutFlags::MirrorHorz ) |
332 | 0 | nBmpMirrorFlags |= BmpMirrorFlags::Horizontal; |
333 | 0 | if( nFlags & XOutFlags::MirrorVert ) |
334 | 0 | nBmpMirrorFlags |= BmpMirrorFlags::Vertical; |
335 | 0 | aGraphic = MirrorGraphic( aGraphic, nBmpMirrorFlags ); |
336 | 0 | } |
337 | |
|
338 | 0 | if (aGraphic.GetType() != GraphicType::NONE) |
339 | 0 | { |
340 | 0 | if( !(nFlags & XOutFlags::DontAddExtension) ) |
341 | 0 | aURL.setExtension(rFilter.GetExportFormatShortName(nFilter).toAsciiLowerCase()); |
342 | 0 | rFileName = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ); |
343 | 0 | if (pMediaType) |
344 | 0 | *pMediaType = rFilter.GetExportFormatMediaType(nFilter); |
345 | 0 | return ExportGraphic( aGraphic, aURL, rFilter, nFilter, pFilterData ); |
346 | 0 | } |
347 | 0 | } |
348 | | |
349 | 0 | return ERRCODE_GRFILTER_FILTERERROR; |
350 | 0 | } |
351 | | |
352 | | bool XOutBitmap::GraphicToBase64(const Graphic& rGraphic, OUString& rOUString, bool bAddPrefix, |
353 | | ConvertDataFormat aTargetFormat) |
354 | 0 | { |
355 | 0 | SvMemoryStream aOStm; |
356 | 0 | GfxLink aLink = rGraphic.GetGfxLink(); |
357 | |
|
358 | 0 | if (aTargetFormat == ConvertDataFormat::Unknown) |
359 | 0 | { |
360 | 0 | switch (aLink.GetType()) |
361 | 0 | { |
362 | 0 | case GfxLinkType::NativeJpg: |
363 | 0 | aTargetFormat = ConvertDataFormat::JPG; |
364 | 0 | break; |
365 | 0 | case GfxLinkType::NativePng: |
366 | 0 | aTargetFormat = ConvertDataFormat::PNG; |
367 | 0 | break; |
368 | 0 | case GfxLinkType::NativeSvg: |
369 | 0 | aTargetFormat = ConvertDataFormat::SVG; |
370 | 0 | break; |
371 | 0 | default: |
372 | | // save everything else (including gif) into png |
373 | 0 | aTargetFormat = ConvertDataFormat::PNG; |
374 | 0 | break; |
375 | 0 | } |
376 | 0 | } |
377 | | |
378 | 0 | ErrCode nErr = GraphicConverter::Export(aOStm,rGraphic,aTargetFormat); |
379 | 0 | if ( nErr ) |
380 | 0 | { |
381 | 0 | SAL_WARN("svx", "XOutBitmap::GraphicToBase64() invalid Graphic? error: " << nErr ); |
382 | 0 | return false; |
383 | 0 | } |
384 | 0 | css::uno::Sequence<sal_Int8> aOStmSeq( static_cast<sal_Int8 const *>(aOStm.GetData()),aOStm.TellEnd() ); |
385 | 0 | OUStringBuffer aStrBuffer; |
386 | 0 | ::comphelper::Base64::encode(aStrBuffer,aOStmSeq); |
387 | 0 | rOUString = aStrBuffer.makeStringAndClear(); |
388 | |
|
389 | 0 | if (bAddPrefix) |
390 | 0 | { |
391 | 0 | OUString aMimeType |
392 | 0 | = comphelper::GraphicMimeTypeHelper::GetMimeTypeForConvertDataFormat(aTargetFormat); |
393 | 0 | rOUString = aMimeType + ";base64," + rOUString; |
394 | 0 | } |
395 | |
|
396 | 0 | return true; |
397 | 0 | } |
398 | | |
399 | | ErrCode XOutBitmap::ExportGraphic( const Graphic& rGraphic, const INetURLObject& rURL, |
400 | | GraphicFilter& rFilter, const sal_uInt16 nFormat, |
401 | | const css::uno::Sequence< css::beans::PropertyValue >* pFilterData ) |
402 | 0 | { |
403 | 0 | DBG_ASSERT( rURL.GetProtocol() != INetProtocol::NotValid, "XOutBitmap::ExportGraphic(...): invalid URL" ); |
404 | |
|
405 | 0 | SfxMedium aMedium( rURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), StreamMode::WRITE | StreamMode::SHARE_DENYNONE | StreamMode::TRUNC ); |
406 | 0 | SvStream* pOStm = aMedium.GetOutStream(); |
407 | 0 | ErrCode nRet = ERRCODE_GRFILTER_IOERROR; |
408 | |
|
409 | 0 | if( pOStm ) |
410 | 0 | { |
411 | 0 | nRet = rFilter.ExportGraphic( rGraphic, rURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ), *pOStm, nFormat, pFilterData ); |
412 | |
|
413 | 0 | aMedium.Commit(); |
414 | |
|
415 | 0 | if( aMedium.GetErrorIgnoreWarning() && ( ERRCODE_NONE == nRet ) ) |
416 | 0 | nRet = ERRCODE_GRFILTER_IOERROR; |
417 | 0 | } |
418 | |
|
419 | 0 | return nRet; |
420 | 0 | } |
421 | | |
422 | | |
423 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |