Bug 1247152 (Part 1) - Use SurfacePipe in the GIF decoder. r=njn
authorSeth Fowler <mark.seth.fowler@gmail.com>
Wed, 09 Mar 2016 15:39:02 -0800
changeset 287974 a1c6dad1153682c6c9fcafe85157bc2b9e02b419
parent 287973 170566dce920857ca11e0c264781c1226d379fb5
child 287975 cb965dc05ec7d56b64581b6bbdb70357f6435052
push id30071
push usercbook@mozilla.com
push dateThu, 10 Mar 2016 10:51:55 +0000
treeherdermozilla-central@dd1abe874252 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnjn
bugs1247152
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1247152 (Part 1) - Use SurfacePipe in the GIF decoder. r=njn
image/Deinterlacer.cpp
image/Deinterlacer.h
image/SurfacePipe.h
image/decoders/GIF2.h
image/decoders/nsBMPDecoder.cpp
image/decoders/nsGIFDecoder2.cpp
image/decoders/nsGIFDecoder2.h
image/decoders/nsJPEGDecoder.cpp
image/decoders/nsPNGDecoder.cpp
image/imgFrame.cpp
image/imgFrame.h
image/moz.build
deleted file mode 100644
--- a/image/Deinterlacer.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-
-#include "Downscaler.h"
-#include "mozilla/UniquePtrExtensions.h"
-
-namespace mozilla {
-namespace image {
-
-Deinterlacer::Deinterlacer(const nsIntSize& aImageSize)
-  : mImageSize(aImageSize)
-{
-  CheckedInt<size_t> bufferSize = mImageSize.width;
-  bufferSize *= mImageSize.height;
-  bufferSize *= sizeof(uint32_t);
-
-  if (!bufferSize.isValid()) {
-    return;
-  }
-
-  mBuffer = MakeUniqueFallible<uint8_t[]>(bufferSize.value());
-}
-
-uint32_t
-Deinterlacer::RowSize() const
-{
-  return mImageSize.width * sizeof(uint32_t);
-}
-
-uint8_t*
-Deinterlacer::RowBuffer(uint32_t aRow)
-{
-  uint32_t offset = aRow * RowSize();
-  MOZ_ASSERT(IsValid(), "Deinterlacer in invalid state");
-  MOZ_ASSERT(offset < mImageSize.width * mImageSize.height * sizeof(uint32_t),
-             "Row is outside of image");
-  return mBuffer.get() + offset;
-}
-
-void
-Deinterlacer::PropagatePassToDownscaler(Downscaler& aDownscaler)
-{
-  MOZ_ASSERT(IsValid(), "Deinterlacer in invalid state");
-  for (int32_t row = 0 ; row < mImageSize.height ; ++row) {
-    memcpy(aDownscaler.RowBuffer(), RowBuffer(row), RowSize());
-    aDownscaler.CommitRow();
-  }
-}
-
-} // namespace image
-} // namespace mozilla
deleted file mode 100644
--- a/image/Deinterlacer.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-
-/**
- * Deinterlacer is a utility class to allow Downscaler to work with interlaced
- * images.
-
- * Since Downscaler needs to receive rows in top-to-bottom or
- * bottom-to-top order, it can't natively handle interlaced images, in which the
- * rows arrive in an interleaved order. Deinterlacer solves this problem by
- * acting as an intermediate buffer that records decoded rows. Unlike
- * Downscaler, it allows the rows to be written in arbitrary order. After each
- * pass, calling PropagatePassToDownscaler() will downscale every buffered row
- * in a single operation. The rows remain in the buffer, so rows that were
- * written in one pass will be included in subsequent passes.
- */
-
-
-#ifndef mozilla_image_Deinterlacer_h
-#define mozilla_image_Deinterlacer_h
-
-#include "Downscaler.h"
-
-namespace mozilla {
-namespace image {
-
-class Deinterlacer
-{
-public:
-  explicit Deinterlacer(const nsIntSize& aImageSize);
-  bool IsValid() { return !!mBuffer; }
-
-  uint8_t* RowBuffer(uint32_t aRow);
-  void PropagatePassToDownscaler(Downscaler& aDownscaler);
-
-private:
-  uint32_t RowSize() const;
-
-  nsIntSize mImageSize;
-  UniquePtr<uint8_t[]> mBuffer;
-};
-
-
-} // namespace image
-} // namespace mozilla
-
-#endif // mozilla_image_Deinterlacer_h
--- a/image/SurfacePipe.h
+++ b/image/SurfacePipe.h
@@ -54,17 +54,17 @@ struct SurfaceInvalidRect
  */
 enum class WriteState : uint8_t
 {
   NEED_MORE_DATA,  /// The lambda ran out of data.
 
   FINISHED,        /// The lambda is done writing to the surface; future writes
                    /// will fail.
 
-  ERROR            /// The lambda encountered an error. The caller may recover
+  FAILURE          /// The lambda encountered an error. The caller may recover
                    /// if possible and continue to write. (This never indicates
                    /// an error in the SurfacePipe machinery itself; it's only
                    /// generated by the lambdas.)
 };
 
 /**
  * A template alias used to make the return value of WritePixels() lambdas
  * (which may return either a pixel value or a WriteState) easier to specify.
@@ -177,22 +177,22 @@ public:
 
           case WriteState::FINISHED:
             // Make sure that IsSurfaceFinished() returns true so the caller
             // can't write anything else to the pipeline.
             mRowPointer = nullptr;
             mCol = 0;
             return WriteState::FINISHED;
 
-          case WriteState::ERROR:
+          case WriteState::FAILURE:
             // Note that we don't need to record this anywhere, because this
             // indicates an error in aFunc, and there's nothing wrong with our
             // machinery. The caller can recover as needed and continue writing to
             // the row.
-            return WriteState::ERROR;
+            return WriteState::FAILURE;
         }
       }
 
       // We've finished the row.
       mRowPointer = AdvanceRow();
       mCol = 0;
     }
 
@@ -238,17 +238,17 @@ public:
     if (IsSurfaceFinished()) {
       return WriteState::FINISHED;  // Already done.
     }
 
     while (true) {
       PixelType* rowPtr = reinterpret_cast<PixelType*>(mRowPointer);
 
       Maybe<WriteState> result = aFunc(rowPtr, mInputSize.width);
-      if (result != Some(WriteState::ERROR)) {
+      if (result != Some(WriteState::FAILURE)) {
         mCol = 0;
         mRowPointer = AdvanceRow();  // We've finished the row.
       }
 
       if (IsSurfaceFinished()) {
         break;
       }
 
--- a/image/decoders/GIF2.h
+++ b/image/decoders/GIF2.h
@@ -62,20 +62,17 @@ typedef struct gif_struct {
     int avail;                  // Index of next available slot in dictionary
     int oldcode;
     uint8_t firstchar;
     int count;                  // Remaining # bytes in sub-block
     int bits;                   // Number of unread bits in "datum"
     int32_t datum;              // 32-bit input buffer
 
     // Output state machine
-    int ipass;                  // Interlace pass; Ranges 1-4 if interlaced.
-    unsigned rows_remaining;    // Rows remaining to be output
-    unsigned irow;              // Current output row, starting at zero
-    uint8_t* rowp;              // Current output pointer
+    int64_t pixels_remaining;  // Pixels remaining to be output.
 
     // Parameters for image frame currently being decoded
     unsigned x_offset, y_offset; // With respect to "screen" origin
     unsigned height, width;
     unsigned clamped_height;    // Size of the frame rectangle clamped to the
     unsigned clamped_width;     //  global logical size after x_ and y_offset.
     int tpixel;                 // Index of transparent pixel
     int32_t disposal_method;    // Restore to background, leave in place, etc.
@@ -89,17 +86,16 @@ typedef struct gif_struct {
     unsigned screen_width;      // Logical screen width & height
     unsigned screen_height;
     uint32_t global_colormap_depth; // Depth of global colormap array
     int images_decoded;         // Counts images for multi-part GIFs
     int loop_count;             // Netscape specific extension block to control
                                 // the number of animation loops a GIF
                                 // renders.
 
-    bool progressive_display;   // If TRUE, do Haeberli interlace hack
     bool interlaced;            // TRUE, if scanlines arrive interlaced order
     bool is_transparent;        // TRUE, if tpixel is valid
 
     uint16_t  prefix[MAX_BITS];            // LZW decoding tables
     uint8_t*  hold;                        // Accumulation buffer
     uint32_t  global_colormap[MAX_COLORS]; // Default colormap if local not
                                            //   supplied
     uint8_t   suffix[MAX_BITS];            // LZW decoding tables
--- a/image/decoders/nsBMPDecoder.cpp
+++ b/image/decoders/nsBMPDecoder.cpp
@@ -257,17 +257,17 @@ nsBMPDecoder::FinishInternal()
     // Invalidate.
     nsIntRect r(0, 0, mH.mWidth, AbsoluteHeight());
     PostInvalidation(r);
 
     if (mDoesHaveTransparency) {
       MOZ_ASSERT(mMayHaveTransparency);
       PostFrameStop(Opacity::SOME_TRANSPARENCY);
     } else {
-      PostFrameStop(Opacity::OPAQUE);
+      PostFrameStop(Opacity::FULLY_OPAQUE);
     }
     PostDecodeDone();
   }
 }
 
 // ----------------------------------------
 // Actual Data Processing
 // ----------------------------------------
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -35,19 +35,22 @@ For further information, please contact 
 CompuServe Incorporated maintains a mailing list with all those individuals and
 organizations who wish to receive copies of this document when it is corrected
 or revised. This service is offered free of charge; please provide us with your
 mailing address.
 */
 
 #include <stddef.h>
 
+#include "imgFrame.h"
 #include "nsGIFDecoder2.h"
 #include "nsIInputStream.h"
 #include "RasterImage.h"
+#include "SurfaceFilters.h"
+#include "SurfacePipeFactory.h"
 
 #include "gfxColor.h"
 #include "gfxPlatform.h"
 #include "qcms.h"
 #include <algorithm>
 #include "mozilla/Telemetry.h"
 
 using namespace mozilla::gfx;
@@ -68,22 +71,18 @@ namespace image {
 
 // Get a 16-bit value stored in little-endian format
 #define GETINT16(p)   ((p)[1]<<8|(p)[0])
 //////////////////////////////////////////////////////////////////////
 // GIF Decoder Implementation
 
 nsGIFDecoder2::nsGIFDecoder2(RasterImage* aImage)
   : Decoder(aImage)
-  , mCurrentRow(-1)
-  , mLastFlushedRow(-1)
   , mOldColor(0)
   , mCurrentFrameIndex(-1)
-  , mCurrentPass(0)
-  , mLastFlushedPass(0)
   , mGIFOpen(false)
   , mSawTransparency(false)
 {
   // Clear out the structure, excluding the arrays
   memset(&mGIFStruct, 0, sizeof(mGIFStruct));
 
   // Initialize as "animate once" in case no NETSCAPE2.0 extension is found
   mGIFStruct.loop_count = 1;
@@ -94,104 +93,41 @@ nsGIFDecoder2::nsGIFDecoder2(RasterImage
 }
 
 nsGIFDecoder2::~nsGIFDecoder2()
 {
   free(mGIFStruct.local_colormap);
   free(mGIFStruct.hold);
 }
 
-uint8_t*
-nsGIFDecoder2::GetCurrentRowBuffer()
-{
-  if (!mDownscaler) {
-    MOZ_ASSERT(!mDeinterlacer, "Deinterlacer without downscaler?");
-    uint32_t bpp = mGIFStruct.images_decoded == 0 ? sizeof(uint32_t)
-                                                  : sizeof(uint8_t);
-    return mImageData + mGIFStruct.irow * mGIFStruct.width * bpp;
-  }
-
-  if (!mDeinterlacer) {
-    return mDownscaler->RowBuffer();
-  }
-
-  return mDeinterlacer->RowBuffer(mGIFStruct.irow);
-}
-
-uint8_t*
-nsGIFDecoder2::GetRowBuffer(uint32_t aRow)
-{
-  MOZ_ASSERT(mGIFStruct.images_decoded == 0,
-             "Calling GetRowBuffer on a frame other than the first suggests "
-             "we're deinterlacing animated frames");
-  MOZ_ASSERT(!mDownscaler || mDeinterlacer,
-             "Can't get buffer for a specific row if downscaling "
-             "but not deinterlacing");
-
-  if (mDownscaler) {
-    return mDeinterlacer->RowBuffer(aRow);
-  }
-
-  return mImageData + aRow * mGIFStruct.width * sizeof(uint32_t);
-}
-
 void
 nsGIFDecoder2::FinishInternal()
 {
   MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");
 
   // If the GIF got cut off, handle it anyway
   if (!IsMetadataDecode() && mGIFOpen) {
     if (mCurrentFrameIndex == mGIFStruct.images_decoded) {
       EndImageFrame();
     }
     PostDecodeDone(mGIFStruct.loop_count - 1);
     mGIFOpen = false;
   }
 }
 
-// Push any new rows according to mCurrentPass/mLastFlushedPass and
-// mCurrentRow/mLastFlushedRow.  Note: caller is responsible for
-// updating mlastFlushed{Row,Pass}.
-void
-nsGIFDecoder2::FlushImageData(uint32_t fromRow, uint32_t rows)
-{
-  nsIntRect r(mGIFStruct.x_offset, mGIFStruct.y_offset + fromRow,
-              mGIFStruct.width, rows);
-  PostInvalidation(r);
-}
-
 void
 nsGIFDecoder2::FlushImageData()
 {
-  if (mDownscaler) {
-    if (mDownscaler->HasInvalidation()) {
-      DownscalerInvalidRect invalidRect = mDownscaler->TakeInvalidRect();
-      PostInvalidation(invalidRect.mOriginalSizeRect,
-                       Some(invalidRect.mTargetSizeRect));
-    }
+  Maybe<SurfaceInvalidRect> invalidRect = mPipe.TakeInvalidRect();
+  if (!invalidRect) {
     return;
   }
 
-  switch (mCurrentPass - mLastFlushedPass) {
-    case 0:  // same pass
-      if (mCurrentRow - mLastFlushedRow) {
-        FlushImageData(mLastFlushedRow + 1, mCurrentRow - mLastFlushedRow);
-      }
-      break;
-
-    case 1:  // one pass on - need to handle bottom & top rects
-      FlushImageData(0, mCurrentRow + 1);
-      FlushImageData(mLastFlushedRow + 1,
-                     mGIFStruct.clamped_height - (mLastFlushedRow + 1));
-      break;
-
-    default: // more than one pass on - push the whole frame
-      FlushImageData(0, mGIFStruct.clamped_height);
-  }
+  PostInvalidation(invalidRect->mInputSpaceRect,
+                   Some(invalidRect->mOutputSpaceRect));
 }
 
 //******************************************************************************
 // GIF decoder callback methods. Part of public API for GIF2
 //******************************************************************************
 
 //******************************************************************************
 void
@@ -258,49 +194,56 @@ nsGIFDecoder2::BeginImageFrame(uint16_t 
 
   bool hasTransparency = CheckForTransparency(frameRect);
   gfx::SurfaceFormat format = hasTransparency ? SurfaceFormat::B8G8R8A8
                                               : SurfaceFormat::B8G8R8X8;
 
   // Make sure there's no animation if we're downscaling.
   MOZ_ASSERT_IF(mDownscaler, !GetImageMetadata().HasAnimation());
 
-  // Compute the target size and target frame rect. If we're downscaling,
-  // Downscaler will automatically strip out first-frame padding, so the target
-  // frame rect takes up the entire frame regardless.
-  IntSize targetSize = mDownscaler ? mDownscaler->TargetSize()
-                                   : GetSize();
-  IntRect targetFrameRect = mDownscaler ? IntRect(IntPoint(), targetSize)
-                                        : frameRect;
+  SurfacePipeFlags pipeFlags = mGIFStruct.interlaced
+                             ? SurfacePipeFlags::DEINTERLACE
+                             : SurfacePipeFlags();
+
+  Maybe<SurfacePipe> pipe;
+  if (mGIFStruct.images_decoded == 0) {
+    // This is the first frame. We may be downscaling, so compute the target
+    // size and target frame rect.
+    IntSize targetSize = mDownscaler ? mDownscaler->TargetSize()
+                                     : GetSize();
+    IntRect targetFrameRect = mDownscaler ? IntRect(IntPoint(), targetSize)
+                                          : frameRect;
 
-  // Use correct format, RGB for first frame, PAL for following frames
-  // and include transparency to allow for optimization of opaque images
-  nsresult rv = NS_OK;
-  if (mGIFStruct.images_decoded) {
-    // Image data is stored with original depth and palette.
-    rv = AllocateFrame(mGIFStruct.images_decoded, targetSize,
-                       targetFrameRect, format, aDepth);
+    // The first frame may be displayed progressively.
+    pipeFlags |= SurfacePipeFlags::PROGRESSIVE_DISPLAY;
+
+    // The first frame is always decoded into an RGB surface.
+    pipe =
+      SurfacePipeFactory::CreateSurfacePipe(this, mGIFStruct.images_decoded,
+                                            GetSize(), targetSize,
+                                            targetFrameRect, format, pipeFlags);
   } else {
-    // Regardless of depth of input, the first frame is decoded into 24bit RGB.
-    rv = AllocateFrame(mGIFStruct.images_decoded, targetSize,
-                       targetFrameRect, format);
+    // This is an animation frame (and not the first). To minimize the memory
+    // usage of animations, the image data is stored in paletted form.
+    MOZ_ASSERT(!mDownscaler);
+    pipe =
+      SurfacePipeFactory::CreatePalettedSurfacePipe(this, mGIFStruct.images_decoded,
+                                                    GetSize(), frameRect, format,
+                                                    aDepth, pipeFlags);
   }
 
   mCurrentFrameIndex = mGIFStruct.images_decoded;
 
-  if (NS_FAILED(rv)) {
-    return rv;
+  if (!pipe) {
+    mPipe = SurfacePipe();
+    return NS_ERROR_FAILURE;
   }
 
-  if (mDownscaler) {
-    rv = mDownscaler->BeginFrame(GetSize(), Some(ClampToImageRect(frameRect)),
-                                 mImageData, hasTransparency);
-  }
-
-  return rv;
+  mPipe = Move(*pipe);
+  return NS_OK;
 }
 
 
 //******************************************************************************
 void
 nsGIFDecoder2::EndImageFrame()
 {
   Opacity opacity = Opacity::SOME_TRANSPARENCY;
@@ -326,30 +269,17 @@ nsGIFDecoder2::EndImageFrame()
       }
     }
 
     // The first frame was preallocated with alpha; if it wasn't transparent, we
     // should fix that. We can also mark it opaque unconditionally if we didn't
     // actually see any transparent pixels - this test is only valid for the
     // first frame.
     if (!mGIFStruct.is_transparent && !mSawTransparency) {
-      opacity = Opacity::OPAQUE;
-    }
-  }
-  mCurrentRow = mLastFlushedRow = -1;
-  mCurrentPass = mLastFlushedPass = 0;
-
-  // Only add frame if we have any rows at all
-  if (mGIFStruct.rows_remaining != mGIFStruct.clamped_height) {
-    if (mGIFStruct.rows_remaining && mGIFStruct.images_decoded) {
-      // Clear the remaining rows (only needed for the animation frames)
-      uint8_t* rowp =
-        mImageData + ((mGIFStruct.clamped_height - mGIFStruct.rows_remaining) *
-                      mGIFStruct.width);
-      memset(rowp, 0, mGIFStruct.rows_remaining * mGIFStruct.width);
+      opacity = Opacity::FULLY_OPAQUE;
     }
   }
 
   // Unconditionally increment images_decoded, because we unconditionally
   // append frames in BeginImageFrame(). This ensures that images_decoded
   // always refers to the frame in mImage we're currently decoding,
   // even if some of them weren't decoded properly and thus are blank.
   mGIFStruct.images_decoded++;
@@ -363,296 +293,173 @@ nsGIFDecoder2::EndImageFrame()
   if (mOldColor) {
     mColormap[mGIFStruct.tpixel] = mOldColor;
     mOldColor = 0;
   }
 
   mCurrentFrameIndex = -1;
 }
 
-
-//******************************************************************************
-// Send the data to the display front-end.
-uint32_t
-nsGIFDecoder2::OutputRow()
+template <typename PixelSize>
+PixelSize
+nsGIFDecoder2::ColormapIndexToPixel(uint8_t aIndex)
 {
-  // Initialize the region in which we're duplicating rows (for the
-  // Haeberli-inspired hack below) to |irow|, which is the row we're writing to
-  // now.
-  int drow_start = mGIFStruct.irow;
-  int drow_end = mGIFStruct.irow;
+  MOZ_ASSERT(sizeof(PixelSize) == sizeof(uint32_t));
 
-  // Protect against too much image data
-  if ((unsigned)drow_start >= mGIFStruct.clamped_height) {
-    NS_WARNING("GIF2.cpp::OutputRow - too much image data");
-    return 0;
+  // Retrieve the next color, clamping to the size of the colormap.
+  uint32_t color = mColormap[aIndex & mColorMask];
+
+  // Check for transparency.
+  if (mGIFStruct.is_transparent) {
+    mSawTransparency = mSawTransparency || color == 0;
   }
 
-  if (!mGIFStruct.images_decoded) {
-    // Haeberli-inspired hack for interlaced GIFs: Replicate lines while
-    // displaying to diminish the "venetian-blind" effect as the image is
-    // loaded. Adjust pixel vertical positions to avoid the appearance of the
-    // image crawling up the screen as successive passes are drawn.
-    if (mGIFStruct.progressive_display && mGIFStruct.interlaced &&
-        (mGIFStruct.ipass < 4)) {
-      // ipass = 1,2,3 results in resp. row_dup = 7,3,1 and row_shift = 3,1,0
-      const uint32_t row_dup = 15 >> mGIFStruct.ipass;
-      const uint32_t row_shift = row_dup >> 1;
+  return color;
+}
+
+template <>
+uint8_t
+nsGIFDecoder2::ColormapIndexToPixel<uint8_t>(uint8_t aIndex)
+{
+  return aIndex & mColorMask;
+}
+
+template <typename PixelSize>
+NextPixel<PixelSize>
+nsGIFDecoder2::YieldPixel(const uint8_t*& aCurrentByteInOut)
+{
+  MOZ_ASSERT(mGIFStruct.stackp >= mGIFStruct.stack);
+
+  // If we don't have any decoded data to yield, try to read some input and
+  // produce some.
+  if (mGIFStruct.stackp == mGIFStruct.stack) {
+    while (mGIFStruct.bits < mGIFStruct.codesize && mGIFStruct.count > 0) {
+      // Feed the next byte into the decoder's 32-bit input buffer.
+      mGIFStruct.datum += int32_t(*aCurrentByteInOut) << mGIFStruct.bits;
+      mGIFStruct.bits += 8;
+      ++aCurrentByteInOut;
+      --mGIFStruct.count;
+    }
 
-      drow_start -= row_shift;
-      drow_end = drow_start + row_dup;
+    if (mGIFStruct.bits < mGIFStruct.codesize) {
+      return AsVariant(WriteState::NEED_MORE_DATA);
+    }
+
+    // Get the leading variable-length symbol from the data stream.
+    int code = mGIFStruct.datum & mGIFStruct.codemask;
+    mGIFStruct.datum >>= mGIFStruct.codesize;
+    mGIFStruct.bits -= mGIFStruct.codesize;
+
+    const int clearCode = ClearCode();
 
-      // Extend if bottom edge isn't covered because of the shift upward.
-      if (((mGIFStruct.clamped_height - 1) - drow_end) <= row_shift) {
-        drow_end = mGIFStruct.clamped_height - 1;
+    // Reset the dictionary to its original state, if requested
+    if (code == clearCode) {
+      mGIFStruct.codesize = mGIFStruct.datasize + 1;
+      mGIFStruct.codemask = (1 << mGIFStruct.codesize) - 1;
+      mGIFStruct.avail = clearCode + 2;
+      mGIFStruct.oldcode = -1;
+      return AsVariant(WriteState::NEED_MORE_DATA);
+    }
+
+    // Check for explicit end-of-stream code. It should only appear after all
+    // image data, but if that was the case we wouldn't be in this function, so
+    // this is always an error condition.
+    if (code == (clearCode + 1)) {
+      return AsVariant(WriteState::FAILURE);
+    }
+
+    if (mGIFStruct.oldcode == -1) {
+      if (code >= MAX_BITS) {
+        return AsVariant(WriteState::FAILURE);  // The code's too big; something's wrong.
       }
 
-      // Clamp first and last rows to upper and lower edge of image.
-      if (drow_start < 0) {
-        drow_start = 0;
-      }
-      if ((unsigned)drow_end >= mGIFStruct.clamped_height) {
-        drow_end = mGIFStruct.clamped_height - 1;
-      }
+      mGIFStruct.firstchar = mGIFStruct.oldcode = code;
+
+      // Yield a pixel at the appropriate index in the colormap.
+      mGIFStruct.pixels_remaining--;
+      return AsVariant(ColormapIndexToPixel<PixelSize>(mGIFStruct.suffix[code]));
     }
 
-    // Row to process
-    uint8_t* rowp = GetCurrentRowBuffer();
+    int incode = code;
+    if (code >= mGIFStruct.avail) {
+      *mGIFStruct.stackp++ = mGIFStruct.firstchar;
+      code = mGIFStruct.oldcode;
 
-    // Convert color indices to Cairo pixels
-    uint8_t* from = rowp + mGIFStruct.clamped_width;
-    uint32_t* to = ((uint32_t*)rowp) + mGIFStruct.clamped_width;
-    uint32_t* cmap = mColormap;
-    for (uint32_t c = mGIFStruct.clamped_width; c > 0; c--) {
-      *--to = cmap[*--from];
-    }
-
-    // check for alpha (only for first frame)
-    if (mGIFStruct.is_transparent && !mSawTransparency) {
-      const uint32_t* rgb = (uint32_t*)rowp;
-      for (uint32_t i = mGIFStruct.clamped_width; i > 0; i--) {
-        if (*rgb++ == 0) {
-          mSawTransparency = true;
-          break;
-        }
+      if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) {
+        return AsVariant(WriteState::FAILURE);  // Stack overflow; something's wrong.
       }
     }
 
-    // If we're downscaling but not deinterlacing, we're done with this row and
-    // can commit it now. Otherwise, we'll let Deinterlacer do the committing
-    // when we call PropagatePassToDownscaler() at the end of this pass.
-    if (mDownscaler && !mDeinterlacer) {
-      mDownscaler->CommitRow();
-    }
+    while (code >= clearCode) {
+      if ((code >= MAX_BITS) || (code == mGIFStruct.prefix[code])) {
+        return AsVariant(WriteState::FAILURE);
+      }
 
-    if (drow_end > drow_start) {
-      // Duplicate rows if needed to reduce the "venetian blind" effect mentioned
-      // above. This writes out scanlines of the image in a way that isn't ordered
-      // vertically, which is incompatible with the filter that we use for
-      // downscale-during-decode, so we can't do this if we're downscaling.
-      MOZ_ASSERT_IF(mDownscaler, mDeinterlacer);
-      const uint32_t bpr = sizeof(uint32_t) * mGIFStruct.clamped_width;
-      for (int r = drow_start; r <= drow_end; r++) {
-        // Skip the row we wrote to above; that's what we're copying *from*.
-        if (r != int(mGIFStruct.irow)) {
-          memcpy(GetRowBuffer(r), rowp, bpr);
-        }
+      *mGIFStruct.stackp++ = mGIFStruct.suffix[code];
+      code = mGIFStruct.prefix[code];
+
+      if (mGIFStruct.stackp >= mGIFStruct.stack + MAX_BITS) {
+        return AsVariant(WriteState::FAILURE);  // Stack overflow; something's wrong.
       }
     }
-  }
 
-  mCurrentRow = drow_end;
-  mCurrentPass = mGIFStruct.ipass;
-  if (mGIFStruct.ipass == 1) {
-    mLastFlushedPass = mGIFStruct.ipass;   // interlaced starts at 1
-  }
+    *mGIFStruct.stackp++ = mGIFStruct.firstchar = mGIFStruct.suffix[code];
 
-  if (!mGIFStruct.interlaced) {
-    MOZ_ASSERT(!mDeinterlacer);
-    mGIFStruct.irow++;
-  } else {
-    static const uint8_t kjump[5] = { 1, 8, 8, 4, 2 };
-    int currentPass = mGIFStruct.ipass;
+    // Define a new codeword in the dictionary.
+    if (mGIFStruct.avail < 4096) {
+      mGIFStruct.prefix[mGIFStruct.avail] = mGIFStruct.oldcode;
+      mGIFStruct.suffix[mGIFStruct.avail] = mGIFStruct.firstchar;
+      mGIFStruct.avail++;
 
-    do {
-      // Row increments resp. per 8,8,4,2 rows
-      mGIFStruct.irow += kjump[mGIFStruct.ipass];
-      if (mGIFStruct.irow >= mGIFStruct.clamped_height) {
-        // Next pass starts resp. at row 4,2,1,0
-        mGIFStruct.irow = 8 >> mGIFStruct.ipass;
-        mGIFStruct.ipass++;
+      // If we've used up all the codewords of a given length increase the
+      // length of codewords by one bit, but don't exceed the specified maximum
+      // codeword size of 12 bits.
+      if (((mGIFStruct.avail & mGIFStruct.codemask) == 0) &&
+          (mGIFStruct.avail < 4096)) {
+        mGIFStruct.codesize++;
+        mGIFStruct.codemask += mGIFStruct.avail;
       }
-    } while (mGIFStruct.irow >= mGIFStruct.clamped_height);
-
-    // We've finished a pass. If we're downscaling, it's time to propagate the
-    // rows we've decoded so far from our Deinterlacer to our Downscaler.
-    if (mGIFStruct.ipass > currentPass && mDownscaler) {
-      MOZ_ASSERT(mDeinterlacer);
-      mDeinterlacer->PropagatePassToDownscaler(*mDownscaler);
-      FlushImageData();
-      mDownscaler->ResetForNextProgressivePass();
     }
-  }
 
-  return --mGIFStruct.rows_remaining;
-}
-
-//******************************************************************************
-// Perform Lempel-Ziv-Welch decoding
-bool
-nsGIFDecoder2::DoLzw(const uint8_t* q)
-{
-  if (!mGIFStruct.rows_remaining) {
-    return true;
-  }
-  if (MOZ_UNLIKELY(mDownscaler && mDownscaler->IsFrameComplete())) {
-    return true;
+    mGIFStruct.oldcode = incode;
   }
 
-  // Copy all the decoder state variables into locals so the compiler
-  // won't worry about them being aliased.  The locals will be homed
-  // back into the GIF decoder structure when we exit.
-  int avail       = mGIFStruct.avail;
-  int bits        = mGIFStruct.bits;
-  int codesize    = mGIFStruct.codesize;
-  int codemask    = mGIFStruct.codemask;
-  int count       = mGIFStruct.count;
-  int oldcode     = mGIFStruct.oldcode;
-  const int clear_code = ClearCode();
-  uint8_t firstchar = mGIFStruct.firstchar;
-  int32_t datum     = mGIFStruct.datum;
-  uint16_t* prefix  = mGIFStruct.prefix;
-  uint8_t* stackp   = mGIFStruct.stackp;
-  uint8_t* suffix   = mGIFStruct.suffix;
-  uint8_t* stack    = mGIFStruct.stack;
-  uint8_t* rowp     = mGIFStruct.rowp;
-
-  uint8_t* rowend = GetCurrentRowBuffer() + mGIFStruct.clamped_width;
-
-#define OUTPUT_ROW()                                        \
-  PR_BEGIN_MACRO                                            \
-    if (!OutputRow())                                       \
-      goto END;                                             \
-    rowp = GetCurrentRowBuffer();                           \
-    rowend = rowp + mGIFStruct.clamped_width;               \
-  PR_END_MACRO
+  if (MOZ_UNLIKELY(mGIFStruct.stackp <= mGIFStruct.stack)) {
+    MOZ_ASSERT_UNREACHABLE("No decoded data but we didn't return early?");
+    return AsVariant(WriteState::FAILURE);
+  }
 
-  for (const uint8_t* ch = q; count-- > 0; ch++) {
-    // Feed the next byte into the decoder's 32-bit input buffer.
-    datum += ((int32_t)* ch) << bits;
-    bits += 8;
-
-    // Check for underflow of decoder's 32-bit input buffer.
-    while (bits >= codesize) {
-      // Get the leading variable-length symbol from the data stream
-      int code = datum & codemask;
-      datum >>= codesize;
-      bits -= codesize;
-
-      // Reset the dictionary to its original state, if requested
-      if (code == clear_code) {
-        codesize = mGIFStruct.datasize + 1;
-        codemask = (1 << codesize) - 1;
-        avail = clear_code + 2;
-        oldcode = -1;
-        continue;
-      }
-
-      // Check for explicit end-of-stream code
-      if (code == (clear_code + 1)) {
-        // end-of-stream should only appear after all image data
-        return (mGIFStruct.rows_remaining == 0);
-      }
-
-      if (MOZ_UNLIKELY(mDownscaler && mDownscaler->IsFrameComplete())) {
-        goto END;
-      }
+  // Yield a pixel at the appropriate index in the colormap.
+  mGIFStruct.pixels_remaining--;
+  return AsVariant(ColormapIndexToPixel<PixelSize>(*--mGIFStruct.stackp));
+}
 
-      if (oldcode == -1) {
-        if (code >= MAX_BITS) {
-          return false;
-        }
-        *rowp++ = suffix[code] & mColorMask; // ensure index is within colormap
-        if (rowp == rowend) {
-          OUTPUT_ROW();
-        }
-
-        firstchar = oldcode = code;
-        continue;
-      }
-
-      int incode = code;
-      if (code >= avail) {
-        *stackp++ = firstchar;
-        code = oldcode;
-
-        if (stackp >= stack + MAX_BITS) {
-          return false;
-        }
-      }
-
-      while (code >= clear_code) {
-        if ((code >= MAX_BITS) || (code == prefix[code])) {
-          return false;
-        }
-
-        *stackp++ = suffix[code];
-        code = prefix[code];
+bool
+nsGIFDecoder2::DoLzw(const uint8_t* aData)
+{
+  const uint8_t* currentByte = aData;
+  while (mGIFStruct.count > 0 && mGIFStruct.pixels_remaining > 0) {
+    auto result = mGIFStruct.images_decoded > 0
+                ? mPipe.WritePixels<uint8_t>([&]() { return YieldPixel<uint8_t>(currentByte); })
+                : mPipe.WritePixels<uint32_t>([&]() { return YieldPixel<uint32_t>(currentByte); });
 
-        if (stackp == stack + MAX_BITS) {
-          return false;
-        }
-      }
-
-      *stackp++ = firstchar = suffix[code];
-
-      // Define a new codeword in the dictionary.
-      if (avail < 4096) {
-        prefix[avail] = oldcode;
-        suffix[avail] = firstchar;
-        avail++;
+    switch (result) {
+      case WriteState::NEED_MORE_DATA:
+        continue;
 
-        // If we've used up all the codewords of a given length
-        // increase the length of codewords by one bit, but don't
-        // exceed the specified maximum codeword size of 12 bits.
-        if (((avail & codemask) == 0) && (avail < 4096)) {
-          codesize++;
-          codemask += avail;
-        }
-      }
-      oldcode = incode;
+      case WriteState::FINISHED:
+        NS_WARN_IF(mGIFStruct.pixels_remaining > 0);
+        mGIFStruct.pixels_remaining = 0;
+        return true;
 
-      // Copy the decoded data out to the scanline buffer.
-      do {
-        *rowp++ = *--stackp & mColorMask; // ensure index is within colormap
-        if (rowp == rowend) {
-          OUTPUT_ROW();
-
-          // Consume decoded data that falls past the end of the clamped width.
-          stackp -= mGIFStruct.width - mGIFStruct.clamped_width;
-          stackp = std::max(stackp, stack);
-        }
-      } while (stackp > stack);
+      case WriteState::FAILURE:
+        return false;
     }
   }
 
-  END:
-
-  // Home the local copies of the GIF decoder state variables
-  mGIFStruct.avail = avail;
-  mGIFStruct.bits = bits;
-  mGIFStruct.codesize = codesize;
-  mGIFStruct.codemask = codemask;
-  mGIFStruct.count = count;
-  mGIFStruct.oldcode = oldcode;
-  mGIFStruct.firstchar = firstchar;
-  mGIFStruct.datum = datum;
-  mGIFStruct.stackp = stackp;
-  mGIFStruct.rowp = rowp;
-
   return true;
 }
 
 /// Expand the colormap from RGB to Packed ARGB as needed by Cairo.
 /// And apply any LCMS transformation.
 static void
 ConvertColormap(uint32_t* aColormap, uint32_t aColors)
 {
@@ -1167,40 +974,20 @@ nsGIFDecoder2::WriteInternal(const char*
             PostInvalidation(IntRect(IntPoint(), GetSize()), Some(targetRect));
           } else {
             nsIntRect r(0, 0, mGIFStruct.screen_width, mGIFStruct.y_offset);
             PostInvalidation(r);
           }
         }
       }
 
-      if (q[8] & 0x40) {
-        mGIFStruct.interlaced = true;
-        mGIFStruct.ipass = 1;
-        if (mDownscaler) {
-          mDeinterlacer.emplace(mDownscaler->FrameSize());
-
-          if (!mDeinterlacer->IsValid()) {
-            mDeinterlacer.reset();
-            mGIFStruct.state = gif_error;
-            break;
-          }
-        }
-      } else {
-        mGIFStruct.interlaced = false;
-        mGIFStruct.ipass = 0;
-      }
-
-      // Only apply the Haeberli display hack on the first frame
-      mGIFStruct.progressive_display = (mGIFStruct.images_decoded == 0);
+      mGIFStruct.interlaced = bool(q[8] & 0x40);
 
       // Clear state from last image
-      mGIFStruct.irow = 0;
-      mGIFStruct.rows_remaining = mGIFStruct.clamped_height;
-      mGIFStruct.rowp = GetCurrentRowBuffer();
+      mGIFStruct.pixels_remaining = mGIFStruct.width * mGIFStruct.height;
 
       // Depth of colors is determined by colormap
       // (q[8] & 0x80) indicates local colormap
       // bits per pixel is (q[8]&0x07 + 1) when local colormap is set
       uint32_t depth = mGIFStruct.global_colormap_depth;
       if (q[8] & 0x80) {
         depth = (q[8]&0x07) + 1;
       }
@@ -1255,19 +1042,19 @@ nsGIFDecoder2::WriteInternal(const char*
       ConvertColormap(mColormap, mGIFStruct.local_colormap_size);
       GETN(1, gif_lzw_start);
       break;
 
     case gif_sub_block:
       mGIFStruct.count = *q;
       if (mGIFStruct.count) {
         // Still working on the same image: Process next LZW data block
-        // Make sure there are still rows left. If the GIF data
+        // Make sure there are still pixels left. If the GIF data
         // is corrupt, we may not get an explicit terminator.
-        if (!mGIFStruct.rows_remaining) {
+        if (mGIFStruct.pixels_remaining <= 0) {
 #ifdef DONT_TOLERATE_BROKEN_GIFS
           mGIFStruct.state = gif_error;
           break;
 #else
           // This is an illegal GIF, but we remain tolerant.
           GETN(1, gif_sub_block);
 #endif
           if (mGIFStruct.count == GIF_TRAILER) {
@@ -1327,18 +1114,16 @@ nsGIFDecoder2::WriteInternal(const char*
 
     mGIFStruct.bytes_to_consume -= len;
   }
 
 // We want to flush before returning if we're on the first frame
 done:
   if (!mGIFStruct.images_decoded) {
     FlushImageData();
-    mLastFlushedRow = mCurrentRow;
-    mLastFlushedPass = mCurrentPass;
   }
 }
 
 bool
 nsGIFDecoder2::SetHold(const uint8_t* buf1, uint32_t count1,
                        const uint8_t* buf2 /* = nullptr */,
                        uint32_t count2 /* = 0 */)
 {
--- a/image/decoders/nsGIFDecoder2.h
+++ b/image/decoders/nsGIFDecoder2.h
@@ -3,19 +3,18 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_image_decoders_nsGIFDecoder2_h
 #define mozilla_image_decoders_nsGIFDecoder2_h
 
 #include "Decoder.h"
-#include "Deinterlacer.h"
 #include "GIF2.h"
-#include "nsCOMPtr.h"
+#include "SurfacePipe.h"
 
 namespace mozilla {
 namespace image {
 class RasterImage;
 
 //////////////////////////////////////////////////////////////////////
 // nsGIFDecoder2 Definition
 
@@ -29,52 +28,54 @@ public:
   virtual Telemetry::ID SpeedHistogram() override;
 
 private:
   friend class DecoderFactory;
 
   // Decoders should only be instantiated via DecoderFactory.
   explicit nsGIFDecoder2(RasterImage* aImage);
 
-  uint8_t*  GetCurrentRowBuffer();
-  uint8_t*  GetRowBuffer(uint32_t aRow);
-
   // These functions will be called when the decoder has a decoded row,
   // frame size information, etc.
   void      BeginGIF();
   nsresult  BeginImageFrame(uint16_t aDepth);
   void      EndImageFrame();
   void      FlushImageData();
-  void      FlushImageData(uint32_t fromRow, uint32_t rows);
 
   nsresult  GifWrite(const uint8_t* buf, uint32_t numbytes);
-  uint32_t  OutputRow();
-  bool      DoLzw(const uint8_t* q);
+
+  /// Transforms a palette index into a pixel.
+  template <typename PixelSize> PixelSize
+  ColormapIndexToPixel(uint8_t aIndex);
+
+  /// A generator function that performs LZW decompression and yields pixels.
+  template <typename PixelSize> NextPixel<PixelSize>
+  YieldPixel(const uint8_t*& aCurrentByte);
+
+  /// The entry point for LZW decompression.
+  bool      DoLzw(const uint8_t* aData);
+
   bool      SetHold(const uint8_t* buf, uint32_t count,
                     const uint8_t* buf2 = nullptr, uint32_t count2 = 0);
   bool      CheckForTransparency(const gfx::IntRect& aFrameRect);
   gfx::IntRect ClampToImageRect(const gfx::IntRect& aFrameRect);
 
   inline int ClearCode() const { return 1 << mGIFStruct.datasize; }
 
-  int32_t mCurrentRow;
-  int32_t mLastFlushedRow;
-
   uint32_t mOldColor;        // The old value of the transparent pixel
 
   // The frame number of the currently-decoding frame when we're in the middle
   // of decoding it, and -1 otherwise.
   int32_t mCurrentFrameIndex;
 
-  uint8_t mCurrentPass;
-  uint8_t mLastFlushedPass;
   uint8_t mColorMask;        // Apply this to the pixel to keep within colormap
   bool mGIFOpen;
   bool mSawTransparency;
 
   gif_struct mGIFStruct;
-  Maybe<Deinterlacer> mDeinterlacer;
+
+  SurfacePipe mPipe;  /// The SurfacePipe used to write to the output surface.
 };
 
 } // namespace image
 } // namespace mozilla
 
 #endif // mozilla_image_decoders_nsGIFDecoder2_h
--- a/image/decoders/nsJPEGDecoder.cpp
+++ b/image/decoders/nsJPEGDecoder.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ImageLogging.h"
+#include "imgFrame.h"
 #include "nsJPEGDecoder.h"
 #include "Orientation.h"
 #include "EXIF.h"
 
 #include "nsIInputStream.h"
 
 #include "nspr.h"
 #include "nsCRT.h"
@@ -578,17 +579,17 @@ nsJPEGDecoder::ReadOrientationFromEXIF()
   EXIFData exif = EXIFParser::Parse(marker->data,
                                     static_cast<uint32_t>(marker->data_length));
   return exif.orientation;
 }
 
 void
 nsJPEGDecoder::NotifyDone()
 {
-  PostFrameStop(Opacity::OPAQUE);
+  PostFrameStop(Opacity::FULLY_OPAQUE);
   PostDecodeDone();
 }
 
 void
 nsJPEGDecoder::OutputScanlines(bool* suspend)
 {
   *suspend = false;
 
--- a/image/decoders/nsPNGDecoder.cpp
+++ b/image/decoders/nsPNGDecoder.cpp
@@ -2,16 +2,17 @@
  *
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ImageLogging.h" // Must appear first
 #include "gfxColor.h"
 #include "gfxPlatform.h"
+#include "imgFrame.h"
 #include "nsColor.h"
 #include "nsIInputStream.h"
 #include "nsMemory.h"
 #include "nsPNGDecoder.h"
 #include "nsRect.h"
 #include "nspr.h"
 #include "png.h"
 #include "RasterImage.h"
@@ -205,17 +206,17 @@ nsPNGDecoder::EndImageFrame()
   if (mFrameIsHidden) {
     return;
   }
 
   mNumFrames++;
 
   Opacity opacity = Opacity::SOME_TRANSPARENCY;
   if (format == gfx::SurfaceFormat::B8G8R8X8) {
-    opacity = Opacity::OPAQUE;
+    opacity = Opacity::FULLY_OPAQUE;
   }
 
   PostFrameStop(opacity, mAnimInfo.mDispose, mAnimInfo.mTimeout,
                 mAnimInfo.mBlend);
 }
 
 void
 nsPNGDecoder::InitInternal()
--- a/image/imgFrame.cpp
+++ b/image/imgFrame.cpp
@@ -638,17 +638,17 @@ void
 imgFrame::Finish(Opacity aFrameOpacity /* = Opacity::SOME_TRANSPARENCY */,
                  DisposalMethod aDisposalMethod /* = DisposalMethod::KEEP */,
                  int32_t aRawTimeout /* = 0 */,
                  BlendMethod aBlendMethod /* = BlendMethod::OVER */)
 {
   MonitorAutoLock lock(mMonitor);
   MOZ_ASSERT(mLockCount > 0, "Image data should be locked");
 
-  if (aFrameOpacity == Opacity::OPAQUE) {
+  if (aFrameOpacity == Opacity::FULLY_OPAQUE) {
     mHasNoAlpha = true;
   }
 
   mDisposalMethod = aDisposalMethod;
   mTimeout = aRawTimeout;
   mBlendMethod = aBlendMethod;
   ImageUpdatedInternal(GetRect());
 }
--- a/image/imgFrame.h
+++ b/image/imgFrame.h
@@ -36,17 +36,17 @@ enum class DisposalMethod : int8_t {
   CLEAR_ALL = -1,  // Clear the whole image, revealing what's underneath.
   NOT_SPECIFIED,   // Leave the frame and let the new frame draw on top.
   KEEP,            // Leave the frame and let the new frame draw on top.
   CLEAR,           // Clear the frame's area, revealing what's underneath.
   RESTORE_PREVIOUS // Restore the previous (composited) frame.
 };
 
 enum class Opacity : uint8_t {
-  OPAQUE,
+  FULLY_OPAQUE,
   SOME_TRANSPARENCY
 };
 
 /**
  * AnimationData contains all of the information necessary for using an imgFrame
  * as part of an animation.
  *
  * It includes pointers to the raw image data of the underlying imgFrame, but
--- a/image/moz.build
+++ b/image/moz.build
@@ -50,17 +50,16 @@ EXPORTS += [
     'SurfaceFlags.h',
 ]
 
 UNIFIED_SOURCES += [
     'ClippedImage.cpp',
     'DecodePool.cpp',
     'Decoder.cpp',
     'DecoderFactory.cpp',
-    'Deinterlacer.cpp',
     'DynamicImage.cpp',
     'FrameAnimator.cpp',
     'FrozenImage.cpp',
     'Image.cpp',
     'ImageCacheKey.cpp',
     'ImageFactory.cpp',
     'ImageOps.cpp',
     'ImageWrapper.cpp',