Coverage Report

Created: 2025-12-08 09:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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: */