Gtk-WARNING gtktreestore.c:1047: Invalid column number 1 added to iter
[LibreOffice.git] / vcl / source / filter / itiff / itiff.cxx
blobbdf4999df514bff16797028b7462fc81fbe63e80
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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>
32 #include <tiffio.h>
34 #include <filter/TiffReader.hxx>
36 namespace
38 struct Context
40 SvStream& rStream;
41 tsize_t nStart;
42 tsize_t nSize;
43 ErrCode nOrigError;
44 bool bAllowOneShortRead;
45 Context(SvStream& rInStream)
46 : rStream(rInStream)
47 , nStart(rInStream.Tell())
48 , nSize(rInStream.remainingSize())
49 , nOrigError(rInStream.GetError())
50 , bAllowOneShortRead(false)
53 ~Context()
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())
64 return 0;
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;
73 return size;
75 return nRead;
78 static tsize_t tiff_write(thandle_t, tdata_t, tsize_t)
80 return -1;
83 static toff_t tiff_seek(thandle_t handle, toff_t offset, int whence)
85 Context* pContext = static_cast<Context*>(handle);
87 switch (whence)
89 case SEEK_SET:
90 offset = pContext->nStart + offset;
91 break;
92 case SEEK_CUR:
93 offset = pContext->rStream.Tell() + offset;
94 break;
95 case SEEK_END:
96 offset = pContext->rStream.TellEnd() + offset;
97 break;
98 default:
99 assert(false && "unknown seek type");
100 break;
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)
114 return 0;
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);
138 if (!tif)
139 return false;
141 const auto nOrigPos = rTIFF.Tell();
143 Animation aAnimation;
145 const bool bFuzzing = comphelper::IsFuzzing();
146 uint64_t nTotalPixelsRequired = 0;
150 uint32_t w, h;
152 if (TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &w) != 1)
154 SAL_WARN("filter.tiff", "missing width");
155 break;
158 if (TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h) != 1)
160 SAL_WARN("filter.tiff", "missing height");
161 break;
164 if (w > SAL_MAX_INT32 / 32 || h > SAL_MAX_INT32 / 32)
166 SAL_WARN("filter.tiff", "image too large");
167 break;
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);
182 bOk = false;
186 uint16_t PhotometricInterpretation(0);
187 uint16_t Compression(COMPRESSION_NONE);
188 if (bOk)
190 TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &PhotometricInterpretation);
191 TIFFGetField(tif, TIFFTAG_COMPRESSION, &Compression);
194 if (bOk && bFuzzing)
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");
202 break;
205 if (TIFFIsTiled(tif))
207 uint32_t tw, th;
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)
226 uint32_t DspRuns;
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);
233 if (!bOk)
234 break;
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);
250 if (!access)
252 SAL_WARN("filter.tiff", "cannot create image " << w << " x " << h);
253 break;
256 AlphaMask bitmapAlpha(Size(w, h));
257 BitmapScopedWriteAccess accessAlpha(bitmapAlpha);
258 if (!accessAlpha)
260 SAL_WARN("filter.tiff", "cannot create alpha " << w << " x " << h);
261 break;
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)
276 nOrientation = 0;
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);
288 uint32_t dest;
289 switch (nOrientation)
291 case ORIENTATION_LEFTBOT:
292 dest = w - 1 - x;
293 break;
294 default:
295 dest = x;
296 break;
299 access->SetPixel(y, dest, Color(r, g, b));
300 accessAlpha->SetPixelIndex(y, dest, a);
301 ++src;
305 raster.clear();
307 access.reset();
308 accessAlpha.reset();
310 BitmapEx aBitmapEx(bitmap, bitmapAlpha);
312 if (!bFuzzing)
314 switch (nOrientation)
316 case ORIENTATION_LEFTBOT:
317 aBitmapEx.Rotate(2700_deg10, COL_BLACK);
318 break;
319 default:
320 break;
324 MapMode aMapMode;
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);
347 else
348 break;
349 } while (TIFFReadDirectory(tif));
351 TIFFClose(tif);
353 const auto nImages = aAnimation.Count();
354 if (nImages)
356 if (nImages == 1)
357 rGraphic = aAnimation.GetBitmapEx();
358 else
359 rGraphic = aAnimation;
361 // seek to end of TIFF if succeeded
362 rTIFF.Seek(STREAM_SEEK_TO_END);
364 return true;
367 rTIFF.Seek(nOrigPos);
368 return false;
371 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */