Thu, 15 Jan 2015 15:59:08 +0100
Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- |
michael@0 | 2 | * |
michael@0 | 3 | * This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | /* |
michael@0 | 7 | The Graphics Interchange Format(c) is the copyright property of CompuServe |
michael@0 | 8 | Incorporated. Only CompuServe Incorporated is authorized to define, redefine, |
michael@0 | 9 | enhance, alter, modify or change in any way the definition of the format. |
michael@0 | 10 | |
michael@0 | 11 | CompuServe Incorporated hereby grants a limited, non-exclusive, royalty-free |
michael@0 | 12 | license for the use of the Graphics Interchange Format(sm) in computer |
michael@0 | 13 | software; computer software utilizing GIF(sm) must acknowledge ownership of the |
michael@0 | 14 | Graphics Interchange Format and its Service Mark by CompuServe Incorporated, in |
michael@0 | 15 | User and Technical Documentation. Computer software utilizing GIF, which is |
michael@0 | 16 | distributed or may be distributed without User or Technical Documentation must |
michael@0 | 17 | display to the screen or printer a message acknowledging ownership of the |
michael@0 | 18 | Graphics Interchange Format and the Service Mark by CompuServe Incorporated; in |
michael@0 | 19 | this case, the acknowledgement may be displayed in an opening screen or leading |
michael@0 | 20 | banner, or a closing screen or trailing banner. A message such as the following |
michael@0 | 21 | may be used: |
michael@0 | 22 | |
michael@0 | 23 | "The Graphics Interchange Format(c) is the Copyright property of |
michael@0 | 24 | CompuServe Incorporated. GIF(sm) is a Service Mark property of |
michael@0 | 25 | CompuServe Incorporated." |
michael@0 | 26 | |
michael@0 | 27 | For further information, please contact : |
michael@0 | 28 | |
michael@0 | 29 | CompuServe Incorporated |
michael@0 | 30 | Graphics Technology Department |
michael@0 | 31 | 5000 Arlington Center Boulevard |
michael@0 | 32 | Columbus, Ohio 43220 |
michael@0 | 33 | U. S. A. |
michael@0 | 34 | |
michael@0 | 35 | CompuServe Incorporated maintains a mailing list with all those individuals and |
michael@0 | 36 | organizations who wish to receive copies of this document when it is corrected |
michael@0 | 37 | or revised. This service is offered free of charge; please provide us with your |
michael@0 | 38 | mailing address. |
michael@0 | 39 | */ |
michael@0 | 40 | |
michael@0 | 41 | #include <stddef.h> |
michael@0 | 42 | |
michael@0 | 43 | #include "nsGIFDecoder2.h" |
michael@0 | 44 | #include "nsIInputStream.h" |
michael@0 | 45 | #include "RasterImage.h" |
michael@0 | 46 | |
michael@0 | 47 | #include "gfxColor.h" |
michael@0 | 48 | #include "gfxPlatform.h" |
michael@0 | 49 | #include "qcms.h" |
michael@0 | 50 | #include <algorithm> |
michael@0 | 51 | |
michael@0 | 52 | namespace mozilla { |
michael@0 | 53 | namespace image { |
michael@0 | 54 | |
michael@0 | 55 | /* |
michael@0 | 56 | * GETN(n, s) requests at least 'n' bytes available from 'q', at start of state 's' |
michael@0 | 57 | * |
michael@0 | 58 | * Colormaps are directly copied in the resp. global_colormap or the local_colormap of the PAL image frame |
michael@0 | 59 | * So a fixed buffer in gif_struct is good enough. |
michael@0 | 60 | * This buffer is only needed to copy left-over data from one GifWrite call to the next |
michael@0 | 61 | */ |
michael@0 | 62 | #define GETN(n,s) \ |
michael@0 | 63 | PR_BEGIN_MACRO \ |
michael@0 | 64 | mGIFStruct.bytes_to_consume = (n); \ |
michael@0 | 65 | mGIFStruct.state = (s); \ |
michael@0 | 66 | PR_END_MACRO |
michael@0 | 67 | |
michael@0 | 68 | /* Get a 16-bit value stored in little-endian format */ |
michael@0 | 69 | #define GETINT16(p) ((p)[1]<<8|(p)[0]) |
michael@0 | 70 | ////////////////////////////////////////////////////////////////////// |
michael@0 | 71 | // GIF Decoder Implementation |
michael@0 | 72 | |
michael@0 | 73 | nsGIFDecoder2::nsGIFDecoder2(RasterImage &aImage) |
michael@0 | 74 | : Decoder(aImage) |
michael@0 | 75 | , mCurrentRow(-1) |
michael@0 | 76 | , mLastFlushedRow(-1) |
michael@0 | 77 | , mOldColor(0) |
michael@0 | 78 | , mCurrentFrame(-1) |
michael@0 | 79 | , mCurrentPass(0) |
michael@0 | 80 | , mLastFlushedPass(0) |
michael@0 | 81 | , mGIFOpen(false) |
michael@0 | 82 | , mSawTransparency(false) |
michael@0 | 83 | { |
michael@0 | 84 | // Clear out the structure, excluding the arrays |
michael@0 | 85 | memset(&mGIFStruct, 0, sizeof(mGIFStruct)); |
michael@0 | 86 | |
michael@0 | 87 | // Initialize as "animate once" in case no NETSCAPE2.0 extension is found |
michael@0 | 88 | mGIFStruct.loop_count = 1; |
michael@0 | 89 | |
michael@0 | 90 | // Start with the version (GIF89a|GIF87a) |
michael@0 | 91 | mGIFStruct.state = gif_type; |
michael@0 | 92 | mGIFStruct.bytes_to_consume = 6; |
michael@0 | 93 | } |
michael@0 | 94 | |
michael@0 | 95 | nsGIFDecoder2::~nsGIFDecoder2() |
michael@0 | 96 | { |
michael@0 | 97 | moz_free(mGIFStruct.local_colormap); |
michael@0 | 98 | moz_free(mGIFStruct.hold); |
michael@0 | 99 | } |
michael@0 | 100 | |
michael@0 | 101 | void |
michael@0 | 102 | nsGIFDecoder2::FinishInternal() |
michael@0 | 103 | { |
michael@0 | 104 | NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call FinishInternal after error!"); |
michael@0 | 105 | |
michael@0 | 106 | // If the GIF got cut off, handle it anyway |
michael@0 | 107 | if (!IsSizeDecode() && mGIFOpen) { |
michael@0 | 108 | if (mCurrentFrame == mGIFStruct.images_decoded) |
michael@0 | 109 | EndImageFrame(); |
michael@0 | 110 | PostDecodeDone(mGIFStruct.loop_count - 1); |
michael@0 | 111 | mGIFOpen = false; |
michael@0 | 112 | } |
michael@0 | 113 | } |
michael@0 | 114 | |
michael@0 | 115 | // Push any new rows according to mCurrentPass/mLastFlushedPass and |
michael@0 | 116 | // mCurrentRow/mLastFlushedRow. Note: caller is responsible for |
michael@0 | 117 | // updating mlastFlushed{Row,Pass}. |
michael@0 | 118 | void |
michael@0 | 119 | nsGIFDecoder2::FlushImageData(uint32_t fromRow, uint32_t rows) |
michael@0 | 120 | { |
michael@0 | 121 | nsIntRect r(mGIFStruct.x_offset, mGIFStruct.y_offset + fromRow, mGIFStruct.width, rows); |
michael@0 | 122 | PostInvalidation(r); |
michael@0 | 123 | } |
michael@0 | 124 | |
michael@0 | 125 | void |
michael@0 | 126 | nsGIFDecoder2::FlushImageData() |
michael@0 | 127 | { |
michael@0 | 128 | switch (mCurrentPass - mLastFlushedPass) { |
michael@0 | 129 | case 0: // same pass |
michael@0 | 130 | if (mCurrentRow - mLastFlushedRow) |
michael@0 | 131 | FlushImageData(mLastFlushedRow + 1, mCurrentRow - mLastFlushedRow); |
michael@0 | 132 | break; |
michael@0 | 133 | |
michael@0 | 134 | case 1: // one pass on - need to handle bottom & top rects |
michael@0 | 135 | FlushImageData(0, mCurrentRow + 1); |
michael@0 | 136 | FlushImageData(mLastFlushedRow + 1, mGIFStruct.height - (mLastFlushedRow + 1)); |
michael@0 | 137 | break; |
michael@0 | 138 | |
michael@0 | 139 | default: // more than one pass on - push the whole frame |
michael@0 | 140 | FlushImageData(0, mGIFStruct.height); |
michael@0 | 141 | } |
michael@0 | 142 | } |
michael@0 | 143 | |
michael@0 | 144 | //****************************************************************************** |
michael@0 | 145 | // GIF decoder callback methods. Part of public API for GIF2 |
michael@0 | 146 | //****************************************************************************** |
michael@0 | 147 | |
michael@0 | 148 | //****************************************************************************** |
michael@0 | 149 | void nsGIFDecoder2::BeginGIF() |
michael@0 | 150 | { |
michael@0 | 151 | if (mGIFOpen) |
michael@0 | 152 | return; |
michael@0 | 153 | |
michael@0 | 154 | mGIFOpen = true; |
michael@0 | 155 | |
michael@0 | 156 | PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height); |
michael@0 | 157 | } |
michael@0 | 158 | |
michael@0 | 159 | //****************************************************************************** |
michael@0 | 160 | void nsGIFDecoder2::BeginImageFrame(uint16_t aDepth) |
michael@0 | 161 | { |
michael@0 | 162 | gfxImageFormat format; |
michael@0 | 163 | if (mGIFStruct.is_transparent) |
michael@0 | 164 | format = gfxImageFormat::ARGB32; |
michael@0 | 165 | else |
michael@0 | 166 | format = gfxImageFormat::RGB24; |
michael@0 | 167 | |
michael@0 | 168 | MOZ_ASSERT(HasSize()); |
michael@0 | 169 | |
michael@0 | 170 | // Use correct format, RGB for first frame, PAL for following frames |
michael@0 | 171 | // and include transparency to allow for optimization of opaque images |
michael@0 | 172 | if (mGIFStruct.images_decoded) { |
michael@0 | 173 | // Image data is stored with original depth and palette |
michael@0 | 174 | NeedNewFrame(mGIFStruct.images_decoded, mGIFStruct.x_offset, |
michael@0 | 175 | mGIFStruct.y_offset, mGIFStruct.width, mGIFStruct.height, |
michael@0 | 176 | format, aDepth); |
michael@0 | 177 | } |
michael@0 | 178 | |
michael@0 | 179 | // Our first full frame is automatically created by the image decoding |
michael@0 | 180 | // infrastructure. Just use it as long as it matches up. |
michael@0 | 181 | else if (!GetCurrentFrame()->GetRect().IsEqualEdges(nsIntRect(mGIFStruct.x_offset, |
michael@0 | 182 | mGIFStruct.y_offset, |
michael@0 | 183 | mGIFStruct.width, |
michael@0 | 184 | mGIFStruct.height))) { |
michael@0 | 185 | // Regardless of depth of input, image is decoded into 24bit RGB |
michael@0 | 186 | NeedNewFrame(mGIFStruct.images_decoded, mGIFStruct.x_offset, |
michael@0 | 187 | mGIFStruct.y_offset, mGIFStruct.width, mGIFStruct.height, |
michael@0 | 188 | format); |
michael@0 | 189 | } else { |
michael@0 | 190 | // Our preallocated frame matches up, with the possible exception of alpha. |
michael@0 | 191 | if (format == gfxImageFormat::RGB24) { |
michael@0 | 192 | GetCurrentFrame()->SetHasNoAlpha(); |
michael@0 | 193 | } |
michael@0 | 194 | } |
michael@0 | 195 | |
michael@0 | 196 | mCurrentFrame = mGIFStruct.images_decoded; |
michael@0 | 197 | } |
michael@0 | 198 | |
michael@0 | 199 | |
michael@0 | 200 | //****************************************************************************** |
michael@0 | 201 | void nsGIFDecoder2::EndImageFrame() |
michael@0 | 202 | { |
michael@0 | 203 | FrameBlender::FrameAlpha alpha = FrameBlender::kFrameHasAlpha; |
michael@0 | 204 | |
michael@0 | 205 | // First flush all pending image data |
michael@0 | 206 | if (!mGIFStruct.images_decoded) { |
michael@0 | 207 | // Only need to flush first frame |
michael@0 | 208 | FlushImageData(); |
michael@0 | 209 | |
michael@0 | 210 | // If the first frame is smaller in height than the entire image, send an |
michael@0 | 211 | // invalidation for the area it does not have data for. |
michael@0 | 212 | // This will clear the remaining bits of the placeholder. (Bug 37589) |
michael@0 | 213 | const uint32_t realFrameHeight = mGIFStruct.height + mGIFStruct.y_offset; |
michael@0 | 214 | if (realFrameHeight < mGIFStruct.screen_height) { |
michael@0 | 215 | nsIntRect r(0, realFrameHeight, |
michael@0 | 216 | mGIFStruct.screen_width, |
michael@0 | 217 | mGIFStruct.screen_height - realFrameHeight); |
michael@0 | 218 | PostInvalidation(r); |
michael@0 | 219 | } |
michael@0 | 220 | // This transparency check is only valid for first frame |
michael@0 | 221 | if (mGIFStruct.is_transparent && !mSawTransparency) { |
michael@0 | 222 | alpha = FrameBlender::kFrameOpaque; |
michael@0 | 223 | } |
michael@0 | 224 | } |
michael@0 | 225 | mCurrentRow = mLastFlushedRow = -1; |
michael@0 | 226 | mCurrentPass = mLastFlushedPass = 0; |
michael@0 | 227 | |
michael@0 | 228 | // Only add frame if we have any rows at all |
michael@0 | 229 | if (mGIFStruct.rows_remaining != mGIFStruct.height) { |
michael@0 | 230 | if (mGIFStruct.rows_remaining && mGIFStruct.images_decoded) { |
michael@0 | 231 | // Clear the remaining rows (only needed for the animation frames) |
michael@0 | 232 | uint8_t *rowp = mImageData + ((mGIFStruct.height - mGIFStruct.rows_remaining) * mGIFStruct.width); |
michael@0 | 233 | memset(rowp, 0, mGIFStruct.rows_remaining * mGIFStruct.width); |
michael@0 | 234 | } |
michael@0 | 235 | } |
michael@0 | 236 | |
michael@0 | 237 | // Unconditionally increment images_decoded, because we unconditionally |
michael@0 | 238 | // append frames in BeginImageFrame(). This ensures that images_decoded |
michael@0 | 239 | // always refers to the frame in mImage we're currently decoding, |
michael@0 | 240 | // even if some of them weren't decoded properly and thus are blank. |
michael@0 | 241 | mGIFStruct.images_decoded++; |
michael@0 | 242 | |
michael@0 | 243 | // Tell the superclass we finished a frame |
michael@0 | 244 | PostFrameStop(alpha, |
michael@0 | 245 | FrameBlender::FrameDisposalMethod(mGIFStruct.disposal_method), |
michael@0 | 246 | mGIFStruct.delay_time); |
michael@0 | 247 | |
michael@0 | 248 | // Reset the transparent pixel |
michael@0 | 249 | if (mOldColor) { |
michael@0 | 250 | mColormap[mGIFStruct.tpixel] = mOldColor; |
michael@0 | 251 | mOldColor = 0; |
michael@0 | 252 | } |
michael@0 | 253 | |
michael@0 | 254 | mCurrentFrame = -1; |
michael@0 | 255 | } |
michael@0 | 256 | |
michael@0 | 257 | |
michael@0 | 258 | //****************************************************************************** |
michael@0 | 259 | // Send the data to the display front-end. |
michael@0 | 260 | uint32_t nsGIFDecoder2::OutputRow() |
michael@0 | 261 | { |
michael@0 | 262 | int drow_start, drow_end; |
michael@0 | 263 | drow_start = drow_end = mGIFStruct.irow; |
michael@0 | 264 | |
michael@0 | 265 | /* Protect against too much image data */ |
michael@0 | 266 | if ((unsigned)drow_start >= mGIFStruct.height) { |
michael@0 | 267 | NS_WARNING("GIF2.cpp::OutputRow - too much image data"); |
michael@0 | 268 | return 0; |
michael@0 | 269 | } |
michael@0 | 270 | |
michael@0 | 271 | if (!mGIFStruct.images_decoded) { |
michael@0 | 272 | /* |
michael@0 | 273 | * Haeberli-inspired hack for interlaced GIFs: Replicate lines while |
michael@0 | 274 | * displaying to diminish the "venetian-blind" effect as the image is |
michael@0 | 275 | * loaded. Adjust pixel vertical positions to avoid the appearance of the |
michael@0 | 276 | * image crawling up the screen as successive passes are drawn. |
michael@0 | 277 | */ |
michael@0 | 278 | if (mGIFStruct.progressive_display && mGIFStruct.interlaced && (mGIFStruct.ipass < 4)) { |
michael@0 | 279 | /* ipass = 1,2,3 results in resp. row_dup = 7,3,1 and row_shift = 3,1,0 */ |
michael@0 | 280 | const uint32_t row_dup = 15 >> mGIFStruct.ipass; |
michael@0 | 281 | const uint32_t row_shift = row_dup >> 1; |
michael@0 | 282 | |
michael@0 | 283 | drow_start -= row_shift; |
michael@0 | 284 | drow_end = drow_start + row_dup; |
michael@0 | 285 | |
michael@0 | 286 | /* Extend if bottom edge isn't covered because of the shift upward. */ |
michael@0 | 287 | if (((mGIFStruct.height - 1) - drow_end) <= row_shift) |
michael@0 | 288 | drow_end = mGIFStruct.height - 1; |
michael@0 | 289 | |
michael@0 | 290 | /* Clamp first and last rows to upper and lower edge of image. */ |
michael@0 | 291 | if (drow_start < 0) |
michael@0 | 292 | drow_start = 0; |
michael@0 | 293 | if ((unsigned)drow_end >= mGIFStruct.height) |
michael@0 | 294 | drow_end = mGIFStruct.height - 1; |
michael@0 | 295 | } |
michael@0 | 296 | |
michael@0 | 297 | // Row to process |
michael@0 | 298 | const uint32_t bpr = sizeof(uint32_t) * mGIFStruct.width; |
michael@0 | 299 | uint8_t *rowp = mImageData + (mGIFStruct.irow * bpr); |
michael@0 | 300 | |
michael@0 | 301 | // Convert color indices to Cairo pixels |
michael@0 | 302 | uint8_t *from = rowp + mGIFStruct.width; |
michael@0 | 303 | uint32_t *to = ((uint32_t*)rowp) + mGIFStruct.width; |
michael@0 | 304 | uint32_t *cmap = mColormap; |
michael@0 | 305 | for (uint32_t c = mGIFStruct.width; c > 0; c--) { |
michael@0 | 306 | *--to = cmap[*--from]; |
michael@0 | 307 | } |
michael@0 | 308 | |
michael@0 | 309 | // check for alpha (only for first frame) |
michael@0 | 310 | if (mGIFStruct.is_transparent && !mSawTransparency) { |
michael@0 | 311 | const uint32_t *rgb = (uint32_t*)rowp; |
michael@0 | 312 | for (uint32_t i = mGIFStruct.width; i > 0; i--) { |
michael@0 | 313 | if (*rgb++ == 0) { |
michael@0 | 314 | mSawTransparency = true; |
michael@0 | 315 | break; |
michael@0 | 316 | } |
michael@0 | 317 | } |
michael@0 | 318 | } |
michael@0 | 319 | |
michael@0 | 320 | // Duplicate rows |
michael@0 | 321 | if (drow_end > drow_start) { |
michael@0 | 322 | // irow is the current row filled |
michael@0 | 323 | for (int r = drow_start; r <= drow_end; r++) { |
michael@0 | 324 | if (r != int(mGIFStruct.irow)) { |
michael@0 | 325 | memcpy(mImageData + (r * bpr), rowp, bpr); |
michael@0 | 326 | } |
michael@0 | 327 | } |
michael@0 | 328 | } |
michael@0 | 329 | } |
michael@0 | 330 | |
michael@0 | 331 | mCurrentRow = drow_end; |
michael@0 | 332 | mCurrentPass = mGIFStruct.ipass; |
michael@0 | 333 | if (mGIFStruct.ipass == 1) |
michael@0 | 334 | mLastFlushedPass = mGIFStruct.ipass; // interlaced starts at 1 |
michael@0 | 335 | |
michael@0 | 336 | if (!mGIFStruct.interlaced) { |
michael@0 | 337 | mGIFStruct.irow++; |
michael@0 | 338 | } else { |
michael@0 | 339 | static const uint8_t kjump[5] = { 1, 8, 8, 4, 2 }; |
michael@0 | 340 | do { |
michael@0 | 341 | // Row increments resp. per 8,8,4,2 rows |
michael@0 | 342 | mGIFStruct.irow += kjump[mGIFStruct.ipass]; |
michael@0 | 343 | if (mGIFStruct.irow >= mGIFStruct.height) { |
michael@0 | 344 | // Next pass starts resp. at row 4,2,1,0 |
michael@0 | 345 | mGIFStruct.irow = 8 >> mGIFStruct.ipass; |
michael@0 | 346 | mGIFStruct.ipass++; |
michael@0 | 347 | } |
michael@0 | 348 | } while (mGIFStruct.irow >= mGIFStruct.height); |
michael@0 | 349 | } |
michael@0 | 350 | |
michael@0 | 351 | return --mGIFStruct.rows_remaining; |
michael@0 | 352 | } |
michael@0 | 353 | |
michael@0 | 354 | //****************************************************************************** |
michael@0 | 355 | /* Perform Lempel-Ziv-Welch decoding */ |
michael@0 | 356 | bool |
michael@0 | 357 | nsGIFDecoder2::DoLzw(const uint8_t *q) |
michael@0 | 358 | { |
michael@0 | 359 | if (!mGIFStruct.rows_remaining) |
michael@0 | 360 | return true; |
michael@0 | 361 | |
michael@0 | 362 | /* Copy all the decoder state variables into locals so the compiler |
michael@0 | 363 | * won't worry about them being aliased. The locals will be homed |
michael@0 | 364 | * back into the GIF decoder structure when we exit. |
michael@0 | 365 | */ |
michael@0 | 366 | int avail = mGIFStruct.avail; |
michael@0 | 367 | int bits = mGIFStruct.bits; |
michael@0 | 368 | int codesize = mGIFStruct.codesize; |
michael@0 | 369 | int codemask = mGIFStruct.codemask; |
michael@0 | 370 | int count = mGIFStruct.count; |
michael@0 | 371 | int oldcode = mGIFStruct.oldcode; |
michael@0 | 372 | const int clear_code = ClearCode(); |
michael@0 | 373 | uint8_t firstchar = mGIFStruct.firstchar; |
michael@0 | 374 | int32_t datum = mGIFStruct.datum; |
michael@0 | 375 | uint16_t *prefix = mGIFStruct.prefix; |
michael@0 | 376 | uint8_t *stackp = mGIFStruct.stackp; |
michael@0 | 377 | uint8_t *suffix = mGIFStruct.suffix; |
michael@0 | 378 | uint8_t *stack = mGIFStruct.stack; |
michael@0 | 379 | uint8_t *rowp = mGIFStruct.rowp; |
michael@0 | 380 | |
michael@0 | 381 | uint32_t bpr = mGIFStruct.width; |
michael@0 | 382 | if (!mGIFStruct.images_decoded) |
michael@0 | 383 | bpr *= sizeof(uint32_t); |
michael@0 | 384 | uint8_t *rowend = mImageData + (bpr * mGIFStruct.irow) + mGIFStruct.width; |
michael@0 | 385 | |
michael@0 | 386 | #define OUTPUT_ROW() \ |
michael@0 | 387 | PR_BEGIN_MACRO \ |
michael@0 | 388 | if (!OutputRow()) \ |
michael@0 | 389 | goto END; \ |
michael@0 | 390 | rowp = mImageData + mGIFStruct.irow * bpr; \ |
michael@0 | 391 | rowend = rowp + mGIFStruct.width; \ |
michael@0 | 392 | PR_END_MACRO |
michael@0 | 393 | |
michael@0 | 394 | for (const uint8_t* ch = q; count-- > 0; ch++) |
michael@0 | 395 | { |
michael@0 | 396 | /* Feed the next byte into the decoder's 32-bit input buffer. */ |
michael@0 | 397 | datum += ((int32_t) *ch) << bits; |
michael@0 | 398 | bits += 8; |
michael@0 | 399 | |
michael@0 | 400 | /* Check for underflow of decoder's 32-bit input buffer. */ |
michael@0 | 401 | while (bits >= codesize) |
michael@0 | 402 | { |
michael@0 | 403 | /* Get the leading variable-length symbol from the data stream */ |
michael@0 | 404 | int code = datum & codemask; |
michael@0 | 405 | datum >>= codesize; |
michael@0 | 406 | bits -= codesize; |
michael@0 | 407 | |
michael@0 | 408 | /* Reset the dictionary to its original state, if requested */ |
michael@0 | 409 | if (code == clear_code) { |
michael@0 | 410 | codesize = mGIFStruct.datasize + 1; |
michael@0 | 411 | codemask = (1 << codesize) - 1; |
michael@0 | 412 | avail = clear_code + 2; |
michael@0 | 413 | oldcode = -1; |
michael@0 | 414 | continue; |
michael@0 | 415 | } |
michael@0 | 416 | |
michael@0 | 417 | /* Check for explicit end-of-stream code */ |
michael@0 | 418 | if (code == (clear_code + 1)) { |
michael@0 | 419 | /* end-of-stream should only appear after all image data */ |
michael@0 | 420 | return (mGIFStruct.rows_remaining == 0); |
michael@0 | 421 | } |
michael@0 | 422 | |
michael@0 | 423 | if (oldcode == -1) { |
michael@0 | 424 | if (code >= MAX_BITS) |
michael@0 | 425 | return false; |
michael@0 | 426 | *rowp++ = suffix[code] & mColorMask; // ensure index is within colormap |
michael@0 | 427 | if (rowp == rowend) |
michael@0 | 428 | OUTPUT_ROW(); |
michael@0 | 429 | |
michael@0 | 430 | firstchar = oldcode = code; |
michael@0 | 431 | continue; |
michael@0 | 432 | } |
michael@0 | 433 | |
michael@0 | 434 | int incode = code; |
michael@0 | 435 | if (code >= avail) { |
michael@0 | 436 | *stackp++ = firstchar; |
michael@0 | 437 | code = oldcode; |
michael@0 | 438 | |
michael@0 | 439 | if (stackp >= stack + MAX_BITS) |
michael@0 | 440 | return false; |
michael@0 | 441 | } |
michael@0 | 442 | |
michael@0 | 443 | while (code >= clear_code) |
michael@0 | 444 | { |
michael@0 | 445 | if ((code >= MAX_BITS) || (code == prefix[code])) |
michael@0 | 446 | return false; |
michael@0 | 447 | |
michael@0 | 448 | *stackp++ = suffix[code]; |
michael@0 | 449 | code = prefix[code]; |
michael@0 | 450 | |
michael@0 | 451 | if (stackp == stack + MAX_BITS) |
michael@0 | 452 | return false; |
michael@0 | 453 | } |
michael@0 | 454 | |
michael@0 | 455 | *stackp++ = firstchar = suffix[code]; |
michael@0 | 456 | |
michael@0 | 457 | /* Define a new codeword in the dictionary. */ |
michael@0 | 458 | if (avail < 4096) { |
michael@0 | 459 | prefix[avail] = oldcode; |
michael@0 | 460 | suffix[avail] = firstchar; |
michael@0 | 461 | avail++; |
michael@0 | 462 | |
michael@0 | 463 | /* If we've used up all the codewords of a given length |
michael@0 | 464 | * increase the length of codewords by one bit, but don't |
michael@0 | 465 | * exceed the specified maximum codeword size of 12 bits. |
michael@0 | 466 | */ |
michael@0 | 467 | if (((avail & codemask) == 0) && (avail < 4096)) { |
michael@0 | 468 | codesize++; |
michael@0 | 469 | codemask += avail; |
michael@0 | 470 | } |
michael@0 | 471 | } |
michael@0 | 472 | oldcode = incode; |
michael@0 | 473 | |
michael@0 | 474 | /* Copy the decoded data out to the scanline buffer. */ |
michael@0 | 475 | do { |
michael@0 | 476 | *rowp++ = *--stackp & mColorMask; // ensure index is within colormap |
michael@0 | 477 | if (rowp == rowend) |
michael@0 | 478 | OUTPUT_ROW(); |
michael@0 | 479 | } while (stackp > stack); |
michael@0 | 480 | } |
michael@0 | 481 | } |
michael@0 | 482 | |
michael@0 | 483 | END: |
michael@0 | 484 | |
michael@0 | 485 | /* Home the local copies of the GIF decoder state variables */ |
michael@0 | 486 | mGIFStruct.avail = avail; |
michael@0 | 487 | mGIFStruct.bits = bits; |
michael@0 | 488 | mGIFStruct.codesize = codesize; |
michael@0 | 489 | mGIFStruct.codemask = codemask; |
michael@0 | 490 | mGIFStruct.count = count; |
michael@0 | 491 | mGIFStruct.oldcode = oldcode; |
michael@0 | 492 | mGIFStruct.firstchar = firstchar; |
michael@0 | 493 | mGIFStruct.datum = datum; |
michael@0 | 494 | mGIFStruct.stackp = stackp; |
michael@0 | 495 | mGIFStruct.rowp = rowp; |
michael@0 | 496 | |
michael@0 | 497 | return true; |
michael@0 | 498 | } |
michael@0 | 499 | |
michael@0 | 500 | /** |
michael@0 | 501 | * Expand the colormap from RGB to Packed ARGB as needed by Cairo. |
michael@0 | 502 | * And apply any LCMS transformation. |
michael@0 | 503 | */ |
michael@0 | 504 | static void ConvertColormap(uint32_t *aColormap, uint32_t aColors) |
michael@0 | 505 | { |
michael@0 | 506 | // Apply CMS transformation if enabled and available |
michael@0 | 507 | if (gfxPlatform::GetCMSMode() == eCMSMode_All) { |
michael@0 | 508 | qcms_transform *transform = gfxPlatform::GetCMSRGBTransform(); |
michael@0 | 509 | if (transform) |
michael@0 | 510 | qcms_transform_data(transform, aColormap, aColormap, aColors); |
michael@0 | 511 | } |
michael@0 | 512 | // Convert from the GIF's RGB format to the Cairo format. |
michael@0 | 513 | // Work from end to begin, because of the in-place expansion |
michael@0 | 514 | uint8_t *from = ((uint8_t *)aColormap) + 3 * aColors; |
michael@0 | 515 | uint32_t *to = aColormap + aColors; |
michael@0 | 516 | |
michael@0 | 517 | // Convert color entries to Cairo format |
michael@0 | 518 | |
michael@0 | 519 | // set up for loops below |
michael@0 | 520 | if (!aColors) return; |
michael@0 | 521 | uint32_t c = aColors; |
michael@0 | 522 | |
michael@0 | 523 | // copy as bytes until source pointer is 32-bit-aligned |
michael@0 | 524 | // NB: can't use 32-bit reads, they might read off the end of the buffer |
michael@0 | 525 | for (; (NS_PTR_TO_UINT32(from) & 0x3) && c; --c) { |
michael@0 | 526 | from -= 3; |
michael@0 | 527 | *--to = gfxPackedPixel(0xFF, from[0], from[1], from[2]); |
michael@0 | 528 | } |
michael@0 | 529 | |
michael@0 | 530 | // bulk copy of pixels. |
michael@0 | 531 | while (c >= 4) { |
michael@0 | 532 | from -= 12; |
michael@0 | 533 | to -= 4; |
michael@0 | 534 | c -= 4; |
michael@0 | 535 | GFX_BLOCK_RGB_TO_FRGB(from,to); |
michael@0 | 536 | } |
michael@0 | 537 | |
michael@0 | 538 | // copy remaining pixel(s) |
michael@0 | 539 | // NB: can't use 32-bit reads, they might read off the end of the buffer |
michael@0 | 540 | while (c--) { |
michael@0 | 541 | from -= 3; |
michael@0 | 542 | *--to = gfxPackedPixel(0xFF, from[0], from[1], from[2]); |
michael@0 | 543 | } |
michael@0 | 544 | } |
michael@0 | 545 | |
michael@0 | 546 | void |
michael@0 | 547 | nsGIFDecoder2::WriteInternal(const char *aBuffer, uint32_t aCount, DecodeStrategy) |
michael@0 | 548 | { |
michael@0 | 549 | NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!"); |
michael@0 | 550 | |
michael@0 | 551 | // These variables changed names, and renaming would make a much bigger patch :( |
michael@0 | 552 | const uint8_t *buf = (const uint8_t *)aBuffer; |
michael@0 | 553 | uint32_t len = aCount; |
michael@0 | 554 | |
michael@0 | 555 | const uint8_t *q = buf; |
michael@0 | 556 | |
michael@0 | 557 | // Add what we have sofar to the block |
michael@0 | 558 | // If previous call to me left something in the hold first complete current block |
michael@0 | 559 | // Or if we are filling the colormaps, first complete the colormap |
michael@0 | 560 | uint8_t* p = (mGIFStruct.state == gif_global_colormap) ? (uint8_t*)mGIFStruct.global_colormap : |
michael@0 | 561 | (mGIFStruct.state == gif_image_colormap) ? (uint8_t*)mColormap : |
michael@0 | 562 | (mGIFStruct.bytes_in_hold) ? mGIFStruct.hold : nullptr; |
michael@0 | 563 | |
michael@0 | 564 | if (len == 0 && buf == nullptr) { |
michael@0 | 565 | // We've just gotten the frame we asked for. Time to use the data we |
michael@0 | 566 | // stashed away. |
michael@0 | 567 | len = mGIFStruct.bytes_in_hold; |
michael@0 | 568 | q = buf = p; |
michael@0 | 569 | } else if (p) { |
michael@0 | 570 | // Add what we have sofar to the block |
michael@0 | 571 | uint32_t l = std::min(len, mGIFStruct.bytes_to_consume); |
michael@0 | 572 | memcpy(p+mGIFStruct.bytes_in_hold, buf, l); |
michael@0 | 573 | |
michael@0 | 574 | if (l < mGIFStruct.bytes_to_consume) { |
michael@0 | 575 | // Not enough in 'buf' to complete current block, get more |
michael@0 | 576 | mGIFStruct.bytes_in_hold += l; |
michael@0 | 577 | mGIFStruct.bytes_to_consume -= l; |
michael@0 | 578 | return; |
michael@0 | 579 | } |
michael@0 | 580 | // Point 'q' to complete block in hold (or in colormap) |
michael@0 | 581 | q = p; |
michael@0 | 582 | } |
michael@0 | 583 | |
michael@0 | 584 | // Invariant: |
michael@0 | 585 | // 'q' is start of current to be processed block (hold, colormap or buf) |
michael@0 | 586 | // 'bytes_to_consume' is number of bytes to consume from 'buf' |
michael@0 | 587 | // 'buf' points to the bytes to be consumed from the input buffer |
michael@0 | 588 | // 'len' is number of bytes left in input buffer from position 'buf'. |
michael@0 | 589 | // At entrance of the for loop will 'buf' will be moved 'bytes_to_consume' |
michael@0 | 590 | // to point to next buffer, 'len' is adjusted accordingly. |
michael@0 | 591 | // So that next round in for loop, q gets pointed to the next buffer. |
michael@0 | 592 | |
michael@0 | 593 | for (;len >= mGIFStruct.bytes_to_consume; q=buf, mGIFStruct.bytes_in_hold = 0) { |
michael@0 | 594 | // Eat the current block from the buffer, q keeps pointed at current block |
michael@0 | 595 | buf += mGIFStruct.bytes_to_consume; |
michael@0 | 596 | len -= mGIFStruct.bytes_to_consume; |
michael@0 | 597 | |
michael@0 | 598 | switch (mGIFStruct.state) |
michael@0 | 599 | { |
michael@0 | 600 | case gif_lzw: |
michael@0 | 601 | if (!DoLzw(q)) { |
michael@0 | 602 | mGIFStruct.state = gif_error; |
michael@0 | 603 | break; |
michael@0 | 604 | } |
michael@0 | 605 | GETN(1, gif_sub_block); |
michael@0 | 606 | break; |
michael@0 | 607 | |
michael@0 | 608 | case gif_lzw_start: |
michael@0 | 609 | { |
michael@0 | 610 | // Make sure the transparent pixel is transparent in the colormap |
michael@0 | 611 | if (mGIFStruct.is_transparent) { |
michael@0 | 612 | // Save old value so we can restore it later |
michael@0 | 613 | if (mColormap == mGIFStruct.global_colormap) |
michael@0 | 614 | mOldColor = mColormap[mGIFStruct.tpixel]; |
michael@0 | 615 | mColormap[mGIFStruct.tpixel] = 0; |
michael@0 | 616 | } |
michael@0 | 617 | |
michael@0 | 618 | /* Initialize LZW parser/decoder */ |
michael@0 | 619 | mGIFStruct.datasize = *q; |
michael@0 | 620 | const int clear_code = ClearCode(); |
michael@0 | 621 | if (mGIFStruct.datasize > MAX_LZW_BITS || |
michael@0 | 622 | clear_code >= MAX_BITS) { |
michael@0 | 623 | mGIFStruct.state = gif_error; |
michael@0 | 624 | break; |
michael@0 | 625 | } |
michael@0 | 626 | |
michael@0 | 627 | mGIFStruct.avail = clear_code + 2; |
michael@0 | 628 | mGIFStruct.oldcode = -1; |
michael@0 | 629 | mGIFStruct.codesize = mGIFStruct.datasize + 1; |
michael@0 | 630 | mGIFStruct.codemask = (1 << mGIFStruct.codesize) - 1; |
michael@0 | 631 | mGIFStruct.datum = mGIFStruct.bits = 0; |
michael@0 | 632 | |
michael@0 | 633 | /* init the tables */ |
michael@0 | 634 | for (int i = 0; i < clear_code; i++) |
michael@0 | 635 | mGIFStruct.suffix[i] = i; |
michael@0 | 636 | |
michael@0 | 637 | mGIFStruct.stackp = mGIFStruct.stack; |
michael@0 | 638 | |
michael@0 | 639 | GETN(1, gif_sub_block); |
michael@0 | 640 | } |
michael@0 | 641 | break; |
michael@0 | 642 | |
michael@0 | 643 | /* All GIF files begin with "GIF87a" or "GIF89a" */ |
michael@0 | 644 | case gif_type: |
michael@0 | 645 | if (!strncmp((char*)q, "GIF89a", 6)) { |
michael@0 | 646 | mGIFStruct.version = 89; |
michael@0 | 647 | } else if (!strncmp((char*)q, "GIF87a", 6)) { |
michael@0 | 648 | mGIFStruct.version = 87; |
michael@0 | 649 | } else { |
michael@0 | 650 | mGIFStruct.state = gif_error; |
michael@0 | 651 | break; |
michael@0 | 652 | } |
michael@0 | 653 | GETN(7, gif_global_header); |
michael@0 | 654 | break; |
michael@0 | 655 | |
michael@0 | 656 | case gif_global_header: |
michael@0 | 657 | /* This is the height and width of the "screen" or |
michael@0 | 658 | * frame into which images are rendered. The |
michael@0 | 659 | * individual images can be smaller than the |
michael@0 | 660 | * screen size and located with an origin anywhere |
michael@0 | 661 | * within the screen. |
michael@0 | 662 | */ |
michael@0 | 663 | |
michael@0 | 664 | mGIFStruct.screen_width = GETINT16(q); |
michael@0 | 665 | mGIFStruct.screen_height = GETINT16(q + 2); |
michael@0 | 666 | mGIFStruct.global_colormap_depth = (q[4]&0x07) + 1; |
michael@0 | 667 | |
michael@0 | 668 | if (IsSizeDecode()) { |
michael@0 | 669 | MOZ_ASSERT(!mGIFOpen, "Gif should not be open at this point"); |
michael@0 | 670 | PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height); |
michael@0 | 671 | return; |
michael@0 | 672 | } |
michael@0 | 673 | |
michael@0 | 674 | // screen_bgcolor is not used |
michael@0 | 675 | //mGIFStruct.screen_bgcolor = q[5]; |
michael@0 | 676 | // q[6] = Pixel Aspect Ratio |
michael@0 | 677 | // Not used |
michael@0 | 678 | // float aspect = (float)((q[6] + 15) / 64.0); |
michael@0 | 679 | |
michael@0 | 680 | if (q[4] & 0x80) { /* global map */ |
michael@0 | 681 | // Get the global colormap |
michael@0 | 682 | const uint32_t size = (3 << mGIFStruct.global_colormap_depth); |
michael@0 | 683 | if (len < size) { |
michael@0 | 684 | // Use 'hold' pattern to get the global colormap |
michael@0 | 685 | GETN(size, gif_global_colormap); |
michael@0 | 686 | break; |
michael@0 | 687 | } |
michael@0 | 688 | // Copy everything, go to colormap state to do CMS correction |
michael@0 | 689 | memcpy(mGIFStruct.global_colormap, buf, size); |
michael@0 | 690 | buf += size; |
michael@0 | 691 | len -= size; |
michael@0 | 692 | GETN(0, gif_global_colormap); |
michael@0 | 693 | break; |
michael@0 | 694 | } |
michael@0 | 695 | |
michael@0 | 696 | GETN(1, gif_image_start); |
michael@0 | 697 | break; |
michael@0 | 698 | |
michael@0 | 699 | case gif_global_colormap: |
michael@0 | 700 | // Everything is already copied into global_colormap |
michael@0 | 701 | // Convert into Cairo colors including CMS transformation |
michael@0 | 702 | ConvertColormap(mGIFStruct.global_colormap, 1<<mGIFStruct.global_colormap_depth); |
michael@0 | 703 | GETN(1, gif_image_start); |
michael@0 | 704 | break; |
michael@0 | 705 | |
michael@0 | 706 | case gif_image_start: |
michael@0 | 707 | switch (*q) { |
michael@0 | 708 | case GIF_TRAILER: |
michael@0 | 709 | mGIFStruct.state = gif_done; |
michael@0 | 710 | break; |
michael@0 | 711 | |
michael@0 | 712 | case GIF_EXTENSION_INTRODUCER: |
michael@0 | 713 | GETN(2, gif_extension); |
michael@0 | 714 | break; |
michael@0 | 715 | |
michael@0 | 716 | case GIF_IMAGE_SEPARATOR: |
michael@0 | 717 | GETN(9, gif_image_header); |
michael@0 | 718 | break; |
michael@0 | 719 | |
michael@0 | 720 | default: |
michael@0 | 721 | /* If we get anything other than GIF_IMAGE_SEPARATOR, |
michael@0 | 722 | * GIF_EXTENSION_INTRODUCER, or GIF_TRAILER, there is extraneous data |
michael@0 | 723 | * between blocks. The GIF87a spec tells us to keep reading |
michael@0 | 724 | * until we find an image separator, but GIF89a says such |
michael@0 | 725 | * a file is corrupt. We follow GIF89a and bail out. */ |
michael@0 | 726 | if (mGIFStruct.images_decoded > 0) { |
michael@0 | 727 | /* The file is corrupt, but one or more images have |
michael@0 | 728 | * been decoded correctly. In this case, we proceed |
michael@0 | 729 | * as if the file were correctly terminated and set |
michael@0 | 730 | * the state to gif_done, so the GIF will display. |
michael@0 | 731 | */ |
michael@0 | 732 | mGIFStruct.state = gif_done; |
michael@0 | 733 | } else { |
michael@0 | 734 | /* No images decoded, there is nothing to display. */ |
michael@0 | 735 | mGIFStruct.state = gif_error; |
michael@0 | 736 | } |
michael@0 | 737 | } |
michael@0 | 738 | break; |
michael@0 | 739 | |
michael@0 | 740 | case gif_extension: |
michael@0 | 741 | mGIFStruct.bytes_to_consume = q[1]; |
michael@0 | 742 | if (mGIFStruct.bytes_to_consume) { |
michael@0 | 743 | switch (*q) { |
michael@0 | 744 | case GIF_GRAPHIC_CONTROL_LABEL: |
michael@0 | 745 | // The GIF spec mandates that the GIFControlExtension header block length is 4 bytes, |
michael@0 | 746 | // and the parser for this block reads 4 bytes, so we must enforce that the buffer |
michael@0 | 747 | // contains at least this many bytes. If the GIF specifies a different length, we |
michael@0 | 748 | // allow that, so long as it's larger; the additional data will simply be ignored. |
michael@0 | 749 | mGIFStruct.state = gif_control_extension; |
michael@0 | 750 | mGIFStruct.bytes_to_consume = std::max(mGIFStruct.bytes_to_consume, 4u); |
michael@0 | 751 | break; |
michael@0 | 752 | |
michael@0 | 753 | // The GIF spec also specifies the lengths of the following two extensions' headers |
michael@0 | 754 | // (as 12 and 11 bytes, respectively). Because we ignore the plain text extension entirely |
michael@0 | 755 | // and sanity-check the actual length of the application extension header before reading it, |
michael@0 | 756 | // we allow GIFs to deviate from these values in either direction. This is important for |
michael@0 | 757 | // real-world compatibility, as GIFs in the wild exist with application extension headers |
michael@0 | 758 | // that are both shorter and longer than 11 bytes. |
michael@0 | 759 | case GIF_APPLICATION_EXTENSION_LABEL: |
michael@0 | 760 | mGIFStruct.state = gif_application_extension; |
michael@0 | 761 | break; |
michael@0 | 762 | |
michael@0 | 763 | case GIF_PLAIN_TEXT_LABEL: |
michael@0 | 764 | mGIFStruct.state = gif_skip_block; |
michael@0 | 765 | break; |
michael@0 | 766 | |
michael@0 | 767 | case GIF_COMMENT_LABEL: |
michael@0 | 768 | mGIFStruct.state = gif_consume_comment; |
michael@0 | 769 | break; |
michael@0 | 770 | |
michael@0 | 771 | default: |
michael@0 | 772 | mGIFStruct.state = gif_skip_block; |
michael@0 | 773 | } |
michael@0 | 774 | } else { |
michael@0 | 775 | GETN(1, gif_image_start); |
michael@0 | 776 | } |
michael@0 | 777 | break; |
michael@0 | 778 | |
michael@0 | 779 | case gif_consume_block: |
michael@0 | 780 | if (!*q) |
michael@0 | 781 | GETN(1, gif_image_start); |
michael@0 | 782 | else |
michael@0 | 783 | GETN(*q, gif_skip_block); |
michael@0 | 784 | break; |
michael@0 | 785 | |
michael@0 | 786 | case gif_skip_block: |
michael@0 | 787 | GETN(1, gif_consume_block); |
michael@0 | 788 | break; |
michael@0 | 789 | |
michael@0 | 790 | case gif_control_extension: |
michael@0 | 791 | mGIFStruct.is_transparent = *q & 0x1; |
michael@0 | 792 | mGIFStruct.tpixel = q[3]; |
michael@0 | 793 | mGIFStruct.disposal_method = ((*q) >> 2) & 0x7; |
michael@0 | 794 | // Some specs say 3rd bit (value 4), other specs say value 3 |
michael@0 | 795 | // Let's choose 3 (the more popular) |
michael@0 | 796 | if (mGIFStruct.disposal_method == 4) |
michael@0 | 797 | mGIFStruct.disposal_method = 3; |
michael@0 | 798 | mGIFStruct.delay_time = GETINT16(q + 1) * 10; |
michael@0 | 799 | GETN(1, gif_consume_block); |
michael@0 | 800 | break; |
michael@0 | 801 | |
michael@0 | 802 | case gif_comment_extension: |
michael@0 | 803 | if (*q) |
michael@0 | 804 | GETN(*q, gif_consume_comment); |
michael@0 | 805 | else |
michael@0 | 806 | GETN(1, gif_image_start); |
michael@0 | 807 | break; |
michael@0 | 808 | |
michael@0 | 809 | case gif_consume_comment: |
michael@0 | 810 | GETN(1, gif_comment_extension); |
michael@0 | 811 | break; |
michael@0 | 812 | |
michael@0 | 813 | case gif_application_extension: |
michael@0 | 814 | /* Check for netscape application extension */ |
michael@0 | 815 | if (mGIFStruct.bytes_to_consume == 11 && |
michael@0 | 816 | (!strncmp((char*)q, "NETSCAPE2.0", 11) || |
michael@0 | 817 | !strncmp((char*)q, "ANIMEXTS1.0", 11))) |
michael@0 | 818 | GETN(1, gif_netscape_extension_block); |
michael@0 | 819 | else |
michael@0 | 820 | GETN(1, gif_consume_block); |
michael@0 | 821 | break; |
michael@0 | 822 | |
michael@0 | 823 | /* Netscape-specific GIF extension: animation looping */ |
michael@0 | 824 | case gif_netscape_extension_block: |
michael@0 | 825 | if (*q) |
michael@0 | 826 | // We might need to consume 3 bytes in |
michael@0 | 827 | // gif_consume_netscape_extension, so make sure we have at least that. |
michael@0 | 828 | GETN(std::max(3, static_cast<int>(*q)), gif_consume_netscape_extension); |
michael@0 | 829 | else |
michael@0 | 830 | GETN(1, gif_image_start); |
michael@0 | 831 | break; |
michael@0 | 832 | |
michael@0 | 833 | /* Parse netscape-specific application extensions */ |
michael@0 | 834 | case gif_consume_netscape_extension: |
michael@0 | 835 | switch (q[0] & 7) { |
michael@0 | 836 | case 1: |
michael@0 | 837 | /* Loop entire animation specified # of times. Only read the |
michael@0 | 838 | loop count during the first iteration. */ |
michael@0 | 839 | mGIFStruct.loop_count = GETINT16(q + 1); |
michael@0 | 840 | GETN(1, gif_netscape_extension_block); |
michael@0 | 841 | break; |
michael@0 | 842 | |
michael@0 | 843 | case 2: |
michael@0 | 844 | /* Wait for specified # of bytes to enter buffer */ |
michael@0 | 845 | // Don't do this, this extension doesn't exist (isn't used at all) |
michael@0 | 846 | // and doesn't do anything, as our streaming/buffering takes care of it all... |
michael@0 | 847 | // See: http://semmix.pl/color/exgraf/eeg24.htm |
michael@0 | 848 | GETN(1, gif_netscape_extension_block); |
michael@0 | 849 | break; |
michael@0 | 850 | |
michael@0 | 851 | default: |
michael@0 | 852 | // 0,3-7 are yet to be defined netscape extension codes |
michael@0 | 853 | mGIFStruct.state = gif_error; |
michael@0 | 854 | } |
michael@0 | 855 | break; |
michael@0 | 856 | |
michael@0 | 857 | case gif_image_header: |
michael@0 | 858 | { |
michael@0 | 859 | /* Get image offsets, with respect to the screen origin */ |
michael@0 | 860 | mGIFStruct.x_offset = GETINT16(q); |
michael@0 | 861 | mGIFStruct.y_offset = GETINT16(q + 2); |
michael@0 | 862 | |
michael@0 | 863 | /* Get image width and height. */ |
michael@0 | 864 | mGIFStruct.width = GETINT16(q + 4); |
michael@0 | 865 | mGIFStruct.height = GETINT16(q + 6); |
michael@0 | 866 | |
michael@0 | 867 | if (!mGIFStruct.images_decoded) { |
michael@0 | 868 | /* Work around broken GIF files where the logical screen |
michael@0 | 869 | * size has weird width or height. We assume that GIF87a |
michael@0 | 870 | * files don't contain animations. |
michael@0 | 871 | */ |
michael@0 | 872 | if ((mGIFStruct.screen_height < mGIFStruct.height) || |
michael@0 | 873 | (mGIFStruct.screen_width < mGIFStruct.width) || |
michael@0 | 874 | (mGIFStruct.version == 87)) { |
michael@0 | 875 | mGIFStruct.screen_height = mGIFStruct.height; |
michael@0 | 876 | mGIFStruct.screen_width = mGIFStruct.width; |
michael@0 | 877 | mGIFStruct.x_offset = 0; |
michael@0 | 878 | mGIFStruct.y_offset = 0; |
michael@0 | 879 | } |
michael@0 | 880 | // Create the image container with the right size. |
michael@0 | 881 | BeginGIF(); |
michael@0 | 882 | if (HasError()) { |
michael@0 | 883 | // Setting the size led to an error. |
michael@0 | 884 | mGIFStruct.state = gif_error; |
michael@0 | 885 | return; |
michael@0 | 886 | } |
michael@0 | 887 | |
michael@0 | 888 | // If we were doing a size decode, we're done |
michael@0 | 889 | if (IsSizeDecode()) |
michael@0 | 890 | return; |
michael@0 | 891 | } |
michael@0 | 892 | |
michael@0 | 893 | /* Work around more broken GIF files that have zero image |
michael@0 | 894 | width or height */ |
michael@0 | 895 | if (!mGIFStruct.height || !mGIFStruct.width) { |
michael@0 | 896 | mGIFStruct.height = mGIFStruct.screen_height; |
michael@0 | 897 | mGIFStruct.width = mGIFStruct.screen_width; |
michael@0 | 898 | if (!mGIFStruct.height || !mGIFStruct.width) { |
michael@0 | 899 | mGIFStruct.state = gif_error; |
michael@0 | 900 | break; |
michael@0 | 901 | } |
michael@0 | 902 | } |
michael@0 | 903 | |
michael@0 | 904 | /* Depth of colors is determined by colormap */ |
michael@0 | 905 | /* (q[8] & 0x80) indicates local colormap */ |
michael@0 | 906 | /* bits per pixel is (q[8]&0x07 + 1) when local colormap is set */ |
michael@0 | 907 | uint32_t depth = mGIFStruct.global_colormap_depth; |
michael@0 | 908 | if (q[8] & 0x80) |
michael@0 | 909 | depth = (q[8]&0x07) + 1; |
michael@0 | 910 | uint32_t realDepth = depth; |
michael@0 | 911 | while (mGIFStruct.tpixel >= (1 << realDepth) && (realDepth < 8)) { |
michael@0 | 912 | realDepth++; |
michael@0 | 913 | } |
michael@0 | 914 | // Mask to limit the color values within the colormap |
michael@0 | 915 | mColorMask = 0xFF >> (8 - realDepth); |
michael@0 | 916 | BeginImageFrame(realDepth); |
michael@0 | 917 | |
michael@0 | 918 | if (NeedsNewFrame()) { |
michael@0 | 919 | // We now need a new frame from the decoder framework. We leave all our |
michael@0 | 920 | // data in the buffer as if it wasn't consumed, copy to our hold and return |
michael@0 | 921 | // to the decoder framework. |
michael@0 | 922 | uint32_t size = len + mGIFStruct.bytes_to_consume + mGIFStruct.bytes_in_hold; |
michael@0 | 923 | if (size) { |
michael@0 | 924 | if (SetHold(q, mGIFStruct.bytes_to_consume + mGIFStruct.bytes_in_hold, buf, len)) { |
michael@0 | 925 | // Back into the decoder infrastructure so we can get called again. |
michael@0 | 926 | GETN(9, gif_image_header_continue); |
michael@0 | 927 | return; |
michael@0 | 928 | } |
michael@0 | 929 | } |
michael@0 | 930 | break; |
michael@0 | 931 | } else { |
michael@0 | 932 | // FALL THROUGH |
michael@0 | 933 | } |
michael@0 | 934 | } |
michael@0 | 935 | |
michael@0 | 936 | case gif_image_header_continue: |
michael@0 | 937 | { |
michael@0 | 938 | // While decoders can reuse frames, we unconditionally increment |
michael@0 | 939 | // mGIFStruct.images_decoded when we're done with a frame, so we both can |
michael@0 | 940 | // and need to zero out the colormap and image data after every new frame. |
michael@0 | 941 | memset(mImageData, 0, mImageDataLength); |
michael@0 | 942 | if (mColormap) { |
michael@0 | 943 | memset(mColormap, 0, mColormapSize); |
michael@0 | 944 | } |
michael@0 | 945 | |
michael@0 | 946 | if (!mGIFStruct.images_decoded) { |
michael@0 | 947 | // Send a onetime invalidation for the first frame if it has a y-axis offset. |
michael@0 | 948 | // Otherwise, the area may never be refreshed and the placeholder will remain |
michael@0 | 949 | // on the screen. (Bug 37589) |
michael@0 | 950 | if (mGIFStruct.y_offset > 0) { |
michael@0 | 951 | nsIntRect r(0, 0, mGIFStruct.screen_width, mGIFStruct.y_offset); |
michael@0 | 952 | PostInvalidation(r); |
michael@0 | 953 | } |
michael@0 | 954 | } |
michael@0 | 955 | |
michael@0 | 956 | if (q[8] & 0x40) { |
michael@0 | 957 | mGIFStruct.interlaced = true; |
michael@0 | 958 | mGIFStruct.ipass = 1; |
michael@0 | 959 | } else { |
michael@0 | 960 | mGIFStruct.interlaced = false; |
michael@0 | 961 | mGIFStruct.ipass = 0; |
michael@0 | 962 | } |
michael@0 | 963 | |
michael@0 | 964 | /* Only apply the Haeberli display hack on the first frame */ |
michael@0 | 965 | mGIFStruct.progressive_display = (mGIFStruct.images_decoded == 0); |
michael@0 | 966 | |
michael@0 | 967 | /* Clear state from last image */ |
michael@0 | 968 | mGIFStruct.irow = 0; |
michael@0 | 969 | mGIFStruct.rows_remaining = mGIFStruct.height; |
michael@0 | 970 | mGIFStruct.rowp = mImageData; |
michael@0 | 971 | |
michael@0 | 972 | /* Depth of colors is determined by colormap */ |
michael@0 | 973 | /* (q[8] & 0x80) indicates local colormap */ |
michael@0 | 974 | /* bits per pixel is (q[8]&0x07 + 1) when local colormap is set */ |
michael@0 | 975 | uint32_t depth = mGIFStruct.global_colormap_depth; |
michael@0 | 976 | if (q[8] & 0x80) |
michael@0 | 977 | depth = (q[8]&0x07) + 1; |
michael@0 | 978 | uint32_t realDepth = depth; |
michael@0 | 979 | while (mGIFStruct.tpixel >= (1 << realDepth) && (realDepth < 8)) { |
michael@0 | 980 | realDepth++; |
michael@0 | 981 | } |
michael@0 | 982 | |
michael@0 | 983 | if (q[8] & 0x80) /* has a local colormap? */ |
michael@0 | 984 | { |
michael@0 | 985 | mGIFStruct.local_colormap_size = 1 << depth; |
michael@0 | 986 | if (!mGIFStruct.images_decoded) { |
michael@0 | 987 | // First frame has local colormap, allocate space for it |
michael@0 | 988 | // as the image frame doesn't have its own palette |
michael@0 | 989 | mColormapSize = sizeof(uint32_t) << realDepth; |
michael@0 | 990 | if (!mGIFStruct.local_colormap) { |
michael@0 | 991 | mGIFStruct.local_colormap = (uint32_t*)moz_xmalloc(mColormapSize); |
michael@0 | 992 | } |
michael@0 | 993 | mColormap = mGIFStruct.local_colormap; |
michael@0 | 994 | } |
michael@0 | 995 | const uint32_t size = 3 << depth; |
michael@0 | 996 | if (mColormapSize > size) { |
michael@0 | 997 | // Clear the notfilled part of the colormap |
michael@0 | 998 | memset(((uint8_t*)mColormap) + size, 0, mColormapSize - size); |
michael@0 | 999 | } |
michael@0 | 1000 | if (len < size) { |
michael@0 | 1001 | // Use 'hold' pattern to get the image colormap |
michael@0 | 1002 | GETN(size, gif_image_colormap); |
michael@0 | 1003 | break; |
michael@0 | 1004 | } |
michael@0 | 1005 | // Copy everything, go to colormap state to do CMS correction |
michael@0 | 1006 | memcpy(mColormap, buf, size); |
michael@0 | 1007 | buf += size; |
michael@0 | 1008 | len -= size; |
michael@0 | 1009 | GETN(0, gif_image_colormap); |
michael@0 | 1010 | break; |
michael@0 | 1011 | } else { |
michael@0 | 1012 | /* Switch back to the global palette */ |
michael@0 | 1013 | if (mGIFStruct.images_decoded) { |
michael@0 | 1014 | // Copy global colormap into the palette of current frame |
michael@0 | 1015 | memcpy(mColormap, mGIFStruct.global_colormap, mColormapSize); |
michael@0 | 1016 | } else { |
michael@0 | 1017 | mColormap = mGIFStruct.global_colormap; |
michael@0 | 1018 | } |
michael@0 | 1019 | } |
michael@0 | 1020 | GETN(1, gif_lzw_start); |
michael@0 | 1021 | } |
michael@0 | 1022 | break; |
michael@0 | 1023 | |
michael@0 | 1024 | case gif_image_colormap: |
michael@0 | 1025 | // Everything is already copied into local_colormap |
michael@0 | 1026 | // Convert into Cairo colors including CMS transformation |
michael@0 | 1027 | ConvertColormap(mColormap, mGIFStruct.local_colormap_size); |
michael@0 | 1028 | GETN(1, gif_lzw_start); |
michael@0 | 1029 | break; |
michael@0 | 1030 | |
michael@0 | 1031 | case gif_sub_block: |
michael@0 | 1032 | mGIFStruct.count = *q; |
michael@0 | 1033 | if (mGIFStruct.count) { |
michael@0 | 1034 | /* Still working on the same image: Process next LZW data block */ |
michael@0 | 1035 | /* Make sure there are still rows left. If the GIF data */ |
michael@0 | 1036 | /* is corrupt, we may not get an explicit terminator. */ |
michael@0 | 1037 | if (!mGIFStruct.rows_remaining) { |
michael@0 | 1038 | #ifdef DONT_TOLERATE_BROKEN_GIFS |
michael@0 | 1039 | mGIFStruct.state = gif_error; |
michael@0 | 1040 | break; |
michael@0 | 1041 | #else |
michael@0 | 1042 | /* This is an illegal GIF, but we remain tolerant. */ |
michael@0 | 1043 | GETN(1, gif_sub_block); |
michael@0 | 1044 | #endif |
michael@0 | 1045 | if (mGIFStruct.count == GIF_TRAILER) { |
michael@0 | 1046 | /* Found a terminator anyway, so consider the image done */ |
michael@0 | 1047 | GETN(1, gif_done); |
michael@0 | 1048 | break; |
michael@0 | 1049 | } |
michael@0 | 1050 | } |
michael@0 | 1051 | GETN(mGIFStruct.count, gif_lzw); |
michael@0 | 1052 | } else { |
michael@0 | 1053 | /* See if there are any more images in this sequence. */ |
michael@0 | 1054 | EndImageFrame(); |
michael@0 | 1055 | GETN(1, gif_image_start); |
michael@0 | 1056 | } |
michael@0 | 1057 | break; |
michael@0 | 1058 | |
michael@0 | 1059 | case gif_done: |
michael@0 | 1060 | MOZ_ASSERT(!IsSizeDecode(), "Size decodes shouldn't reach gif_done"); |
michael@0 | 1061 | FinishInternal(); |
michael@0 | 1062 | goto done; |
michael@0 | 1063 | |
michael@0 | 1064 | case gif_error: |
michael@0 | 1065 | PostDataError(); |
michael@0 | 1066 | return; |
michael@0 | 1067 | |
michael@0 | 1068 | // We shouldn't ever get here. |
michael@0 | 1069 | default: |
michael@0 | 1070 | break; |
michael@0 | 1071 | } |
michael@0 | 1072 | } |
michael@0 | 1073 | |
michael@0 | 1074 | // if an error state is set but no data remains, code flow reaches here |
michael@0 | 1075 | if (mGIFStruct.state == gif_error) { |
michael@0 | 1076 | PostDataError(); |
michael@0 | 1077 | return; |
michael@0 | 1078 | } |
michael@0 | 1079 | |
michael@0 | 1080 | // Copy the leftover into mGIFStruct.hold |
michael@0 | 1081 | if (len) { |
michael@0 | 1082 | // Add what we have sofar to the block |
michael@0 | 1083 | if (mGIFStruct.state != gif_global_colormap && mGIFStruct.state != gif_image_colormap) { |
michael@0 | 1084 | if (!SetHold(buf, len)) { |
michael@0 | 1085 | PostDataError(); |
michael@0 | 1086 | return; |
michael@0 | 1087 | } |
michael@0 | 1088 | } else { |
michael@0 | 1089 | uint8_t* p = (mGIFStruct.state == gif_global_colormap) ? (uint8_t*)mGIFStruct.global_colormap : |
michael@0 | 1090 | (uint8_t*)mColormap; |
michael@0 | 1091 | memcpy(p, buf, len); |
michael@0 | 1092 | mGIFStruct.bytes_in_hold = len; |
michael@0 | 1093 | } |
michael@0 | 1094 | |
michael@0 | 1095 | mGIFStruct.bytes_to_consume -= len; |
michael@0 | 1096 | } |
michael@0 | 1097 | |
michael@0 | 1098 | // We want to flush before returning if we're on the first frame |
michael@0 | 1099 | done: |
michael@0 | 1100 | if (!mGIFStruct.images_decoded) { |
michael@0 | 1101 | FlushImageData(); |
michael@0 | 1102 | mLastFlushedRow = mCurrentRow; |
michael@0 | 1103 | mLastFlushedPass = mCurrentPass; |
michael@0 | 1104 | } |
michael@0 | 1105 | |
michael@0 | 1106 | return; |
michael@0 | 1107 | } |
michael@0 | 1108 | |
michael@0 | 1109 | bool |
michael@0 | 1110 | nsGIFDecoder2::SetHold(const uint8_t* buf1, uint32_t count1, const uint8_t* buf2 /* = nullptr */, uint32_t count2 /* = 0 */) |
michael@0 | 1111 | { |
michael@0 | 1112 | // We have to handle the case that buf currently points to hold |
michael@0 | 1113 | uint8_t* newHold = (uint8_t *) moz_malloc(std::max(uint32_t(MIN_HOLD_SIZE), count1 + count2)); |
michael@0 | 1114 | if (!newHold) { |
michael@0 | 1115 | mGIFStruct.state = gif_error; |
michael@0 | 1116 | return false; |
michael@0 | 1117 | } |
michael@0 | 1118 | |
michael@0 | 1119 | memcpy(newHold, buf1, count1); |
michael@0 | 1120 | if (buf2) { |
michael@0 | 1121 | memcpy(newHold + count1, buf2, count2); |
michael@0 | 1122 | } |
michael@0 | 1123 | |
michael@0 | 1124 | moz_free(mGIFStruct.hold); |
michael@0 | 1125 | mGIFStruct.hold = newHold; |
michael@0 | 1126 | mGIFStruct.bytes_in_hold = count1 + count2; |
michael@0 | 1127 | return true; |
michael@0 | 1128 | } |
michael@0 | 1129 | |
michael@0 | 1130 | Telemetry::ID |
michael@0 | 1131 | nsGIFDecoder2::SpeedHistogram() |
michael@0 | 1132 | { |
michael@0 | 1133 | return Telemetry::IMAGE_DECODE_SPEED_GIF; |
michael@0 | 1134 | } |
michael@0 | 1135 | |
michael@0 | 1136 | |
michael@0 | 1137 | } // namespace image |
michael@0 | 1138 | } // namespace mozilla |