1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <com/sun/star/geometry/RealSize2D.hpp>
21 #include <com/sun/star/geometry/IntegerSize2D.hpp>
22 #include <com/sun/star/geometry/IntegerPoint2D.hpp>
23 #include <com/sun/star/geometry/IntegerRectangle2D.hpp>
25 #include <com/sun/star/rendering/ColorSpaceType.hpp>
26 #include <com/sun/star/rendering/RenderingIntent.hpp>
27 #include <com/sun/star/rendering/VolatileContentDestroyedException.hpp>
28 #include <com/sun/star/rendering/XBitmap.hpp>
29 #include <com/sun/star/rendering/IntegerBitmapLayout.hpp>
30 #include <com/sun/star/rendering/ColorComponentTag.hpp>
32 #include <basegfx/point/b2dpoint.hxx>
33 #include <basegfx/vector/b2dsize.hxx>
34 #include <basegfx/range/b2drectangle.hxx>
35 #include <basegfx/point/b2ipoint.hxx>
36 #include <basegfx/range/b2irectangle.hxx>
38 #include <sal/log.hxx>
39 #include <tools/helpers.hxx>
40 #include <comphelper/diagnose_ex.hxx>
42 #include <vcl/bitmapex.hxx>
44 #include <canvasbitmap.hxx>
45 #include <vcl/canvastools.hxx>
46 #include <bitmap/BitmapWriteAccess.hxx>
48 using namespace ::com::sun::star
;
50 namespace vcl::unotools
52 uno::Reference
< rendering::XBitmap
> xBitmapFromBitmapEx(const ::BitmapEx
& inputBitmap
)
54 SAL_INFO( "vcl.helper", "vcl::unotools::xBitmapFromBitmapEx()" );
56 return new vcl::unotools::VclCanvasBitmap( inputBitmap
);
61 bool equalsLayout( const rendering::IntegerBitmapLayout
& rLHS
,
62 const rendering::IntegerBitmapLayout
& rRHS
)
65 rLHS
.ScanLineBytes
== rRHS
.ScanLineBytes
&&
66 rLHS
.ScanLineStride
== rRHS
.ScanLineStride
&&
67 rLHS
.PlaneStride
== rRHS
.PlaneStride
&&
68 rLHS
.ColorSpace
== rRHS
.ColorSpace
&&
69 rLHS
.Palette
== rRHS
.Palette
&&
70 rLHS
.IsMsbFirst
== rRHS
.IsMsbFirst
;
72 bool readBmp( sal_Int32 nWidth
,
74 const rendering::IntegerBitmapLayout
& rLayout
,
75 const uno::Reference
< rendering::XIntegerReadOnlyBitmap
>& xInputBitmap
,
76 BitmapScopedWriteAccess
& rWriteAcc
,
77 BitmapScopedWriteAccess
& rAlphaAcc
)
79 rendering::IntegerBitmapLayout aCurrLayout
;
80 geometry::IntegerRectangle2D aRect
;
81 uno::Sequence
<sal_Int8
> aPixelData
;
82 uno::Sequence
<rendering::RGBColor
> aRGBColors
;
83 uno::Sequence
<rendering::ARGBColor
> aARGBColors
;
85 for( aRect
.Y1
=0; aRect
.Y1
<nHeight
; ++aRect
.Y1
)
87 aRect
.X1
= 0; aRect
.X2
= nWidth
; aRect
.Y2
= aRect
.Y1
+1;
90 aPixelData
= xInputBitmap
->getData(aCurrLayout
,aRect
);
92 catch( rendering::VolatileContentDestroyedException
& )
94 // re-read bmp from the start
97 if( !equalsLayout(aCurrLayout
, rLayout
) )
98 return false; // re-read bmp from the start
100 Scanline pScanline
= rWriteAcc
->GetScanline( aRect
.Y1
);
101 if( rAlphaAcc
.get() )
103 Scanline pScanlineAlpha
= rAlphaAcc
->GetScanline( aRect
.Y1
);
105 aARGBColors
= rLayout
.ColorSpace
->convertIntegerToARGB(aPixelData
);
107 if( rWriteAcc
->HasPalette() )
109 for( sal_Int32 x
=0; x
<nWidth
; ++x
)
111 const rendering::ARGBColor
& rColor
=aARGBColors
[x
];
112 rWriteAcc
->SetPixelOnData( pScanline
, x
,
113 BitmapColor(static_cast<sal_uInt8
>(rWriteAcc
->GetBestPaletteIndex(
114 BitmapColor( toByteColor(rColor
.Red
),
115 toByteColor(rColor
.Green
),
116 toByteColor(rColor
.Blue
))))) );
117 rAlphaAcc
->SetPixelOnData( pScanlineAlpha
, x
,
118 BitmapColor( 255 - toByteColor(rColor
.Alpha
) ));
123 for( sal_Int32 x
=0; x
<nWidth
; ++x
)
125 const rendering::ARGBColor
& rColor
=aARGBColors
[x
];
126 rWriteAcc
->SetPixelOnData( pScanline
, x
,
127 BitmapColor( toByteColor(rColor
.Red
),
128 toByteColor(rColor
.Green
),
129 toByteColor(rColor
.Blue
) ));
130 rAlphaAcc
->SetPixelOnData( pScanlineAlpha
, x
,
131 BitmapColor( 255 - toByteColor(rColor
.Alpha
) ));
138 aRGBColors
= rLayout
.ColorSpace
->convertIntegerToRGB(aPixelData
);
139 if( rWriteAcc
->HasPalette() )
141 for( sal_Int32 x
=0; x
<nWidth
; ++x
)
143 const rendering::RGBColor
& rColor
=aRGBColors
[x
];
144 rWriteAcc
->SetPixelOnData( pScanline
, x
,
145 BitmapColor(static_cast<sal_uInt8
>(rWriteAcc
->GetBestPaletteIndex(
146 BitmapColor( toByteColor(rColor
.Red
),
147 toByteColor(rColor
.Green
),
148 toByteColor(rColor
.Blue
))))) );
153 for( sal_Int32 x
=0; x
<nWidth
; ++x
)
155 const rendering::RGBColor
& rColor
=aRGBColors
[x
];
156 rWriteAcc
->SetPixelOnData( pScanline
, x
,
157 BitmapColor( toByteColor(rColor
.Red
),
158 toByteColor(rColor
.Green
),
159 toByteColor(rColor
.Blue
) ));
169 ::BitmapEx
bitmapExFromXBitmap( const uno::Reference
< rendering::XIntegerReadOnlyBitmap
>& xInputBitmap
)
171 SAL_INFO( "vcl.helper", "vcl::unotools::bitmapExFromXBitmap()" );
173 if( !xInputBitmap
.is() )
176 // tunnel directly for known implementation
177 VclCanvasBitmap
* pImplBitmap
= dynamic_cast<VclCanvasBitmap
*>(xInputBitmap
.get());
179 return pImplBitmap
->getBitmapEx();
181 // retrieve data via UNO interface
183 // volatile bitmaps are a bit more complicated to read
186 // loop a few times, until successfully read (for XVolatileBitmap)
187 for( int i
=0; i
<10; ++i
)
190 sal_Int32 nAlphaDepth
=0;
191 const rendering::IntegerBitmapLayout
aLayout(
192 xInputBitmap
->getMemoryLayout());
194 OSL_ENSURE(aLayout
.ColorSpace
.is(),
195 "Cannot convert image without color space!");
196 if( !aLayout
.ColorSpace
.is() )
199 nDepth
= aLayout
.ColorSpace
->getBitsPerPixel();
201 if( xInputBitmap
->hasAlpha() )
203 // determine alpha channel depth
204 const uno::Sequence
<sal_Int8
> aTags(
205 aLayout
.ColorSpace
->getComponentTags() );
206 const sal_Int8
* pStart(aTags
.getConstArray());
207 const std::size_t nLen(aTags
.getLength());
208 const sal_Int8
* pEnd(pStart
+nLen
);
210 const std::ptrdiff_t nAlphaIndex
=
211 std::find(pStart
,pEnd
,
212 rendering::ColorComponentTag::ALPHA
) - pStart
;
214 if( nAlphaIndex
< sal::static_int_cast
<std::ptrdiff_t>(nLen
) )
216 nAlphaDepth
= aLayout
.ColorSpace
->getComponentBitCounts()[nAlphaIndex
] > 1 ? 8 : 1;
217 nDepth
-= nAlphaDepth
;
221 BitmapPalette aPalette
;
222 if( aLayout
.Palette
.is() )
224 uno::Reference
< rendering::XColorSpace
> xPaletteColorSpace(
225 aLayout
.Palette
->getColorSpace());
226 ENSURE_OR_THROW(xPaletteColorSpace
.is(),
227 "Palette without color space");
229 const sal_Int32
nEntryCount( aLayout
.Palette
->getNumberOfEntries() );
230 if( nEntryCount
<= 256 )
232 if( nEntryCount
<= 2 )
237 const sal_uInt16
nPaletteEntries(
238 sal::static_int_cast
<sal_uInt16
>(
239 std::min(sal_Int32(255), nEntryCount
)));
241 // copy palette entries
242 aPalette
.SetEntryCount(nPaletteEntries
);
243 uno::Reference
<rendering::XBitmapPalette
> xPalette( aLayout
.Palette
);
244 uno::Reference
<rendering::XColorSpace
> xPalColorSpace( xPalette
->getColorSpace() );
246 uno::Sequence
<double> aPaletteEntry
;
247 for( sal_uInt16 j
=0; j
<nPaletteEntries
; ++j
)
249 if( !xPalette
->getIndex(aPaletteEntry
,j
) &&
254 uno::Sequence
<rendering::RGBColor
> aColors
=xPalColorSpace
->convertToRGB(aPaletteEntry
);
255 ENSURE_OR_THROW(aColors
.getLength() == 1,
256 "Palette returned more or less than one entry");
257 const rendering::RGBColor
& rColor
=aColors
[0];
258 aPalette
[j
] = BitmapColor(toByteColor(rColor
.Red
),
259 toByteColor(rColor
.Green
),
260 toByteColor(rColor
.Blue
));
265 const ::Size
aPixelSize(
266 sizeFromIntegerSize2D(xInputBitmap
->getSize()));
268 // normalize bitcount
270 ( nDepth
<= 8 ) ? vcl::PixelFormat::N8_BPP
:
271 vcl::PixelFormat::N24_BPP
;
272 auto eAlphaPixelFormat
= vcl::PixelFormat::N8_BPP
;
274 ::Bitmap
aBitmap( aPixelSize
,
276 aLayout
.Palette
.is() ? &aPalette
: nullptr );
279 aAlpha
= Bitmap(aPixelSize
,
281 &Bitmap::GetGreyPalette(
282 sal::static_int_cast
<sal_uInt16
>(1 << nAlphaDepth
)) );
284 { // limit scoped access
285 BitmapScopedWriteAccess
pWriteAccess( aBitmap
);
286 BitmapScopedWriteAccess
pAlphaWriteAccess( nAlphaDepth
? aAlpha
.AcquireWriteAccess() : nullptr,
289 ENSURE_OR_THROW(pWriteAccess
.get() != nullptr,
290 "Cannot get write access to bitmap");
292 const sal_Int32
nWidth(aPixelSize
.Width());
293 const sal_Int32
nHeight(aPixelSize
.Height());
295 if( !readBmp(nWidth
,nHeight
,aLayout
,xInputBitmap
,
296 pWriteAccess
,pAlphaWriteAccess
) )
298 } // limit scoped access
301 return ::BitmapEx( aBitmap
,
302 AlphaMask( aAlpha
) );
304 return ::BitmapEx( aBitmap
);
307 // failed to read data 10 times - bail out
311 geometry::RealSize2D
size2DFromSize( const Size
& rSize
)
313 return geometry::RealSize2D( rSize
.Width(),
317 Size
sizeFromRealSize2D( const geometry::RealSize2D
& rSize
)
319 return Size( static_cast<tools::Long
>(rSize
.Width
+ .5),
320 static_cast<tools::Long
>(rSize
.Height
+ .5) );
323 ::Size
sizeFromB2DSize( const basegfx::B2DVector
& rVec
)
325 return ::Size( FRound( rVec
.getX() ),
326 FRound( rVec
.getY() ) );
329 ::Point
pointFromB2DPoint( const basegfx::B2DPoint
& rPoint
)
331 return pointFromB2IPoint(basegfx::fround(rPoint
));
334 ::tools::Rectangle
rectangleFromB2DRectangle( const basegfx::B2DRange
& rRect
)
336 return rectangleFromB2IRectangle(basegfx::fround(rRect
));
339 Point
pointFromB2IPoint( const basegfx::B2IPoint
& rPoint
)
341 return ::Point( rPoint
.getX(),
345 basegfx::B2IPoint
b2IPointFromPoint(Point
const& rPoint
)
347 return basegfx::B2IPoint(rPoint
.X(), rPoint
.Y());
350 tools::Rectangle
rectangleFromB2IRectangle( const basegfx::B2IRange
& rRect
)
352 return ::tools::Rectangle( rRect
.getMinX(),
358 basegfx::B2IRectangle
b2IRectangleFromRectangle(tools::Rectangle
const& rRect
)
360 // although B2IRange internally has separate height/width emptiness, it doesn't
361 // expose any API to let us set them separately, so just do the best we can.
362 if (rRect
.IsWidthEmpty() && rRect
.IsHeightEmpty())
363 return basegfx::B2IRange( basegfx::B2ITuple( rRect
.Left(), rRect
.Top() ) );
364 return basegfx::B2IRange( rRect
.Left(),
366 rRect
.IsWidthEmpty() ? rRect
.Left() : rRect
.Right(),
367 rRect
.IsHeightEmpty() ? rRect
.Top() : rRect
.Bottom() );
370 basegfx::B2DSize
b2DSizeFromSize(const Size
& rSize
)
372 return basegfx::B2DSize(rSize
.Width(), rSize
.Height());
375 basegfx::B2DVector
b2DVectorFromSize(const Size
& rSize
)
377 return basegfx::B2DVector(rSize
.Width(), rSize
.Height());
380 basegfx::B2DPoint
b2DPointFromPoint( const ::Point
& rPoint
)
382 return basegfx::B2DPoint( rPoint
.X(),
386 basegfx::B2DRange
b2DRectangleFromRectangle( const ::tools::Rectangle
& rRect
)
388 // although B2DRange internally has separate height/width emptiness, it doesn't
389 // expose any API to let us set them separately, so just do the best we can.
390 if (rRect
.IsWidthEmpty() && rRect
.IsHeightEmpty())
391 return basegfx::B2DRange( basegfx::B2DTuple( rRect
.Left(), rRect
.Top() ) );
392 return basegfx::B2DRectangle( rRect
.Left(),
394 rRect
.IsWidthEmpty() ? rRect
.Left() : rRect
.Right(),
395 rRect
.IsHeightEmpty() ? rRect
.Top() : rRect
.Bottom() );
398 geometry::IntegerSize2D
integerSize2DFromSize( const Size
& rSize
)
400 return geometry::IntegerSize2D( rSize
.Width(),
404 Size
sizeFromIntegerSize2D( const geometry::IntegerSize2D
& rSize
)
406 return Size( rSize
.Width
,
410 Point
pointFromIntegerPoint2D( const geometry::IntegerPoint2D
& rPoint
)
412 return Point( rPoint
.X
,
416 tools::Rectangle
rectangleFromIntegerRectangle2D( const geometry::IntegerRectangle2D
& rRectangle
)
418 return tools::Rectangle( rRectangle
.X1
, rRectangle
.Y1
,
419 rRectangle
.X2
, rRectangle
.Y2
);
424 class StandardColorSpace
: public cppu::WeakImplHelper
< css::rendering::XColorSpace
>
427 uno::Sequence
< sal_Int8
> m_aComponentTags
;
429 virtual ::sal_Int8 SAL_CALL
getType( ) override
431 return rendering::ColorSpaceType::RGB
;
433 virtual uno::Sequence
< ::sal_Int8
> SAL_CALL
getComponentTags( ) override
435 return m_aComponentTags
;
437 virtual ::sal_Int8 SAL_CALL
getRenderingIntent( ) override
439 return rendering::RenderingIntent::PERCEPTUAL
;
441 virtual uno::Sequence
< beans::PropertyValue
> SAL_CALL
getProperties( ) override
443 return uno::Sequence
< beans::PropertyValue
>();
445 virtual uno::Sequence
< double > SAL_CALL
convertColorSpace( const uno::Sequence
< double >& deviceColor
,
446 const uno::Reference
< rendering::XColorSpace
>& targetColorSpace
) override
448 // TODO(P3): if we know anything about target
449 // colorspace, this can be greatly sped up
450 uno::Sequence
<rendering::ARGBColor
> aIntermediate(
451 convertToARGB(deviceColor
));
452 return targetColorSpace
->convertFromARGB(aIntermediate
);
454 virtual uno::Sequence
< rendering::RGBColor
> SAL_CALL
convertToRGB( const uno::Sequence
< double >& deviceColor
) override
456 const double* pIn( deviceColor
.getConstArray() );
457 const std::size_t nLen( deviceColor
.getLength() );
458 ENSURE_ARG_OR_THROW2(nLen
%4==0,
459 "number of channels no multiple of 4",
460 static_cast<rendering::XColorSpace
*>(this), 0);
462 uno::Sequence
< rendering::RGBColor
> aRes(nLen
/4);
463 rendering::RGBColor
* pOut( aRes
.getArray() );
464 for( std::size_t i
=0; i
<nLen
; i
+=4 )
466 *pOut
++ = rendering::RGBColor(pIn
[0],pIn
[1],pIn
[2]);
471 virtual uno::Sequence
< rendering::ARGBColor
> SAL_CALL
convertToARGB( const uno::Sequence
< double >& deviceColor
) override
473 const double* pIn( deviceColor
.getConstArray() );
474 const std::size_t nLen( deviceColor
.getLength() );
475 ENSURE_ARG_OR_THROW2(nLen
%4==0,
476 "number of channels no multiple of 4",
477 static_cast<rendering::XColorSpace
*>(this), 0);
479 uno::Sequence
< rendering::ARGBColor
> aRes(nLen
/4);
480 rendering::ARGBColor
* pOut( aRes
.getArray() );
481 for( std::size_t i
=0; i
<nLen
; i
+=4 )
483 *pOut
++ = rendering::ARGBColor(pIn
[3],pIn
[0],pIn
[1],pIn
[2]);
488 virtual uno::Sequence
< rendering::ARGBColor
> SAL_CALL
convertToPARGB( const uno::Sequence
< double >& deviceColor
) override
490 const double* pIn( deviceColor
.getConstArray() );
491 const std::size_t nLen( deviceColor
.getLength() );
492 ENSURE_ARG_OR_THROW2(nLen
%4==0,
493 "number of channels no multiple of 4",
494 static_cast<rendering::XColorSpace
*>(this), 0);
496 uno::Sequence
< rendering::ARGBColor
> aRes(nLen
/4);
497 rendering::ARGBColor
* pOut( aRes
.getArray() );
498 for( std::size_t i
=0; i
<nLen
; i
+=4 )
500 *pOut
++ = rendering::ARGBColor(pIn
[3],pIn
[3]*pIn
[0],pIn
[3]*pIn
[1],pIn
[3]*pIn
[2]);
505 virtual uno::Sequence
< double > SAL_CALL
convertFromRGB( const uno::Sequence
< rendering::RGBColor
>& rgbColor
) override
507 const std::size_t nLen( rgbColor
.getLength() );
509 uno::Sequence
< double > aRes(nLen
*4);
510 double* pColors
=aRes
.getArray();
511 for( const auto& rIn
: rgbColor
)
513 *pColors
++ = rIn
.Red
;
514 *pColors
++ = rIn
.Green
;
515 *pColors
++ = rIn
.Blue
;
520 virtual uno::Sequence
< double > SAL_CALL
convertFromARGB( const uno::Sequence
< rendering::ARGBColor
>& rgbColor
) override
522 const std::size_t nLen( rgbColor
.getLength() );
524 uno::Sequence
< double > aRes(nLen
*4);
525 double* pColors
=aRes
.getArray();
526 for( const auto& rIn
: rgbColor
)
528 *pColors
++ = rIn
.Red
;
529 *pColors
++ = rIn
.Green
;
530 *pColors
++ = rIn
.Blue
;
531 *pColors
++ = rIn
.Alpha
;
535 virtual uno::Sequence
< double > SAL_CALL
convertFromPARGB( const uno::Sequence
< rendering::ARGBColor
>& rgbColor
) override
537 const std::size_t nLen( rgbColor
.getLength() );
539 uno::Sequence
< double > aRes(nLen
*4);
540 double* pColors
=aRes
.getArray();
541 for( const auto& rIn
: rgbColor
)
543 *pColors
++ = rIn
.Red
/rIn
.Alpha
;
544 *pColors
++ = rIn
.Green
/rIn
.Alpha
;
545 *pColors
++ = rIn
.Blue
/rIn
.Alpha
;
546 *pColors
++ = rIn
.Alpha
;
552 StandardColorSpace() : m_aComponentTags(4)
554 sal_Int8
* pTags
= m_aComponentTags
.getArray();
555 pTags
[0] = rendering::ColorComponentTag::RGB_RED
;
556 pTags
[1] = rendering::ColorComponentTag::RGB_GREEN
;
557 pTags
[2] = rendering::ColorComponentTag::RGB_BLUE
;
558 pTags
[3] = rendering::ColorComponentTag::ALPHA
;
563 uno::Reference
<rendering::XColorSpace
> createStandardColorSpace()
565 return new StandardColorSpace();
568 uno::Sequence
< double > colorToStdColorSpaceSequence( const Color
& rColor
)
572 toDoubleColor(rColor
.GetRed()),
573 toDoubleColor(rColor
.GetGreen()),
574 toDoubleColor(rColor
.GetBlue()),
575 toDoubleColor(rColor
.GetAlpha())
579 Color
stdColorSpaceSequenceToColor( const uno::Sequence
< double >& rColor
)
581 ENSURE_ARG_OR_THROW( rColor
.getLength() == 4,
582 "color must have 4 channels" );
586 aColor
.SetRed ( toByteColor(rColor
[0]) );
587 aColor
.SetGreen( toByteColor(rColor
[1]) );
588 aColor
.SetBlue ( toByteColor(rColor
[2]) );
589 aColor
.SetAlpha( toByteColor(rColor
[3]) );
594 uno::Sequence
< double > colorToDoubleSequence(
596 const uno::Reference
< rendering::XColorSpace
>& xColorSpace
)
598 uno::Sequence
<rendering::ARGBColor
> aSeq
601 toDoubleColor(rColor
.GetAlpha()),
602 toDoubleColor(rColor
.GetRed()),
603 toDoubleColor(rColor
.GetGreen()),
604 toDoubleColor(rColor
.GetBlue())
608 return xColorSpace
->convertFromARGB(aSeq
);
611 Color
doubleSequenceToColor(
612 const uno::Sequence
< double >& rColor
,
613 const uno::Reference
< rendering::XColorSpace
>& xColorSpace
)
615 const rendering::ARGBColor
aARGBColor(
616 xColorSpace
->convertToARGB(rColor
)[0]);
618 return Color( ColorAlpha
, toByteColor(aARGBColor
.Alpha
),
619 toByteColor(aARGBColor
.Red
),
620 toByteColor(aARGBColor
.Green
),
621 toByteColor(aARGBColor
.Blue
) );
624 } // namespace canvas
626 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */