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 <sal/config.h>
21 #include <sal/log.hxx>
23 #include <comphelper/scopeguard.hxx>
24 #include <vcl/graph.hxx>
25 #include <vcl/BitmapTools.hxx>
26 #include <vcl/animate/Animation.hxx>
27 #include <vcl/BitmapWriteAccess.hxx>
28 #include <tools/fract.hxx>
29 #include <tools/stream.hxx>
30 #include <comphelper/configuration.hxx>
34 #include <filter/TiffReader.hxx>
44 bool bAllowOneShortRead
;
45 Context(SvStream
& rInStream
)
47 , nStart(rInStream
.Tell())
48 , nSize(rInStream
.remainingSize())
49 , nOrigError(rInStream
.GetError())
50 , bAllowOneShortRead(false)
55 rStream
.SetError(nOrigError
);
60 static tsize_t
tiff_read(thandle_t handle
, tdata_t buf
, tsize_t size
)
62 Context
* pContext
= static_cast<Context
*>(handle
);
63 if (pContext
->rStream
.bad())
66 tsize_t nRead
= pContext
->rStream
.ReadBytes(buf
, size
);
67 // tdf#149417 allow one short read, which is similar to what
68 // we do for jpeg since tdf#138950
69 if (nRead
< size
&& pContext
->bAllowOneShortRead
)
71 memset(static_cast<char*>(buf
) + nRead
, 0, size
- nRead
);
72 pContext
->bAllowOneShortRead
= false;
78 static tsize_t
tiff_write(thandle_t
, tdata_t
, tsize_t
)
83 static toff_t
tiff_seek(thandle_t handle
, toff_t offset
, int whence
)
85 Context
* pContext
= static_cast<Context
*>(handle
);
90 offset
= pContext
->nStart
+ offset
;
93 offset
= pContext
->rStream
.Tell() + offset
;
96 offset
= pContext
->rStream
.TellEnd() + offset
;
99 assert(false && "unknown seek type");
103 if (pContext
->rStream
.bad() || !checkSeek(pContext
->rStream
, offset
))
105 offset
= pContext
->rStream
.Tell();
106 pContext
->rStream
.SetError(SVSTREAM_SEEK_ERROR
);
109 return offset
- pContext
->nStart
;
112 static int tiff_close(thandle_t
)
117 static toff_t
tiff_size(thandle_t handle
)
119 Context
* pContext
= static_cast<Context
*>(handle
);
120 return pContext
->nSize
;
123 bool ImportTiffGraphicImport(SvStream
& rTIFF
, Graphic
& rGraphic
)
125 auto origErrorHandler
= TIFFSetErrorHandler(nullptr);
126 auto origWarningHandler
= TIFFSetWarningHandler(nullptr);
127 comphelper::ScopeGuard
restoreDefaultHandlers([&]() {
128 TIFFSetErrorHandler(origErrorHandler
);
129 TIFFSetWarningHandler(origWarningHandler
);
132 Context
aContext(rTIFF
);
133 TIFF
* tif
= TIFFClientOpen("libtiff-svstream", "r", &aContext
,
134 tiff_read
, tiff_write
,
135 tiff_seek
, tiff_close
,
136 tiff_size
, nullptr, nullptr);
141 const auto nOrigPos
= rTIFF
.Tell();
143 Animation aAnimation
;
145 const bool bFuzzing
= comphelper::IsFuzzing();
146 uint64_t nTotalPixelsRequired
= 0;
152 if (TIFFGetField(tif
, TIFFTAG_IMAGEWIDTH
, &w
) != 1)
154 SAL_WARN("filter.tiff", "missing width");
158 if (TIFFGetField(tif
, TIFFTAG_IMAGELENGTH
, &h
) != 1)
160 SAL_WARN("filter.tiff", "missing height");
164 if (w
> SAL_MAX_INT32
/ 32 || h
> SAL_MAX_INT32
/ 32)
166 SAL_WARN("filter.tiff", "image too large");
170 uint32_t nPixelsRequired
;
171 constexpr size_t nMaxPixelsAllowed
= SAL_MAX_INT32
/4;
172 // two buffers currently required, so limit further
173 bool bOk
= !o3tl::checked_multiply(w
, h
, nPixelsRequired
) && nPixelsRequired
<= nMaxPixelsAllowed
/ 2;
174 SAL_WARN_IF(!bOk
, "filter.tiff", "skipping oversized tiff image " << w
<< " x " << h
);
176 if (!TIFFIsTiled(tif
))
178 size_t nStripSize
= TIFFStripSize(tif
);
179 if (nStripSize
> SAL_MAX_INT32
)
181 SAL_WARN("filter.tiff", "skipping oversized tiff strip size " << nStripSize
);
186 uint16_t PhotometricInterpretation(0);
187 uint16_t Compression(COMPRESSION_NONE
);
190 TIFFGetField(tif
, TIFFTAG_PHOTOMETRIC
, &PhotometricInterpretation
);
191 TIFFGetField(tif
, TIFFTAG_COMPRESSION
, &Compression
);
196 const uint64_t MAX_PIXEL_SIZE
= 120000000;
197 const uint64_t MAX_TILE_SIZE
= 100000000;
198 nTotalPixelsRequired
+= nPixelsRequired
;
199 if (TIFFTileSize64(tif
) > MAX_TILE_SIZE
|| nTotalPixelsRequired
> MAX_PIXEL_SIZE
)
201 SAL_WARN("filter.tiff", "skipping large tiffs");
205 if (TIFFIsTiled(tif
))
208 TIFFGetField(tif
, TIFFTAG_TILEWIDTH
, &tw
);
209 TIFFGetField(tif
, TIFFTAG_TILELENGTH
, &th
);
211 if (tw
> w
|| th
> h
)
213 bOk
= th
< 1000 * tw
&& tw
< 1000 * th
;
214 SAL_WARN_IF(!bOk
, "filter.tiff", "skipping slow bizarre ratio tile of " << tw
<< " x " << th
<< " for image of " << w
<< " x " << h
);
217 if (PhotometricInterpretation
== PHOTOMETRIC_LOGL
)
219 uint32_t nLogLBufferRequired
;
220 bOk
&= !o3tl::checked_multiply(tw
, th
, nLogLBufferRequired
) && nLogLBufferRequired
< MAX_PIXEL_SIZE
;
221 SAL_WARN_IF(!bOk
, "filter.tiff", "skipping oversized tiff tile " << tw
<< " x " << th
);
224 if (Compression
== COMPRESSION_CCITTFAX4
)
227 bOk
&= !o3tl::checked_multiply(tw
, static_cast<uint32_t>(4), DspRuns
) && DspRuns
< MAX_PIXEL_SIZE
;
228 SAL_WARN_IF(!bOk
, "filter.tiff", "skipping oversized tiff tile width: " << tw
);
236 std::vector
<uint32_t> raster(nPixelsRequired
);
238 const bool bNewCodec
= Compression
>= COMPRESSION_ZSTD
; // >= 50000 at time of writing
239 // For tdf#149417 we generally allow one short read for fidelity with the old
240 // parser that this replaced. But don't allow that for:
241 // a) new compression variations that the old parser didn't handle
242 // b) complicated pixel layout variations that the old parser didn't handle
243 // so we don't take libtiff into uncharted territory.
244 aContext
.bAllowOneShortRead
= !bNewCodec
&& PhotometricInterpretation
!= PHOTOMETRIC_YCBCR
;
246 if (TIFFReadRGBAImageOriented(tif
, w
, h
, raster
.data(), ORIENTATION_TOPLEFT
, 1))
248 Bitmap
bitmap(Size(w
, h
), vcl::PixelFormat::N24_BPP
);
249 BitmapScopedWriteAccess
access(bitmap
);
252 SAL_WARN("filter.tiff", "cannot create image " << w
<< " x " << h
);
256 AlphaMask
bitmapAlpha(Size(w
, h
));
257 BitmapScopedWriteAccess
accessAlpha(bitmapAlpha
);
260 SAL_WARN("filter.tiff", "cannot create alpha " << w
<< " x " << h
);
265 ORIENTATION_TOPLEFT = 1
266 ORIENTATION_TOPRIGHT = 2
267 ORIENTATION_BOTRIGHT = 3
268 ORIENTATION_BOTLEFT = 4
269 ORIENTATION_LEFTTOP = 5
270 ORIENTATION_RIGHTTOP = 6
271 ORIENTATION_RIGHTBOT = 7
272 ORIENTATION_LEFTBOT = 8
274 uint16_t nOrientation
;
275 if (TIFFGetField(tif
, TIFFTAG_ORIENTATION
, &nOrientation
) != 1)
278 for (uint32_t y
= 0; y
< h
; ++y
)
280 const uint32_t* src
= raster
.data() + w
* y
;
281 for (uint32_t x
= 0; x
< w
; ++x
)
283 sal_uInt8 r
= TIFFGetR(*src
);
284 sal_uInt8 g
= TIFFGetG(*src
);
285 sal_uInt8 b
= TIFFGetB(*src
);
286 sal_uInt8 a
= TIFFGetA(*src
);
289 switch (nOrientation
)
291 case ORIENTATION_LEFTBOT
:
299 access
->SetPixel(y
, dest
, Color(r
, g
, b
));
300 accessAlpha
->SetPixelIndex(y
, dest
, a
);
310 BitmapEx
aBitmapEx(bitmap
, bitmapAlpha
);
314 switch (nOrientation
)
316 case ORIENTATION_LEFTBOT
:
317 aBitmapEx
.Rotate(2700_deg10
, COL_BLACK
);
325 uint16_t ResolutionUnit
= RESUNIT_NONE
;
326 if (TIFFGetField(tif
, TIFFTAG_RESOLUTIONUNIT
, &ResolutionUnit
) == 1 && ResolutionUnit
!= RESUNIT_NONE
)
328 float xres
= 0, yres
= 0;
330 if (TIFFGetField(tif
, TIFFTAG_XRESOLUTION
, &xres
) == 1 &&
331 TIFFGetField(tif
, TIFFTAG_YRESOLUTION
, &yres
) == 1 &&
332 xres
!= 0 && yres
!= 0)
334 if (ResolutionUnit
== RESUNIT_INCH
)
335 aMapMode
= MapMode(MapUnit::MapInch
, Point(0,0), Fraction(1/xres
), Fraction(1/yres
));
336 else if (ResolutionUnit
== RESUNIT_CENTIMETER
)
337 aMapMode
= MapMode(MapUnit::MapCM
, Point(0,0), Fraction(1/xres
), Fraction(1/yres
));
340 aBitmapEx
.SetPrefMapMode(aMapMode
);
341 aBitmapEx
.SetPrefSize(Size(w
, h
));
343 AnimationFrame
aAnimationFrame(aBitmapEx
, Point(0, 0), aBitmapEx
.GetSizePixel(),
344 ANIMATION_TIMEOUT_ON_CLICK
, Disposal::Back
);
345 aAnimation
.Insert(aAnimationFrame
);
349 } while (TIFFReadDirectory(tif
));
353 const auto nImages
= aAnimation
.Count();
357 rGraphic
= aAnimation
.GetBitmapEx();
359 rGraphic
= aAnimation
;
361 // seek to end of TIFF if succeeded
362 rTIFF
.Seek(STREAM_SEEK_TO_END
);
367 rTIFF
.Seek(nOrigPos
);
371 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */