Example #1
0
bool CompressStream(Serializer& dest, Deserializer& src)
{
    unsigned srcSize = src.GetSize() - src.GetPosition();
    // Prepend the source and dest. data size in the stream so that we know to buffer & uncompress the right amount
    if (!srcSize)
    {
        dest.WriteUInt(0);
        dest.WriteUInt(0);
        return true;
    }
    
    unsigned maxDestSize = LZ4_compressBound(srcSize);
    SharedArrayPtr<unsigned char> srcBuffer(new unsigned char[srcSize]);
    SharedArrayPtr<unsigned char> destBuffer(new unsigned char[maxDestSize]);
    
    if (src.Read(srcBuffer, srcSize) != srcSize)
        return false;
    
    unsigned destSize = LZ4_compressHC((const char*)srcBuffer.Get(), (char*)destBuffer.Get(), srcSize);
    bool success = true;
    success &= dest.WriteUInt(srcSize);
    success &= dest.WriteUInt(destSize);
    success &= dest.Write(destBuffer, destSize) == destSize;
    return success;
}
// -----------------------------------------------------------------------------
// CDrmUtilityGlobalNoteWrapper::ShowNoteL
// -----------------------------------------------------------------------------
//
TInt DRM::CDrmUtilityGlobalNoteWrapper::ShowNoteL(
    TInt aResourceId,
    const TDesC& aString )
    {
    TBuf<DRM::KDRMNoteBufferMaxSize> srcBuffer(
        iResourceReader->ReadResourceString( aResourceId ) );

    StringLoader::Format( iTextBuffer, srcBuffer, -1, aString );
    return DoShowNoteL( aResourceId, aString );
    }
Example #3
0
// Test out the layer replacement functionality with and w/o a BBH
void test_replacements(skiatest::Reporter* r, GrContext* context, bool doReplace) {
    sk_sp<SkPicture> pic;

    {
        SkPictureRecorder recorder;
        SkCanvas* canvas = recorder.beginRecording(SkIntToScalar(kWidth), SkIntToScalar(kHeight));
        SkPaint paint;
        canvas->saveLayer(nullptr, &paint);
        canvas->clear(SK_ColorRED);
        canvas->restore();
        canvas->drawRect(SkRect::MakeWH(SkIntToScalar(kWidth / 2), SkIntToScalar(kHeight / 2)),
                         SkPaint());
        pic = recorder.finishRecordingAsPicture();
    }

    SkAutoTUnref<GrTexture> texture;
    SkPaint paint;
    GrLayerCache* layerCache = context->getLayerCache();

    if (doReplace) {
        int key[1] = { 0 };

        GrCachedLayer* layer = layerCache->findLayerOrCreate(pic->uniqueID(), 0, 2,
                                                             SkIRect::MakeWH(kWidth, kHeight),
                                                             SkIRect::MakeWH(kWidth, kHeight),
                                                             SkMatrix::I(), key, 1, &paint);

        GrSurfaceDesc desc;
        desc.fConfig = kSkia8888_GrPixelConfig;
        desc.fFlags = kRenderTarget_GrSurfaceFlag;
        desc.fWidth = kWidth;
        desc.fHeight = kHeight;
        desc.fSampleCnt = 0;

        // Giving the texture some initial data so the Gpu (specifically vulkan) does not complain
        // when reading from an uninitialized texture.
        SkAutoTMalloc<uint32_t> srcBuffer(kWidth*kHeight);
        memset(srcBuffer.get(), 0, kWidth*kHeight*sizeof(uint32_t));

        texture.reset(context->textureProvider()->createTexture(
                desc, SkBudgeted::kNo, srcBuffer.get(), 0));
        layer->setTexture(texture, SkIRect::MakeWH(kWidth, kHeight), false);
    }

    SkRecord rerecord;
    SkRecorder canvas(&rerecord, kWidth, kHeight);
    GrRecordReplaceDraw(pic.get(), &canvas, layerCache, SkMatrix::I(), nullptr/*callback*/);

    int numLayers = count_instances_of_type<SkRecords::SaveLayer>(rerecord);
    if (doReplace) {
        REPORTER_ASSERT(r, 0 == numLayers);
    } else {
        REPORTER_ASSERT(r, 1 == numLayers);
    }
}
Example #4
0
void
aggPixelPainter<pixel_fmt>::copy(void* data, unsigned width, unsigned height,
                                 int stride, aggCanvas::PixelFormat format)
{
    agg::rendering_buffer srcBuffer(reinterpret_cast<agg::int8u*>(data), width, height, -stride);
    
    switch (format) {
        case aggCanvas::Gray8_Blend: {
            gray_pixel_fmt srcPixFmt(srcBuffer);
            agg::copy_rect(srcPixFmt, pixFmt);
            break;
        }
        
        case aggCanvas::Gray16_Blend: {
            gray16_pixel_fmt srcPixFmt(srcBuffer);
            agg::copy_rect(srcPixFmt, pixFmt);
            break;
        }
        
        case aggCanvas::RGBA8_Blend: {
            color32_pixel_fmt srcPixFmt(srcBuffer);
            agg::copy_rect(srcPixFmt, pixFmt);
            break;
        }
        
        case aggCanvas::RGBA16_Blend: {
            color64_pixel_fmt srcPixFmt(srcBuffer);
            agg::copy_rect(srcPixFmt, pixFmt);
            break;
        }
        
        case aggCanvas::RGB8_Blend: {
            color24_pixel_fmt srcPixFmt(srcBuffer);
            agg::copy_rect(srcPixFmt, pixFmt);
            break;
        }
        
        case aggCanvas::RGB16_Blend: {
            color48_pixel_fmt srcPixFmt(srcBuffer);
            agg::copy_rect(srcPixFmt, pixFmt);
            break;
        }
        
        case aggCanvas::FF_Blend:
        case aggCanvas::FF24_Blend:
        case aggCanvas::QT_Blend:
            assert(false);
            break;
            
        default:
            break;
    }
    
}
already_AddRefed<VideoData>
GonkVideoDecoderManager::CreateVideoDataFromGraphicBuffer(MediaBuffer* aSource,
                                                          gfx::IntRect& aPicture)
{
  sp<GraphicBuffer> srcBuffer(aSource->graphicBuffer());
  RefPtr<TextureClient> textureClient;

  if (mNeedsCopyBuffer) {
    // Copy buffer contents for bug 1199809.
    if (!mCopyAllocator) {
      mCopyAllocator = new TextureClientRecycleAllocator(ImageBridgeChild::GetSingleton());
    }
    if (!mCopyAllocator) {
      GVDM_LOG("Create buffer allocator failed!");
      return nullptr;
    }

    gfx::IntSize size(Align(aPicture.width, 2) , Align(aPicture.height, 2));
    textureClient =
      mCopyAllocator->CreateOrRecycle(gfx::SurfaceFormat::YUV, size,
                                      BackendSelector::Content,
                                      TextureFlags::DEFAULT,
                                      ALLOC_DISALLOW_BUFFERTEXTURECLIENT);
    if (!textureClient) {
      GVDM_LOG("Copy buffer allocation failed!");
      return nullptr;
    }
    // Update size to match buffer's.
    aPicture.width = size.width;
    aPicture.height = size.height;

    sp<GraphicBuffer> destBuffer =
      static_cast<GrallocTextureClientOGL*>(textureClient.get())->GetGraphicBuffer();

    CopyGraphicBuffer(srcBuffer, destBuffer);
  } else {
    textureClient = mNativeWindow->getTextureClientFromBuffer(srcBuffer.get());
    textureClient->SetRecycleCallback(GonkVideoDecoderManager::RecycleCallback, this);
    GrallocTextureClientOGL* grallocClient = static_cast<GrallocTextureClientOGL*>(textureClient.get());
    grallocClient->SetMediaBuffer(aSource);
  }

  RefPtr<VideoData> data = VideoData::Create(mInfo.mVideo,
                                             mImageContainer,
                                             0, // Filled later by caller.
                                             0, // Filled later by caller.
                                             1, // No way to pass sample duration from muxer to
                                                // OMX codec, so we hardcode the duration here.
                                             textureClient,
                                             false, // Filled later by caller.
                                             -1,
                                             aPicture);
  return data.forget();
}
Example #6
0
void
BasicThebesLayerBuffer::SetBackingBufferAndUpdateFrom(
  gfxASurface* aBuffer,
  gfxASurface* aSource, const nsIntRect& aRect, const nsIntPoint& aRotation,
  const nsIntRegion& aUpdateRegion)
{
  SetBackingBuffer(aBuffer, aRect, aRotation);
  nsRefPtr<gfxContext> destCtx =
    GetContextForQuadrantUpdate(aUpdateRegion.GetBounds());
  destCtx->SetOperator(gfxContext::OPERATOR_SOURCE);
  if (IsClippingCheap(destCtx, aUpdateRegion)) {
    gfxUtils::ClipToRegion(destCtx, aUpdateRegion);
  }

  BasicThebesLayerBuffer srcBuffer(aSource, aRect, aRotation);
  srcBuffer.DrawBufferWithRotation(destCtx, 1.0);
}
// -----------------------------------------------------------------------------
// CDrmUtilityGlobalNoteWrapper::ShowNoteWithButtonsL
// -----------------------------------------------------------------------------
//
TInt DRM::CDrmUtilityGlobalNoteWrapper::ShowNoteWithButtonsL(
    TInt aResourceId,
    TInt aButtonsId,
    const TDesC& aString )
    {
    TInt ret( 0 );

    TBuf<DRM::KDRMNoteBufferMaxSize> srcBuffer(
        iResourceReader->ReadResourceString( aResourceId ) );

    StringLoader::Format( iTextBuffer, srcBuffer, -1, aString );

    iButtonsId = aButtonsId;
    ret = DoShowNoteL( aResourceId, aString );
    iButtonsId = R_AVKON_SOFTKEYS_YES_NO__YES;
    return ret;
    }
Example #8
0
bool DecompressStream(Serializer& dest, Deserializer& src)
{
    if (src.IsEof())
        return false;
    
    unsigned destSize = src.ReadUInt();
    unsigned srcSize = src.ReadUInt();
    if (!srcSize || !destSize)
        return true; // No data
    
    if (srcSize > src.GetSize())
        return false; // Illegal source (packed data) size reported, possibly not valid data
    
    SharedArrayPtr<unsigned char> srcBuffer(new unsigned char[srcSize]);
    SharedArrayPtr<unsigned char> destBuffer(new unsigned char[destSize]);
    
    if (src.Read(srcBuffer, srcSize) != srcSize)
        return false;
    
    LZ4_decompress_fast((const char*)srcBuffer.Get(), (char*)destBuffer.Get(), destSize);
    return dest.Write(destBuffer, destSize) == destSize;
}
void dng_limit_float_depth_task::Process (uint32 /* threadIndex */,
										  const dng_rect &tile,
										  dng_abort_sniffer * /* sniffer */)
	{
	
	dng_const_tile_buffer srcBuffer (fSrcImage, tile);
	dng_dirty_tile_buffer dstBuffer (fDstImage, tile);
	
	uint32 count0 = tile.H ();
	uint32 count1 = tile.W ();
	uint32 count2 = fDstImage.Planes ();
	
	int32 sStep0 = srcBuffer.fRowStep;
	int32 sStep1 = srcBuffer.fColStep;
	int32 sStep2 = srcBuffer.fPlaneStep;
	
	int32 dStep0 = dstBuffer.fRowStep;
	int32 dStep1 = dstBuffer.fColStep;
	int32 dStep2 = dstBuffer.fPlaneStep;

	const void *sPtr = srcBuffer.ConstPixel (tile.t,
											 tile.l,
											 0);
											 
		  void *dPtr = dstBuffer.DirtyPixel (tile.t,
											 tile.l,
											 0);

	OptimizeOrder (sPtr,
			       dPtr,
				   srcBuffer.fPixelSize,
				   dstBuffer.fPixelSize,
				   count0,
				   count1,
				   count2,
				   sStep0,
				   sStep1,
				   sStep2,
				   dStep0,
				   dStep1,
				   dStep2);
				   
	const real32 *sPtr0 = (const real32 *) sPtr;
		  real32 *dPtr0 = (      real32 *) dPtr;
		  
	real32 scale = fScale;
		  
	bool limit16 = (fBitDepth == 16);
	bool limit24 = (fBitDepth == 24);
				   
	for (uint32 index0 = 0; index0 < count0; index0++)
		{
		
		const real32 *sPtr1 = sPtr0;
			  real32 *dPtr1 = dPtr0;
			  
		for (uint32 index1 = 0; index1 < count1; index1++)
			{
			
			// If the scale is a NOP, and the data is packed solid, we can just do memory
			// copy.
			
			if (scale == 1.0f && sStep2 == 1 && dStep2 == 1)
				{
				
				if (dPtr1 != sPtr1)			// srcImage != dstImage
					{
				
					memcpy (dPtr1, sPtr1, count2 * (uint32) sizeof (real32));
					
					}
				
				}
				
			else
				{
			
				const real32 *sPtr2 = sPtr1;
					  real32 *dPtr2 = dPtr1;
					  
				for (uint32 index2 = 0; index2 < count2; index2++)
					{
					
					real32 x = sPtr2 [0];
					
					x *= scale;
					
					dPtr2 [0] = x;
					
					sPtr2 += sStep2;
					dPtr2 += dStep2;
					
					}
					
				}
				
			// The data is now in the destination buffer.
				
			if (limit16)
				{
			
				uint32 *dPtr2 = (uint32 *) dPtr1;
					  
				for (uint32 index2 = 0; index2 < count2; index2++)
					{
					
					uint32 x = dPtr2 [0];
					
					uint16 y = DNG_FloatToHalf (x);
					
					x = DNG_HalfToFloat (y);
											
					dPtr2 [0] = x;
					
					dPtr2 += dStep2;
					
					}
					
				}
				
			else if (limit24)
				{
			
				uint32 *dPtr2 = (uint32 *) dPtr1;
					  
				for (uint32 index2 = 0; index2 < count2; index2++)
					{
					
					uint32 x = dPtr2 [0];
											
					uint8 temp [3];
					
					DNG_FloatToFP24 (x, temp);
					
					x = DNG_FP24ToFloat (temp);
					
					dPtr2 [0] = x;
					
					dPtr2 += dStep2;
					
					}
					
				}
			  
			sPtr1 += sStep1;
			dPtr1 += dStep1;
			
			}
				   
		sPtr0 += sStep0;
		dPtr0 += dStep0;
		
		}
				   	
	}
Example #10
0
/*
 * Performs the jpeg decode
 */
SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo,
                                         void* dst, size_t dstRowBytes,
                                         const Options& options, SkPMColor*, int*) {

    // Rewind the stream if needed
    if (!this->handleRewind()) {
        fDecoderMgr->returnFailure("could not rewind stream", kCouldNotRewind);
    }

    // Get a pointer to the decompress info since we will use it quite frequently
    jpeg_decompress_struct* dinfo = fDecoderMgr->dinfo();

    // Set the jump location for libjpeg errors
    if (setjmp(fDecoderMgr->getJmpBuf())) {
        return fDecoderMgr->returnFailure("setjmp", kInvalidInput);
    }

    // Check if we can decode to the requested destination
    if (!conversion_possible(dstInfo, this->getInfo())) {
        return fDecoderMgr->returnFailure("conversion_possible", kInvalidConversion);
    }

    // Perform the necessary scaling
    if (!this->scaleToDimensions(dstInfo.width(), dstInfo.height())) {
        fDecoderMgr->returnFailure("cannot scale to requested dims", kInvalidScale);
    }

    // Now, given valid output dimensions, we can start the decompress
    if (!jpeg_start_decompress(dinfo)) {
        return fDecoderMgr->returnFailure("startDecompress", kInvalidInput);
    }

    // Create the swizzler
    this->initializeSwizzler(dstInfo, dst, dstRowBytes, options);
    if (NULL == fSwizzler) {
        return fDecoderMgr->returnFailure("getSwizzler", kUnimplemented);
    }

    // This is usually 1, but can also be 2 or 4.
    // If we wanted to always read one row at a time, we could, but we will save space and time
    // by using the recommendation from libjpeg.
    const uint32_t rowsPerDecode = dinfo->rec_outbuf_height;
    SkASSERT(rowsPerDecode <= 4);

    // Create a buffer to contain decoded rows (libjpeg requires a 2D array)
    SkASSERT(0 != fSrcRowBytes);
    SkAutoTDeleteArray<uint8_t> srcBuffer(SkNEW_ARRAY(uint8_t, fSrcRowBytes * rowsPerDecode));
    JSAMPLE* srcRows[4];
    uint8_t* srcPtr = srcBuffer.get();
    for (uint8_t i = 0; i < rowsPerDecode; i++) {
        srcRows[i] = (JSAMPLE*) srcPtr;
        srcPtr += fSrcRowBytes;
    }

    // Ensure that we loop enough times to decode all of the rows
    // libjpeg will prevent us from reading past the bottom of the image
    uint32_t dstHeight = dstInfo.height();
    for (uint32_t y = 0; y < dstHeight + rowsPerDecode - 1; y += rowsPerDecode) {
        // Read rows of the image
        uint32_t rowsDecoded = jpeg_read_scanlines(dinfo, srcRows, rowsPerDecode);

        // Convert to RGB if necessary
        if (JCS_CMYK == dinfo->out_color_space) {
            convert_CMYK_to_RGB(srcRows[0], dstInfo.width() * rowsDecoded);
        }

        // Swizzle to output destination
        for (uint32_t i = 0; i < rowsDecoded; i++) {
            fSwizzler->next(srcRows[i]);
        }

        // If we cannot read enough rows, assume the input is incomplete
        if (rowsDecoded < rowsPerDecode && y + rowsDecoded < dstHeight) {
            // Fill the remainder of the image with black. This error handling
            // behavior is unspecified but SkCodec consistently uses black as
            // the fill color for opaque images.  If the destination is kGray,
            // the low 8 bits of SK_ColorBLACK will be used.  Conveniently,
            // these are zeros, which is the representation for black in kGray.
            SkSwizzler::Fill(fSwizzler->getDstRow(), dstInfo, dstRowBytes,
                    dstHeight - y - rowsDecoded, SK_ColorBLACK, NULL);

            // Prevent libjpeg from failing on incomplete decode
            dinfo->output_scanline = dstHeight;

            // Finish the decode and indicate that the input was incomplete.
            jpeg_finish_decompress(dinfo);
            return fDecoderMgr->returnFailure("Incomplete image data", kIncompleteInput);
        }
    }
    jpeg_finish_decompress(dinfo);

    return kSuccess;
}
Example #11
0
bool GifTranscoder::resizeBoxFilter(GifFileType* gifIn, GifFileType* gifOut) {
    ASSERT(gifIn != NULL, "gifIn cannot be NULL");
    ASSERT(gifOut != NULL, "gifOut cannot be NULL");

    if (gifIn->SWidth < 0 || gifIn->SHeight < 0) {
        LOGE("Input GIF has invalid size: %d x %d", gifIn->SWidth, gifIn->SHeight);
        return false;
    }

    // Output GIF will be 50% the size of the original.
    if (EGifPutScreenDesc(gifOut,
                          gifIn->SWidth / 2,
                          gifIn->SHeight / 2,
                          gifIn->SColorResolution,
                          gifIn->SBackGroundColor,
                          gifIn->SColorMap) == GIF_ERROR) {
        LOGE("Could not write screen descriptor");
        return false;
    }
    LOGD("Wrote screen descriptor");

    // Index of the current image.
    int imageIndex = 0;

    // Transparent color of the current image.
    int transparentColor = NO_TRANSPARENT_COLOR;

    // Buffer for reading raw images from the input GIF.
    std::vector<GifByteType> srcBuffer(gifIn->SWidth * gifIn->SHeight);

    // Buffer for rendering images from the input GIF.
    std::unique_ptr<ColorARGB> renderBuffer(new ColorARGB[gifIn->SWidth * gifIn->SHeight]);

    // Buffer for writing new images to output GIF (one row at a time).
    std::unique_ptr<GifByteType> dstRowBuffer(new GifByteType[gifOut->SWidth]);

    // Many GIFs use DISPOSE_DO_NOT to make images draw on top of previous images. They can also
    // use DISPOSE_BACKGROUND to clear the last image region before drawing the next one. We need
    // to keep track of the disposal mode as we go along to properly render the GIF.
    int disposalMode = DISPOSAL_UNSPECIFIED;
    int prevImageDisposalMode = DISPOSAL_UNSPECIFIED;
    GifImageDesc prevImageDimens;

    // Background color (applies to entire GIF).
    ColorARGB bgColor = TRANSPARENT;

    GifRecordType recordType;
    do {
        if (DGifGetRecordType(gifIn, &recordType) == GIF_ERROR) {
            LOGE("Could not get record type");
            return false;
        }
        LOGD("Read record type: %d", recordType);
        switch (recordType) {
            case IMAGE_DESC_RECORD_TYPE: {
                if (DGifGetImageDesc(gifIn) == GIF_ERROR) {
                    LOGE("Could not read image descriptor (%d)", imageIndex);
                    return false;
                }

                // Sanity-check the current image position.
                if (gifIn->Image.Left < 0 ||
                        gifIn->Image.Top < 0 ||
                        gifIn->Image.Left + gifIn->Image.Width > gifIn->SWidth ||
                        gifIn->Image.Top + gifIn->Image.Height > gifIn->SHeight) {
                    LOGE("GIF image extends beyond logical screen");
                    return false;
                }

                // Write the new image descriptor.
                if (EGifPutImageDesc(gifOut,
                                     0, // Left
                                     0, // Top
                                     gifOut->SWidth,
                                     gifOut->SHeight,
                                     false, // Interlace
                                     gifIn->Image.ColorMap) == GIF_ERROR) {
                    LOGE("Could not write image descriptor (%d)", imageIndex);
                    return false;
                }

                // Read the image from the input GIF. The buffer is already initialized to the
                // size of the GIF, which is usually equal to the size of all the images inside it.
                // If not, the call to resize below ensures that the buffer is the right size.
                srcBuffer.resize(gifIn->Image.Width * gifIn->Image.Height);
                if (readImage(gifIn, srcBuffer.data()) == false) {
                    LOGE("Could not read image data (%d)", imageIndex);
                    return false;
                }
                LOGD("Read image data (%d)", imageIndex);
                // Render the image from the input GIF.
                if (renderImage(gifIn,
                                srcBuffer.data(),
                                imageIndex,
                                transparentColor,
                                renderBuffer.get(),
                                bgColor,
                                prevImageDimens,
                                prevImageDisposalMode) == false) {
                    LOGE("Could not render %d", imageIndex);
                    return false;
                }
                LOGD("Rendered image (%d)", imageIndex);

                // Generate the image in the output GIF.
                for (int y = 0; y < gifOut->SHeight; y++) {
                    for (int x = 0; x < gifOut->SWidth; x++) {
                      const GifByteType dstColorIndex = computeNewColorIndex(
                          gifIn, transparentColor, renderBuffer.get(), x, y);
                      *(dstRowBuffer.get() + x) = dstColorIndex;
                    }
                    if (EGifPutLine(gifOut, dstRowBuffer.get(), gifOut->SWidth) == GIF_ERROR) {
                        LOGE("Could not write raster data (%d)", imageIndex);
                        return false;
                    }
                }
                LOGD("Wrote raster data (%d)", imageIndex);

                // Save the disposal mode for rendering the next image.
                // We only support DISPOSE_DO_NOT and DISPOSE_BACKGROUND.
                prevImageDisposalMode = disposalMode;
                if (prevImageDisposalMode == DISPOSAL_UNSPECIFIED) {
                    prevImageDisposalMode = DISPOSE_DO_NOT;
                } else if (prevImageDisposalMode == DISPOSE_PREVIOUS) {
                    prevImageDisposalMode = DISPOSE_BACKGROUND;
                }
                if (prevImageDisposalMode == DISPOSE_BACKGROUND) {
                    prevImageDimens.Left = gifIn->Image.Left;
                    prevImageDimens.Top = gifIn->Image.Top;
                    prevImageDimens.Width = gifIn->Image.Width;
                    prevImageDimens.Height = gifIn->Image.Height;
                }

                if (gifOut->Image.ColorMap) {
                    GifFreeMapObject(gifOut->Image.ColorMap);
                    gifOut->Image.ColorMap = NULL;
                }

                imageIndex++;
            } break;
            case EXTENSION_RECORD_TYPE: {
                int extCode;
                GifByteType* ext;
                if (DGifGetExtension(gifIn, &extCode, &ext) == GIF_ERROR) {
                    LOGE("Could not read extension block");
                    return false;
                }
                LOGD("Read extension block, code: %d", extCode);
                if (extCode == GRAPHICS_EXT_FUNC_CODE) {
                    GraphicsControlBlock gcb;
                    if (DGifExtensionToGCB(ext[0], ext + 1, &gcb) == GIF_ERROR) {
                        LOGE("Could not interpret GCB extension");
                        return false;
                    }
                    transparentColor = gcb.TransparentColor;

                    // This logic for setting the background color based on the first GCB
                    // doesn't quite match the GIF spec, but empirically it seems to work and it
                    // matches what libframesequence (Rastermill) does.
                    if (imageIndex == 0 && gifIn->SColorMap) {
                        if (gcb.TransparentColor == NO_TRANSPARENT_COLOR) {
                            GifColorType bgColorIndex =
                                    gifIn->SColorMap->Colors[gifIn->SBackGroundColor];
                            bgColor = gifColorToColorARGB(bgColorIndex);
                            LOGD("Set background color based on first GCB");
                        }
                    }

                    // Record the original disposal mode and then update it.
                    disposalMode = gcb.DisposalMode;
                    gcb.DisposalMode = DISPOSE_BACKGROUND;
                    EGifGCBToExtension(&gcb, ext + 1);
                }
                if (EGifPutExtensionLeader(gifOut, extCode) == GIF_ERROR) {
                    LOGE("Could not write extension leader");
                    return false;
                }
                if (EGifPutExtensionBlock(gifOut, ext[0], ext + 1) == GIF_ERROR) {
                    LOGE("Could not write extension block");
                    return false;
                }
                LOGD("Wrote extension block");
                while (ext != NULL) {
                    if (DGifGetExtensionNext(gifIn, &ext) == GIF_ERROR) {
                        LOGE("Could not read extension continuation");
                        return false;
                    }
                    if (ext != NULL) {
                        LOGD("Read extension continuation");
                        if (EGifPutExtensionBlock(gifOut, ext[0], ext + 1) == GIF_ERROR) {
                            LOGE("Could not write extension continuation");
                            return false;
                        }
                        LOGD("Wrote extension continuation");
                    }
                }
                if (EGifPutExtensionTrailer(gifOut) == GIF_ERROR) {
                    LOGE("Could not write extension trailer");
                    return false;
                }
            } break;
        }

    } while (recordType != TERMINATE_RECORD_TYPE);
    LOGD("No more records");

    return true;
}